How to Check Your Logs for Exploit Attempts After a CVE Drops
Last verified on Nov 8, 2026.
When a CVE drops, the useful question is whether anyone tried it against you and whether it worked. You answer it from logs you already keep: decide whether the CVE is worth a hunt, set a window that starts before the disclosure, search each log layer for the exploit string and its encoded variants, then separate failed attempts from signs of success. This guide uses Log4Shell, CVE-2021-44228, as the worked example because its exploit string is distinctive.
Decide Whether the CVE Is Worth a Hunt
You cannot hunt every CVE. Triage with three public signals.
- Known exploited. CISA's BOD 22-01 created a catalog of known exploited vulnerabilities that carry significant risk. A CVE on that list (see CISA KEV) has evidence of attacks in the wild.
- Exploit probability. EPSS, maintained by FIRST, estimates the probability that a published CVE will be exploited in the next 30 days, and is published daily for every CVE.
- Severity and exposure. CVSS rates the flaw. It does not say whether you run the affected component on something reachable.
cve.now shows all three on one page. For CVE-2021-44228 it lists CVSS 10.0, a KEV entry dated 2021-12-10, and public exploit code including Metasploit modules and a Nuclei check. Follow the KEV feed for new entries, or browse by technology, for example Log4j.
Then confirm you run the affected software. Upload a lockfile on cve.now or post it from the command line, as described in the check your stack docs. For Log4Shell, Apache's own page says only log4j-core is affected: applications using only log4j-api are not. A hunt is justified when the CVE is exploited, the component is yours, and the service is reachable from outside. For a recurring process, see new advisories.
Set the Window Before the Disclosure Date
Search from before the date on the CVE record. The record date is when the advisory was published. It is not when exploitation started, and for a vulnerability found by attackers first, probing precedes disclosure. For CVE-2021-44228, cve.now lists 2021-12-10 as both the published date and the KEV date. Start your window days or weeks earlier, and extend it back to your oldest retained logs if the first hit sits at the edge of the window.
Note your retention too. If your edge logs keep 14 days, you can only say what happened in the last 14 days.
Know Which Log Layer Sees What
AWS WAF logs carry action, terminatingRuleId, clientIp, uri, args, and headers, and Cloudflare HTTP request logs include ClientRequestURI, the full path and query string. Neither documented field list includes a request body. nginx exposes $request_body only in locations that pass requests to a backend after the body is read into memory, so POST bodies are the usual gap. If the exploit string could travel in a body, an empty access log search does not clear you.
Search for the Exploit String
CISA's Log4j guidance notes that log files for services using affected versions contain user-controlled strings, which is how the payload reaches the vulnerable code. In Log4j, a ${...} expression is evaluated, can be recursive, and can have defaults, and lower: and upper: take the result of a nested lookup. Attackers use that to hide the word jndi.
Start broad and read the results. This finds any lookup syntax, plain or percent-encoded, across rotated and compressed files:
zgrep -hiE '(\$|%24)(\{|%7b)' /var/log/nginx/access.log* | head -50Then narrow to Log4j lookups, including obfuscated forms such as ${${lower:j}ndi: and ${${::-j}ndi::
zgrep -hiE '(\$|%24)(\{|%7b)[^ ]*(jndi|lower:|upper:|::-)' /var/log/nginx/access.log*Check user agents and other headers, not only the URL. Attackers put the string in whatever the application is likely to log. If your logs are JSON, filter with jq:
jq -r 'select((.request_uri // .uri // "") | test("\\$\\{|%24%7b"; "i"))
| [.time, .remote_addr, .status, (.request_uri // .uri)] | @tsv' access.jsonDo the same for other CVE classes. For path traversal, OWASP lists encodings such as %2e%2e%2f, double encoding %252e%252e%255c, and ..%c0%af:
zgrep -hiE '(\.\.|%2e%2e|%252e%252e)(/|%2f|%5c|\\|%252f|%255c|%c0%af)' /var/log/nginx/access.log*For scanner traffic, match default tool user agents, and treat the result as a lower bound, because user agents are trivial to change:
zgrep -hiE 'sqlmap|nikto|nuclei|masscan|zgrab' /var/log/nginx/access.log*For payload families beyond one CVE, see attack payloads.
Separate Attempts from Successes
A hit proves someone sent the string. It does not prove it worked. Work through these checks for each source address that hit.
- Status and size. Count status codes for the matching requests. A 400 or 404 means the request was rejected or routed nowhere. A 200 means only that the server answered.
- IP sequence. List every request from that address. One probe then silence is a scan. A probe, then a changed payload, then a probe against a new path is someone iterating.
- Outbound traffic. Exploiting a JNDI flaw makes the server connect out to an attacker-controlled LDAP or similar endpoint, per Apache's description. Look in DNS and flow logs for an outbound lookup or connection from the affected host that follows the hit within seconds, to an address nothing else talks to.
- Host changes. On a host with a success signal, look for new processes spawned by the application user, new files, new accounts, and new scheduled tasks.
zgrep -hiE '(\$|%24)(\{|%7b)[^ ]*(jndi|lower:|upper:|::-)' /var/log/nginx/access.log* \
| awk '{print $1, $9}' | sort | uniq -c | sort -rnDecide What to Do
- Hits, all rejected, no outbound traffic. Upgrade to a release that also fixes the follow-up flaws, not just the first fix: for Log4j that means at least 2.17.1 on Java 8 and later (2.12.4 on Java 7, 2.3.2 on Java 6), the releases that fixed the last follow-up flaw, CVE-2021-44832, and preferably the current release listed on the Log4j security page. Keep the evidence, and set a block rule at the edge.
- Hits with a success status and a matching outbound connection. Treat the host as compromised. Isolate it, preserve logs and disk, and rotate any credentials the application held.
- No hits, but a gap you cannot see. Record the gap. If POST bodies were unlogged and the component was exposed, assume attempts happened and go straight to checking outbound activity.
CISA's guidance also advises considering reporting compromises to CISA and the FBI.
Searching These Logs with Sazabi
Sazabi, an AI-native observability and security platform, can take the edge logs this guide relies on. Its AWS WAF log source runs an OpenTelemetry Collector that reads your aws-waf-logs- CloudWatch log groups and forwards web ACL logs to Sazabi. Once the logs are in, ask in a thread for requests containing the lookup strings above in a given window. The agent shows each search it runs and the matching lines, so you can check its work.
Frequently Asked Questions
How far back should I search logs after a CVE is published?
Start before the disclosure date, not after it. A CVE record is dated when it was published, and exploitation can begin before that. Search back to the earliest date your logs allow, then narrow once you know when the first hit appears.
Does a hit in my logs mean I was compromised?
No. A hit is an attempt. Most attempts are internet-wide scans that fail. Compromise needs evidence beyond the request, such as a success status followed by outbound connections or changes on the host.
What if I cannot see POST bodies in my logs?
Many access logs record only the request line and headers. If the exploit string could travel in a body, you cannot rule out attempts from the access log alone. Check WAF logs, application logs, and outbound DNS or network records for the follow-on activity.
How do I know if a CVE is worth hunting for?
Check whether it is in the CISA Known Exploited Vulnerabilities catalog, look at its EPSS score, and confirm you run the affected software on a reachable service. Listed as exploited, affecting something you expose, with public exploit code, is a hunt.
Sources
- Log4j CVE-2021-44832 (JDBC Appender with a JNDI LDAP data source) affects 2.0-beta7 through 2.17.0 and is fixed in 2.17.1, 2.12.4, and 2.3.2 (accessed Nov 8, 2026)
- CISA BOD 22-01 establishes a CISA-managed catalog of known exploited vulnerabilities that carry significant risk (accessed Nov 8, 2026)
- EPSS estimates the probability that a published CVE will be exploited in the wild in the next 30 days and is published daily for every CVE (accessed Nov 8, 2026)
- CVE-2021-44228 was published and added to the CISA KEV catalog on 2021-12-10, is rated CVSS 10.0, and has public exploit code including Metasploit modules and a Nuclei check (accessed Nov 8, 2026)
- CVE-2021-44228 affects log4j-core; an attacker who controls log messages or parameters can run code loaded from LDAP servers; fixed in 2.3.1, 2.12.2, and 2.15.0 (accessed Nov 8, 2026)
- Log4j evaluates ${...} expressions that can be recursive and have default values, with lower: and upper: lookups taking nested lookup results (accessed Nov 8, 2026)
- Log files for services using affected Log4j versions contain user-controlled strings, and CISA advises a security review for signs of compromise (accessed Nov 8, 2026)
- Path traversal payloads use encodings such as %2e%2e%2f, %252e%252e%255c, and ..%c0%af for dot-dot-slash sequences (accessed Nov 8, 2026)
- AWS WAF logs include the action, terminatingRuleId, clientIp, uri, args and headers fields (accessed Nov 8, 2026)
- Cloudflare HTTP request logs record ClientRequestURI, the full path and query string requested by the client (accessed Nov 8, 2026)
- nginx exposes the request body as $request_body only in locations that pass the request to a backend after reading the body into memory (accessed Nov 8, 2026)
- An OpenTelemetry Collector reads aws-waf-logs- CloudWatch log groups and forwards AWS WAF web ACL logs to Sazabi (accessed Nov 8, 2026)
- You can ask about your logs in a thread and the agent searches them, showing each search and its results (accessed Nov 8, 2026)