5

What is considered good practice for application security wise when returning errors from a backend API?

I have inherited a project with a lot of technical debt, which I intend to improve.

One noticeable problem is that error codes are not informative at all. Sometimes it gives a 500 "error", with no further. Sometimes it gives a 200 with an error in the JSON. Sometimes it returns a 403 not authorized which is nicer.

I want to make debugging easier, so giving more information in the error would be useful, but I wonder if there are security considerations to take into account when returning more information in error responses.

Are codes considered better than informative messages? (Seems like security through obscurity).

Are there any recommended best practices?

4
  • Does this answer your question? Should HTTP status codes be used to represent business logic errors on a server?
    – gnat
    Mar 4, 2021 at 11:05
  • No. I was more interested in how much information is acceptable to be returned, or what would cause problems. Mar 4, 2021 at 15:45
  • 2
    You've already got a lot of answers here but one thing to keep in mind is that returning 403 can be used to 'feel around' a website looking for targets. For that reason the RFC states that you can use 404 instead. I've also seen some services (e.g. AWS) doing the opposite: returning 403 instead of 404.
    – JimmyJames
    Mar 11, 2021 at 17:22
  • @thank you JimmyJames. That is the sort of level I am looking at. The other answers have been helpful, but basically what I had already guessed through common sense. Mar 12, 2021 at 9:49

6 Answers 6

6

The key thing is being able to list all possible error messages and demonstrate that none contain problematic data.

So. throw new Exception("incorrect password") good. throw new Exception("problem with password : {ex.Message}") bad.

the argument being that ex.Message might be "sql error incorrect syntax near 'otheruserspassword'" or something.

Obviously if you return codes then its makes this kind of audit very easy, but it makes your api hard to use.

Your API, http or otherwise needs to be able to return errors to both the user (or logfile which is later read by a user) and to code, which may have to make some run time decision on the type of error.

So for example if you have an authentication error when a token expires, the code calling your api will want to recognise this, refresh their token and make the call again. The code wont want to parse the error message todo this, so a code is appropriate.

However, an unexpected error can't be dealt with by the calling code, only logged, a code is useless here and the error message can be bubbled up or logged for a human to read.

1
  • Our API is already hard to use, as there appears to have been little thought put into its design. Mar 4, 2021 at 15:46
4

The rule

The advice to not leak just any unchecked error message it's good, but it's not supposed to be taken as if it means that you should avoid sending any error message.

The main target for the "don't leak errors" advice is about unchecked error messages and exceptions details such as stack traces, because these can reveal things you don't want to reveal.

Any error message you write yourself (in its entirety) is perfectly fine to return. This of course assumes you don't go and willfully expose things you'd rather keep secret in those messages.

Some error states are sufficiently informative using only a HTTP status code, such as a 404.

I also tend to let more error information bleed through when in DEBUG mode. Since debug mode is only used by devs working locally, this means that outsiders don't have access to it.


Codes

Are codes considered better than informative messages? (Seems like security through obscurity)

The mapped message behind a code is effectively a vetted message, so it is safe in the sense of not spilling unintentional details to the end user.

The issue with codes is that the kind of code you'd want to use is exactly the opposite of being human-friendly, and this is going to impact your developers, who are going to have to look up all these intentionally counterintuitive codes.

What you can do, however, is log your complete stack trace (however you choose to do your logging: file, table, ...), and make sure to include a generated unique identifier in that log entry. Then you can pass this identifier to the end user, telling them something along the lines of "An error occurred. For more help, contact the developers and mention this reference: XXXX"

This makes sure that the end user's don't see the internal details, but they are able to direct a developer towards the correct log entry that contains all of the details, which developers are interested in (and allowed to see).


Example

This is just an example on how I approach error handling. This is by no means the only way to do it. I just wanted to add what I think strikes a reasonable balance between information and security.

  • I have a BusinessException class. I throw this to indicate that the message is allowed to be passed to the user. There are further derived classes from this, such as ValidationException or UserPermissionsException.
  • I put a filter on my api controllers, which catches every exception that bubbles up.
  • The exception handling logic checks the exception type and acts appropriately:
    • ValidationException - Return message, code 400
    • UserPermissionsException - Return message, code 403
    • Any not already unspecified BusinessException - Return message, code 500
    • NotFoundException - No message, code 404
    • Else, for any other exception:
      • If DEBUG: Return message, code 500
      • If RELEASE: Return default "an error has occurred" message, code 500

Notice how in DEBUG, business exceptions are no different from regular exceptions. But in RELEASE, business exceptions remain informative (because they have been vetted by the developers) whereas any unexpected exception is assumed to not have been vetted and is therefore obfuscated.

This setup means that right from the get go, my codebase catches and handles all exceptions, but by default only the last step exists so the real information is obscured when in release mode.

And then you expand the handling logic with more specific exception types, so that when you are developing your application/domain/... logic, you have a way of returning an error message to the user by using a custom exception type that the filter can handle.

This requires discipline to use the appropriate exception type. There's no way to technically stop an incompetent or malevolent developer from doing things like throw new ValidationException() { Message = myLeakyException.ToString() }.

