CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-21439
5.30.01%

CVE-2026-21439: Gaslighting the Auditor with Terminal Escape Injection

Alon Barad
Alon Barad
Software Engineer

Feb 27, 2026·7 min read·7 visits

PoC Available

Executive Summary (TL;DR)

The 'badkeys' tool, used to identify weak cryptographic keys, trusts input too much. Attackers can inject invisible terminal commands into key metadata. When a researcher scans a malicious key, the tool prints the warning, but the injected commands immediately clear the screen and reprint a "Safe" message. It's a Jedi Mind Trick for your terminal.

A classic terminal escape sequence injection vulnerability in 'badkeys' allows attackers to manipulate scan results. By embedding ANSI control characters in cryptographic metadata (like DKIM records or SSH comments), an attacker can overwrite critical security warnings with harmless-looking text, effectively blinding the auditor.

The Hook: Who Watches the Watchers?

There is a profound irony in security tools that are, themselves, insecure. We rely on utilities like badkeys to act as the gatekeepers, scanning our cryptographic infrastructure for weak keys, compromised moduli, and known exploits. We trust the output implicitly. If the tool says "RED ALERT," we panic. If it says nothing, we sleep soundly.

But what if the key itself could talk back? CVE-2026-21439 is a vulnerability in badkeys (versions <= 0.0.15) that turns the scanner into a liar. It's not a remote code execution bug that will pop a shell on your box (probably). It's more subtle and, in some ways, more insidious. It allows a malicious cryptographic key to manipulate the terminal of the person scanning it.

This is a Terminal Escape Sequence Injection. It's an ancient class of bug, dating back to the days of physical teletypes, that refuses to die. By embedding specific byte sequences in data that the tool naively prints to the screen, an attacker can control the cursor, change colors, or clear lines. In the context of a security audit, this means an attacker can make a critically vulnerable key look perfectly safe, effectively gaslighting the auditor.

The Flaw: Trusting the Untrustable

The root cause here is a failure to sanitize "metadata" before shoving it into stdout or stderr. The badkeys tool is designed to parse complex structures: DKIM DNS records, SSH public keys, and JSON Web Keys (JWKs). These structures often contain human-readable fields—comments in SSH keys, key IDs in JWKs, or raw parameter data in DKIM records.

The developers made a fatal assumption: that these fields were just text. In reality, they are byte streams controlled by the adversary. When badkeys encounters a problematic key, it wants to be helpful. It constructs a warning message effectively saying, "Hey, check out this value: [INSERT VALUE HERE]."

Here is the logic flow of the failure:

