Web application exploit probe hunting¶
Eight hunts for attack-class probe activity beyond the injection classes covered in webapp-inject-hunt: file upload abuse, JWT manipulation, Host header attacks, web cache poisoning probes, prototype pollution, deserialisation payloads, OAuth manipulation, and CORS probes. Two classes (request smuggling, WebSocket abuse) are noted at the end with a caveat on why access logs are insufficient for detection.
Data source: nginx or Apache access logs in combined log format. URL-encoded characters in the request path are logged as-encoded; the patterns below match encoded and decoded forms where practical. POST body content does not appear in access logs: injection into body parameters requires application-level logging of decoded parameter values.
File upload probes¶
Hypothesis: a caller is testing file upload endpoints with executable file types or filename bypass techniques.
# executable extensions in upload paths (adjust path prefix to match the application)
grep -iE '\.(php|phtml|pht|phar|php3|php5|asp|aspx|cer|asa|jsp|jspx|cfm|shtml)' \
access.log | \
grep -i "upload\|file\|attach" | \
awk '{print $1, $7, $9}' | head -20
# null byte in filename (used to bypass extension checks)
grep -E '%00' access.log | \
grep -i "upload\|file\|attach" | \
awk '{print $1, $7, $9}' | head -20
# double-extension patterns (.php.jpg, .jsp.png, .asp.gif)
grep -iE '\.(php|jsp|asp|aspx)\.(jpg|jpeg|png|gif|bmp|webp)' \
access.log | \
awk '{print $1, $7, $9}' | head -20
Successful upload of an executable file shows as a 200 response to the upload endpoint
followed by a 200 response to a GET request for the uploaded file path. A subsequent
request to the uploaded file that triggers a process (visible as unusual child process
activity or OOB callback) confirms execution; the access log alone confirms the upload.
JWT manipulation¶
Hypothesis: a caller is manipulating JWT tokens to bypass algorithm validation or forge claims.
JWT tokens begin with base64url-encoded {"alg":. The none algorithm attack sends a token
with an empty signature; the token will contain alg\":\"none\" or alg\":\"None\" in its
decoded header. Detection in access logs requires the token to appear in the URL (unusual)
or for application-level logging to decode and log the JWT header.
# alg:none in URL-encoded tokens (base64 of {"alg":"none"...} starts with eyJhbGciOiJub25l)
grep -E 'eyJhbGciOiJub25l' access.log | \
awk '{print $1, $7}' | head -20
# tokens with very short or absent signature parts (two dots, nothing after the second)
# JWT format is header.payload.signature; absent signature is header.payload.
grep -oE 'eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.' access.log | \
sort | uniq -c | sort -rn | head -20
# IPs sending repeated requests with varying tokens (brute-forcing weak secrets)
grep -oE 'Bearer eyJ[A-Za-z0-9_-]+' access.log | \
awk '{print $1}' | sort | uniq -c | sort -rn | head -20
Token structure analysis requires base64 decoding. Where application logs record JWT
header content, grep for "alg":"none", "alg":"HS256" on services expected to use RS256,
and "kid" values containing path separators or SQL syntax.
HTTP Host header attacks¶
Hypothesis: a caller is injecting values into the Host header or override headers to reach internal services, poison password reset links, or influence cache keys.
# Host headers containing unexpected domains (adapt expected_domain)
awk '$0 ~ /Host:/ {print}' access.log | \
grep -vE 'Host: (app\.example\.com|www\.example\.com)' | \
awk '{print $1}' | sort | uniq -c | sort -rn | head -20
# X-Forwarded-Host and similar override headers appearing in requests
grep -iE 'X-Forwarded-Host|X-Host|X-Forwarded-Server|X-HTTP-Host-Override' \
access.log | \
awk '{print $1, $7}' | sort | uniq -c | sort -rn | head -20
# port injection in Host header
grep -E 'Host:.*:[0-9]{4,5}' access.log | \
grep -vE ':(443|80|8080|8443)' | \
awk '{print $1}' | sort | uniq -c | sort -rn | head -10
Access logs record the Host header only if the server is configured to log it. In nginx,
$http_host includes the header value; in combined log format it does not appear by default.
Where the Host header is not logged, detection relies on application logs or WAF inspection.
Web cache poisoning probes¶
Hypothesis: a caller is injecting unkeyed headers to poison cached responses.
# override headers used as unkeyed cache inputs
grep -iE 'X-Forwarded-Host:|X-Forwarded-Scheme:|X-Host:|X-Original-URL:|X-Rewrite-URL:' \
access.log | \
awk '{print $1, $7}' | sort | uniq -c | sort -rn | head -20
# cache-buster parameter patterns (Param Miner and similar tools add random strings)
grep -E '[?&](cb|cachebuster|utm_content|_cache)=[a-f0-9]{6,}' \
access.log | \
awk '{print $1, $7}' | head -20
# fat GET requests (GET with Content-Length > 0, suggesting a body)
grep -E '"GET ' access.log | \
awk '$10 > 0 && $10 !~ /^-$/ {print $1, $7, $10}' | head -20
Cache poisoning is confirmed when a clean request (no injected headers) receives a poisoned response. Detection from access logs alone shows probe activity; confirmation requires comparing response bodies or observing the injected value reflected to a different caller.
Prototype pollution probes¶
Hypothesis: a caller is injecting __proto__ or constructor.prototype keys into query
parameters or JSON to test for prototype pollution.
# __proto__ in URL query strings (encoded and decoded)
grep -E '(__proto__|%5F%5Fproto%5F%5F|__proto%5B%5D)' \
access.log | \
awk '{print $1, $7}' | head -20
# constructor[prototype] variants
grep -E '(constructor(%5B|\[)prototype(%5D|\])|constructor\.prototype)' \
access.log | \
awk '{print $1, $7}' | head -20
Prototype pollution in POST body parameters (JSON payloads) does not appear in access logs. Detection of server-side pollution from body content requires application logging of decoded request bodies or a WAF with JSON inspection.
Deserialisation probes¶
Hypothesis: a caller is sending serialised objects in parameters or cookies that may trigger gadget chain execution on deserialisation.
# Java serialization magic bytes (base64: rO0AB...) in URL parameters or cookies
grep -E '(rO0AB|%72%4F%30%41%42)' access.log | \
awk '{print $1, $7}' | head -20
# PHP serialized object patterns (O:<n>:"<classname>":)
grep -E 'O%3A[0-9]+%3A' access.log | \
awk '{print $1, $7}' | head -20
# ysoserial payload indicator strings
grep -iE '(commons|spring|hibernate|jdk7u21|jdk8u20|groovy)' access.log | \
grep -E '(rO0AB|%72%4F%30)' | \
awk '{print $1, $7}' | head -10
Deserialisation payloads are most often sent in request bodies or cookies. Application logs that record cookie values or body parameters are more sensitive for this class. OOB DNS callbacks to Collaborator or Interactsh domains in the access log indicate successful execution:
grep -iE '(burpcollaborator|oastify|interactsh|canarytokens)' access.log | \
awk '{print $1, $7}' | head -20
OAuth and SSO manipulation¶
Hypothesis: a caller is manipulating OAuth flow parameters to redirect tokens or bypass state validation.
# redirect_uri values pointing outside the registered domain
grep -iE 'redirect_uri=' access.log | \
grep -vE 'redirect_uri=(https?://(app|auth)\.example\.com)' | \
awk '{print $1, $7}' | head -20
# missing state parameter on authorisation requests (state should always be present)
grep -E '/oauth/(authorize|auth|token)' access.log | \
grep -v 'state=' | \
awk '{print $1, $7}' | head -20
# repeated authorisation requests with the same state value from the same IP (static state)
grep -E 'state=[a-zA-Z0-9_-]+' access.log | \
grep -oE 'state=[a-zA-Z0-9_-]+' | \
sort | uniq -c | sort -rn | head -10
Adapt the domain patterns in the redirect_uri check to the actual registered origins.
SSRF via dynamic client registration is not visible in application access logs; it appears
in outbound network logs as unexpected HTTP requests to attacker-controlled hosts.
CORS probes¶
Hypothesis: a caller is testing CORS configuration by varying the Origin header to
discover permissive policies.
# multiple distinct Origin values from the same IP in a short window
grep -oE 'Origin: [^ ]+' access.log | \
sort | uniq -c | sort -rn | head -20
# null origin requests (used to test for ACAO: null misconfiguration)
grep -iE 'Origin: null' access.log | \
awk '{print $1, $7, $9}' | head -20
# origin values containing attacker domains
grep -iE 'Origin: https?://[^[:space:]]*(attacker|evil|test\.|burp)' \
access.log | \
awk '{print $1, $7, $9}' | head -20
CORS probes are often paired with a preflight OPTIONS request. A sequence of OPTIONS
followed by GET or POST from the same IP, with varying Origin values, is a reliable signal:
grep '"OPTIONS ' access.log | \
awk '{print $1}' | sort | uniq -c | sort -rn | head -10
Request smuggling and WebSocket abuse¶
Request smuggling is not reliably detectable from standard web server access logs. The mechanism operates at the TCP/HTTP framing layer: the front-end logs a single request, while the back-end processes the smuggled prefix as a separate request attributed to another connection. Effective detection requires protocol-level inspection at the reverse proxy or WAF, correlation of response anomalies (unexpected status codes or body content for subsequent legitimate requests), or a proxy that logs raw HTTP framing.
WebSocket abuse similarly produces limited signal in HTTP access logs. The handshake upgrade
request appears as a standard GET with a 101 Switching Protocols response, after which
the connection is opaque to the access log. Detection of injection or hijacking within a
WebSocket session requires application-level logging of message content.
Last updated: 10 July 2026