CWE-209, Generation of Error Message Containing Sensitive Information, is simple to describe. The product produces an error message that includes sensitive information about its environment, users or associated data.
MITRE lists PHP and Java among the platforms where it appears, and it is not tied to any language. Any application that shows raw exceptions, database errors or file paths to the caller can have it.
How it happens
Developers want errors to be useful, so they print the exception. In development that is helpful. In production the same detail goes to strangers.
MITRE's observed examples include:
- CVE-2007-5172: a program reveals a password in an error message if an attacker can trigger certain database errors.
- CVE-2008-2049: a POP3 server reveals a password in an error message after multiple APOP commands. MITRE notes this might result from another weakness.
- CVE-2008-4638: a composite where an application running with high privileges lets a user name a restricted file to process, and the resulting parsing error leaks the file contents.
- CVE-2008-1579: user names can be determined by requesting a nonexistent blog and reading the error message.
A common vulnerable pattern:
# Vulnerable: returns the raw exception to the client
@app.route("/report")
def report():
try:
return render(load(request.args["file"]))
except Exception as e:
return f"Error: {e}", 500
An exception such as a file-not-found can contain the full server path. MITRE's own example is a path traversal attempt (CWE-22) that yields the full pathname in the error.
What an attacker gains
MITRE lists confidentiality: the attacker reads application data. Often the information is not the end goal. It reveals details that let the attacker launch another, more focused attack, or discloses private data stored on the server.
How to find it
MITRE describes four detection approaches.
- Manual analysis. Needs domain knowledge, and the number of error conditions may be too large to cover completely in the time available.
- Automated analysis. Can detect certain idioms such as exposed stack traces or pathnames. It generally cannot judge violations of business rules or privacy requirements.
- Automated dynamic analysis. Fuzzing, robustness testing and fault injection against the running app with many diverse inputs.
- Manual dynamic analysis. Trigger error conditions that are unlikely in normal use: low memory, insufficient privileges, an interrupted transaction, disabled connectivity to a back end.
A quick check you can do today: send a malformed ID, an oversized value and an unexpected content type to each endpoint and read what comes back.
Click through it at your own pace, or open it full screen.
How to fix it
MITRE's mitigations, in plain steps:
- Show minimal detail. Messages should contain only what the intended audience needs, balanced between too cryptic and too revealing. Do not reveal how the error was determined.
- Log the detail instead. If you need detail, put it in log messages, and think about what happens if attackers can read the logs. Never save highly sensitive information such as passwords to logs.
- Be consistent. Avoid messages that tip off an attacker about internal state, such as whether a user account exists.
- Handle exceptions internally. Do not display errors with sensitive information to the user.
- Make sensitive data easy to spot. Use naming conventions and strong types, and keep sensitive and non-sensitive data separate in structures. MITRE rates this as defense in depth.
import logging, uuid
log = logging.getLogger("app")
@app.route("/report")
def report():
try:
return render(load(request.args["file"]))
except Exception:
ref = uuid.uuid4().hex[:8]
log.exception("report failed ref=%s", ref)
return f"Something went wrong. Reference {ref}.", 500
The user gets a reference to quote to support. The stack trace stays on the server.
Quick check
Is a stack trace in a 500 page really that risky?
It can expose paths, library versions, queries and credentials. Each one helps an attacker choose a better next step.
Where should the detail go instead?
Into access-controlled logs, with passwords and other highly sensitive values kept out.