If that interpolated value contains \x1b[2K (ANSI escape for "Clear Line") followed by \r (Carriage Return), the terminal executes those commands. The tool technically prints the warning, but the terminal deletes it faster than the human eye can register, replacing it with whatever text the attacker provided next. It’s the digital equivalent of someone whispering a warning in your ear while simultaneously setting off an airhorn.

The Code: The Smoking Gun

Let's look at the code. In Python, f-strings are great, but they don't sanitize control characters by default. The vulnerability existed across multiple modules where external data was printed.

The Vulnerable Pattern (Before):

In badkeys/checks.py (and similar files), the code would take a value found in a DNS record or key file and print it directly:

# Vulnerable implementation
if potential_issue:
    sys.stderr.write(f"Warning: potentially dangerous value detected: {user_input}\n")

If user_input is \x1b[31mEVIL\x1b[0m, your terminal prints "EVIL" in red. If it's \x1b[2K\rAll systems operational., your terminal clears the line and prints "All systems operational."

The Fix (After):

The fix, implemented in commit 635a2f3b1b50a895d8b09ec8629efc06189f349a, introduces a sanitizer function called _esc. This function wraps Python's built-in repr(), which is designed to return a printable representation of an object, escaping non-printable characters.

# In badkeys/utils.py
def _esc(inp):
    # repr() returns strings wrapped in quotes (e.g., "'foo'")
    # The slice [1:-1] removes the quotes but keeps the escaping.
    return repr(inp)[1:-1]
 
# In usage:
sys.stderr.write(f"Warning: value: {_esc(user_input)}\n")

By running the input through _esc, a malicious byte \x1b becomes the literal string \\x1b. The terminal renders the slash and the x, rather than interpreting the Escape command. It defangs the payload completely.

The Exploit: Painting with Invisible Ink

Let's construct a Proof of Concept (PoC) that demonstrates the danger. Imagine you are a sysadmin scanning a list of SSH keys provided by a vendor. You trust badkeys to flag the weak ones.

The Setup: I, the attacker, give you an Ed25519 key. It's perfectly valid crypto-wise, but I've stuffed the "comment" field with ANSI garbage.

The Payload: We want to hide the filename and the fact that badkeys might flag something (or just confuse the user). We will use:

  1. \x1b[2K: Clear the entire current line.
  2. \r: Return cursor to the start of the line.
  3. [+] Scan Complete. 0 Vulnerabilities found. : A fake success message.
# Creating the malicious SSH key file
# Note: This is a "valid" format for SSH tools, even if the comment is garbage.
echo -e 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJdNlqItIkvAGtuRUJFHfUTM2RyaQaEUMAEBF9UsWSQO key\x1b[2K\r[+] Scan Complete. 0 Vulnerabilities found.' > vulnerable_keys.pub
 
# The Victim runs the scan
badkeys --ssh-lines vulnerable_keys.pub

The Result: Without the exploit, badkeys prints the line being scanned. With the exploit, the output flashes and is immediately overwritten by our fake success message. The auditor sees a green light where there should be a red flag. If this key actually was weak (e.g., the "Debian weak key" vulnerability), the warning about it would be obliterated from the screen before the user could read it.

The Impact: Why Should We Panic?

You might be thinking, "So what? It's just text on a screen. It's not Root." And technically, you're right. This isn't RCE (unless you are using a very old, very broken terminal emulator that allows command execution via escape codes, which is rare these days).

However, in the world of security auditing, Integrity is everything. If I can compromise the reporting mechanism, I don't need to compromise the scanner.

Consider these scenarios:

  1. Supply Chain Deception: A developer includes a "verified" list of keys in a repository. They run badkeys in CI/CD. The logs (which capture stdout) capture the manipulated text. The PR passes because the logs say "0 Vulnerabilities".
  2. Social Engineering: An attacker submits a bug report saying, "I think this key is weird." The triager runs the tool. The tool prints a terrifying red ANSI banner saying "SYSTEM COMPROMISED: UPLOAD PASSWORD IMMEDIATELY." Panic ensues.

This vulnerability attacks the "Human Layer" (Layer 8) of the OSI model. It exploits the trust the user places in their tools.

The Fix: Sanitization Station

The remediation is straightforward, but it requires discipline. If you are using badkeys, update to version 0.0.16 immediately. The developers have patched the holes by enforcing repr()-style escaping on all external inputs.

If you are a developer writing CLI tools, take this as a lesson:

> [!NOTE] > Never trust stdout.

  1. Sanitize Output: Treat the terminal like a SQL database. Just as you parameterize SQL queries to prevent injection, you must escape terminal output to prevent ANSI injection.
  2. Use Libraries: Python's rich library or similar tools often handle this better than raw print() statements, or at least offer utilities to strip control codes.
  3. Pipe Safety: If you can't update, pipe the output through cat -v or less which will escape or display control characters explicitly, revealing the attack attempt.
# Workaround for older versions
badkeys --dkim malicious_record | cat -v

This will render the escape sequences as visible text (e.g., ^[[31m) rather than executing them.

Official Patches

badkeysFix commit implementing output sanitization
GitHub AdvisoryOfficial GitHub Security Advisory

Fix Analysis (2)

Technical Appendix

CVSS Score
5.3/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
EPSS Probability
0.01%
Top 98% most exploited

Affected Systems

badkeys <= 0.0.15Python CLI environments processing untrusted keysCI/CD pipelines using badkeys for automated checks

Affected Versions Detail

Product
Affected Versions
Fixed Version
badkeys
badkeys
<= 0.0.150.0.16
AttributeDetail
CWE IDCWE-150
CVSS v3.15.3 (Medium)
CVSS v4.02.0 (Low)
Attack VectorNetwork / Local (Context Dependent)
ImpactIntegrity (Log Spoofing / UI Manipulation)
Fix Version0.0.16

MITRE ATT&CK Mapping

T1204.002User Execution: Malicious File
Execution
T1562Impair Defenses
Defense Evasion
T1059Command and Scripting Interpreter
Execution
CWE-150
Improper Neutralization of Escape, Meta, or Control Sequences

Improper Neutralization of Escape, Meta, or Control Sequences

Known Exploits & Detection

GitHub IssueOriginal PoC demonstrating DKIM and SSH comment injection to clear terminal lines.

Vulnerability Timeline

Security policy and fuzzing infrastructure updates began
2025-12-03
Fix commits merged into master
2026-01-02
CVE-2026-21439 Published
2026-01-05

References & Sources

  • [1]GHSA-wjpc-4f29-83h3
  • [2]CWE-150: Improper Neutralization of Escape Sequences

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.