CWE-1295 is the weakness of a talkative system. The product fails to adequately prevent the revealing of unnecessary and potentially sensitive system information within debugging messages.
Every engineer has added a log line at two in the morning that says too much. This weakness is what happens when it ships.
How it happens
Debug messages exist to help troubleshoot an issue by exposing the internal state of the system. MITRE's hardware examples include internal memory array dumps, and boot logs reachable over interfaces such as UART through TAP commands or scan chain. The same applies to software logs, error pages and diagnostic endpoints.
There is a real tension. The more information in a debug message, the easier the debugging. But extra information can help an attacker decipher a vulnerability or understand the system better. MITRE says it can lower the security by obscurity factor, and while obscurity alone is insufficient, it can help as part of defense in depth.
The record gives two demonstrative examples that show how small the leak can be.
- In response to a Test Access Port (TAP) chaining request, the debug message reveals the full current TAP hierarchy in addition to a success or failure message.
- In response to a password-filling request, the system prints an elaborate message saying the entered password does not match the actual password stored in a named directory, instead of a plain granted or denied.
In both cases the user gathers extra unauthorized information about the system. The fix MITRE states is to make sure debug messages do not reveal additional details.
The observed examples show larger consequences:
- CVE-2022-34364: a Java-based SDK for TLS has a debug message with unnecessary information.
- CVE-2021-25476: the Digital Rights Management capability of a mobile platform leaks pointer information, simplifying an ASLR bypass.
- CVE-2020-24491: a processor generates a debug message that contains sensitive information, namely the addresses of memory transactions.
- CVE-2017-18326: modem debug messages include cryptographic keys.
Notice the range. An address helps an exploit land. A key skips the exploit.
Here is the software version in Python. This is a vulnerable pattern, shown for learning.
# VULNERABLE: debug output includes secrets and internal detail
def login(user, password):
record = db.get_user(user)
if record is None or not check(password, record.hash):
log.debug("login failed for %s: expected hash %s, got %s",
user, record.hash if record else None, password)
return "Password does not match the value stored in users table"
return "ok"
What an attacker gains
MITRE rates the likelihood of exploit as medium. Listed consequences are reading memory, bypassing protection mechanisms, and gaining privileges or assuming identity, with the full set of confidentiality, integrity, availability, access control, accountability, authentication, authorization and non-repudiation affected depending on context.
Information disclosure rarely ends the story. It is usually the first step that makes the next one cheaper, like the ASLR bypass above.
How to find it
The record lists automated static analysis, often called SAST, which can find some instances by analyzing source code or compiled code without executing it, typically by modeling data flow. Combine it with these checks:
- Search for debug and verbose logging that includes variables named like secret, key, token, password, address or pointer.
- Review what error responses say to an unauthenticated caller. They should say little.
- Check which debug interfaces remain reachable in production builds, and what they print.
- Read your own boot and startup logs the way an attacker would.
Secrets that end up in logs can also be caught by scanning. The free scan at gold.safeguard.sh/scan checks dependencies in your browser with no login, and a secrets scan covers the code itself.
How to fix it
MITRE's mitigation for the implementation phase: make sure a debug message does not reveal any unnecessary information during the debug process for the intended response.
In practice:
- Decide what each message is for and include only what that purpose needs.
- Return generic results to callers, such as granted or denied. Keep detail in a protected log.
- Never log secrets, keys or raw credentials, and avoid logging pointers or addresses unless the build is strictly internal.
- Make debug verbosity a build or configuration setting that is off in production.
- Protect any debug channel with authentication, and keep sensitive output off it entirely.
The fixed version:
# FIX: generic response, minimal log entry
def login(user, password):
record = db.get_user(user)
if record is None or not check(password, record.hash):
log.info("login failed for user id %s", user)
return "Invalid credentials"
return "ok"
The log still records that a failure happened and for whom, which is what an operator needs. It no longer holds the hash or the password, and the caller learns nothing about where credentials are stored.
Try it yourself
Secrets in logs and code are a common result of this weakness. The demo shows how secrets scanning surfaces them.
Click through it at your own pace, or open it full screen.
Quick check
Why is a detailed password failure message a problem?
It tells the user about the system, such as where the real password is stored. A plain granted or denied is enough.
Can obscurity alone protect a system?
No. MITRE says it is insufficient on its own, but it can help as part of defense in depth.
What can SAST find here?
Some instances, by analyzing source or compiled code without running it.