Moving Beyond The OWASP Top 10, Part 1: Race Conditions
Most organizations use the OWASP Top 10 as the standard against which they test for security vulnerabilities in their web applications. The problem with this is that there are a large (and growing) number of web vulnerabilities that don’t fit into the categories used in the OWASP Top 10. Furthermore, the categories are becoming more broad and harder to test against as they lack specificity that is needed to know exactly what to test for.
Fig. 1, Answers to the question, “What application security standards or models do you follow? Select all that apply.”
At Security Compass, we are constantly improving our processes to test for web vulnerabilities. We even build tools to make these tests faster and reach further (e.g. race the web, racetheweb.io, and a number of internal tools), so we can find the vulnerabilities that most others don’t test for. In this series of informational articles, Aaron Hnatiw, Senior Security Researcher at Security Compass, will be bringing awareness to these other types of vulnerabilities so companies of all sizes can improve the security baseline of their web applications.
The first article in this series will focus on race conditions, or manipulating the timing of actions to produce anomalous results. Hackers can use race conditions in numerous ways to create adverse effects, ranging from crashing an application to stealing money from a business.
OWASP Top 10, 2013
In order to get started, it’s important to take a look at most recent full release of the OWASP Top 10, from 2013, and understand what it currently includes. Here’s an explanation of each security risk with example vulnerabilities.
A1 — Injection: Injection flaws occur when untrusted data is sent to an interpreter as part of a command or query. The attacker’s hostile data can trick the interpreter into executing unintended commands or accessing data without proper authorization.
Example vulnerabilities:
-SQL Injection
-Direct command injection
A2 — Broken authentication and session management: Application functions related to authentication and session management are often not implemented correctly, allowing attackers to compromise passwords, keys, or session tokens, or to exploit other implementation flaws to assume other users’ identities.
Example vulnerabilities:
-Weak session IDs
-Sessions not invalidated on logout
A3 — Cross-site scripting (XSS): XSS flaws occur whenever an application takes untrusted data and sends it to a web browser without proper validation or escaping, resulting in attacker-controlled code running in a user’s browser.
Example vulnerabilities:
-Reflected XSS
-Stored XSS
A4 — Insecure direct object references (IDOR): When an internal implementation object (e.g. file, directory, user ID) is referenced directly, usually in the URL, an attacker may manipulate these references to access unauthorized data.
Example vulnerabilities:
-User ID included in URL
-File ID referenced in POST request
A5 — Security misconfiguration: When a component of an application or infrastructure is not configured securely, an attacker may be able to leverage the lack of protection to access unauthorized areas of the application or system. This is a very general “catch-all” vulnerability class.
Example vulnerabilities:
-Java Spring Security has CSRF protection disabled
-MongoDB open database (no password; default configuration)
A6 — Sensitive data exposure: When sensitive data (e.g. credit cards, passwords, insurance information) is not properly protected at rest or in transit, it may be exposed to attackers, who can then carry out outside attacks such as credit card fraud and identity fraud.
Example vulnerabilities:
-Missing TLS encryption in transit
-Passwords stored in cleartext in the database
A7 — Missing function level access control: When access control checks are not performed on the server when a function is accessed. If requests are not verified, attackers will be able to forge requests in order to access functionality without proper authorization.
Example vulnerabilities:
-Admin functions accessible through hidden parameter
-Horizontal privilege escalation
A8 — Cross-site request forgery (CSRF): A CSRF attack forces a logged-on victim’s browser to send a forged HTTP request, including the victim’s session cookie and any other automatically included authentication information, to a vulnerable web application. This allows the attacker to force the victim’s browser to generate requests that the vulnerable application thinks are legitimate requests from the victim.
Example vulnerability:
-Missing CSRF token in form submission
A9 — Using components with known vulnerabilities: If a component (e.g. libraries, frameworks, and other software modules) is not kept up to date or patched, it may be exposed to any number of known vulnerabilities, most of which have public exploits available.
Example vulnerabilities:
-Database software missing critical security patch
-PHP missing critical security patch
A10 — Unvalidated redirects and forwards: in the case where a web application redirects or forwards a user to another page or website, if the destination address is not validated (e.g. by comparing it to a whitelist), attackers can redirect victims to phishing or malware sites, or use forwards to bypass authorization checks.
Example vulnerability:
-Open redirect
Race Conditions (CWE-362)
So what are race conditions, and why are they worth talking about alongside the common vulnerabilities above?
From the OWASP Testing Guide:
“A race condition is a flaw that produces an unexpected result when the timing of actions impact other actions. An example may be seen on a multithreaded application where actions are being performed on the same data. Race conditions, by their very nature, are difficult to test for.”
Another way of putting it is: when the timing of actions impact other actions, events may happen out of sequence, resulting in anomalous behavior. This anomalous behavior is a race condition, which can result in a serious security vulnerability.
Exploiting Race Conditions
One instance of a race condition found by a security researcher (Egor Homakov) resulted in essentially unlimited money on Starbucks gift cards. How did it happen?
The exploit took advantage of the way that funds were transferred between gift cards. The server-side logic for transferring funds between accounts went something like this:
1. Check balance on card 1.
2. If sufficient funds, increment funds in card 2.
3. Decrement funds in card 1.
The above steps were programmed to happen synchronously (one step at a time), however these actions were actually performed in a multi-threaded asynchronous environment. To be clear, most modern computing environments are multi-threaded and asynchronous (even when using PHP—more on that later). So how does one exploit this race condition?
Simple: send a ton of requests for this function at the same time, thus creating a high probability that events would occur simultaneously. For example, it may result in the following execution logic:
1. 5 threads check the balance on card 1 (let’s say it’s $5).
2. 5 threads find that the balance is sufficient, and so they increment card 2 by $5, resulting in a total balance of $25.
3. 5 threads each try to decrement the funds in card 1 by $5, however the card can’t go below $0, thus 4 of the threads fail.
Wallet 2 has now gained $25 and wallet 1 has lost only $5.
For the complete write-up of this exploit, you can check out the blog post here.
Testing For Race Conditions
The best way to test for race condition vulnerabilities is to have access to source code, in what is known as a “white box” assessment. If you have access to source code, then it is much easier to look through all of the functions in the code and identify logic that is assuming synchronous actions, without the proper defensive programming techniques applied (more on this below). Once you find such a function, in order to validate the vulnerability, you would simply call that function a large number of times simultaneously, forcing the likelihood that a collision will occur.
Race conditions have a significant impact when accessing shared resources (e.g. databases, files, objects in code), as they can result in the corruption of said resource, or other unpredictable behavior. By simultaneously accessing and modifying a shared resource, the integrity of the resource can be affected as well.
In the case of “black box” testing (when you do not have access to the source code), testing for race conditions can be a little trickier, but not impossible. The same principles described above still apply, however finding functions may be more difficult, and testing them even more so. Thankfully, we have written and released an open-source cross-platform tool that does all of this for you, given a set of requests to a web application of any sort. Not only that, we have also created a deliberately vulnerable web application that you can use to test this tool on in the wild.
You can check out the tool, dubbed “Race-The-Web,” on GitHub here. If you’d like to test the tool out, you can find our deliberately vulnerable web application at RaceTheWeb.io.
If you are using Burp Suite, it is still possible to test for race conditions in a similar manner using Intruder, however the benefit of using Race-The-Web is that it is faster (thus more likely to trigger a race condition on more performant web applications), as well as free and open-source. Of course, use which ever tooling is best for you and your environment.
Defence and Mitigation
The key to preventing a race condition is to find a way to synchronize or otherwise strictly control the order of operations in potentially vulnerable functions and actions. The best way to do this is through locks. Most programming languages have a built-in locking functionality for data; for example, Python has “threading.Lock”, and Go has “sync.Mutex”. Refer to the documentation for your programming language of choice for more information. If the language has multi-threaded or asynchronous capabilities built-in, it should have some form of locking mechanism available to you.
You can also force synchronization in ACID-compliant databases. ACID stands for atomicity, consistency, isolation, and durability. Basically, to have the most reliable and effective general-purpose database, you will want to ensure that it implements all four components into its design. The key component for race synchronization is isolation, and the highest level of isolation is “serializable”. By setting your database’s level of isolation to serializable, it will effectively force all transactions to occur sequentially. While it will guarantee safety from race conditions in the database, the downside to this is that operations will be seriously slowed down. Not only that, it will typically involve using exclusive locks, which can cause deadlocks. In reality, you can still achieve a higher level of safety from race conditions by using a lower level of isolation; for example, “repeatable read” in MySQL (more information on isolation in MySQL is available here), however, doing so will not guarantee full protection. As with all security mitigations, you must balance business needs with security requirements, so you will likely have to find the balance that works for your organization.
Another point related to databases- if you have the option, always use inserts over updates in your SQL queries. The reason for this is because inserts have more error protection in most configurations, which prevents modifying a single database entry simultaneously.
You can also enforce locking at the file level. To do so, you can usually use a system call at the kernel level (“LockFile” in Windows, and “flock” or “lockf” in Unix), however this is likely abstracted well enough in your programming language of choice. Another method used by many modern applications like Microsoft Word is to create a temporary file (e.g. ~myfile.lck), which exists while a file needs to be locked from concurrent access. The program would then check for the existence of the lock file before granting write access to the true source file.
There are other ways to mitigate race conditions, such as implementing CSRF tokens in your web application, which makes it more difficult to automate the large number of requests needed to trigger a race condition. However, the most effective way to mitigate or completely remove race conditions is through locking, as described above.
For more information on race conditions, I expanded on this topic at Hackfest 2016:
Bonus- Race Conditions in Synchronous Environments?
Did you know that PHP is also vulnerable to race conditions, even though it has no native concept of asynchronous events or multi-threading in the language?
Because PHP is running on an asynchronous, multi-threaded platform (usually Nginx or Apache), that underlying platform is making a number of requests to PHP functions simultaneously. PHP itself is just a language that executes a set of functions. It doesn’t have any functionality for multi-threaded processing built into the language, however, if it attempts to access a shared resource outside of the scope of the code (a database entry, for example), it will do so as many times as it is called by the underlying platform.
Here’s an article outlining a practical example that demonstrates a race condition using PHP. The author has also provided the source code as well, so you can see how it works on your own.
What’s Next
In this article, we covered a security vulnerability that falls outside of the OWASP Top 10, showing why it is important for organizations to consider testing for web vulnerabilities outside of that scope. Look out for the next part in the series, where we’ll cover another vulnerability type that falls outside of the OWASP Top 10.
At Security Compass, our consultants work on the cutting edge of application security. We know how to find these vulnerabilities for you, while you focus on the core competencies of your business. Contact us to find out more about how we can help you move your application security practices beyond the OWASP top 10.
Follow Aaron Hnatiw on Twitter @insp3ctre.