In volume 1 of the 2020 Application Protection Report, we argued that the immature state of API security indicates that, while APIs might be well understood as individual components, the security implications of APIs at an architectural level remain unclear to many in our field. The increasing frequency of API incidents shows that the risks are starting to catch up with the more obvious rewards of API-based architectures. If we are going to effectively implement the various technical controls necessary to reap these rewards, it is critical to recognize that, at an architectural level, API-based applications are not just another new trick, but represent a foundational transformation in the nature of web applications.
Our evaluation of API security as underdeveloped in practice, despite the deep body of knowledge that exists about API controls, stems from the kinds of incidents we saw between 2018 and 2020. The three most prevalent categories for API incidents included no authentication at API endpoints, broken API authentication, and broken API authorization.1
Since APIs are designed for machine-to-machine communications, many APIs represent a direct route to sensitive data, meaning that most API endpoints need at least the same degree of risk control as end-user machines and conventional servers.
The problem surrounding a complete lack of authentication is, we hope, an obvious one. Since all REST APIs require a URI at which they can be reached over the web (such as api.example.com/v1/ or www.example.com/api/), attackers can quickly discover and abuse any API endpoint lacking authentication. This article focuses on lessons learned from incidents in which system owners attempted to put authentication in place, but attackers or researchers were able to circumvent it. In addition to revealing some pitfalls and unexpected failure modes, we intend to use these quick case studies to help show how authentication and authorization differ for APIs compared with more traditional devices on networks, such as servers.
API Authentication: The Obscurity Fallacy
The list of authentication failures in our API incident data set covers organizations from a wide variety of sectors and countries. The platforms they run and the ways they implemented their APIs vary considerably as well. Given this, and the relatively small number of incidents (n = 67) we have to draw from, we will focus on the themes unifying these disparate cases to look at the general ways that API authentication goes bad, as opposed to diving into the details of a specific environment and implementation. Since security is both hard and complex, we won’t be naming organizations—our intention is not to shame but to raise the bar by spreading the word.
The first theme that emerges from the authentication failures we’ve seen is the mistake of security through obscurity—that is, leaving valuables hidden but accessible, with fingers crossed. Because APIs are designed for connecting computers and not human consumption of information, it is still unfortunately common for development teams to assume that no malicious human is ever going to communicate with the API endpoint.
We looked at one incident in which a researcher was able to gain hundreds of thousands of dollars in free credits for a mobile application by chaining together several requests to the app’s mobile API. In nearly every step, the application generated a token that was designed to prevent abuse, but the tokens never expired and could be reused an unlimited number of times. For a legitimate user, working through the mobile app user interface, there was no sign of anything wrong, but all it took for the attacker to recognize the opportunity was looking under the hood and inspecting the requests and responses from the API. In addition, the API transmitted the promotional codes that the researcher used for credits in cleartext. By itself, this is not a glaring issue, but it does reinforce the impression that the developers assumed that API traffic was unobtainable to malicious users.
Another scenario we looked at also exemplified this assumption that API traffic is somehow inaccessible to humans. In this case, a web application offered the option of using another platform’s credentials for authentication, like Google or Facebook (which, incidentally, is one of the more common uses of APIs today, something we’ll address in a future article), or gave the user the option of using a four-digit numerical passcode. Let’s leave aside the fact that this is a weak secret for a system that contains users’ full names, dates of birth, genders, contact information, and physical descriptors. The password reset process would send a new four-digit passcode to the account email on request, but would also include that code in cleartext through the API traffic. Knowing this, an attacker could script the reset process based on email addresses from other data breaches and harvest new passcodes at scale.
Obscurity is never a substitute for security, but in the case of APIs, it is actually the antithesis of security.
These kinds of incidents make it clear that the development teams behind these applications assumed that APIs were difficult to find. In all likelihood, they were prioritizing both application functionality and development speed over security. In other words, they “just had to get it to work.” This is a practical illustration of our thesis from volume 1 that until we understand the implications of API-based architectures, the existence of robust controls and standards in isolation will not solve the problem. These incidents also illustrate that just because APIs are obscure to normal users does not make them hard to find. Obscurity is never a substitute for security, but in the case of APIs, it is actually the antithesis of security.1
API Authentication Failures With Authorization Impacts
The other theme of unsuccessful API authentication is that when these authentication models fail, they tend to manifest as authorization failures or escalations of privilege. On a logical level, this seems intuitive; successful authentication is a precondition for successful authorization, since a system needs to know who the user is in order to determine what actions they can take. At the same time, it is an example of a system failing openDefinition: a failure state for authentication systems that allows the user to proceed using the application in question. Opposite of failing closed. in an obscure but dramatic way. It appears that these applications were designed and implemented with functionality and speed to market as the only priorities.
The most egregious case we saw of this was a system in which the API server generated an authentication token for the client upon connection, as it should, but then the server never subsequently checked the token. The result is that an attacker could use brute force authentication tokens to retrieve data about every user in the system—in this case, extraordinarily sensitive data about customers’ children.
No design principle or different set of priorities can explain this; it is simply a bug. It does, however, indicate that this app and the device to which it connected probably did not go through rigorous code review or testing, and that the people who developed it were almost certainly focused on functionality over security.
In another case, an operating system for network infrastructure contained a bug in which a malicious HTTP request to the API resulted in the attacker gaining an authorization token that granted them administrator permissions, although this exploit only worked if an admin was already authenticated to the system.
There were also a handful of incidents from the “no authentication” category, in which the next step for the attacker was to gain an authorization token that allowed them to make requests of other APIs or use other functionality in the web app.
The point is that, in theory, the two systems—authentication and authorization—should function separately, but a failure in one is driving a failure in the other. In practice, many systems manage secrets surrounding sessions and permissions in similar ways, and this kind of cascading failure is not rare. However, permissions and authorization are more important for API connections than for human users, both because malicious human users are increasingly beginning their attacks at API endpoints and because those endpoints are designed expressly for the rapid and easy transmission of machine-structured data.
This kind of cross-pollination of authentication and authorization failure magnifies the impact of any given bug or misconfiguration. The onus to design and build these systems with security in mind has never been more critical, and yet these systems are most often built with security as an afterthought.
These complexities also illuminate an issue that is central to the question of API security. Taken to their logical conclusions, API-based systems not only blur the boundaries of trust between system owners, partners, and users, but they blur the boundaries of the web application itself. As systems become more disintegrated and place greater emphasis on connections between disparate elements, determining the likelihood and impact of an event, as well as locating the burden of due care, becomes significantly more difficult. Added to the fact that APIs represent a significant expansion of the attack surface, and the task ahead for security teams starts to look daunting.
How to Get API Authentication Right
Other than a glaring lack of awareness about API risk, one of the themes that recurs the most in comparing API incidents is the lack of standardization and the ways that home-built processes play into the different failure modes we’ve encountered. This is why we are joining everyone else in the API security conversation to recommend embracing standards and established libraries, and avoiding reinventing the wheel. Put another way, one of the core problems in API security is that, as an industry, we are treating APIs as though they are a protocol with detailed specifications, when they are really just a way of thinking about a problem.
With that in mind, we recommend OpenID Connect for authentication and OAuth 2.0 for authorization. OpenID Connect is, in fact, a specific implementation of the OAuth 2.0 base protocol that uses JSON Web Tokens (JWTs) to exchange and evaluate identity claims. Both are well-established protocols for which information, advice, and solutions abound.2
However, these protocols are not immune to risks either, though they tend to be misconfigurations or poor implementations rather than vulnerabilities in the protocols themselves. In the next few months, we will review case studies in API authorization failures as well as release a JWT hardening guide with tactical advice for ensuring that your API access control systems are built as soundly as possible.
To mitigate these types of attacks, consider implementing the following security controls based on your specific circumstances:
- Use established libraries and best practices for API authentication, such as OpenID Connect.
- Use an API gateway to manage API authentication and authorization.
- Conduct frequent inventories to maintain awareness of attack surface.
- External scans of the environment can help identify vulnerabilities in practice, especially in complex environments.