ASP.NET 1.1 Security Guidelines - Cross-Site Scripting

From Guidance Share
Jump to navigationJump to search

- J.D. Meier, Alex Mackman, Michael Dunner, Srinath Vasireddy, Ray Escamilla and Anandha Murukan


Validate Input

Validate any input that is received from outside your application's trust boundary for type, length, format, and range.


References


Encode Output

If you write text output to a Web page and you do not know with absolute certainty that the text does not contain HTML special characters (such as <, >, and &), then make sure to pre-process it using the HttpUtility.HtmlEncode method. Do this even if the text came from user input, a database, or a local file. Similarly, use HttpUtility.UrlEncode to encode URL strings.


The HtmlEncode method replaces characters that have special meaning in HTML to HTML variables that represent those characters. For example, < is replaced with &lt and " is replaced with &quot. Encoded data does not cause the browser to execute code. Instead, the data is rendered as harmless HTML.

Response.Write(HttpUtility.HtmlEncode(Request.Form["name"]));


Data-Bound Controls

Data bound controls are web controls that are bindable to data components through a public inherited 'DataSource' property. To mention a few, you will find DataGrid, ListBox and DropDownList to be used very often. Not all data bound controls perform encoding when displaying data retrieved from a bound data component; thus, it will be your responsibility to perform encoding on non-trusted data components in order to prevent XSS attacks. For example, a data component cannot be trusted in a scenario where different applications share a single database. If an attacker has the ability to insert malicious XSS code into the database (by abusing a vulnerability in one of the applications, for instance) all applications using non-encoding web controls bound to it, will turn vulnerable. Only avoid encoding if you can be certain that the output from the data component will always be valid.


Examples of data bound controls that do not perform encoding are DataGrid, DataList, RadioButtonList and CheckBoxList. Performing encoding for a data bound control may vary depending on each specific control. For example, for a DataGrid control, you have the following options:

  • Turn all columns into templates and manually use HtmlEncode()/UrlEncode() on each call to DataBinder.Eval
  • Override one of its DataBinding methods, such as OnDatabinding or OnItemDataBound and perform encoding on its items. The following example illustrates how to override the OnItemDataBound method of a DataGrid control in order to encode its items by either using HtmlEncode() or UrlEncode() when required:
 ...
 [DefaultProperty("Text"),
  ToolboxData("<{0}:DataGrid runat=server></{0}:DataGrid>")]
 
 public class DataGrid : System.Web.UI.WebControls.DataGrid
 {
    /// <summary>
    /// The ItemDataBound event is raised after an item is data bound to the DataGrid
    /// control. This event provides you with the last opportunity to access the data
    /// item before it is displayed on the client. After this event is raised, the data
    /// item is nulled out and no longer available. - .NET Framework Class Library
    /// </summary>
    /// <param name="e"></param>
    protected override void OnItemDataBound(DataGridItemEventArgs e)
    {
      base.OnItemDataBound (e);
 
      switch (e.Item.ItemType)
      {
        case ListItemType.Item:
        case ListItemType.AlternatingItem:
        case ListItemType.EditItem:
        case ListItemType.SelectedItem:
        case ListItemType.Footer:
        case ListItemType.Header:
        case ListItemType.Pager:  
          // even though not all of these ListItemTypes are data bound,
          // perform HtmlEncode or UrlEncode on each control. If there are
          // no controls, we perform HtmlEncode on any available text.
          // Also, don't let &nbsp;'s be encoded.
          TableCellCollection cCells = e.Item.Cells;
          foreach (TableCell tc in cCells)
          {
            if (tc.Controls.Count > 0)
            {
              foreach (Control ctrl in tc.Controls)
              {
                
                // don't perform HtmlEncode on URL's
                if (ctrl is HyperLink)
                {
                  HyperLink hLnk = (HyperLink)ctrl;
 
                  if (hLnk.Text.Length > 0)
                    hLnk.Text = HttpUtility.HtmlEncode(hLnk.Text);
                  if (hLnk.NavigateUrl.Length > 0)
                    hLnk.NavigateUrl = HttpUtility.UrlEncode(hLnk.NavigateUrl);
                }
                else if (ctrl is LinkButton)
                {
                  LinkButton lButton = (LinkButton)ctrl;
 
                  if (lButton.Text.Length > 0)
                    lButton.Text = HttpUtility.HtmlEncode(lButton.Text);
                }
                else if (ctrl is Button)
                {
                  Button cButton = (Button)ctrl;
 
                  if (cButton.Text.Length > 0)
                    cButton.Text = HttpUtility.HtmlEncode(cButton.Text);
                }
              }
            } 
            else 
            {              
              // there are no controls in the table cell
              // HTMLEncode any available text
              if (tc.Text.Length > 0) 
              {
                if ("&nbsp;" != tc.Text) 
                  tc.Text = HttpUtility.HtmlEncode(tc.Text);
              }
            }
          }
          break;
         default:
          break;
      }
     }
   }
 ...


Sanitizing Free Format Input