9
  • 2
    Does your exception handling logic mean that a StockTooLow business exception would result in a 500 HTTP Status code? That doesn't seem like a logical mapping to me. Mar 4, 2021 at 13:53
  • I like your idea of "I also tend to let more error information bleed through when in DEBUG mode". Unfortunately our software has enough bugs in production that only appear with the right combination of data. Mar 4, 2021 at 15:43
  • @BartvanIngenSchenau: I dramatically oversimplified this example. You're right, but the general idea here is to have specific handling for specific exceptions. The general approach we take is 400 = don't bother retrying and 500 = our bad, maybe try again. "Stock too low" can be either case. If the end user has no control over the stock and it's erroneously unavailable, I'd say that's a 500 (since retrying later might work), but if they are the ones who are taking a nonsensical action, that's a 400.
    – Flater
    Mar 4, 2021 at 15:44
  • @wobbily_col: If you're unable to reproduce production bugs in other build (of the same version) and are now trying to design the software for in-production-debugging, then you've got significantly bigger fish to fry than deciding which error message to return.
    – Flater
    Mar 4, 2021 at 15:45
  • We have plenty of problems to fix, it is an absolute mess that I have been left, but it is making the company money. Not being able to easily diagnose errors slows down that process significantly, which is I want to fix that going forward. Ideally I would spit out the stack trace and variables in the error response, but I doubt that meets security guidelines. Mar 4, 2021 at 16:34
2

For me it depends on who will use the error messages.

For internal developers who work on the API you need to have a dev environnment with clear and very detailed error messages.

For API users what is needed is proper HTTP error codes to be able to understand what type of error happened with, when possible, a clear and concise message (maybe just a few words) to help understand what they did wrong when the error is on the user side.

2

Some ideas:

  • Give feedback to the user about what to change. Example: "bad request date must not be in the past" or "no permission to execute process".
  • Never show the exception stack trace. It can reveal your libraries and versions and help them to identify vulnerabilities. For unexpected exceptions you will probably want to log the stacktrace. If a hacker was able to read your logs you already have a problem.
  • Of course never send to logs or display any passwords, credit cards or other sensible data. It is even a good idea to have a safe toString() in the Credit Card Object (something like "credit card ending in 1234)
  • Http Codes are a standard so use them when applicable. But you may add more specific messages too.
  • 500 internal errors may not need an explanation. Something bad happen do not tell if it is a NullPointerException or StackTrace. At least not in production.
  • Separate use case scenarios from errors. Business concepts like "Unable to charge to your credit card" or "Product is out of stock" and "Password does not match" need to clear to the user but do no represent an error.

Some optional ideas:

  • Some people define a list of the system errors with a code. But it is quite cumbersome
  • Do the error messages need to be translated?
2
  • Yeah, i had assumed that the stack trace thing and password was an obvious no go Mar 4, 2021 at 15:42
  • But sometimes you log some system request o response or some objects. Then you move the logs may to some magnetic backup or S3 bucket and it ends being another point of attack
    – Borjab
    Mar 5, 2021 at 12:20
1

Honestly, it depends on the purpose of the application, and where it is being used.

The answer I will give will be for a sensitive application like a banking site. You need to be very careful with error messages. It is good to have informative error messages to a sense, but too much information can cause serious issues. One example of revealing too much information could be like on a login page, let's assume that it is a banking site. If at the login page when you type the wrong password it says:

"Sorry Wrong Password" or if your username is incorrect "Sorry Wrong Username"

Then you are causing a security concern. Especially on websites where users are not supposed to interact with each other or know other users/things like groups/friends (again like banking sites), you could map out user accounts that are supposed to be hidden through these error messages. These mapped accounts could be then attacked by an attacker later (Usernames/passwords could be brute-forced, or if emails revealed, could be attacked by phishing).

Having more broad error messages like "Please check your username and password" are a safer solution as the message is a lot broader.

If you are in the process of creating the site and it is not yet ready for publishing, having verbose error messages can be a great help for finding errors. Once you finish, go through and change the messages from verbose to very mild and remember, don't reveal more than you have or need to.

Again it depends on the application, and where / how it's used. Just do your best and realize the more information you give your users, the more you are giving to potential attackers.

1

One pattern I like for handling internal API errors is:

  • Log the error with all the data which could be useful for analyzing it in a database or logfile
  • Give that entry a unique ID under which it can be found. Like a GUID, for example. But when you expect users to read them to you over the phone you might want to opt for something shorter. I would not recommend to use sequential numbers, because that might leak information and might have synchronization issues if your API is distributed on multiple hosts.
  • Include that ID in the error message sent through the API

That way anyone who is authorized to read the local error database can take the ID from the error message and look up what really went wrong. And you also got data about errors which you can analyze retroactively. This allows you to monitor your application, notice when the error rate goes up before the end-users complaints reach you and often even find the cause for those errors before an end-user provides you with the proper steps to reproduce the problem.

1
  • Has anyone built this into a library, it sounds like a decent solution. Mar 11, 2021 at 10:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.