Files
SKELETONKEY/docs/index.html
T
leviathan 9a4cc91619 pack2theroot (CVE-2026-41651) + --auto accuracy work
Adds the third ported module — Pack2TheRoot, a userspace PackageKit
D-Bus TOCTOU LPE — and spends real effort hardening --auto so its
detect step gives an accurate, robust verdict before deploying.

pack2theroot (CVE-2026-41651):
- Ported from the public Vozec PoC
  (github.com/Vozec/CVE-2026-41651). Original disclosure by the
  Deutsche Telekom security team.
- Two back-to-back InstallFiles D-Bus calls (SIMULATE then NONE)
  overwrite the cached transaction flags between polkit auth and
  dispatch. GLib priority ordering makes the overwrite deterministic,
  not a timing race; postinst of the malicious .deb drops a SUID bash
  in /tmp.
- detect() reads PackageKit's VersionMajor/Minor/Micro directly over
  D-Bus and compares against the pinned fix release 1.3.5 (commit
  76cfb675). This is a high-confidence verdict, not precondition-only.
- Debian-family only (PoC builds its own .deb in pure C; ar/ustar/
  gzip-stored inline). Cleanup removes /tmp .debs + best-effort
  unlinks /tmp/.suid_bash + sudo -n dpkg -r the staging packages.
- Adds an optional GLib/GIO build dependency. The top-level Makefile
  autodetects via `pkg-config gio-2.0`; when absent the module
  compiles as a stub returning PRECOND_FAIL.
- Embedded auditd + sigma rules cover the file-side footprint
  (/tmp/.suid_bash, /tmp/.pk-*.deb, non-root dpkg/apt execve).

--auto accuracy improvements:
- Auto-enables --active before the scan. Per-module sentinel probes
  (page-cache /tmp files, fork-isolated namespace mounts) turn
  version-only checks into definitive verdicts, so silent distro
  backports don't fool the scan and --auto won't pick blind on
  TEST_ERROR.
- Per-module verdict printing — every module's result is shown
  (VULNERABLE / patched / precondition / indeterminate), not just
  VULNERABLE rows. Operator sees the full picture.
- Scan-end summary line: "N vulnerable, M patched/n.a., K
  precondition-fail, L indeterminate" with a separate callout when
  modules crashed.
- Distro fingerprint added to the auto banner (ID + VERSION_ID from
  /etc/os-release alongside kernel/arch).
- Fork-isolated detect() — each detector runs in a child process so
  a SIGILL/SIGSEGV in one module's probe is contained and the scan
  continues. Surfaced live while testing: entrybleed's prefetchnta
  KASLR sweep SIGILLs on emulated CPUs (linuxkit on darwin); without
  isolation the whole --auto died at module 7 of 31. With isolation
  the scan reports "detect() crashed (signal 4) — continuing" and
  finishes cleanly.