If your Web page includes a free-format text box, such as a "comments" field, in which you want to permit certain safe HTML elements such as <b> and <i>, you can handle this safely by first pre-processing with HtmlEncode, and then selectively removing the encoding on the permitted elements, as follows:

 StringBuilder sb = new StringBuilder( HttpUtility.HtmlEncode(userInput) ) ;
 sb.Replace("&lt;b&gt;", "<b>");
 sb.Replace("&lt;/b&gt;", "</b>");
 sb.Replace("&lt;i&gt;", "<i>");
 sb.Replace("&lt;/i&gt;", "</i>");
 Response.Write(sb.ToString());


References


Set the correct character encoding

To successfully restrict what data is valid for your Web pages, it is important to limit the ways in which the input data can be represented. This prevents malicious users from using canonicalization and multi-byte escape sequences to trick your input validation routines.


ASP.NET allows you to specify the character set at the page level or at the application level by using the <globalization> element in Web.config. Both approaches are shown below using the ISO-8859-1 character encoding, which is the default in early versions of HTML and HTTP.


To set the character encoding at the page level, use the <meta> element or the ResponseEncoding page-level attribute as follows:

<meta http-equiv="Content Type" 
     content="text/html; charset=ISO-8859-1" />

OR

<% @ Page ResponseEncoding="ISO-8859-1" %>


To set the character encoding in Web.config, use the following configuration:

<configuration>
  <system.web>
     <globalization 
        requestEncoding="ISO-8859-1"
        responseEncoding="ISO-8859-1"/>
  </system.web>
</configuration>


Validating Unicode Characters

Use the following code to validate Unicode characters in a page:

using System.Text.RegularExpressions;
. . .
private void Page_Load(object sender, System.EventArgs e)
{
 // Name must contain between 1 and 40 alphanumeric characters
 // together with (optionally) special characters '`´ for names such
 // as D'Angelo
 if (!Regex.IsMatch(Request.Form["name"], @"^[\p{L}\p{Zs}\p{Lu}\p{Ll}\']{1,40}$"))
   throw new ArgumentException("Invalid name parameter");
 // Use individual regular expressions to validate other parameters
 . . .
}


The following explains the regular expression shown in the preceding code:

  • {<name>} specifies a named Unicode character class.
  • \p{<name>} matches any character in the named character class specified by {<name>}.
  • {L} performs a left-to-right match.
  • {Lu} performs a match of uppercase.
  • {Ll} performs a match of lowercase.
  • {Zs} matches separator and space.
  • {1,40} means no less that 1 and no more than 40 characters.
  • {Mn} matches mark and non-spacing characters.
  • {Zs} matches separator and space.
  • * specifies zero or more matches.
  • $ means stop looking at this position.


References


Use the ASP.NET validateRequest option

The validateRequest attribute is set to true by default on the <pages> element in Machine.config. It instructs ASP.NET to examine all data received from the browser for potentially malicious input, for example, input that contains <script> elements. ASP.NET examines input received from HTML form fields, cookies, and query strings. .NET Framework version 1.0 does not provide any equivalent functionality, but the IIS URLScan Internet Server Application Programming Interface (ISAPI) filter can perform a similar job. You can also

<% @ Page validateRequest="True" %>


References


Install URLScan on your Web server

URLScan is an ISAPI filter that is installed when you run the IISLockdown tool. This helps mitigate the threat of XSS attacks by rejecting potentially malicious input. For more information about IISLockdown and URLScan, see Chapter 16, "Securing Your Web Server." at http://msdn.microsoft.com/library/en-us/dnnetsec/html/THCMCh16.asp

Note IIS 6.0 on Windows Server 2003 has functionality equivalent to URLScan built in.


References


Use the HttpOnly cookie option

Internet Explorer 6 Service Pack 1 supports a new HttpOnly cookie attribute, which prevents client-side script from accessing the cookie from the document.cookie property. Instead, an empty string is returned. The cookie is still sent to the server whenever the user browses to a Web site in the current domain.

Note Web browsers that do not support the HttpOnly cookie attribute either ignore the cookie or ignore the attribute, which means it is still subject to XSS attacks.


The System.Net.Cookie class does not currently support an HttpOnly property. To add an HttpOnly attribute to the cookie, you need to use an ISAPI filter, or if you want a managed code solution, add the following code to your application's Application_EndRequest event handler in Global.asax:

protected void Application_EndRequest(Object sender, EventArgs e) 
{
 string authCookie = FormsAuthentication.FormsCookieName;
 foreach (string sCookie in Response.Cookies) 
 {
   // Just set the HttpOnly attribute on the Forms authentication cookie
   // Skip this check to set the attribute on all cookies in the collection
   if (sCookie.Equals(authCookie))
   { 
     // Force HttpOnly to be added to the cookie header
     Response.Cookies[sCookie].Path += ";HttpOnly";
   }
 }
}

Note ASP.NET 2.0 provides an HttpOnly property on the HttpCookie class, which you can directly set to true.


References


Use the frame security attribute

Internet Explorer 6 and later supports a new security attribute on the <frame> and <iframe> elements. You can use the security attribute to apply the user's Restricted Sites Internet Explorer security zone settings to an individual frame or iframe. By default, the Restricted Sites zone doesn't support script execution. If you use the security attribute, it must currently be set to "restricted" as shown below:

<frame security="restricted" src="http://www.somesite.com/somepage.htm"></frame>


References


Use the innerText property

If you create a page with untrusted input, use the innerText property instead of innerHTML. The innerText property renders content safe and ensures that script is not executed.


References