Back to Blog
critical SEVERITY8 min read

Decrypted Secrets in Plain Sight: Fixing AES Log Exposure in Java

A critical vulnerability was discovered in AESEncryption.java where decrypted plaintext was being printed directly to standard output, exposing sensitive data to anyone with access to application logs. This fix eliminates the dangerous logging pattern that completely undermined the purpose of AES encryption. Understanding this vulnerability is essential for any developer building applications that handle sensitive encrypted data.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published April 23, 2026•Reviewed June 3, 2026

Answer Summary

This vulnerability is a sensitive data exposure flaw (CWE-532: Insertion of Sensitive Information into Log File) in Java's AESEncryption.java, where decrypted AES plaintext was printed directly to standard output via a System.out.println() call. The encryption itself was technically correct, but the act of logging the decryption result nullified all confidentiality protections. The fix removes the System.out.println() statement that exposed the plaintext, ensuring decrypted values remain confined to memory and are never written to logs or stdout.

Vulnerability at a Glance

cweCWE-532
fixRemove the System.out.println() statement that printed decrypted plaintext to standard output
riskDecrypted secrets written to application logs, accessible to anyone with log access
languageJava
root causeSystem.out.println() called on the return value of AES decryption in AESEncryption.java
vulnerabilitySensitive Data Exposure via Plaintext Logging

Vulnerability Severity: Critical
Affected File: src/main/java/com/thealgorithms/ciphers/AESEncryption.java
Fix Type: Remove plaintext logging of decrypted data


Introduction

Imagine spending significant engineering effort implementing AES encryption — one of the strongest symmetric encryption algorithms available — to protect your users' sensitive data. You choose the right key size, implement proper padding, and feel confident your data is secure. Then, quietly, a single line of code hands that protected data to anyone who can read a log file.

This is exactly the scenario uncovered in AESEncryption.java. At line 41, the result of AES decryption was being printed directly to standard output via System.out.println. On the surface, this looks like harmless debugging code. In practice, it is a security catastrophe waiting to happen.

This blog post breaks down the vulnerability, explains why it matters in real-world deployments, and walks through the fix and the broader lessons every developer should take away.


The Vulnerability Explained

What Happened?

AES (Advanced Encryption Standard) is a symmetric encryption algorithm widely used to protect sensitive information — passwords, tokens, personal data, financial records, and more. The entire security model of AES depends on one guarantee: only parties who possess the encryption key can read the plaintext.

The vulnerable code broke this guarantee not by attacking the cryptography itself, but by casually printing the decrypted result to standard output:

// VULNERABLE CODE (before fix)
String decryptedText = decrypt(encryptedData, secretKey);
System.out.println(decryptedText); // <-- This line destroys your encryption guarantees

This pattern is extremely common in development. Developers add System.out.println statements to verify their code is working, and sometimes those debug lines make it into production. The result is that the decrypted plaintext travels directly into your logging infrastructure.

Why Is This So Dangerous?

In modern deployment environments, standard output is almost never truly "standard." It gets captured, aggregated, stored, and monitored. Consider where your System.out output actually ends up:

Environment Where stdout goes
Docker / Kubernetes Container log streams, accessible via kubectl logs or Docker daemon
AWS Lambda / Cloud Functions Automatically shipped to CloudWatch Logs
Heroku / PaaS platforms Log drains, accessible to all team members
ELK Stack / Splunk Centralized log aggregation, often with broad read access
CI/CD Pipelines Build logs, sometimes stored for months
Traditional servers /var/log/ files, often with weak access controls

In every one of these environments, the decrypted plaintext is now accessible to anyone with read access to the logs — without ever needing the AES encryption key.

The Real-World Impact

The consequences of this vulnerability scale with the sensitivity of the data being decrypted:

  • OAuth tokens and API keys decrypted for validation are now visible in logs, allowing attackers to impersonate users or services
  • Personally Identifiable Information (PII) exposed in logs creates GDPR, HIPAA, and PCI-DSS compliance violations
  • Passwords or session tokens logged in plaintext become trivially harvestable
  • Business-critical secrets (database credentials, private keys) become accessible to anyone who can query your log system

The attack scenario is chillingly simple:

1. Attacker gains read access to log aggregation system
   (compromised credentials, misconfigured S3 bucket, insider threat)

2. Attacker searches logs for decrypted output patterns

3. Attacker extracts plaintext secrets — no cryptography required

4. Encryption provided zero protection

Why This Is Classified as Critical

This vulnerability doesn't require a sophisticated exploit. There's no buffer overflow, no injection attack, no race condition to win. The application itself is doing the attacker's work, broadcasting decrypted secrets to anyone watching the logs. The cryptographic protection — regardless of key strength — is rendered completely ineffective.


The Fix

What Changed

The fix removes the System.out.println call that was printing decrypted plaintext to standard output. The decryption logic itself is sound; the problem was solely in how the result was being handled after decryption.

