Security - Avoiding Cross-site Scripting (XSS)
In this article I will describe how cross-site scripting (XSS) works and how to write secure code to avoid this vulnerability. OWASP pages classify cross-site scripting as a high severity vulnerability. XSS is also one of the most frequently exploited vulnerabilities in web applications.
Description
Cross-site scripting (XSS) allows an attacker to inject code (typically HTML or Javascript) into the content of a website. When a victim views a compromised page, the injected code executes in the victim's browser. For example XSS allows an attacker to:
- steal user’s identity,
- disclosure end user files,
- install a Trojan horse programs,
- redirect the user to the other site,
- modify page content.
There are two well-known types of XSS attacks:
- Stored XSS – the injected code is permanently stored on the target servers.
- Reflected XSS – the injected code is reflected off the web server, such as in an error message, search result, etc.
How to find XSS
XSS belongs to the “injections” class of exploits. Therefore, searching for XSS is similar to searching for SQL injection. First, you try to inject different forms of malicious code into all inputs on a page. For example:
- <script>alert('XSS')</script>
- “><script>alert('XSS')</script>
- </script><script>alert('XSS')</script>
- <img src="source.not.exists" onerror=alert(document.cookie);>
You have to test all user inputs to make sure none of them are vulnerable to XSS:
- Input fields (text boxes, text areas, etc.)
- Query string parameters (GET parameters)
- POST parameters
- Hidden input fields
- Cookies
An XSS attack is successful if:
- Malicious code is executed immediately (reflected XSS)
- Malicious code is executed if user accesses the page where the input is rendered (stored XSS)
Protection against XSS in Kentico 8.1
There are three areas where you need to handle XSS:
- Kentico API
- Macros
- Transformations
Kentico API
Every developer is responsible for the code they write, including its security. The best protection from XSS is to encode the data your code works with. Kentico API provides several useful methods to help you with that.
Before you encode your data, there is one important thing to consider, and that is the context. Knowing the context determines the type of encoding you will need to apply to the data.
Usually, you don’t know what type of encoding to employ until you use the data in that context. When you get data from a database, you don’t know what context that data is being used in. So it wouldn’t make sense to encode at that point. The location where the data eventually renders is the moment where encoding comes in. At that point, you know exactly what context the data is used in, whether it is an HTML Attribute, an HTML Element, a URL, or JavaScript code.
The following text helps you choose the best type of encoding.
HTML context
Available Kentico API methods:
- HTMLHelper.HTMLEncode(string inputText) – converts special characters in a string into HTML entities.
- HTMLHelper.HTMLDecode(string inputText) – decodes HTML entities in a string.
- HTMLHelper.EncodeForHtmlAttribute(string inputText) – returns an HTML-encoded string to be used in an HTML attribute.
- QueryHelper.GetText(string name, string defaultValue) – returns an HTML-encoded query string parameter.
Method outputs:
Examples
Task 1: Create a custom page - "Page not found". Get the page path from the query string parameter "path" and display the path in a label.
Code vulnerable to XSS:
// Get the query string parameter
string page = QueryHelper.GetString("path", "");
// Set the query string parameter as a label text
lblPageNotFound.Text = String.Format("Page - {0} - was not found.", page);
Solution:
// Get the query string parameter
string page = QueryHelper.GetString("path", "");
// Set the encoded query string parameter as a label text
lblPageNotFound.Text = String.Format("Page - {0} - was not found.", HTMLHelper.HTMLEncode(page));
Since the label text is rendered as HTML code on the page, malicious input – such as
<script>alert('XSS')</script> – will be executed. In this case, you need to encode the input for rendering inside HTML code using
HTMLHelper.HTMLEncode.
Task 2: Create an HTML image element that displays a user’s avatar and a tooltip.
Code vulnerable to XSS:
// Get the avatar GUID from the query string
Guid avatarGuid = QueryHelper.GetGuid("avatarguid", Guid.Empty);
// Get the user ID from the query string
int userId = QueryHelper.GetInteger("userid", 0);
// Resolve the avatar URL
string avatarUrl = ResolveUrl("~/CMSModules/Avatars/CMSPages/GetAvatar.aspx?avatarguid=" + avatarGuid);
// Get the user name
string userName = UserInfoProvider.GetUserNameById(userId);
// Create the HTML image element that displays the user’s avatar and the tooltip
literalImage.Text = "<img alt=\"" + userName + "\" src=\"" + avatarUrl + "\" />";
Solution:
// Get the avatar GUID from the query string
Guid avatarGuid = QueryHelper.GetGuid("avatarguid", Guid.Empty);
// Get the user ID from the query string
int userId = QueryHelper.GetInteger("userid", 0);
// Resolve the avatar URL
string avatarUrl = ResolveUrl("~/CMSModules/Avatars/CMSPages/GetAvatar.aspx?avatarguid=" + avatarGuid);
// Get the user name
string userName = UserInfoProvider.GetUserNameById(userId);
// Create the HTML image element that displays the user’s avatar and the encoded tooltip
literalImage.Text = "<img alt=\"" + HTMLHelper.EncodeForHtmlAttribute(userName) + "\" src=\"" + avatarUrl + "\" />";
The image element is rendered on the page as literal text, therefore, malicious input– will be executed. In this case, you need to encode the input in HTML context again. Since the input is rendered into a
HTML attribute, use
HTMLHelper.EncodeForHtmlAttribute.
JavaScript context
Available Kentico API method:
- ScriptHelper.GetString(string text) – returns encoded text for use in JavaScript strings encapsulated with apostrophes.
Method outputs:
Examples
Task: Register a script which sets the selected value (from a selector dialog) to a text box.
Code vulnerable to XSS:
// Get selector identifier
string selectorId = QueryHelper.GetString("selectorid", "");
// Get UniGrid selected items
string retval = tagsUnigrid.SelectedItem;
// Register JavaScript on a page
ltlScript.Text = ScriptHelper.GetScript("wopener.setTagsToTextBox(" + selectorId + ", " + retval + "); ");
Solution:
// Get selector identifier
string selectorid = QueryHelper.GetString("selectorid", "");
// Get UniGrid selected items
string retval = tagsUnigrid.SelectedItem;
// Register JavaScript on a page
ltlScript.Text = ScriptHelper.GetScript("wopener.setTagsToTextBox(" + ScriptHelper.GetString(selectorId) + ", " + ScriptHelper.GetString(retval) + "); ");
The script, which is being registered on the page, contains input from the query string. If an attacker modifies the query string value to –
</script><script>alert('XSS')</script> – the malicious code will be executed. In this case, you need to encode the value in JavaScript context. Use the
ScriptHelper.GetString method.
Note: ScriptHelper.GetString(string input) encodes the input value and wraps it in apostrophes. If you only want to encode the input string without adding apostrophes, use
ScriptHelper.GetString(string input, bool encapsulate) with the
encapsulate parameter set to
false.
Macros
To avoid XSS in K# macros you can use either a macro parameter or a macro method.
Macro parameter (available in all macro types):
- Encode - converts special characters in a string into HTML entities.
Macro method (available in all macro types since Kentico 8):
- HTMLEncode(string text) - converts special characters in a string into HTML entities.
Output of the mentioned macro parameter and macro method is the same as the output of the HTMLEncode method.
Examples
Task: Create a text box using the Static HTML web part. Display the customer name from the query string as the default text box value.
Code vulnerable to XSS:
Customer name: <input name="customername" type="text" value="{%QueryString.name%}" />
If you want to encode an input in other context, you can use the following macro methods:
- JSEncode – encodes input for use in JavaScript code.
- UrlEncode – encodes input to be used as part of a URL.
Transformation
Transformations are a crucial part of the process used to display pages and other data in Kentico. Transformations produce HTML output which renders at the client. Therefore, the best XSS protection in transformations is to convert data into properly formatted HTML.
Available transformation methods:
- Eval(input, true) – displays database data. Set the second parameter to true if you want the output to be HTML-encoded.
- EvalForHtmlAttribute(input) – encodes input to be used as an HTML attribute.
Output of the mentioned transformation methods is equivalent to the HTMLEncode and EncodeForHtmlAttribute method outputs.
Example
Task: Create transformation used to display media library teaser image, followed by media library name and description.
Transformation vulnerable to XSS:
<%# <img src=\"" GetAbsoluteUrl(ValidationHelper.GetString(Eval("LibraryTeaserPath"), "")) "?width=180\" alt=\"" ValidationHelper.GetString(Eval("LibraryDisplayName"), "") "\" />") %>
<strong>
<%# ResHelper.LocalizeString(Convert.ToString(Eval("LibraryDisplayName"))) %>
</strong>
<%# ResHelper.LocalizeString(Convert.ToString(Eval("LibraryDescription"))) %>
Solution:
<%# <img src=\"" GetAbsoluteUrl(ValidationHelper.GetString(Eval("LibraryTeaserPath"), "")) "?width=180\" alt=\"" ValidationHelper.GetString(EvalHtmlAttribute("LibraryDisplayName"), "") "\" />") %>
<strong>
<%# ResHelper.LocalizeString(Convert.ToString(Eval("LibraryDisplayName", true))) %>
</strong>
<%# ResHelper.LocalizeString(Convert.ToString(Eval("LibraryDescription", true))) %>
There are three possible XSS vulnerabilities in the above transformations. The first problem is in the alt attribute. The attribute can be hijacked by inserting malicious code into the library display name. The other XSS’s can occur because the library display name and description are rendered directly into HTML.
To avoid XSS you can use the
encode parameter of the
Eval function, or you can use
EvalForHtmlAttribute method, as I demonstrated in the examples above.
Summary
If you write custom code, you should use appropriate methods to avoid cross-site scripting (XSS) vulnerability. Remember that XSS typically occurs when:
- Display name rendered in HTML is not encoded
- Script registered on a page contains non-encoded input
- Query string parameter values are rendered directly on page
- Macros contain non-encoded input
- Transformations render non-encoded input
Finding XSS vulnerabilities doesn't have to be as easy as it seems to be. Of course, the best way to find (not only) XSS is to use scanners, which automatically scan your website and find security vulnerabilities.
If you do penetration testing manually, you can use a handy Firefox plugin called RightClickXSS. The tool is very helpful because it offers a lot of predefined malicious inputs. With RightClickXSS, you don’t need to type the inputs every time. You can just choose one of the many predefined inputs.
You can also download all XSS prevention cheat sheets in PDF format.
I hope you’ll find this article helpful in making your sites more secure.
I’ll appreciate if you post your comments and questions using the form below.