The Blind Spots of Automated Web App Assessments

by Kevin Joensen on 02 Jan 2024 |
  • |
  • Introduction

    This post illustrates the importance of manual code review when doing application security. Relying heavily on automated tools can give you some grim blind spots that pose a significant risk to the application.

    This is not a post that states that automated application tools are useless, but merely an attempt to give a correct picture of where these tools are lacking. Nor is this a blog post telling you that scanner x is better than scanner y. The scanners picked for this test was purely of what we could get access to in a given timeframe while we assessed the scanners for other reasons.

    There are several great ways to utilize these scanners and have an impact. But your critical applications should be subject for a manual assessment.

    The issue of OWASP #1

    "A01:2021-Broken Access Control moves up from the fifth position; 94% of applications were tested for some form of broken access control. The 34 Common Weakness Enumerations (CWEs) mapped to Broken Access Control had more occurrences in applications than any other category." - OWASP TOP 10

    94% of applications are vulnerable. So this is quite crucial to catch right?

    We're going to write an application with 3 simple vulnerabilities from the Broken Access Control category which is the OWASP #1 vulnerability, and then attempt to explain why automated tools very often cannot figure out even simple vulnerabilities like this.

    This is vulnerabilities that may seem obvious, but they are very real and we've seen them all in the wild. They are fundamental to BAC style vulnerabilities and could be adjusted to fit most cases. Normally they would be even more complex than the examples, making the odds even worse for automated scanners.

    Note: we are not saying that all of these scanners claim to resolve these issues. It is simply to give you an idea of the usecase of automated scanners.

    Application setup notes

    To help the automated scanners along we made some extra application settings to avoid problems.

    1. Disable all CSRF checks
    2. Disable logout functionality

    The following 3 vulnerabilities were implemented in out test application VulnApp

    1. Password reset to account takeover
    2. Update another users email
    3. Retrieve another users credit card.

    The scanners we're going to test are the following:

    • Acunetix (https://www.acunetix.com/)
    • Burp Suite Automated Scanner (https://portswigger.net/burp)
    • Nuclei (https://github.com/projectdiscovery/nuclei)
    • AppScan (https://www.hcl-software.com/appscan)
    • Wapiti (https://wapiti-scanner.github.io/)
    • ZAP (https://www.zaproxy.org/)
    • Netsparker/Invicti (https://www.invicti.com/)

    (We will happily add more scanners here)

    If this option is available, we will try to include credentials so it can log in. In some cases we will try to provide urls in the DOM so the crawler can more easily get the routes.

    So let's spin up a django project and build out little test application.

    django-admin startapp vulnapp

    We're going to make all of our features in one simple view with the following user story:

    1. User can login to the application with a username/password combination
    2. User can update his own email
    3. User can see his credit card details which is automatically assigned to the user.
    4. User can reset his password

    Luckily for us, django fixes most of our problems with user registrations, CSRF tokens, passwords and more. We simply extend the user model to contain randomly generated credit card information for each user.

    Let's create our alice and bob user to get ready for some testing.

    >>> User.objects.create_user(
    <User: bob>
    >>> User.objects.create_user(
    <User: alice>

    We're also just going to create some simple creditcard information for both of them:

    >>> bob = User.objects.get(username="bob")
    >>> alice = User.objects.get(username="alice")
    >>> CreditCard.objects.create(card_number="1234", card_holder="Bob bobsen", security_code="1111", balance=1000, user=bob)
    <CreditCard: 1234>
    >>> CreditCard.objects.create(card_number="9876", card_holder="Alice Alicen", security_code="9999", balance=12000, user=alice)
    <CreditCard: 9876>

    So now we have set up everything for our vulnerable application called VulnApp. We can now proceed to login and see our very insecure credit card information:

    Alt text

    Here comes the first vulnerability!

    Bug #1 - Retrieve another users credit card

    This vulnerability is critical. We've seen this in a ton of examples just with other data. From being able to view medical records, to other PII. Even our blog post about the WithSecure elements vulnerability has the same mechanics as this bug.

    This is what happens when the user goes to the view_user page

    1. The user views his own user profile through the request /users/view_user/<uuid>
    2. The uuid for the credit card on his account is returned and a request is now sent to /users/card/<uuid>

    To exploit this issue we simply do the following:

    1. Use the endpoint /users/list_users/ to leak the victim users uuid
    2. Requests /users/view_user/<uuid> to the leak the victim users card id
    3. Requests /users/card/<uuid> to leak their credit card information.

    This is a very common insecure direct object reference or in other terms broken access control. We see this vulnerability of varying nature in many applications. This example was simply made very obvious.

    So great, everyone with simple skills can view other users credit card details. Let's fire some automated scanners at this issue and see how well it fares:

    Product Name Detected vulnerabilities
    Acunetix/Netsparker Not Detected
    Burp Suite Automated Scanner Not Detected
    Nuclei Not Detected
    AppScan Not Detected
    Wapiti Not Detected
    ZAP Not Detected

    Why is this not detected?

    The human mind will easily contextualize what is happening here. The view_user endpoint is obviously a user object and the card data is private. Furthermore since there is no way to view other users directly, we can conclude that this is not the applications intended behavior. Now when we view the view_user endpoint we can extrapolate that the credit card info is belonging to the user and viewing the response object, we can contextualize that the uuid belongs to specifc credit cards. If we can get other users uuid's then we might be able to get their info.

    The scanners simply can't do this. All of these rules are application logic which is laws defined by the programmer. One application might have a friend invitation system where you actually are allowed to view other users profiles. Another is a private application where you are only supposed to see your own profile. How does the scanner distinguish between these rules that are man made on a project-to-project basis?

    Furthermore the whole action of leaking uuid's through the list_user endpoint is an easy task for any vetted application security professional or any bug bounty hunter, but for the scanner, leaking things on a second route and utilizing it on another one is a tedious task.

    Bug #2 - Update another users email

    This vulnerability is also very straightforward. The endpoint /users/view_user/<uuid> allows for a POST method. In this you can update the victim users email address, without being authenticated as them.

    So the attacker can simply conduct the following steps for a critical account takeover:

    1. Use the endpoint /users/list_users/ to leak the victim users uuid
    2. Send a POST request to /users/view_user/<uuid> with email field set in the body
    3. The victim email is now updated through an insecure direct object reference
    POST /users/view_user/d758b9a2-eaa3-4ba0-a9ec-8033bb8e9a32 HTTP/1.1
    Host: localhost:8001
    Cookie: csrftoken=Qg7VQMNLrURCsimgXdDVzLwKRd3bkREy; sessionid=0zv5mhtjkpw5ef86rl0m0gtcqstzm35o
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 84

    image of email is changed

    This bug varies greatly but is also something that is often seen in applications with broken access control.

    Let's fire some automated scanners at this issue and see how well it fares:

    Product Name Detected vulnerabilities
    Acunetix/Netsparker Not Detected
    Burp Suite Automated Scanner Not Detected
    Nuclei Not Detected
    AppScan Not Detected
    Wapiti Not Detected
    ZAP Not Detected

    Why is this not detected?

    The reason is almost exactly the same as the first bug

    Bug #3 - Password reset to account takeover

    This vulnerability is also something that is spotted in the wild at times, varying in nature. The flow in the vulnerability we implemented is the following:

    Image of forgot password

    1. The forgot_password endpoint generates a uuid token that is used for the reset.
    2. This token is then sent by email with a link to the reset password form
    3. The user now clicks the link in the email and edits the password

    The problem here is that when we conduct the actual reset we have the following body:


    Alt text

    The issue here is that the backend does not verify that the token belongs to the username. So any valid token can reset any other users password. In our example we simply use bobs password reset token to reset alice password, and we now have an account takeover. This is a simple yet critical vulnerability allowing full account takeover.

    Let's fire some automated scanners at this issue and see how well it fares:

    Product Name Detected vulnerabilities
    Acunetix/Netsparker Not Detected
    Burp Suite Automated Scanner Not Detected
    Nuclei Not Detected
    AppScan Not Detected
    Wapiti Not Detected
    ZAP Not Detected

    Why is this not detected?

    Everything in the previous two vulnerabilities also applies here. The automated scanner would need to know that it is submitting a username parameter and have a list of all valid usernames. However this is the least of the scanners problems.

    It needs the secret-uuid-token-sent-by-mail. And since you automated web app scanner cannot read emails, it will never fetch the token and test the endpoint.

    But you need to think further than this. What if your application has any sharing features for unauthenticated users that are sent by email? Every communication flow that is out of bound will be unavailable for the scanner which will leave these untested.

    Even if it was the case that the scanner had everything it needed, then how does it distinguish this bug from an admin panel where you are allowed to set users passwords? Again, the developer decides the rules, and the intended rules. The scanner cannot reason about this.

    What now?

    We're not here to tell you to stop using automated tools. They're great for the job, but you should use them with an understanding of what might be missed if you don't couple them with manual assessments by a vetted application security professional. Automated scanners can be great for low-hanging bugs, but if you already write code in modern frameworks, you are very well protected against the common issues such as Cross-site scripting, SQL Injection and more. Furthermore nowadays browsers handle issues like Cross-Site-Request-Forgery with great success.

    So evaluate where to apply your automated scanners by metrics such as exposure, maturity and how critical the data in the application is.

    If you need any assistance within application security feel free to contact us at contact@baldur.dk