Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / security

401 and/or 403 and a Short Story of Secure RESTful

4.75/5 (4 votes)
3 Jul 2019CPOL3 min read 6K  
401 and/or 403 and a short story of secure RESTful

The problem of what HTTP codes to use inevitably comes when designing RESTful services. There is a bit of subtlety, however, with regards to the 401 and 403 status codes. Here and there, you will read advices suggesting to use:

  • 401 for when an access token isn’t provided, or is invalid
  • 403 for when an access token is valid, but requires more privileges

and people will follow these advices without considering the security part of the problem.

And the problem is that such an implementation can leak sensitive information. Here is my reply to the above mentioned article. In cryptography, this is known as oracle and can lead to pretty serious attacks.

If the attacker sees a 403 status code, after a huge array of failed attempts with 401, this means that whatever the attacker tried passed the authentication phase, but failed the authorization. Bingo, they just figure out a valid set of credentials (or tokens or ...) and your system said that in "clear text". It is similar to the "Username or password invalid" best practices (and don't read things like this!).

How to avoid the problem? Stick with one status code (e.g., 403) for both cases (failed authentication and failed authorization), avoid being too user (or client) friendly. You can log the incident and return a unique request ID to the client. If a genuine client reports the problem, that request ID should help finding more details in logs. And of course, track/monitor all the authentication/authorization failures for anomaly detection purposes.

Now the fun part, all of the above as a mathematical proof. Let's assume a system where the maximum number of credentials possible is N, has M registered users and K of those users have access to a specific resource that the attacker tries to brute-force, N>M≥ K. Let's consider the following propositions/events:

  • A - Guess a credential by accessing the given resource.
  • B - The system is designed to return: HTTP 200 for valid credentials with access to the resource, HTTP 403 for valid credentials with no access to the resource, HTTP 401 for invalid credentials. For simplicity, we can say B={200}∪ {403}∪ {401}.
  • C - The system is designed to return: HTTP 200 for valid credentials with access to the resource, HTTP 403 for valid credentials with no access to the resource or invalid credentials. Or C={200}∪ {403}

In other words:

  • Cardinality of A given B is: K possible cases of HTTP 200, plus M-K possible cases of HTTP 403. Grand total is M.
  • Cardinality of A given C is: K possible cases of HTTP 200, which is also the grand total.

Now let's compute probabilities:

P(A | B)=MN and P(A | C)=KN

Obviously (because M ≥ K):

P(A | B) ≥ P(A | C)

which means, a system designed like B gives greater chances to the attacker to guess credentials. Obviously, it doesn't matter when all the registered users have access to the resource (i.e. K=M). End of discussion!

P.S. The way I computed probabilities may look a bit superficial. For a more mathematically elaborated version, visit the original post.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)