module_safety_rank additions:
- pack2theroot: 95 (userspace D-Bus TOCTOU; dpkg + /tmp SUID footprint
  — clean but heavier than pwnkit's gconv-modules-only path).
- dirtydecrypt / fragnesia: 86 (page-cache writes; one step below the
  verified copy_fail/dirty_frag family at 88 to prefer verified
  modules when both apply).

Docs:
- README badge / tagline / tier table /  block / example output /
  v0.5.0 status — all updated to "28 verified + 3 ported".
- CVES.md counts line, the ported-modules note (now calling out
  pack2theroot's high-confidence detect vs. precondition-only for
  the page-cache pair), inventory row, operations table row.
- ROADMAP Phase 7+: pack2theroot moved out of carry-overs into the
  "landed (ported, pending VM verification)" group; added a new
  "--auto accuracy work" subsection documenting the dispatcher
  hardening landed in this commit.
- docs/index.html: scanning-count example bumped to 31, status line
  updated to mention 3 ported modules.

Build verification: full `make clean && make` in `docker gcc:latest`
with libglib2.0-dev installed: links into a 31-module skeletonkey
ELF (413KB), `--list` shows all modules including pack2theroot,
`--detect-rules --format=auditd` emits the new pack2theroot section,
`--auto --i-know --no-shell` exercises the new banner + active
probes + verdict table + fork isolation + scan summary end-to-end.
Only build warning is the pre-existing
`-Wunterminated-string-initialization` in dirty_pipe (not introduced
here).
2026-05-22 22:42:07 -04:00

290 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SKELETONKEY — Curated Linux LPE corpus with detection rules</title>
<meta name="description" content="One curated binary. 28 Linux privilege-escalation exploits from 2016 → 2026. Auditd + sigma + yara + falco rules in the box. One command picks the safest LPE and runs it.">
<meta property="og:title" content="SKELETONKEY — Curated Linux LPE corpus">
<meta property="og:description" content="28 Linux LPE exploits, 2016 → 2026, with detection rules in the box. One command picks the safest one and runs it.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://karazajac.github.io/SKELETONKEY/">
<meta name="twitter:card" content="summary">
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav class="nav">
<span class="nav-brand">SKELETONKEY</span>
<a class="nav-github" href="https://github.com/KaraZajac/SKELETONKEY"
aria-label="View on GitHub">
<svg height="20" viewBox="0 0 16 16" width="20" fill="currentColor" aria-hidden="true">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38
0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13
-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66
.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15
-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0
1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82
1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01
1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
</svg>
<span>GitHub</span>
</a>
</nav>
<header class="hero">
<div class="container">
<h1>SKELETONKEY</h1>
<p class="tag">
One curated binary. <strong>28 Linux LPE exploits</strong> from
2016 → 2026. Detection rules in the box.
<strong>One command picks the safest one and runs it.</strong>
</p>
<div class="install-block">
<button class="copy" onclick="copyInstall(this)">copy</button>
<pre id="install-cmd"><span class="prompt">$</span> curl -sSL https://github.com/KaraZajac/SKELETONKEY/releases/latest/download/install.sh | sh \
&amp;&amp; skeletonkey --auto --i-know</pre>
</div>
<p class="warn">⚠ Authorized testing only — see <a href="https://github.com/KaraZajac/SKELETONKEY/blob/main/docs/ETHICS.md">ETHICS.md</a></p>
<div class="cta-row">
<a class="btn btn-primary" href="https://github.com/KaraZajac/SKELETONKEY/releases/latest">Latest release</a>
<a class="btn" href="https://github.com/KaraZajac/SKELETONKEY">View on GitHub</a>
<a class="btn" href="https://github.com/KaraZajac/SKELETONKEY/blob/main/CVES.md">Full CVE inventory</a>
</div>
</div>
</header>
<section>
<div class="container">
<h2>Why this exists</h2>
<p class="lead">
Most Linux privesc tooling is broken in one of three ways:
</p>
<ul class="tight">
<li><strong>linux-exploit-suggester / linpeas</strong> — tell you what <em>might</em> work, run nothing</li>
<li><strong>auto-root-exploit / kernelpop</strong> — bundle exploits but ship no detection signatures and went stale years ago</li>
<li><strong>Per-CVE PoC repos</strong> — one author, one distro, abandoned within months</li>
</ul>
<p class="lead" style="margin-top:1rem">
SKELETONKEY is one binary, actively maintained, with detection
rules for every CVE it bundles — same project for red and blue
teams.
</p>
</div>
</section>
<section>
<div class="container">
<h2>Corpus at a glance</h2>
<div class="stats">
<div class="stat">
<span class="stat-num">28</span>
<span class="stat-label">verified modules</span>
</div>
<div class="stat">
<span class="stat-num green">14</span>
<span class="stat-label">🟢 land root by default</span>
</div>
<div class="stat">
<span class="stat-num yellow">14</span>
<span class="stat-label">🟡 primitive + opt-in chain</span>
</div>
<div class="stat">
<span class="stat-num">10y</span>
<span class="stat-label">2016 → 2026 coverage</span>
</div>
</div>
<h3 style="color: var(--green);">🟢 Lands root on a vulnerable host</h3>
<p style="color: var(--text-muted); font-size:0.92rem; margin:0.25rem 0 0.25rem;">Structural exploits + page-cache writes. No per-kernel offsets needed.</p>
<div class="pills">
<span class="pill green">copy_fail</span>
<span class="pill green">copy_fail_gcm</span>
<span class="pill green">dirty_frag_esp</span>
<span class="pill green">dirty_frag_esp6</span>
<span class="pill green">dirty_frag_rxrpc</span>
<span class="pill green">dirty_pipe</span>
<span class="pill green">dirty_cow</span>
<span class="pill green">pwnkit</span>
<span class="pill green">overlayfs</span>
<span class="pill green">overlayfs_setuid</span>
<span class="pill green">cgroup_release_agent</span>
<span class="pill green">ptrace_traceme</span>
<span class="pill green">sudoedit_editor</span>
<span class="pill green">entrybleed</span>
</div>
<h3 style="color: var(--yellow);">🟡 Fires kernel primitive · opt-in <code>--full-chain</code></h3>
<p style="color: var(--text-muted); font-size:0.92rem; margin:0.25rem 0 0.25rem;">Default returns <code>EXPLOIT_FAIL</code> honestly. With <code>--full-chain</code> + resolved offsets, runs the shared modprobe_path finisher.</p>
<div class="pills">
<span class="pill yellow">nf_tables</span>
<span class="pill yellow">nft_set_uaf</span>
<span class="pill yellow">nft_fwd_dup</span>
<span class="pill yellow">nft_payload</span>
<span class="pill yellow">netfilter_xtcompat</span>
<span class="pill yellow">af_packet</span>
<span class="pill yellow">af_packet2</span>
<span class="pill yellow">af_unix_gc</span>
<span class="pill yellow">cls_route4</span>
<span class="pill yellow">fuse_legacy</span>
<span class="pill yellow">stackrot</span>
<span class="pill yellow">sudo_samedit</span>
<span class="pill yellow">sequoia</span>
<span class="pill yellow">vmwgfx</span>
</div>
</div>
</section>
<section>
<div class="container">
<h2>Who it's for</h2>
<div class="cards">
<div class="card">
<h3>🔴 Red team / pentesters</h3>
<p>One tested binary. <code>--auto</code> ranks vulnerable modules by safety and runs the safest. Honest scope reporting — never claims root it didn't actually get. No more curating stale PoC repos.</p>
</div>
<div class="card">
<h3>🔵 Blue team / SOC</h3>
<p>Auditd + sigma + yara + falco rules for every CVE. One command ships SIEM coverage: <code>--detect-rules --format=auditd | sudo tee /etc/audit/rules.d/99-skeletonkey.rules</code>.</p>
</div>
<div class="card">
<h3>🛠 Sysadmins</h3>
<p><code>skeletonkey --scan</code> (no sudo needed) tells you which boxes still need patching. JSON output for CI gates. Fleet-scan tool included. No SaaS, no telemetry.</p>
</div>
<div class="card">
<h3>🎓 CTF / training</h3>
<p>Reproducible LPE environment with public CVEs across a 10-year timeline. Each module documents the bug, the trigger, and the fix. Detection rules let you practice both sides.</p>
</div>
</div>
</div>
</section>
<section>
<div class="container">
<h2>What it looks like</h2>
<p class="lead"><code>--auto</code> on a vulnerable Ubuntu 22.04 box:</p>
<pre class="code"><span class="prompt">$</span> id
uid=1000(kara) gid=1000(kara) groups=1000(kara)
<span class="prompt">$</span> skeletonkey --auto --i-know
<span class="hl-muted">[*]</span> auto: host=demo kernel=5.15.0-56-generic arch=x86_64
<span class="hl-muted">[*]</span> auto: scanning 31 modules for vulnerabilities...
<span class="hl-green">[+]</span> auto: dirty_pipe <span class="hl-yellow">VULNERABLE</span> (safety rank 90)
<span class="hl-green">[+]</span> auto: cgroup_release_agent <span class="hl-yellow">VULNERABLE</span> (safety rank 98)
<span class="hl-green">[+]</span> auto: pwnkit <span class="hl-yellow">VULNERABLE</span> (safety rank 100)
<span class="hl-muted">[*]</span> auto: 3 vulnerable modules found. Safest is <span class="hl-accent">'pwnkit'</span> (rank 100).
<span class="hl-muted">[*]</span> auto: launching --exploit pwnkit...
<span class="hl-green">[+]</span> pwnkit: writing gconv-modules cache + payload.so...
<span class="hl-green">[+]</span> pwnkit: execve(pkexec) with NULL argv + crafted envp...
<span class="hl-green">#</span> id
uid=0(root) gid=0(root) groups=0(root)</pre>
<p style="color: var(--text-muted); font-size: 0.92rem; margin-top: 1rem">
Safety ranking goes <strong>structural escapes</strong>
<strong>page-cache writes</strong>
<strong>userspace cred-races</strong>
<strong>kernel primitives</strong>
<strong>kernel races</strong>. The goal is to never crash a
production box looking for root.
</p>
</div>
</section>
<section>
<div class="container">
<h2>The verified-vs-claimed bar</h2>
<p class="lead">
Most public PoC repos hardcode offsets for one kernel build and
silently break elsewhere. SKELETONKEY refuses to ship fabricated
offsets.
</p>
<ul class="tight">
<li>The shared <code>--full-chain</code> finisher returns <code>EXPLOIT_OK</code> only when a setuid bash sentinel file <em>actually appears</em></li>
<li>Modules with a primitive but no portable cred-overwrite chain default to firing the primitive + grooming the slab + recording a witness, then return <code>EXPLOIT_FAIL</code> with diagnostic</li>
<li>Operators populate the offset table once per kernel via <code>skeletonkey --dump-offsets</code> (parses <code>/proc/kallsyms</code> or <code>/boot/System.map</code>) and upstream the entry via PR — see <a href="https://github.com/KaraZajac/SKELETONKEY/blob/main/CONTRIBUTING.md">CONTRIBUTING.md</a></li>
</ul>
</div>
</section>
<section>
<div class="container">
<h2>Quickstart commands</h2>
<pre class="code"><span class="cmt"># Install (x86_64 / arm64; checksum-verified)</span>
<span class="prompt">$</span> curl -sSL https://github.com/KaraZajac/SKELETONKEY/releases/latest/download/install.sh | sh
<span class="cmt"># What's this box vulnerable to? (no sudo)</span>
<span class="prompt">$</span> skeletonkey --scan
<span class="cmt"># Pick the safest LPE and run it</span>
<span class="prompt">$</span> skeletonkey --auto --i-know
<span class="cmt"># Deploy detection rules (needs sudo to write into /etc/audit/rules.d/)</span>
<span class="prompt">$</span> skeletonkey --detect-rules --format=auditd \
| sudo tee /etc/audit/rules.d/99-skeletonkey.rules
<span class="cmt"># Fleet scan — many hosts via SSH, aggregated JSON for SIEM</span>
<span class="prompt">$</span> ./tools/skeletonkey-fleet-scan.sh --binary skeletonkey \
--ssh-key ~/.ssh/id_rsa hosts.txt</pre>
</div>
</section>
<section>
<div class="container">
<h2>Status</h2>
<p class="lead">
<strong>v0.5.0</strong> cut 2026-05-17. 28 verified modules build
clean on Debian 13 (kernel 6.12) and refuse cleanly on patched
hosts; 3 further modules (dirtydecrypt, fragnesia, pack2theroot)
are ported from public PoCs but not yet VM-verified.
Empirical end-to-end validation on a vulnerable-kernel VM matrix
is the next roadmap item; until then, the corpus is best
understood as "compiles + detects + structurally correct +
honest on failure."
</p>
<p style="margin-top:1rem">
<a class="btn" href="https://github.com/KaraZajac/SKELETONKEY/blob/main/ROADMAP.md">Read the roadmap</a>
<a class="btn" href="https://github.com/KaraZajac/SKELETONKEY/blob/main/CONTRIBUTING.md">How to contribute</a>
</p>
</div>
</section>
<footer>
<div class="container">
<p>
Each module credits the original CVE reporter and PoC author in its
<code>NOTICE.md</code>. The research credit belongs to the people
who found the bugs.
</p>
<p>
MIT licensed ·
<a href="https://github.com/KaraZajac/SKELETONKEY">github.com/KaraZajac/SKELETONKEY</a>
</p>
</div>
</footer>
<script>
function copyInstall(btn) {
var cmd = document.getElementById('install-cmd').innerText.replace(/^\$\s*/, '');
navigator.clipboard.writeText(cmd).then(function() {
btn.textContent = 'copied!';
btn.classList.add('copied');
setTimeout(function() {
btn.textContent = 'copy';
btn.classList.remove('copied');
}, 1500);
});
}
</script>
</body>
</html>