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