The API Map That Lied: Overflow and Race Conditions Ahead

Hidden endpoints can be a serious risk to web application security. These endpoints are often overlooked or unintentionally missed in the codebase, and can easily go unnoticed by developers. While they may seem harmless, they can expose critical vulnerabilities if left unchecked.

While using our new tool Inspectra, to review a public project, I discovered three CVEs tied to endpoints that were missing from Swagger API Documentation due to the [ApiExplorerSettings(IgnoreApi = true)] attribute.

Inspectra, a tool developed by our team at AppSec, is designed to extract all endpoints from a project’s codebase, whether in static code, build artifacts, or runtime. This tool helps uncover potentially overlooked or undocumented endpoints.

In this blog post, I’ll explain why hidden endpoints are a security risk and how they can be missed by developers, as we'll see in the scenario that follows.

The Scenario: Endpoints in an ASP.NET Core Project

In this case, I was reviewing the SimplCommerce project, an e-commerce web app built with ASP.NET Core, where the developers were using Swagger API Documentation to document their endpoints. To control which endpoints appeared in Swagger, they used the [ApiExplorerSettings(IgnoreApi = true)] attribute to hide entire controllers from the documentation.

The developers initially used this attribute because they were still working on certain endpoints and didn’t want them to show up in Swagger. However, the issue arose when they forgot to remove the attribute after development was completed.

To uncover all the endpoints, I used our tool Inspectra. I compared the endpoints found in Inspectra with those listed in Swagger and identified several that were missing from Swagger due to the attribute.

I then conducted a code review on these missing endpoints to assess their security and find potential vulnerabilities.

Integer Overflow

Integer overflow occurs when a program tries to store a value in an integer variable that exceeds the maximum limit of that variable type. In simpler terms, if a number is too large (or too small, in the case of negative numbers) to fit into the allocated space for the integer, it "wraps around" and results in an unintended value.

For example, if you have an 8-bit integer that can hold values from -128 to 127, and you try to assign it a value of 127+1, the integer will overflow and the value will wrap around to -128.

Unsigned vs Signed Integer Overflow

Unsigned integers only represent non-negative numbers. For example, in an 8-bit unsigned integer, the range is 0 to 255, so adding 1 to 255 causes it to overflow back to 0. If you enter a negative value like -1, it is interpreted as the maximum positive value (255 in this case). For instance, unsigned int value = -1; would result in value becoming 255 (the maximum unsigned int value in the example). Signed integers can store both positive and negative numbers, but the range is smaller. For an 8-bit signed integer, the range is from -128 to 127. Adding 1 to 127 causes it to overflow to -128, like int value = 127; value += 1; which overflows to -128.

Before diving into the details, let's take a moment to appreciate this perfect meme:

The First Vulnerability: Exploiting Integer Overflow to Manipulate Product Quantity

In this scenario, I identified an issue with an endpoint that handled a quantity parameter for product orders. The developers had taken a positive step by validating that the input should be a positive number.

But!!!, they missed a critical flow: integer overflow.

To make this clear, imagine an e-commerce website with the following products:

  • iPhone priced at $1000

  • iPad priced at $999

Here’s how the attack happened:

  1. Add an iPad to the cart with a quantity of 2,147,483,647 which is the max for 32-bit signed integer.

  2. Now, go back to the product page and add one more product, causing it to overflow to -2,147,483,648.

  3. Now, reduce the quantity to -1, by adding 2,147,483,647 so the quantity will be quantity = -2,147,483,648 + 2,147,483,647 which will equal -1. The total price now is: total_price = 999 × -1 which will equal = -$999

  4. Add an iPhone to the cart, priced at $1000.

  5. The final total price for the entire cart is: total_price = 1000 + (-999) = $1

Now I can checkout and purchase an iPhone for just $1 instead of $1000.

CVE-2024-50944

In the world of web applications, speed is a must. Developers want their websites to be fast like the Flash. But there’s a hidden villain called “race condition”.

How Race Condition Becomes A Vulnerability

