HTTP/HTTPS Analysis
HTTP is the most common transport for both legitimate traffic and attacker activity. Attackers use it because it blends in, crosses almost every firewall, and is trivially proxied. Understanding how to extract forensic evidence from HTTP — request headers, URIs, user agents, response codes, content types, and the payload — is foundational to network forensics. HTTPS adds TLS but doesn't eliminate metadata; the SNI, certificate, and connection timing remain visible.
An analyst has a PCAP from a suspected web shell compromise. The attacker connected to an internal web server on port 80 and issued commands via POST requests to /cms/upload.php. The request bodies contain base64-encoded PowerShell. The responses contain the command output. You need to: identify every POST to that URI, decode each request body, extract the commands executed, and map the timeline.
HTTP Evidence Hierarchy
HTTP Forensic Evidence — What Each Layer Tells You
═══════════════════════════════════════════════════════════════════
Request Line: GET/POST/PUT + URI path + query string
└── URI reveals resource accessed; query string may contain commands
Request Headers:
├── Host: actual target hostname (useful for virtual hosting)
├── User-Agent: client identity (often fake in C2 but still distinctive)
├── Referer: navigation path (legitimate browsers always include it)
├── Cookie: session tokens (credential theft, session hijacking)
├── Authorization: Basic/Bearer credentials
├── Content-Type: tells you how to parse the body
└── X-Forwarded-For: true client IP behind proxies
Request Body (POST/PUT):
└── May contain: commands (web shells), credentials (phishing),
exfiltrated data (upload), binary payloads (file uploads)
Response Line: HTTP status code
├── 200 OK: command executed successfully (web shell)
├── 302 Redirect: phishing flow
├── 403/404: scanning — attacker mapping server paths
└── 500 Internal Server Error: exploitation attempt caused crash
Response Headers:
├── Server: server software version (fingerprinting)
├── Set-Cookie: session cookie creation
└── Content-Type: how to parse the response body
Response Body:
└── Command output (web shells), stolen data (exfil),
malware downloads (malware delivery)
HTTP Analysis with tshark
#!/bin/bash
PCAP="$1"
echo "=== HTTP Request Summary ==="
tshark -r "$PCAP" -n -Y "http.request" -T fields \
-E separator="|" \
-e frame.time_epoch \
-e ip.src \
-e ip.dst \
-e http.request.method \
-e http.host \
-e http.request.uri \
-e http.user_agent \
| sort | uniq -c | sort -rn | head -50
echo ""
echo "=== POST requests with bodies ==="
tshark -r "$PCAP" -n \
-Y "http.request.method == POST" \
-T fields \
-E separator="\t" \
-e frame.time_epoch \
-e ip.src \
-e http.host \
-e http.request.uri \
-e http.file_data \
| head -100
echo ""
echo "=== HTTP responses with error codes ==="
tshark -r "$PCAP" -n \
-Y "http.response.code >= 400" \
-T fields \
-E separator="\t" \
-e frame.time_epoch \
-e ip.dst \
-e http.request.uri \
-e http.response.code \
| sort | uniq -c | sort -rn | head -50
echo ""
echo "=== Unique User Agents ==="
tshark -r "$PCAP" -n -Y "http.user_agent" -T fields \
-e http.user_agent \
| sort | uniq -c | sort -rn
echo ""
echo "=== Non-standard HTTP ports ==="
tshark -r "$PCAP" -n -Y "http.request and not (tcp.port == 80 or tcp.port == 8080 or tcp.port == 8443 or tcp.port == 443)" \
-T fields -e ip.dst -e tcp.dstport -e http.host
Web Shell Traffic Patterns
| Indicator | Legitimate | Web Shell |
|---|---|---|
| HTTP method | GET dominant, POST for forms | POST for commands, GET for check-in |
| URI pattern | Stable paths matching site structure | Unusual PHP/ASPX in non-application dirs |
| Request body encoding | Form data, JSON, XML | Base64 blobs, concatenated chars, gzip+base64 |
| User-Agent | Real browser string with version | curl, python-requests, custom, or empty |
| Response body | HTML page content | Command output: plaintext, directory listings, file contents |
| Session pattern | Multiple requests per session, navigation | Individual commands: one request → one response |
| Timing | Human-paced click patterns | Sub-second consistent intervals (scripted) |
| Response size | Varies with page content | Consistent small responses (command output) |
#!/usr/bin/env python3
"""Hunt for web shell activity in HTTP traffic."""
import subprocess, base64, re, sys
PCAP = sys.argv[1]
def tshark_fields(pcap, display_filter, fields):
cmd = ["tshark", "-r", pcap, "-n", "-Y", display_filter, "-T", "fields",
"-E", "separator=\t"] + [f for fld in fields for f in ["-e", fld]]
result = subprocess.run(cmd, capture_output=True, text=True)
rows = []
for line in result.stdout.splitlines():
parts = line.split("\t")
rows.append(parts)
return rows
# Find POST requests with potential command injection patterns
print("=== POST requests with encoded payloads ===")
for row in tshark_fields(PCAP, "http.request.method==POST",
["frame.time_epoch","ip.src","http.host","http.request.uri","http.file_data"]):
if len(row) < 5:
continue
ts, src, host, uri, body = row[0], row[1], row[2], row[3], row[4]
# Check for base64 in body
if re.search(r'[A-Za-z0-9+/]{40,}={0,2}', body):
# Try to decode
matches = re.findall(r'[A-Za-z0-9+/]{40,}={0,2}', body)
for m in matches[:3]:
try:
decoded = base64.b64decode(m).decode("utf-8", errors="replace")
if any(kw in decoded.lower() for kw in ["powershell","cmd","whoami","net user","tasklist"]):
print(f" [{ts}] {src} POST {host}{uri}")
print(f" DECODED: {decoded[:200]}")
except Exception:
pass
# Find unusual file extensions being requested (web shell paths)
SUSPICIOUS_EXT = [".php",".aspx",".jsp",".cfm",".ashx",".shtml"]
print("\n=== Requests to script files ===" )
seen_paths = set()
for row in tshark_fields(PCAP, "http.request",
["ip.src","http.host","http.request.uri","http.request.method"]):
if len(row) < 4:
continue
src, host, uri, method = row[0], row[1], row[2], row[3]
if any(uri.lower().endswith(ext) for ext in SUSPICIOUS_EXT):
key = f"{host}{uri}"
if key not in seen_paths:
seen_paths.add(key)
print(f" [{method}] {src} → {host}{uri}")
User-Agent is the single most commonly faked HTTP header by both attackers and legitimate applications. C2 frameworks routinely spoof User-Agent to mimic Chrome or Firefox. However, User-Agent is still forensically valuable for two reasons: (1) Outliers are meaningful: in an environment where 99% of HTTP traffic uses Chrome or Firefox User-Agents, a single connection using python-requests/2.28.0 or a Cobalt Strike default profile UA stands out immediately. (2) Consistency is a fingerprint: if a host sends 100 HTTPS connections all with the exact same User-Agent string including micro-version (e.g., Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36) but never updates, that suggests automation. Real Chrome updates frequently. A static User-Agent across months of traffic from the same host is a weak but real indicator.
Q & A
Q: The web shell POSTs have Content-Type: application/x-www-form-urlencoded but the body is just random-looking base64. How does that work forensically?
The Content-Type header claims the body is form-encoded (key=value pairs), but the attacker's web shell handler on the server decodes base64 regardless of Content-Type — the server-side PHP doesn't check the declared content type, it just reads the raw POST body and base64-decodes it. For forensics: don't rely on Content-Type to tell you how to parse HTTP bodies. Instead: (1) Look at the raw body directly, (2) Check for base64 patterns with regex [A-Za-z0-9+/]{20,}={0,2}, (3) Try to decode any sufficiently long base64 string you find, (4) Look for PowerShell, cmd, or shell command keywords in the decoded output. Also: web shells often obfuscate further — base64 inside URL encoding inside another base64 layer. Decoding one layer may reveal another base64 string. Try decoding up to 3 layers deep before giving up.