In this 3-part blog series, I’ll provide deep dive instructions and specific examples on how you can avoid common security threats by hacking your own API. This second post covers the anatomy of some of the most common API security hacks.
In our last post, we prepared our API hacking weaponry – we looked at the basics of Web-based APIs (HTTP, Message Formats, Security Standards) and how to discover the attack surface of an API. This time around we’re going to start with some basic attacks. Let’s see if we can get our API out of balance and joviality – welcome back!
The immediate purpose of an API attack
Before we fire our trebuchets and spout our boiling canola oil, let’s take a quick step back and consider the actual immediate purpose of an attack. Although the high-level goal of an API hack might be to get access to credit card numbers or user passwords – a single attack is often just a step on the way. To get to those credit card numbers, we have to learn about a system’s underpinnings and its weaknesses. We have to pry around to find out how it works and what its vulnerabilities are.
A common approach is to provoke an API with unexpected content in the hope that its inability to handle it correctly will teach us about its inner workings. For example, getting the target API to return a database error for an unexpected input is of great value; we now know that there is a database behind the scenes (SQL Injection vulnerabilities?) and, if we’re “lucky”, we might even know a little about its vendor / version / schema / etc. – all pieces that help us put the vulnerability-puzzle. And the more verbose the error message, the better for us. There is plenty of information online that will tell us of any known security vulnerabilities of any specific server/framework/OS setup.
As a Security Tester, you would most likely be doing the same; trying to get your target system to behave in a way that helps hackers get under its skin – but in your case you’ll tell your development team so they can fix the issues instead. Right?
Fuzzing is a classic way of attacking (and testing!) a target system; the basic idea is to repeatedly generate randomized input for a target API until we stumble upon something that provokes an error message or system. With today’s tools, it’s easy to set up a script that runs millions of requests in the hope that some combination of input parameters will achieve our purpose.
As a tester, set up assertions that validate the response to be consistent and concise, use the correct HTTP error code, and do not expose any sensitive system information.
Invalid Input Attacks
Where fuzzing has little structure to it, invalid input attacks are slightly more intelligent as they aim to provoke a target system by sending input that it doesn’t expect. The better an API is described via metadata or documentation, the easier it is to do this efficiently. Examples would be sending strings when the API expects numbers, sending text when it expects dates, or for any field, send nothing or send something too long. Given our knowledge of HTTP this can be done at the protocol level also by sending invalid HTTP Headers and values – targeting both the APIs HTTP layer and its own logic.
Just as for fuzzing, a security tester can automate boundary/invalid-input tests and configure assertions that validate the error message. Be sure to take into account that, for usability reasons, your API might provide “friendly” error messages – make sure that these are “generic” and don’t give away any details of the underlying technologies.
While the above mentioned attacks generally hope to provoke unexpected system behavior and “bad” error messages, some input can be directly malicious and target both the API itself and its hosting infrastructure. Message parsers have been a common target for these attacks, with XML parsers being the prime example. Consider the following “XML Bomb” attack:
When a misconfigured or vulnerable XML parser attempts to expand the lol9 entity in the actual document, the recursive nature of the “lol” entities will most likely result in out-of-memory errors. This could possibly bring down the entire server and leave it in a state where it might be vulnerable to attacks.
Another possibility for vulnerabilities and malicious input is where files are uploaded – does that target system process these files “safely”? What if the system expects a PDF but you upload a shell script instead – will that get executed on the server? Or get propagated to a 3rd party when they try to access the file (that they thought was a PDF) via another API call? Will corrupt files be handled gracefully or give away information about the underlying file system?
As a Security Tester, you can easily attempt to provoke your system by “playing the hacker” and sending malicious data as described. Obviously you need to be careful so you don’t bring down your system (unless that’s your goal) – but on the other hand going “full throttle” might be the only way of ensuring that your system isn’t vulnerable to these kinds of attacks. At least you’ll know what hit you.
According to OWASP, injection vulnerabilities are the most common types of security vulnerabilities out there. Many of the attacks that have gained media attention in recent times exploited related weaknesses. In OWASP-speak, an injection attack is one where an “Attacker sends simple text-based attacks that exploit the syntax of the targeted interpreter. Almost any source of data can be an injection vector, including internal sources.”
Let’s have a look at a simple SQL Injection example. Let’s say we have a REST API for a pet store (the one described in the Swagger document in the previous post of this series):
The actual implementation of the API uses the ID of the pet (“123” in the example URL) to look up the data in a database using the following SQL statement:
“SELECT * FROM pets WHERE petID='” + petId +”‘”;
When the request is as above, this expands to
“SELECT * FROM pets WHERE petID = ‘123’”
Which is totally fine.
Now consider what happens if an attacker sends the following instead
which with the above (admittedly naive) logic becomes
SELECT * FROM pets WHERE petID = ‘’ or ‘1’ = ‘1’
Whoa!! Suddenly we have a SQL statement that will give us all pets in the database, which could result in both a severely overloaded server (if the database of pets is large) and pet owners gaining access to other clients’ pet information – the horror! Although somewhat contrived, this simple example shows the danger; a more malicious SQL injection will attempt to augment SQL statements to delete data, change passwords, etc.
As a Security Tester, injection attacks are a little bit more challenging than the invalid-input related attacks we looked at to start with. First of all, you need to know and understand a little about the inner workings of the target API to choose the “right” injection attacks (this is what a hacker would use other attacks to find out). Then you have to decide on what a “successful” injection attack would look like. For the above example, you would probably want to validate that you either get a 404 (Not Found) or a 400 (Bad Request).
Cross-Site Scripting Attacks
Let’s finish this post with a look at another very common way to attack a system and its users – XSS (cross-site-scripting) attacks. The name of the attack is somewhat misleading; you might spontaneously think that this applies to web applications only, but since APIs are often driving the interface of a web application, it makes sense to test that the API keeps things as invulnerable as possible.
XSS attacks can be either “reflective” or “persistent” – the underlying “objective” of either being the insertion of a script that is executed in the browser/system of an unsuspecting user.
For reflective XSS, the script is often included in link in an email – when the user clicks the link the script is sent to a target server and returned to the client where it is executed:
So what is the API’s role here? Well, in today’s world, the actual request to the victim’s server that returns data is an API call that is formatted on the client – if your API “reflects” its input in any way (example: a search API might include the search-string in the returned result), you will need to decide if it’s up to the API or the client to handle input that is potentially malicious. Then test the API accordingly.
For persistent XSS – the malicious script is injected into a backend system. It is then retrieved and executed by a separate client at a later time (this could also be seen as a client-injection attack):
Once again, it’s not obvious if an involved API should be handling this – or the client UI. For example, let’s say you have an issue tracker with an API where you can create issues. If someone inserts a script into the description of an issue, is it up to the API backend to remove/escape that script before it is returned to an API client? Or is it up to the client to perform this handling?
One way to look at it is that this XSS attacks are basically injection attacks – a script is injected into the system – and then either executed on the client, or somewhere during the processing on the backend, and in either case it’s a potential vulnerability and something you need to assess (before a hacker does it for you).
Whatever you decide, it is easy to set up the appropriate tests, Vectors of common cross-site scripting attacks are available online, allowing you to set up data-driven tests with these vectors using your favorite API testing tool (cough, cough) to run and perhaps even automate corresponding tests.
Thus concludes our lesson this week, outlining the various weapons in your API hacking arsenal. But this is only a portion of the methods at your disposal – join me again next week to look at some more common attacks and how to test for them. More importantly, I’ll provide advice on how to set up your API security tests and how to stay up-to-date with the latest security vulnerabilities so that the only person hacking your API is you.