The term "race condition" has been mentioned for the first time in 1954, when David A. Huffman used it in his doctoral thesis "The Synthesis of Sequential Switching Circuits", Huffman used "race condition" to describe when the outcome of a sequential circuit depends on which event happens first, making the result unpredictable. The Huffman idea was the brilliant key! Which continues to challenge developers and security researchers until today.

Race conditions can turn into vulnerability when multiple processes or threads try to access or edit the same resource at the same time, and that means attackers can exploit these timing issues to cause unexpected behavior like sneaking past restrictions, as you’re about to see.

What is a Race Condition Window?

A race condition window is the moment when two or more operations can overlap before a system updates its state.

In the example shown, consider a 10% discount meant for single use, if multiple requests arrive almost at the same time, each checks whether the code is still valid before it’s marked as used. The first two requests can both succeed, granting a total discount of 20% rather than the intended 10%. Subsequent requests may fail once they are late more than 3ms and the system finally updates its state, but by then, the attacker has already doubled their benefit.

Keep in mind that not every race condition window has the same length. It might be as short as 3ms, but the actual timing depends on the complexity of the logic between the initial validity check and the final state update. If there is more code execution, validation steps, or database queries involved, the race window could become longer.

The Second Vulnerability: Exploiting Race Conditions to Oversell Products

This time, I discovered that by sending multiple requests at almost the same millisecond, it became possible to purchase 2 units of a product when there should only be 1 available in stock.

This happened due to a race condition in the checkout logic, which allowed multiple users to simultaneously check and update the stock before it was properly adjusted.

At first look, the code might seem fine. The code checks if the product is in stock, and then subtracts one after completing the purchase. But! nothing is preventing two separate requests from doing these checks at the same time.

Let’s make this clear, what happens when User A and User B press Checkout at the exact same time:

  1. User A and User B both check if StockQuantity is enough. Since both see StockQuantity = 1, they both think it’s safe to continue.

  2. While User A is still running through the additional logic, User B passes the same check and moves ahead.

  3. Both users reach the update step and subtract 1 from the stock. This results in the stock going from 1 to -1, and both orders are processed successfully.

Finally, now you have purchased the same product from both accounts, while there was only 1 in stock.

CVE-2024-53476

Fixing the Race Condition

In most cases, the most effective and simplest approach is to use an Atomic SQL statement, although the best option fully depends on the specific scenario. Now, I know what you’re thinking: “Atomic? Isn't that something from nuclear physics?”

Nah, the Atomic statement ensures that all the steps of reading, checking, and updating happen together in one step.

This simple Atomic SQL statement does the following:

  1. Check if the stock is available StockQuantity >= @Quantity

  2. Update the stock StockQuantity = StockQuantity - @Quantity

And I know what the curious hacker inside you is thinking:

"But wait, what if two users press Checkout at the exact same millisecond? Will atomic still be safe?"

Yes, it will.

SQL databases handle this with an atomic update operation. If two transactions try to update the same row at the same time, the database ensures only one of them succeeds by applying a row-level lock. The second transaction waits until the first one is finished and the lock is released, and if the stock is no longer enough StockQuantity < @Quantity, the update fails.

The Third Vulnerability: Exploiting Broken Access Controls to Post Unauthorized Reviews

Logical bugs like Broken Access Control are often missed by automated security tools because they require logical context to detect.

Most e-commerce sites enforce a rule ”Only users who purchased a product can leave a review” But in this case, the developers only enforced this rule on the Frontend, not the Backend.

The backend never checks if the user actually purchased the product. As long as I can send a properly formatted request, I can leave a review for any product.

So basically:

  1. Changed the EntityId in the POST request to a product I never purchased.

  2. Send the request, and the review added successfully.

CVE-2024-50945

References

Timeline

  • October 2, 2024: Vulnerability discovered and reported to SimplCommerce.

  • October 8, 2024: Follow-up email sent to the vendor.

  • October 11, 2024: CVE ID request submitted to MITRE.

  • October 15, 2024: Vendor replied to the initial report.

  • November 14, 2024: CVE IDs assigned by MITRE.

  • December 21, 2024: Affected versions patched by the vendor.

  • December 24, 2024: Public disclosure of the vulnerability.

Last updated