d84b3b0033
Five new modules close the 2018 gap entirely and thicken 2019 / 2020 / 2024. All five carry the full 4-format detection-rule corpus + opsec_notes + arch_support + register helpers. CVE-2018-14634 — mutagen_astronomy (Qualys, closes 2018) create_elf_tables() int wrap → SUID-execve stack corruption. CISA KEV-listed Jan 2026 despite the bug's age; legacy RHEL 7 / CentOS 7 / Debian 8 fleets still affected. 🟡 PRIMITIVE. arch_support: x86_64+unverified-arm64. CVE-2019-14287 — sudo_runas_neg1 (Joe Vennix) sudo -u#-1 → uid_t underflow → root despite (ALL,!root) blacklist. Pure userspace logic bug; the famous Apple Information Security finding. detect() looks for a (ALL,!root) grant in sudo -ln output; PRECOND_FAIL when no such grant exists for the invoking user. arch_support: any (4 -> 5 userspace 'any' modules). CVE-2020-29661 — tioscpgrp (Jann Horn / Project Zero) TTY TIOCSPGRP ioctl race on PTY pairs → struct pid UAF in kmalloc-256. Affects everything through Linux 5.9.13. 🟡 PRIMITIVE (race-driver + msg_msg groom). Public PoCs from grsecurity / spender + Maxime Peterlin. CVE-2024-50264 — vsock_uaf (a13xp0p0v / Pwnie Award 2025 winner) AF_VSOCK connect-race UAF in kmalloc-96. Pwn2Own 2024 + Pwnie 2025 winner. Reachable as plain unprivileged user (no userns required — unusual). Two public exploit paths: @v4bel+@qwerty kernelCTF (BPF JIT spray + SLUBStick) and Alexander Popov / PT SWARM (msg_msg). 🟡 PRIMITIVE. CVE-2024-26581 — nft_pipapo (Notselwyn II, 'Flipping Pages') nft_set_pipapo destroy-race UAF. Sibling to nf_tables (CVE-2024-1086) from the same Notselwyn paper. Distinct bug in the pipapo set substrate. Same family signature. 🟡 PRIMITIVE. Plumbing changes: core/registry.h + registry_all.c — 5 new register declarations + calls. Makefile — 5 new MUT/SRN/TIO/VSK/PIP module groups in MODULE_OBJS. tests/test_detect.c — 7 new test rows covering the new modules (above-fix OK, predates-the-bug OK, sudo-no-grant PRECOND_FAIL). tools/verify-vm/targets.yaml — verifier entries for all 5 with honest 'expect_detect' values based on what Vagrant boxes can realistically reach (mutagen_astronomy gets OK on stock 18.04 since 4.15.0-213 is post-fix; sudo_runas_neg1 gets PRECOND_FAIL because no (ALL,!root) grant on default vagrant user; tioscpgrp + nft_pipapo VULNERABLE with kernel pins; vsock_uaf flagged manual because vsock module rarely available on CI runners). tools/refresh-cve-metadata.py — added curl fallback for the CISA KEV CSV fetch (urlopen times out intermittently against CISA's HTTP/2 endpoint). Corpus growth across v0.8.0 + v0.9.0: v0.7.1 v0.8.0 v0.9.0 Modules 31 34 39 Distinct CVEs 26 29 34 KEV-listed 10 10 11 (mutagen_astronomy) arch 'any' 4 6 7 (sudo_runas_neg1) Years 2016-2026: 10/11 10/11 **11/11** Year-by-year coverage: 2016: 1 2017: 1 2018: 1 2019: 2 2020: 2 2021: 5 2022: 5 2023: 8 2024: 3 2025: 2 2026: 4 CVE-2018 gap → CLOSED. Every year from 2016 through 2026 now has at least one module. Surfaces updated: - README.md: badge → 22 VM-verified / 34, Status section refreshed - docs/index.html: hero eyebrow + footer → v0.9.0, hero tagline 'every year 2016 → 2026', stats chips → 39 / 22 / 11 / 151 - docs/RELEASE_NOTES.md: v0.9.0 entry added on top with year coverage matrix + per-module breakdown; v0.8.0 + v0.7.1 entries preserved below - docs/og.svg + og.png: regenerated with new numbers + 'Every year 2016 → 2026' tagline CVE metadata refresh (tools/refresh-cve-metadata.py) deferred to follow-up — CISA KEV CSV + NVD CVE API were timing out during the v0.9.0 push window. The 5 new CVEs will return NULL from cve_metadata_lookup() until the refresh runs (—module-info simply skips the WEAKNESS/THREAT INTEL header for them; no functional impact). Re-run 'tools/refresh-cve-metadata.py' when network cooperates. Tests: macOS local 33/33 kernel_range pass; detect-test stubs (88 total) build clean; ASan/UBSan + clang-tidy CI jobs still green from the v0.7.x setup.
271 lines
12 KiB
Markdown
271 lines
12 KiB
Markdown
# SKELETONKEY
|
||
|
||
[](https://github.com/KaraZajac/SKELETONKEY/releases/latest)
|
||
[](LICENSE)
|
||
[](docs/VERIFICATIONS.jsonl)
|
||
[](#)
|
||
|
||
> **One curated binary. 39 Linux LPE modules covering 34 CVEs from 2016 → 2026.
|
||
> Every year 2016 → 2026 covered. 22 confirmed end-to-end against real Linux
|
||
> VMs via `tools/verify-vm/`. Detection rules in the box. One command picks
|
||
> the safest one and runs it.**
|
||
|
||
```bash
|
||
curl -sSL https://github.com/KaraZajac/SKELETONKEY/releases/latest/download/install.sh | sh \
|
||
&& skeletonkey --auto --i-know
|
||
```
|
||
|
||
> ⚠️ **Authorized testing only.** SKELETONKEY runs real exploits. By
|
||
> using it you assert you have explicit authorization to test the
|
||
> target system. See [`docs/ETHICS.md`](docs/ETHICS.md).
|
||
|
||
## Why use this
|
||
|
||
Most Linux privesc tooling is broken in one of three ways:
|
||
|
||
- **`linux-exploit-suggester` / `linpeas`** — tell you what *might*
|
||
work, run nothing
|
||
- **`auto-root-exploit` / `kernelpop`** — bundle exploits but ship
|
||
no detection signatures and went stale years ago
|
||
- **Per-CVE PoC repos** — one author, one distro, abandoned within
|
||
months
|
||
|
||
SKELETONKEY is one binary, actively maintained, with detection rules
|
||
for every CVE in the bundle — same project for red and blue teams.
|
||
|
||
## Who it's for
|
||
|
||
| Audience | What you get |
|
||
|---|---|
|
||
| **Red team / pentesters** | One tested binary. `--auto` ranks vulnerable modules by safety and runs the safest. Honest scope reporting — never claims root it didn't actually get. |
|
||
| **Sysadmins** | `skeletonkey --scan` (no sudo needed) tells you which boxes still need patching. Fleet-scan tool included. JSON output for CI gates ([schema](docs/JSON_SCHEMA.md)). |
|
||
| **Blue team / SOC** | Auditd + sigma + yara + falco rules for every CVE. `--detect-rules --format=auditd \| sudo tee …` ships SIEM coverage in one command. |
|
||
| **CTF / training** | Reproducible LPE environment with public CVEs across a 10-year timeline. Each module documents the bug, the trigger, and the fix. |
|
||
|
||
## Corpus at a glance
|
||
|
||
**31 modules covering 26 distinct CVEs** across the 2016 → 2026 LPE
|
||
timeline. **22 of the 26 CVEs have been empirically verified** in real
|
||
Linux VMs via `tools/verify-vm/`; the 4 still-pending entries are
|
||
blocked by their target environment, not by missing code.
|
||
|
||
| Tier | Count | What it means |
|
||
|---|---|---|
|
||
| 🟢 Full chain | **14** | Lands root (or its canonical capability) end-to-end. No per-kernel offsets needed. |
|
||
| 🟡 Primitive | **14** | Fires the kernel primitive + grooms the slab + records a witness. Default returns `EXPLOIT_FAIL` honestly. Pass `--full-chain` to engage the shared `modprobe_path` finisher (needs offsets — see [`docs/OFFSETS.md`](docs/OFFSETS.md)). |
|
||
|
||
**🟢 Modules that land root on a vulnerable host:**
|
||
copy_fail family ×5 · dirty_pipe · dirty_cow · pwnkit · overlayfs
|
||
(CVE-2021-3493) · overlayfs_setuid (CVE-2023-0386) ·
|
||
cgroup_release_agent · ptrace_traceme · sudoedit_editor · entrybleed
|
||
(KASLR leak primitive)
|
||
|
||
**🟡 Modules with opt-in `--full-chain`:**
|
||
af_packet · af_packet2 · af_unix_gc · cls_route4 · fuse_legacy ·
|
||
nf_tables · nft_set_uaf · nft_fwd_dup · nft_payload ·
|
||
netfilter_xtcompat · stackrot · sudo_samedit · sequoia · vmwgfx
|
||
|
||
### Empirical verification (22 of 26 CVEs)
|
||
|
||
Records in [`docs/VERIFICATIONS.jsonl`](docs/VERIFICATIONS.jsonl) prove
|
||
each verdict against a known-target VM. Coverage:
|
||
|
||
| Distro / kernel | Modules verified |
|
||
|---|---|
|
||
| Ubuntu 18.04 (4.15.0) | af_packet · ptrace_traceme · sudo_samedit |
|
||
| Ubuntu 20.04 (5.4 stock + 5.15 HWE) | af_packet2 · cls_route4 · nft_payload · overlayfs · pwnkit · sequoia |
|
||
| Ubuntu 22.04 (5.15 stock + mainline 5.15.5 / 6.1.10) | af_unix_gc · dirty_pipe · entrybleed · nf_tables · nft_set_uaf · overlayfs_setuid · stackrot · sudoedit_editor |
|
||
| Debian 11 (5.10 stock) | cgroup_release_agent · fuse_legacy · netfilter_xtcompat · nft_fwd_dup |
|
||
| Debian 12 (6.1 stock) | pack2theroot |
|
||
|
||
**Not yet verified (4):** `vmwgfx` (VMware-guest-only — no public
|
||
Vagrant box), `dirty_cow` (needs ≤ 4.4 kernel — older than every
|
||
supported box), `dirtydecrypt` & `fragnesia` (need Linux 7.0 — not
|
||
shipping as any distro kernel yet). All four are flagged in
|
||
[`tools/verify-vm/targets.yaml`](tools/verify-vm/targets.yaml) with
|
||
rationale.
|
||
|
||
See [`CVES.md`](CVES.md) for per-module CVE, kernel range, and
|
||
detection status. Run `skeletonkey --module-info <name>` for the
|
||
embedded verification records per module.
|
||
|
||
## Quickstart
|
||
|
||
```bash
|
||
# Install (x86_64 / arm64; checksum-verified)
|
||
curl -sSL https://github.com/KaraZajac/SKELETONKEY/releases/latest/download/install.sh | sh
|
||
|
||
# What's this box vulnerable to? (no sudo)
|
||
skeletonkey --scan
|
||
|
||
# One-page operator briefing for a single CVE: CWE / MITRE ATT&CK /
|
||
# CISA KEV status, live detect() trace, OPSEC footprint, detection
|
||
# coverage. Useful for triage tickets and SOC analyst handoffs.
|
||
skeletonkey --explain nf_tables
|
||
|
||
# Pick the safest LPE and run it
|
||
skeletonkey --auto --i-know
|
||
|
||
# Deploy detection rules (needs sudo to write into /etc/audit/rules.d/)
|
||
skeletonkey --detect-rules --format=auditd \
|
||
| sudo tee /etc/audit/rules.d/99-skeletonkey.rules
|
||
|
||
# Fleet scan — many hosts via SSH, aggregated JSON for SIEM
|
||
./tools/skeletonkey-fleet-scan.sh --binary skeletonkey \
|
||
--ssh-key ~/.ssh/id_rsa hosts.txt
|
||
```
|
||
|
||
**SKELETONKEY runs as a normal unprivileged user** — that's the point.
|
||
`--scan`, `--audit`, `--exploit`, and `--detect-rules` all work without
|
||
`sudo`. Only `--mitigate` and rule-file installation write root-owned
|
||
paths.
|
||
|
||
### Example: unprivileged → root
|
||
|
||
```text
|
||
$ id
|
||
uid=1000(kara) gid=1000(kara) groups=1000(kara)
|
||
|
||
$ skeletonkey --auto --i-know
|
||
[*] auto: host=demo distro=ubuntu/24.04 kernel=5.15.0-56-generic arch=x86_64
|
||
[*] auto: active probes enabled — brief /tmp file touches and fork-isolated namespace probes
|
||
[*] auto: scanning 31 modules for vulnerabilities...
|
||
[+] auto: dirty_pipe VULNERABLE (safety rank 90)
|
||
[+] auto: cgroup_release_agent VULNERABLE (safety rank 98)
|
||
[+] auto: pwnkit VULNERABLE (safety rank 100)
|
||
[ ] auto: copy_fail patched or not applicable
|
||
[ ] auto: nf_tables precondition not met
|
||
...
|
||
|
||
[*] auto: scan summary — 3 vulnerable, 21 patched/n.a., 7 precondition-fail, 0 indeterminate
|
||
[*] auto: 3 vulnerable modules found. Safest is 'pwnkit' (rank 100).
|
||
[*] auto: launching --exploit pwnkit...
|
||
|
||
[+] pwnkit: writing gconv-modules cache + payload.so...
|
||
[+] pwnkit: execve(pkexec) with NULL argv + crafted envp...
|
||
# id
|
||
uid=0(root) gid=0(root) groups=0(root)
|
||
```
|
||
|
||
The safety ranking goes: **structural escapes** (no kernel state
|
||
touched) → **page-cache writes** → **userspace cred-races** →
|
||
**kernel primitives** → **kernel races** (least predictable). The
|
||
goal is to never crash a production box looking for root.
|
||
|
||
## How it works
|
||
|
||
Each CVE (or tightly-related family) is a **module** under `modules/`.
|
||
Modules export a standard interface (`detect / exploit / mitigate /
|
||
cleanup`) plus metadata (kernel range, detection rule text). The
|
||
top-level binary dispatches per command:
|
||
|
||
- `--scan` walks every module's `detect()` against the running host
|
||
- `--exploit <name> --i-know` runs the named module's exploit (the
|
||
`--i-know` flag is the authorization gate)
|
||
- `--auto --i-know` does the scan, ranks by safety, runs the safest
|
||
- `--detect-rules --format=<auditd|sigma|yara|falco>` emits the
|
||
embedded rule corpus
|
||
- `--mitigate <name>` / `--cleanup <name>` apply / undo temporary
|
||
mitigations (module-dependent — most kernel modules say "upgrade")
|
||
- `--dump-offsets` reads `/proc/kallsyms` + `/boot/System.map` and
|
||
emits a ready-to-paste C entry for the `--full-chain` offset table
|
||
|
||
See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the
|
||
module-loader design.
|
||
|
||
## The verified-vs-claimed bar
|
||
|
||
Most public PoC repos hardcode offsets for one kernel build and
|
||
silently break elsewhere. SKELETONKEY refuses to ship fabricated
|
||
offsets. The shared `--full-chain` finisher only returns
|
||
`EXPLOIT_OK` after a setuid bash sentinel file *actually appears*;
|
||
otherwise modules return `EXPLOIT_FAIL` with a diagnostic. Operators
|
||
populate the offset table once per target kernel via
|
||
`skeletonkey --dump-offsets` and either set env vars or upstream the
|
||
entry via PR ([`CONTRIBUTING.md`](CONTRIBUTING.md)).
|
||
|
||
## Build from source
|
||
|
||
```bash
|
||
git clone https://github.com/KaraZajac/SKELETONKEY.git
|
||
cd SKELETONKEY
|
||
make
|
||
./skeletonkey --version
|
||
```
|
||
|
||
Builds clean with gcc or clang on any modern Linux. macOS dev builds
|
||
also compile (modules with Linux-only headers stub out gracefully).
|
||
|
||
## Status
|
||
|
||
**v0.9.0 cut 2026-05-24.** 39 modules across 34 CVEs — **every
|
||
year 2016 → 2026 now covered**. v0.9.0 adds 5 gap-fillers:
|
||
`mutagen_astronomy` (CVE-2018-14634 — closes 2018), `sudo_runas_neg1`
|
||
(CVE-2019-14287), `tioscpgrp` (CVE-2020-29661), `vsock_uaf`
|
||
(CVE-2024-50264 — Pwnie 2025 winner), `nft_pipapo` (CVE-2024-26581 —
|
||
Notselwyn II). v0.8.0 added 3 (`sudo_chwoot`/CVE-2025-32463,
|
||
`udisks_libblockdev`/CVE-2025-6019, `pintheft`/CVE-2026-43494).
|
||
**22 empirically verified** against real Linux VMs (Ubuntu 18.04 /
|
||
20.04 / 22.04 + Debian 11 / 12 + mainline kernels 5.15.5 / 6.1.10
|
||
from kernel.ubuntu.com). 88-test unit harness + ASan/UBSan +
|
||
clang-tidy on every push. 4 prebuilt binaries (x86_64 + arm64, each
|
||
in dynamic + static-musl flavors).
|
||
|
||
Reliability + accuracy work in v0.7.x:
|
||
- Shared **host fingerprint** (`core/host.{h,c}`) populated once at
|
||
startup — kernel/distro/userns gates/sudo+polkit versions — exposed
|
||
to every module via `ctx->host`.
|
||
- **Test harness** (`tests/`, `make test`) — 88 tests: 33 kernel_range
|
||
unit tests + 55 detect() integration tests over mocked host
|
||
fingerprints. Runs in CI on every push.
|
||
- **VM verifier** (`tools/verify-vm/`) — Vagrant + Parallels scaffold
|
||
that boots known-vulnerable kernels (stock distro + mainline via
|
||
kernel.ubuntu.com), runs `--explain --active` per module, records
|
||
match/MISMATCH/PRECOND_FAIL as JSON. 22 modules confirmed end-to-end.
|
||
- **`--explain <module>`** — single-page operator briefing: CVE / CWE
|
||
/ MITRE ATT&CK / CISA KEV status, host fingerprint, live detect()
|
||
trace, OPSEC footprint, detection-rule coverage, verified-on
|
||
records. Paste-into-ticket ready.
|
||
- **CVE metadata pipeline** (`tools/refresh-cve-metadata.py`) — fetches
|
||
CISA KEV catalog + NVD CWE; 10 of 26 modules cover KEV-listed CVEs.
|
||
- **119 detection rules** across auditd / sigma / yara / falco; one
|
||
command exports the corpus to your SIEM.
|
||
- `--auto` upgrades: per-detect 15s timeout, fork-isolated detect +
|
||
exploit, structured verdict table, scan summary, `--dry-run`.
|
||
|
||
Not yet verified (4 of 26 CVEs): `vmwgfx` (VMware-guest only),
|
||
`dirty_cow` (needs ≤ 4.4 kernel), `dirtydecrypt` + `fragnesia` (need
|
||
Linux 7.0 — not shipping yet). Rationale in
|
||
[`tools/verify-vm/targets.yaml`](tools/verify-vm/targets.yaml).
|
||
|
||
See [`ROADMAP.md`](ROADMAP.md) for the next planned modules and
|
||
infrastructure work.
|
||
|
||
## Contributing
|
||
|
||
PRs welcome for: kernel offsets (run `--dump-offsets` on a target
|
||
kernel, paste into `core/offsets.c`), new modules, detection rules,
|
||
and CVE-status corrections. See [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
||
|
||
**Keeping `kernel_range` tables current.** `tools/refresh-kernel-ranges.py`
|
||
polls Debian's security tracker and reports drift between each
|
||
module's hardcoded `kernel_patched_from` thresholds and the
|
||
fixed-versions Debian actually ships. Run periodically (or in CI)
|
||
to catch new backports that need to land in the corpus:
|
||
|
||
```bash
|
||
tools/refresh-kernel-ranges.py # human report
|
||
tools/refresh-kernel-ranges.py --json # machine-readable
|
||
tools/refresh-kernel-ranges.py --patch # proposed C-source edits
|
||
```
|
||
|
||
## Acknowledgments
|
||
|
||
Each module credits the original CVE reporter and PoC author in its
|
||
`NOTICE.md`. SKELETONKEY is the bundling and bookkeeping layer;
|
||
the research credit belongs to the people who found the bugs.
|
||
|
||
## License
|
||
|
||
MIT — see [`LICENSE`](LICENSE).
|