This article is a part of Web API Security Champion series focused on API security vulnerabilities presented in a practical manner.
Broken Authentication
Description
Broken Authentication API security vulnerabilities ranks #2 on the OWASP TOP 10 API Security Risks list. These vulnerabilities are associated with various aspects of authentication, including:
- Authenticating the user within the application.
- Brute-force attacks against auth endpoint (e.g. credential stuffing, password spraying).
- Password policy.
- Security of network/transport layer communications.
- Storage and management of authentication tokens/session identifiers.
- Use of weak cryptography algorithms during the authentication process (e.g., for password hashing).
Broken Authentication risks are focused on authentication aspects, not authorization. There is a subtle difference between them: authentication verifies who a user is, while authorization verifies what they have access to. During the authentication process, the user must provide valid authentication factors (e.g., username/password, API token, one-time security tokens) to successfully identify themselves. After successful authentication, effective authorization checks can validate if the identified user should have access.
Impact
In the worst-case scenario, an attacker could take over any arbitrarily chosen account within the application by exploiting certain broken authentication vulnerabilities. By taking over another user’s account, the attacker could authenticate as this user and perform any actions in the context of this user within the application. This could have severe financial, reputational, and regulatory consequences for the affected company.
However, the worst-case scenario is not as common as other vulnerabilities related to authentication, such as weak password policies, lack of protections against credential stuffing/password spraying, and user enumeration issues. In my opinion, these vulnerabilities are underrated by developers and security engineers because in most cases they cannot be exploited directly by threat actors, so they are not considered as weaknesses in the application. In the real world appliacation, with a large number of users, the risk of such vulnerabilities increases because threat actors are more likely to utilize databases with leaked credentials from other apps or password spraying techniques and perform attacks against such vulnerable applications. As a result, user accounts with low complexity passwords, such as “12345678”, might be compromised in automated way. With a larger scale, such vulnerabilities may also have severe consequences.
A few examples of the broken authentication vulnerabilities reported via HackerOne can be found below:
- Authentication bypass on gist.github.com through SSH Certificates
- OneLogin authentication bypass on WordPress sites via XMLRPC
- Steam Account takeover bruteforcing SteamGuard
Case Study — Damn Vulnerable RESTaurant API
To demonstrate the vulnerability through a real case example and code, I’ve chosen my open-source project — Damn Vulnerable RESTaurant API. The project is available on my GitHub:
Damn Vulnerable RESTaurant is an intentionally vulnerable web application, with an interactive game focused on developers where they can investigate and fix vulnerabilities directly in the code. I recommend taking a look at this project if you’re a developer, an ethical hacker, or a security engineer. More about the project can be found in a dedicated article where I present it deeply.
Damn Vulnerable RESTaurant includes several broken authentication security issues, all implemented for educational purposes. To highlight a high-severity vulnerability, I’ve chosen to focus on a classical JWT issue.
Vulnerability Description
The restaurant’s API uses JSON Web Token (JWT) for authentication purposes. JWT is a standard for creating data with optional signature and/or optional encryption. Its payload contains JSON that asserts certain claims. The token can be signed using either a private secret or a public/private key pair. For security purposes, JWTs should be properly signed when created and validated before reading their content to ensure they were issued by a trusted party.
The JWT utilized by the restaurant’s API stores the username of the currently authenticated user and an expiration date to determine the token’s validity. However, the application fails to properly validate the JWT signature when it is received from a user. As a result, a potential attacker could create a malicious JWT and impersonate any user, effectively bypassing the authentication login mechanism. This vulnerability is relatively easy to detect and exploit but is also a high-severity security issue, as it allows complete compromise of the application’s API.
The code below presents the authentication logic that decodes JWT token to obtain username
from its content.
async def get_current_user(
token: Annotated[str, Depends(oauth2_scheme)],
db: Session = Depends(get_db),
):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(
token,
SECRET_KEY,
algorithms=[ALGORITHM],
options={"verify_signature": VERIFY_SIGNATURE},
)
username: str = payload.get("sub")
if username is None:
raise credentials_exception
In the above code, it’s not immediately clear whether the signature is validated because this mechanism is controlled by the verify_signature
flag, which takes the value of the VERIFY_SIGNATURE
variable. This variable is set in the following code in the config.py file:
class Settings:
JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", generate_random_secret())
CHEF_USERNAME = os.getenv("CHEF_USERNAME", "chef")
# someone needs to remember to set this variable to True in env variables
JWT_VERIFY_SIGNATURE = os.getenv("JWT_VERIFY_SIGNATURE")
The JWT_VERIFY_SIGNATURE
is intended to be taken from environment variables, which are typically stored in a .env
file. However, if these do not contain this variable, JWT_VERIFY_SIGNATURE
is set to None
. In Python, a None
value is interpreted as False
, which means the JWT signature is not validated at all!
Based on this, an attacker could create a JWT containing any chosen username without a proper signature. As a result, an attacker may authenticate as any user by placing a maliciously crafted JWT in the Authorization HTTP header.
Broken Authentication Proof of Concept
First, let’s log in as an existing user to the web application’s API using the credentials “Mike:kaylee123” as shown below:
After entering the username and password, a JWT token is generated and included in all HTTP requests that require authentication. The token is sent in the Authorization HTTP header. Here is the generated token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJNaWtlIiwiZXhwIjoxNzE1MDM1MzQ4fQ.xFj0SPBNxX3owFt0bKZ82jbSdMy-6yC1NcCNuhMd8Lc
JWT can be easily encoded or decoded using various tools, such as https://jwt.io/. However, I wouldn’t recommend using such online tools for decoding potentially sensitive data. In this case, it’s acceptable as Damn Vulnerable RESTaurant is a demo application intentionally created for educational purposes.
Using the above tool, the JWT can be decoded to reveal the payload which contains JSON data:
Now, let’s modify the sub
field to “Saul”. The tool automatically updates the encoded format of the JWT. The modified token can then be placed in an HTTP request and sent to the /profile
API endpoint to validate if it’s possible to authenticate as a different user. The Proof of Concept is shown below:
curl 'http://localhost:8080/profile' \
-H 'accept: application/json' \
-H 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJTYXVsIiwiZXhwIjoxNzE1MDM1MzQ4fQ.wMO55On-PfSVaDPQTlvItIUD9vctRsoMjjxY8lRO2-s'
This request successfully authenticates as “Saul” without needing his credentials! This demonstrates a clear case of broken authentication where the lack of proper validation for JWT signatures allows users to impersonate others.
Broken Authentication Fix
In this case, fixing the vulnerability is quite straightforward. It simply requires setting the JWT_VERIFY_SIGNATURE
variable to True
as the default value in the config file, as shown below:
class Settings:
JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", generate_random_secret())
CHEF_USERNAME = os.getenv("CHEF_USERNAME", "chef")
# someone needs to remember to set this variable to True in env variables
JWT_VERIFY_SIGNATURE = os.getenv("JWT_VERIFY_SIGNATURE", True)
With this setting, the JWT signature will be validated during the authentication process to ensure it was created by a trusted party. Additionally, setting a default value for JWT_VERIFY_SIGNATURE
allows developers to disable JWT validation in a local environment for testing purposes if needed. This configuration provides a secure and practical approach to managing application settings.
For the curious, I suggest taking a look at the generate_random_secret function, which is used to generate the JWT_SECRET_KEY
for signing the JWT payload. Do you think it can be brute-forced by an attacker, and how? 😉
Broken Authentication Recommendations
To prevent authentication security issues, I recommend the following actions during the design and implementation phases:
- Use standard authentication mechanisms: Don’t reinvent the wheel. Follow the “Keep It Simple Stupid” (KISS) design principle and implement straightforward authentication mechanisms if the application is simple. Follow OWASP Authentication Cheat Sheet.
- Understand authentication mechanisms and configurations: Gain a thorough understanding of the authentication mechanisms you implement, including all their flows. This will help identify potential security weaknesses and logic bugs at the design level.
- Follow Security Recommendations/Best Practices: Adhere to security recommendations and best practices dedicated to the chosen authentication mechanism. For handling JWTs, refer to Top 3 security best practices for handling JWTs.
- Implement Strict Rate Limiting and Captcha: Apply strict rate limiting and Captcha on authentication-related endpoints such as login, registration, and password reset to prevent automated attacks.
- Implement Multi-Factor Authentication (MFA): Add an additional layer of security by requiring more than one piece of evidence to verify a user’s identity.
- Enforce a Strong Password Policy: Implement a strong password policy requiring at least 8 characters and exclude the most common passwords to prevent users from setting weak passwords like “12345678”. Consider utilising Have I Been Pwned API.
- Return Generic Error Messages: Use generic error messages in authentication mechanisms to avoid giving away clues if user exist which could lead to user enumeration.
- Require Re-authentication for Sensitive Operations: For actions like changing the account owner’s email address or 2FA phone number, require users to re-authenticate.
- Implement Unit Tests: Cover potential security weaknesses in authentication logic with thorough unit testing.
These recommendations are based on my experience and relevant OWASP documentation. Lastly, always perform code reviews within your team, as colleagues may provide critical insights into the code and help spot security issues.
Broken Authentication Vulnerability Detection
Broken authentication security issues range from configuration problems, as previously mentioned, to custom business logic. Issues related to configuration or the use of insecure flags can often be detected by Static Application Security Testing (SAST) tools. For instance, Semgrep offers a variety of rules for detecting JWT issues across different programming languages. Additionally, SAST tools can be used to identify weak cryptography functions and algorithms.
However, more advanced business logic issues present challenges for both SAST and Dynamic Application Security Testing (DAST), underscoring the importance of designing and developing authentication mechanisms with security in mind from the outset. It’s also advisable to perform manual security assessments regularly, particularly on the most sensitive API mechanisms. While modern application security testing vendors may claim that broken authentication issues can be effectively detected in an automated manner, the reality is that custom implementations and complex business logic often require a mixed approach. The most effective strategy for detecting such issues typically involves combining automation with regular manual security assessments. This dual approach ensures a more comprehensive evaluation and helps uncover vulnerabilities that automated tools might miss.
Summary
In this article, I presented a list with a number of security vulnerabilities related to the Broken Authentication API Security Risk. This class of vulnerabilities contain various security issues that can sometimes be easily detected and remediated by changing a configuration flag, but often may require more advanced manual reviews and modifications to business logic. For any developer and security engineer, I suggest to follow the list of recommendation shared in the article to develop a secure API.