Broken Access Control refers to a vulnerability where an attacker, whether they are a user of the application or not, gains unauthorized access to resources they should not have. This vulnerability has become the most common in 2021, leading to its prominent position at the top of the OWASP Top 10 list of vulnerabilities.
Bypassing access control checks by modifying the URL
Let’s examine an example web application that enables users to list their documents. The URL to access this list is https://architecturestack.com/my-documents
Each document title on the list refers to a separate view of the document, which is available at the address: https://architecturestack.com/documents/{documentId}.
One of the most common Broken Access Control issues is when we can manually modify the URL of a page by changing the document ID and gain access to documents belonging to other users.
The same vulnerability applies to JavaScript applications and api calls.
Protections
Design and implement business access rules
Ensure that you design and put into acceptance criteria clear business rules that define access to documents and implement the necessary business logic to enforce those rules within your code.
Test it every time
When testing the application, it is essential to thoroughly check access to actions with various scenarios, including different roles, no roles assigned, and unauthorized access. This comprehensive testing will help identify any vulnerabilities or misconfigurations in the access control implementation, ensuring that proper restrictions and permissions are enforced accurately.
Do not expose predictable identifiers
To prevent exposing incremental or predictable identifiers, it is recommended not to expose such iterative identifiers in URLs. One automated method for identifying vulnerabilities is by traversing all available URLs and attempting to replace IDs in a predictable manner. Instead, it is advisable to use randomized tokens or non-sequential identifiers like:
http://architecturestack.com/documents/122b766e-1dc6-11ee-be56-0242ac120002
By using randomized tokens or non-sequential identifiers, you can mitigate the risk of unauthorized access to resources through guesswork or enumeration attacks. These randomized tokens should be sufficiently complex and unpredictable, making it difficult for attackers to guess valid identifiers and gain unauthorized access.
Implementing randomized tokens or non-sequential identifiers in your URLs adds an additional layer of security and reduces the likelihood of successful enumeration or access to unauthorized resources. It is a recommended practice to enhance the overall security posture of your application.
Deny be default
Let’s examine a block of code that validates if a user is authenticated and possesses the necessary permissions to view their own document list. Provided these conditions are met, the code subsequently displays a page featuring the user’s list of documents.
def my_documents_view(request):
if not request.user.is_anonymous:
return HttpResponse('Unauthorized', status=401)
if not request.user.is_allowed(to=ACL_VIEW_OWN_DOCUMENTS):
return HttpResponse('Forbidden', status=403)
lists = ListService()
template_data = {"documents": lists.get_user_documents(request.user)}
return render(request, "documents/list.html", template_data)
What’s the issue with the existing code? The challenge here lies in the potential for inadvertent oversights by the software developer. By default, the webpage may be displayed without the necessary access rights validations, leading to unintended access if such checks are not diligently implemented.
Protections
The most straightforward solution is to establish a default setting that restricts access to all views. Consequently, only users possessing specified roles, as detailed in the view implementation, would be permitted to gain access.
class MyDocumentsListView(ListView):
service = DocumentsModel
template_name = 'documents/list.html'
allowed_roles = ['ACTIVE_USER']
In order to intentionally grant universal access to a page, we must expressly declare this within our code. This can be achieved, for instance, by including the ‘GUEST’ role within the allowed_roles
attribute, as demonstrated in our example.
class UserLoginPageView(LoginView):
template_name = 'user/login.html'
allowed_roles = ['GUEST']
In this simplistic example, the issue may not appear to be of significant concern. However, as the software evolves, incorporating increasingly complex rules pertaining to permissions and roles, the risk of error rises. In such a scenario, a precautionary approach is beneficial. We would rather encounter an ‘access denied’ error on a page the user should be able to access, than inadvertently grant access to a page intended to remain concealed.
Don’t Repeat Yourself
Adopt a generic solution and propagate it across the system. A well-designed, singular piece of code has less potential for error compared to numerous hastily duplicated solutions. While unique business constraints should be woven into your domain model code layer, recurring access rights checks should leverage a unified mechanism.
Replaying JWT Token
JWTs are stateless, implying that there are no server-side copies that can be invalidated or cross-verified. Frequently, they encompass a list of resources that the bearer is permitted to access. This arrangement presents a predicament wherein access to these resources cannot be rescinded until the token has reached its expiration. Furthermore, there’s the potential security risk that an attacker could pilfer the token directly from the application.
Protections
Short Expiry Times
Keeping the JWT expiration (the “exp” claim) as short as possible limits the time window in which a token can be replayed.
Token Blacklisting
In some cases, it may be beneficial to maintain a server-side blacklist of invalidated tokens, although this undermines the stateless nature of JWTs.
One-Time Use Tokens
For particularly sensitive operations, a one-time use token could be issued. Once used, it can’t be used again, preventing replay attacks.
Secured transmission
Always use secure channels (HTTPS) to transmit tokens. This makes it significantly more difficult for an attacker to intercept the token in the first place.
Token binding
This involves binding the JWT to the client’s TLS session, so even if an attacker obtains the JWT, they won’t be able to use it over a different TLS session.
Tampering JWT Token.
Here we have an example JWT token:
It consists of three distinct parts, each separated by a dot.
The header contains metadata about the token, including the algorithm used to generate the signature.
{
"alg": "HS256",
"typ": "JWT"
}
The payload contains the claims or pieces of information being passed about the user and the token.
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
The signature is used to verify that the sender of the JWT is who it says it is and to ensure that the message wasn’t changed along the way.
While the token’s design inherently includes a signature for security, certain intricate technical scenarios can potentially render it vulnerable.
Signature Secret Disclosure
If an attacker gains access to the secret key used to sign the JWT, they can generate their own tokens, or modify existing ones, and sign them with the same secret. This allows them to impersonate any user or elevate their privileges.
None Algorithm Exploit
In certain JWT libraries, an “alg” (algorithm) header of “none” was incorrectly accepted, which indicates that the token is not signed, allowing anyone to create tokens at will. Although this has been patched in most libraries, systems using outdated libraries might still be vulnerable.
Algorithm Switching
Some JWT libraries support both symmetric (HMAC) and asymmetric (RSA, ECDSA) algorithms. An attacker might be able to change the “alg” header from an asymmetric algorithm to a symmetric one (for example, from RSA to HMAC), and then use the public key (which is not secret) to sign the token. This is because, in the case of symmetric algorithms, the same key is used for both signing and verifying the token, while asymmetric algorithms use a private key for signing and a separate public key for verification.
Weak Secret Key
If the secret key used for signing the JWT is weak or guessable, an attacker can brute-force the secret and then tamper with the token.
Protections
Keep secret key secure.
Ensure robust protection measures are in place for your secret key, including safeguards against potential exposure through URL guessing.
Use strong keys
Employ a complex and unpredictable private key to thwart potential attackers from deciphering it through guessing tactics or brute force attacks.
Secret key rotation
Regularly rotate and change the secret key used to sign tokens. If an attacker does manage to obtain a key, this limits the time they can misuse it.
Check the algorithm claim in header
Always verify the algorithm used to sign the token, and ensure adherence to your predetermined choice, rejecting any token signed with a different algorithm.
Use libraries wisely
Use well-reviewed and widely accepted libraries for JWT token handling. These libraries are more likely to be updated and patched regularly, reducing the chances of security flaws.
No sensitive data
Avoid incorporating sensitive data into the token. JWT tokens are publicly viewable and can be read by anyone as their payload is merely encoded by default, not encrypted. While it’s possible to encrypt the payload, it’s generally better to explore other alternatives for managing sensitive data.
Cookies
hould you employ Cookies or any other mechanism for storing session identifiers created on the server, it’s crucial to meticulously manage session deletion or marking on the backend. Merely eliminating the session ID from your frontend application doesn’t suffice, as it may be susceptible to future reuse. This can pose significant risks, particularly if you have additional data tied to the backend session, such as Access Control List roles, permissions, and so forth.
Preventions
Ensure Proper Logout
During a logout operation, make sure that the session cookie is not only deleted on the client-side but also invalidated on the server-side. This prevents old or stolen cookies from being reused.
Short Expiration Times
Cookies with long lifetimes can increase the risk of an attacker using them for unauthorized access. Setting short expiration times can help to mitigate this risk.
Sensitive data
Refrain from incorporating sensitive information within cookies. Always maintain a cautious stance and avoid relying on data stored and transmitted by frontend applications.