コンテンツにスキップ

Internal Server Error

Overview

500 Internal Server responses indicate that the server encountered an unexpected condition that prevented it from fulfilling the request. This error response is a generic "catch-all" response used when an unhandled exception is triggered while processing a request.

Internal Server Errors are usually benign. However, an unhandled exception may be indicative of a security issue, such as a SQL injection.

Recommendation

Consider adding consistent error handling mechanisms which are capable of handling any user input to your API. Ideally, the error would return a 4xx status like '400 Bad Request', and provide meaningful details to end-users so they understand how to fix their request.

Also, ensure that in production those error messages are not providing information useful to an attacker, like a stacktrace.

Examples

Internal Server Error usually stems from unexpected inputs that are not handled properly by an endpoint. Let's look at a FastAPI example:

from fastapi import FastAPI

app = FastAPI()
DB = ["✨", "🔥"]

@app.get("/items")
async def read_item(idx: int):
    return DB[idx]

The API consists of a single endpoint /items which takes idx as a query parameter. The endpoint returns an item from an in-memory database. Let's try to call the endpoint with a few different inputs:

$ curl 'localhost:8080/items?idx=0'
"✨"
$ curl 'localhost:8080/items?idx=1'
"🔥"
$ curl 'localhost:8080/items?idx=2'
Internal Server Error

This last query returned in an internal server error. This happens because the endpoint is dereferencing an index out of bounds, which leads to an unhandled exception. However, our client just sees 'Internal Server Error', and has no idea what's going on.

The 500 Internal Server Error response might have some helpful information included, but will usually be some opaque text telling you that something went wrong. Was there something wrong with your request? Did you use an incorrect URL?

Every consumer of the API now has to go through some or all of these steps: - Look closely at the request for obvious errors and try again - Start looking on stack overflow, Google, ... for similar issues - Contact support and wait... - Maybe it was just temporary? -- try the request again - ⚠️ A retry might also be dangerous - Did the request partially complete? Will my credit card be double billed now (assuming this was a payment endpoint)?

This cost, in terms of time and frustration, is compounded by every consumer that runs into the 500 response code with the API. The feeling of empowerment that developers gained using the API will start to fade away.

Adding extra input validation prevents unhandled exceptions. Here's how we would fix our problematic endpoint by validating that the query parameter idx is within bounds:

from typing import Optional
from fastapi import FastAPI, Query

app = FastAPI()
DB = ["✨", "🔥"]

@app.get("/items")
async def read_item(idx: int = Query(0, ge=0, lt=len(DB))):
    return DB[idx]

Now if we send the problematic request again (idx=2), instead of a cryptic message we will see a helpful 400 Bad Request response with details on what was wrong with our request:

{
  "detail": [
    {
      "loc": [
        "query",
        "idx"
      ],
      "msg": "ensure this value is less than 2",
      "type": "value_error.number.not_lt",
      "ctx": {
        "limit_value": 2
      }
    }
  ]
}

Since a 500 Internal Server Error is an unhandled exception, it might be tempting to return the stack trace as part of the 500 Internal Server Error response. This is helpful when running in development mode. Software developers often add stack traces to error messages, as a debugging aid. Stack traces can tell the developer more about the sequence of events that led to a failure, as opposed to merely the final state of the software when the error occurred.

(Only) in test environments while fuzzing with Mayhem for API, we recommend sending back stack traces so that we can give you more detailed bug reports. However, 500 Internal Server Error should not return any details or stack traces in production! This is a common server misconfiguration, and may expose sensitive information to an attacker.

Information in the stack trace like file paths or function names can reveal the structure of the application as well as any internal libraries it relies on. In addition, the error message at the top of a stack trace can include information such as SQL queries that the application relies on, allowing an attacker to fine-tune a subsequent attack.

References