// BEFORE (vulnerable)
public static String decrypt(String encryptedText, SecretKey secretKey)
        throws Exception {
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.DECRYPT_MODE, secretKey);
    byte[] decryptedBytes = cipher.doFinal(
        Base64.getDecoder().decode(encryptedText)
    );
    String decryptedText = new String(decryptedBytes);
    System.out.println(decryptedText); // VULNERABILITY: logs plaintext
    return decryptedText;
}
// AFTER (fixed)
public static String decrypt(String encryptedText, SecretKey secretKey)
        throws Exception {
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.DECRYPT_MODE, secretKey);
    byte[] decryptedBytes = cipher.doFinal(
        Base64.getDecoder().decode(encryptedText)
    );
    return new String(decryptedBytes); // Plaintext returned, never logged
}

How This Solves the Problem

By removing the System.out.println call, the decrypted plaintext is only ever held in memory and returned directly to the calling code. It never touches the logging infrastructure. The AES encryption guarantee is restored: only code that explicitly handles the return value can access the plaintext.

The Broader Context: Credential Storage

It's worth noting that this vulnerability exists within a broader pattern of credential handling issues. OAuth tokens and API keys stored on the filesystem without encryption compound the risk — even if the logging issue is fixed, credentials at rest without cryptographic protection remain a concern. Libraries like PBKDF2 (available in the dependency tree) should be used to derive encryption keys for protecting credentials stored on disk. Defense in depth means fixing both the logging exposure and the storage exposure.


Conclusion

The vulnerability fixed here is a powerful reminder that cryptographic security is not just about the algorithm — it's about the entire lifecycle of sensitive data. You can implement AES-256 perfectly and still expose every secret you decrypt if you log the results carelessly.

The fix is simple: remove the debug logging of plaintext. But the lesson is deeper:

  1. Treat decrypted data as sensitive at every point in your code, not just at storage
  2. Log events and metadata, never data content
  3. Use static analysis tools to catch these patterns before they reach production
  4. Encrypt credentials at rest using proper key derivation functions
  5. Review cryptographic code with extra scrutiny — the stakes are higher than ordinary logic bugs

Security vulnerabilities like this one often aren't the result of malicious intent or ignorance of cryptography. They're the result of a debug println that never got cleaned up. The best defense is a culture of security awareness, automated tooling, and thorough code review.

Every line of code that touches sensitive data deserves a moment of pause: "If this line ended up in a log file, what would an attacker learn?"


This vulnerability was identified and fixed as part of an automated security scanning process. Security fixes like this one are most effective when paired with developer education — understanding the "why" behind a vulnerability helps prevent the entire class of issues, not just the single instance.


Further Reading:
- OWASP Cryptographic Storage Cheat Sheet
- OWASP Logging Cheat Sheet
- CWE-532: Insertion of Sensitive Information into Log File
- NIST Guidelines for Cryptographic Key Management

Prevention and further reading

Related Articles

critical

JdbcSinkFunction.invoke() SQL Injection via Unvalidated Identifiers

The `JdbcSinkFunction.invoke()` method in Lacus's RTC engine built SQL INSERT statements with `String.format()`, placing database name, table name, and column names directly into the query string without validation. Although the actual row values used parameterized `?` placeholders, the identifiers themselves were injectable, letting a crafted sink configuration break out of the intended INSERT statement. The fix adds a strict allowlist regex that rejects any identifier containing characters out

high

JOSMFileHack TransformerFactory XXE: External DTD Processing Enabled

OSM2World's JOSMFileHack utility, which processed OpenStreetMap files generated by the JOSM editor, contained an insecure TransformerFactory configuration that permitted external DTD and stylesheet access. The vulnerability was resolved by completely removing the vulnerable code path rather than hardening it in place.

high

How command injection happens in Java ProcessBuilder and how to fix it

The `efw` framework exposes OS command execution to application code through `CmdManager.execute(String[] params)`, which passed its parameter array straight into `new ProcessBuilder(...)` at `CmdManager.java:25` with no validation and no documented trust boundary. Because `params` is commonly assembled in event JavaScript from HTTP request parameters — often via string concatenation — the call site was a ready-made command and argument injection primitive. The fix adds explicit parameter valida

critical

How Unverified JWT Decoding Happens in Java and How to Fix It

A critical authentication bypass was discovered in `JwtExtractor.java` where `JWT.decode()` was used instead of a proper signature-verifying method, allowing any attacker to forge a JWT with an arbitrary username — including `admin` — and gain unauthorized access. The fix adds clear documentation establishing the trust boundary: signature validation must occur upstream, and the extracted claims are for display purposes only. This change prevents the class from being misused as an authorization g

high

How EL Injection happens in Java JSF applications and how to fix it

A high-severity Expression Language (EL) injection vulnerability was discovered and fixed in `PrimeFacesResourceProcessor.java`, a JSF phase listener responsible for resolving the PrimeFaces theme configuration. The flaw allowed a dynamically sourced theme parameter value to be passed directly into an EL expression factory without first verifying whether the value was actually an EL expression or plain text. The fix introduces explicit input branching that separates EL expressions from literal s

high

How Spring Boot EndpointRequest.to() security bypass happens in Java Spring Boot and how to fix it

CVE-2025-22235 is a high-severity vulnerability in Spring Boot where `EndpointRequest.to()` creates an incorrect request matcher when an actuator endpoint is not exposed, potentially allowing unauthorized access to protected endpoints. The fix upgrades Spring Boot from 3.4.4 to 3.4.5 in the anti-corruption-layer service's `pom.xml`. This is particularly dangerous because actuator endpoints can expose sensitive operational data and administrative functions.