Academy / Web Application Exploitation (Beyond OWASP Top 10)
MODULE 02 · Advanced
Web Application Exploitation (Beyond OWASP Top 10)
Server-side trust failures: SSRF, injection into back-ends, and template/deserialization flaws — and how to shut them.
Module overview
The Top 10 is the floor, not the ceiling. This module focuses on vulnerability classes that arise when an application trusts data it shouldn't and uses it to act on the server's behalf: Server-Side Request Forgery reaching internal services and cloud metadata, server-side template injection turning a templating engine into code execution, and insecure deserialization. For each, you exploit a deliberately vulnerable lab target, then implement the real fix and a detection. All testing is against apps you own.
Lesson 1
SSRF to Cloud Metadata & Internal Services
Deep explanation
SSRF occurs when an app fetches a URL the user controls without validating the destination. The damage is leverage: the server can reach places the attacker can't — internal admin panels, databases, and especially the cloud metadata endpoint (169.254.169.254) that historically returned credentials. SSRF is why IMDSv1 was dangerous and IMDSv2 (session-token, hop-limit) exists.
Good defenses are positive (allow-list of permitted hosts/schemes), enforced after DNS resolution to defeat rebinding, and network-level (egress filtering, blocking link-local). The NeoShield codebase itself models this: its outbound checker resolves and pins the IP, rejects internal ranges, and disables redirects.
Examples
A \"fetch preview\" feature accepts ?url=; supplying http://169.254.169.254/latest/meta-data/ returns instance metadata on a misconfigured host.
url=http://127.0.0.1:8080/admin reaches an internal-only console bound to loopback.
Commands & tools
# Illustrative request against YOUR lab app only
curl "http://dvwa.lab/fetch?url=http://169.254.169.254/latest/meta-data/"
# The FIX (PHP) — validate AFTER resolving, allow-list scheme+host, block internal IPs
$host = parse_url($url, PHP_URL_HOST);
$ip = gethostbyname($host);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE|FILTER_FLAG_NO_RES_RANGE) === false) {
http_response_code(400); exit("blocked");
}
# pin the connection to $ip, disable redirects, allow only http/https
# Cloud fix: require IMDSv2 (AWS)
aws ec2 modify-instance-metadata-options --http-tokens required --http-put-response-hop-limit 1
Deploy a lab app with a deliberate SSRF "URL preview" endpoint (provided in the lab pack) plus a mock metadata service.
Confirm the flaw: request the internal mock-metadata URL through the preview feature and observe the response.
Implement the allow-list + post-resolution IP validation fix; re-test and confirm the internal request is now blocked and logged.
Add a detection: log every outbound fetch destination; alert on requests to link-local/RFC1918 ranges.
Expected output: Before the fix, the internal/metadata response is returned to the client. After the fix, the request is rejected with 400 and an audit log entry is written.
What to observe: SSRF severity comes from the server's network position. Validation must happen after name resolution (to defeat DNS rebinding), and egress filtering is the backstop.
How attackers exploit · how defenders respond
Exploit: Abuse any server-side fetch (webhooks, PDF/image renderers, link unfurlers) to reach internal services and cloud metadata for credential theft and lateral movement.
Detect & respond: Egress monitoring for requests to 169.254.169.254 and RFC1918 from app servers; WAF rules for metadata IPs in parameters; application audit logs of outbound destinations; GuardDuty's IMDS-exfil findings in AWS.
Red teamEnumerate fetch features, probe internal ranges and metadata, escalate via recovered cloud credentials.
Blue teamAllow-list + post-resolution validation, IMDSv2, egress filtering, and outbound-destination detections.
Real-world scenario
The 2019 Capital One breach hinged on SSRF reaching instance metadata to obtain a role's credentials and read S3 — the textbook case for IMDSv2 and egress controls.
When user input is concatenated into a server-side template (Jinja2, Twig, Freemarker) instead of passed as data, the templating engine evaluates attacker expressions — often a direct path to code execution. Insecure deserialization is the sibling: feeding crafted serialized objects to a parser that instantiates arbitrary types triggers "gadget chains." Both stem from the same root: mixing untrusted data into a powerful evaluator.
Defense is structural: never build templates from user input (pass variables as context), use safe/sandboxed loaders, and never deserialize untrusted data with native binary formats — prefer signed JSON with strict schemas.
Examples
A Jinja2 app reflecting {{7*7}} as 49 confirms template evaluation rather than literal output.
A Java service accepting a base64 serialized object and instantiating classes on the path is a deserialization sink.
Commands & tools
# SSTI confirmation probe (illustrative, lab app only)
name={{7*7}} # reflects 49 => engine is evaluating input
# The FIX (Python/Jinja2): pass data as context, never format user input into the template
return render_template("hello.html", name=user_input) # safe
# NOT: render_template_string("Hi " + user_input) # vulnerable
# Deserialization: prefer signed JSON with a strict schema; avoid pickle/native java serialize
data = json.loads(body) # then validate against schema
# verify HMAC signature before trusting any serialized blob
Use the provided SSTI lab app; confirm evaluation with the {{7*7}} probe (observe 49).
Refactor the endpoint to pass input as template context; re-test that {{7*7}} now renders literally.
For deserialization: replace a native-deserialize endpoint with schema-validated, HMAC-verified JSON.
Add detection: WAF/RASP rules for template metacharacters in inputs and alerts on deserialization errors/spikes.
Expected output: Vulnerable build evaluates {{7*7}} to 49; fixed build echoes the literal string. Deserialization endpoint rejects unsigned/invalid payloads.
What to observe: These are evaluator-trust bugs. The fix is architectural (data vs. code separation), not a blocklist of payloads.
How attackers exploit · how defenders respond
Exploit: Probe reflective inputs for template evaluation, escalate to code execution; supply gadget-chain objects to unsafe deserializers.
Detect & respond: RASP/WAF for template metacharacters and known gadget signatures; monitor for unusual child processes spawned by the web user; alert on deserialization exceptions.
Red teamFind reflection points, fingerprint the engine, escalate evaluation to execution within scope.
Blue teamContext-only templating, sandboxed loaders, signed-JSON-with-schema instead of native deserialization, and process-spawn detections on web servers.
Real-world scenario
Multiple high-profile RCEs traced to Freemarker/Java deserialization sinks where untrusted input met a powerful evaluator — fixed only by removing the trust, not by filtering.
End-of-module assessment
Tap an answer to check it.
1. SSRF is most dangerous because the server can:
SSRF leverages the server's network position to reach internal services and metadata.
2. To defeat DNS rebinding, URL validation must happen:
Validate the resolved IP and pin the connection so the name cannot re-resolve to an internal address.
3. The structural fix for SSTI is:
Keep user input as data; never concatenate it into the template source.
Key takeaways
SSRF, SSTI, and deserialization are all "untrusted data meets a powerful evaluator" bugs.
Fix with positive allow-lists and data/code separation, validated after resolution, not payload blocklists.
Back every app fix with a detection: outbound-destination logging, RASP/WAF signatures, and process-spawn alerts.