Analysis of two types of web attacks: Cross-Site Scripting and Cross-Site Request Forgery.
Cross-Site Scripting (XSS) is a common web application vulnerability that occurs when a web application returns unsanitized input to the front end of an application. In an XSS attack, an attacker takes advantage of this vulnerability by inputting malicious code, generally in the form of JavaScript, through the browser. This can lead to the attacker stealing information from a user, redirecting users to malicious pages, or taking control of their browser!
The three categories of XSS attacks are Stored XSS, Reflected XSS, and DOM-Based XSS, which differ in how the payload is stored and executed.
Stored XSS
Stored XSS attacks are generally considered the most serious. A stored XSS vulnerability occurs when a web server saves an attacker’s input into its datastores. Because this input is saved, it may be harder for a user to detect. In a worst-case scenario, this input will be saved, and then returned to numerous victims. The below image provides a basic overview detailing this process.
A common example of a Stored XSS attack would be a poorly designed comment function. If a developer does not properly sanitize a user’s comment, it may be possible for an attacker to add arbitrary JavaScript to their comment. If this were to occur, then any time their comment was loaded by the server, the attacker’s code would execute in the victim’s browser.
Reflected XSS
Reflected XSS occurs when a user’s input is immediately returned back to the user. This return may come in the form of an error message, a popup, or a search term. In these instances, the payload is never stored by the server. Rather, it exists as a value in the URL or request. Despite this, these payloads still pose a risk to users. Through social engineering, an attacker could spread their payload to unsuspecting victims as shown below.
DOM-Based XSS
DOM is an abbreviation for Document Object Model. The DOM is used to help scripts and the underlying webpage interact. However, when user input is interpreted by the DOM, an attacker is able to inject arbitrary code. These types of vulnerabilities do not cause any changes in how the server responds. Rather, these attacks are completely client-side.
For example, a web page may use client-side Javascript to customize a welcome page, displaying their name based on a value in the URL. Depending on how the javascript runs, an attacker may be able to replace the name value with a malicious script. If a victim loaded the page with the attacker’s code, the vulnerable javascript may execute the code!
Identifying XSS Vulnerabilities
Let’s look at how we can uncover XSS vulnerabilities in a web application.
As with any vulnerability, it is important that we investigate any potential input areas. When looking at the application, consider all possible fields. Comments, usernames, custom settings, and parameters all provide great starting points.
Once we have identified a potential inject point, we can begin testing various inputs to create a proof-of-concept payload (POC). A POC payload will demonstrate that an issue exists, without causing damage. The most basic POC payload is shown below.
<script>alert(1);</script>
If a web server is not properly sanitizing user input, this will return a pop-up box similar to the below image.
If this payload does not work, that does not necessarily mean the system is secure. In fact, many systems will take a flawed approach to protection and block certain words. If a blocklist is in effect your request may be blocked, or your <script>
tags could be removed. If this happens, we can look to alternative mechanisms to execute JavaScript. In fact, there are numerous ways we can execute code, without ever using a <script>
tag. Below are some potential workarounds.
<img src="X" onerror=alert(1);>
<b onmouseover=alert(1)>click me!</b>
<body onload=alert('test1')>
Preventing XSS Vulnerabilities
Similar to SQL injections, XSS is preventable with both application-level firewalls and sanitization. Similar to the SQL injections, firewalls should be used to aid defense, but should not be used as your only line of defense.
Sanitization
Sanitization is the process of removing/replacing problematic characters with safe versions. Depending on the backend language, there may or may not be built-in functions to aid in this process.
However, if these functions do not exist, we can generally succeed in preventing XSS attacks by removing characters such as <
, >
, "
, =
, and potentially dangerous keywords.
Rather than remove characters, we can also replace them with HTML-encoded versions of the characters. This allows us to retain the characters, but remove their capacity to affect the page’s HTML.
For example, the <
character would be converted to the “<” string. The browser will render this string as the “<” character, but it will not interpret it as actual HTML, preventing the attack.
It is important to note, however, that depending on how the data is used, this type of escaping may not be enough. It’s important to consider all potential avenues for an attack.
Cross-Site Request Forgery
Cross-Site Request Forgery (CSRF) is another class of vulnerability focused on poor session controls and session management. When we log into Codecademy, we create an active user session, and this session allows us to interact with our courses, profile, and the site in general. By sending the right request, we can change our password, email, and what classes we are enrolled in.
However, in some cases, the way these sessions, and the requests we send, are handled is flawed. Sometimes, the requests sent by an application aren’t unique. As such, it’s possible for an attacker to craft a special request and send that to a user. If the user interacts with the crafted request, and sessions aren’t handled properly, an attacker may be able to make changes on behalf of a user.
Let’s look at an example.
Suppose our web application allows users to change their password via the following link, where [USER PASSWORD] would contain the new desired password.
https://oursite.com/changepassword.php?new_password=\[USER PASSWORD]
If the developers didn’t consider the impact of cross-site request forgery attacks, a threat actor may be able to create a new password link and send it to the user. When the user opens the link, they would initiate the password change, but with the attacker-supplied password! This would allow a hacker to take over their account!
Because the request considers nothing but the current session, a user would have their password changed.
Preventing CSRF
While the impacts of CSRF can be large, they are relatively easy to mitigate.
One of the simplest ways to prevent these attacks is to add a CSRF token. This token is a unique value that is added to each request. This value is dynamically generated by the server and used to verify all requests.
Since this value is unique for every request, and constantly changing, it is nearly impossible for an attacker to pre-create the URLs/requests for an attack.
While a CSRF token can prevent many malicious requests, it can still fail. If an application is vulnerable to XSS a hacker could use their XSS attack to extract this token! In some cases, we may want a user to manually enter additional information prior to a critical request.
For example, prior to changing a username, email, or password, we may want the user to enter their current password. By ensuring the request has the correct password, we can ensure that an attacker isn’t able to compromise a user, even with XSS.
Conclusion
While cross-site scripting and cross-site request forgery both pose a serious risk to users, and the systems they interact with, developers can take many steps to prevent these attacks. However, when developers fail, attackers can execute arbitrary code, steal information, and cause serious harm. Through careful control of sessions, we can prevent cross-site request forger attacks. And through careful handling of data and sanitization, we can easily prevent cross-site scripting attacks.