OSWA cheat sheet
OSWA · Keep the key ideas within reach.
Web Application Reconnaissance and Enumeration
Passive recon sources: certificate transparency logs (crt.sh), DNS records (A, AAAA, MX, TXT, CNAME), reverse-IP lookups, WHOIS/RDAP, and archived snapshots (Wayback Machine) reveal hosts and paths without touching the target.
Content discovery decision rule: if directory brute force returns mostly 403, the server exists but listing is disabled - switch to filename/extension fuzzing (e.g., .bak, .old, .php~, .swp) and check response length and status deltas rather than status alone.
Virtual host enumeration: send the same request with different Host headers and compare response body length, status code, and TLS certificate SANs; a differing body length indicates a distinct vhost on the same IP.
robots.txt, sitemap.xml, and security.txt are high-value first requests; disallowed entries frequently name administrative paths that are otherwise unlinked.
Technology fingerprinting signals: response header order and casing, cookie names (PHPSESSID, JSESSIONID, ASP.NET_SessionId), default error page text, and static asset paths (e.g., /wp-content/, /_next/, /static/admin/).
Parameter discovery: compare responses to the same path with and without candidate parameters; hidden parameters often appear in JavaScript source, HTML comments, or as reflected values in error messages.
Subdomain takeover check: a CNAME pointing to a deprovisioned third-party service (e.g., an unclaimed cloud bucket or PaaS app) returns the provider's 'not found' page - that CNAME is claimable.
Scope rule: only test hosts inside the authorized scope; a shared IP or wildcard DNS record does not automatically authorize sibling vhosts.
Cross-Site Scripting (XSS)
Three types by data flow: reflected (payload in the immediate request), stored (payload persisted server-side and served to other users), DOM-based (sink executes attacker data entirely in the browser, payload may never reach the server).
Context determines the break-out character: HTML body needs < >, attribute value needs the matching quote, JavaScript string needs ' or " plus a way to escape the literal, and URL context needs javascript: or a scheme-relative payload.
Encoding rule: HTML-entity encoding (< > " ') is correct for HTML body and attribute contexts; JavaScript string contexts require \x3C-style escaping, not HTML entities.
DOM sink list to grep for: innerHTML, outerHTML, document.write, insertAdjacentHTML, eval, setTimeout with a string, location assignment, and jQuery .html()/.append().
DOM source list: location.hash, location.search, document.referrer, window.name, postMessage data, and localStorage/sessionStorage values.
CSP evaluation: a policy without 'unsafe-inline' blocks inline handlers and inline
HttpOnly on the session cookie does not prevent XSS - it only blocks document.cookie theft; the attacker can still perform actions as the victim via the DOM.
Proof-of-concept rule: alert(1) demonstrates execution but does not demonstrate impact; a stronger PoC reads a non-HttpOnly token or performs a state-changing request.
Cross-Site Request Forgery (CSRF)
CSRF requires three conditions simultaneously: a relevant state-changing action, cookie-based session handling with no unpredictable parameter, and no server-side verification that the request came from the intended origin.
Defense baseline: a per-session or per-request anti-CSRF token compared server-side; tokens must be unpredictable and must not be accepted from a URL query string that leaks via Referer.
SameSite cookie values: Strict blocks the cookie on all cross-site requests; Lax sends it on top-level GET navigations only; None requires the Secure flag. Lax leaves top-level GET state changes exposed.
Origin/Referer validation is a valid defense only if the header is required to be present and matched exactly; a check that passes when the header is absent is bypassable.
Bypass patterns to test: removing the token parameter entirely, sending an empty token, sending a token from the attacker's own session, changing POST to GET, and switching the Content-Type to text/plain or multipart/form-data.
JSON endpoints are not automatically CSRF-safe: a text/plain body containing JSON is a simple request (no preflight), so a server that parses the body regardless of Content-Type is vulnerable.
Custom-header requirements (e.g., X-Requested-With) act as a defense only because cross-origin simple requests cannot set them without a CORS preflight.
A GET endpoint that mutates state is CSRF-vulnerable by design; an image tag or link can trigger it with no script execution.
SQL Injection (SQLi)
Classification axes: injection point (GET/POST parameter, cookie, header, JSON field) and technique (in-band UNION, error-based, blind boolean, blind time-based, out-of-band).
UNION rule: the injected SELECT must match the original column count and compatible column types; determine count with ORDER BY n incrementing until error, or with UNION SELECT NULL,NULL,... until the response changes.
Database fingerprinting: MySQL uses /*! */ version comments, -- (space required) and # for comments; MSSQL uses -- and WAITFOR DELAY; Oracle requires FROM dual and uses DBMS_PIPE for OOB; PostgreSQL supports stacked queries and pg_sleep.
Blind boolean baseline: compare TRUE condition (AND 1=1) against FALSE condition (AND 1=2) and diff response length, status, or content; extract data one bit or one character at a time.
Blind time-based: MySQL SLEEP(5), PostgreSQL pg_sleep(5), MSSQL WAITFOR DELAY '0:0:5', Oracle DBMS_LOCK.SLEEP(5); confirm by repeating to rule out network jitter.
Second-order SQLi: input is stored safely but later concatenated into a query elsewhere (e.g., a stored username used in an admin report); testing must follow the data, not just the immediate response.
WAF/blacklist bypass levers: inline comments (UN//ION), case variation, URL/double URL encoding, equivalent operators (|| for OR, && for AND), and whitespace alternatives (//, %09, %0a).
sqlmap baseline invocation: sqlmap -r request.txt --batch --level=3 --risk=2; use -r to preserve headers and cookies rather than reconstructing the request manually.
Server-side Request Forgery (SSRF)
SSRF exists when the server fetches a URL or host supplied by the user; common features are URL previews, image importers, webhooks, PDF renderers, and XML/SAML fetchers.
Internal targets by default: 127.0.0.1, localhost, [::1], 169.254.169.254 (cloud metadata), 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, and internal DNS names such as metadata.google.internal.
Cloud metadata quick reference: AWS IMDSv1 is a plain GET to http://169.254.169.254/latest/meta-data/iam/security-credentials/; IMDSv2 requires a PUT to /latest/api/token first, which defeats simple GET-only SSRF. GCP requires the header Metadata-Flavor: Google.
Blacklist bypass techniques: alternate IP encodings (2130706433, 0x7f000001, 0177.0.0.1), DNS names resolving to 127.0.0.1 (e.g., 127.0.0.1.nip.io), URL userinfo tricks (http://[email protected]/), and redirects from an allowed host.
Scheme abuse: file:// reads local files, gopher:// and dict:// speak raw TCP to internal services (Redis, SMTP, memcached), and gopher is the standard pivot for crafting arbitrary internal protocol payloads.
Blind SSRF confirmation: measure response time differences, use a collaborator/listener domain to detect the callback, and compare error messages for connection refused vs. timeout (open vs. closed port).
Defense: allowlist destination hosts, resolve DNS and check the resolved IP against private ranges, disable redirects, and block non-HTTP schemes; validating only the input string is insufficient.
XML External Entity (XXE) Injection
XXE requires that the XML parser resolves external entities: a DOCTYPE with an ENTITY declaration and a reference to it in the document body.
Classic file read: ]>&e; - the entity value is substituted into the response.
Blind XXE uses an external DTD hosted on an attacker-controlled server: %remote; with a parameter entity that exfiltrates file content via a second SYSTEM URL.
Parameter entities (%) are used only inside the DTD; general entities (&name;) are used in the document body - mixing them up is the most common payload error.
PHP-specific wrapper: expect://id and php://filter/convert.base64-encode/resource=/etc/passwd extend impact beyond plain file reads.
Out-of-band exfiltration channels: FTP and HTTP callbacks, plus error-based leakage where the parser includes the file content in a parse error message.
SVG, SOAP, SAML, DOCX/XLSX, and RSS import features are common XXE entry points; any endpoint accepting XML content type is a candidate.
Defense: disable DTDs entirely (disallow-doctype-decl in Java, resolve_entities=False in Python lxml, LIBXML_NOENT off in libxml), or use a parser configured to not fetch external resources.
Cross-Origin Resource Sharing (CORS) Misconfiguration
The browser sends Origin on cross-origin requests; the server answers with Access-Control-Allow-Origin (ACAO). If ACAO is * the response is readable only when credentials are not included.
Access-Control-Allow-Credentials: true combined with a reflected Origin (ACAO echoing the attacker's Origin) allows authenticated cross-origin reads - the highest-impact CORS finding.
The wildcard * cannot be combined with credentials: browsers reject ACAO: * when ACAC is true, so a reflected origin is required for the exploitable case.
Null origin: ACAO: null is trusted by browsers when the request comes from a sandboxed iframe or a file:// page, so an attacker can generate the null origin and read the response.
Preflight caching: Access-Control-Max-Age controls how long the browser caches the preflight result; a large value extends the window in which a permissive policy applies.
Prefix/suffix matching flaws: a server that allows any origin ending in example.com also allows attacker-example.com or example.com.evil.net; test with lookalike domains.
Simple requests (GET/POST with form content types) do not trigger a preflight, so the request is sent even if the response is unreadable - useful for CSRF-style side effects even when CORS blocks reading.
Server-Side Template Injection (SSTI)
Detection polyglot: ${77}{{77}}<%= 77 %>#{77} - a response containing 49 in place of the literal identifies template evaluation; then narrow by engine-specific syntax.
Engine identification by arithmetic and string output: {{77}} returning 49 suggests Twig or Jinja2; {{7'7'}} returning 7777777 indicates Jinja2, while 49 indicates Twig.
Jinja2 (Python) exploitation path: reach os via ''.class.mro[1].subclasses() and locate a class such as subprocess.Popen or use cycler/joiner globals to import os.
Twig (PHP) exploitation path: {{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}} on older versions; newer Twig requires reaching PHP functions through the filter/attribute chain.
FreeMarker (Java) path: <#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")} - the Execute utility is the classic RCE gadget.
Velocity (Java) path: #set($x='') followed by $x.getClass().forName('java.lang.Runtime').getRuntime().exec('id') via reflection.
SSTI vs. XSS distinction: SSTI executes on the server and can lead to RCE; client-side template injection (Angular, Vue) executes in the browser and is bounded by the sandbox.
Sandbox escape rule: when a template sandbox blocks direct attribute access, use the engine's built-in globals (Jinja2 config, lipsum, cycler) rather than trying to bypass the filter directly.