Compare commits

..

2 Commits

Author SHA1 Message Date
KaraZajac 28a9289989 fix: normalize CVE_METADATA.json to sorted order (drift-check)
release / build (arm64) (push) Waiting to run
release / build (x86_64) (push) Waiting to run
release / build (x86_64-static / musl) (push) Waiting to run
release / build (arm64-static / musl) (push) Waiting to run
release / release (push) Blocked by required conditions
The weekly drift-check stayed red after v0.9.9's KEV+CWE fixes because of a
latent ordering bug: sudo_host (CVE-2025-32462), added in v0.9.8, was
appended to the end of CVE_METADATA.json instead of its sorted position.
check_drift compares the record list in discover_cves() sorted order, so
the misplaced entry read as drift independent of its field values.
Regenerated via tools/refresh-cve-metadata.py — sorted order restored, all
field values reconfirmed against CISA KEV + NVD in a clean fetch.
2026-06-08 10:30:25 -04:00
KaraZajac e457b22c1f release v0.9.9: install.sh needs no sudo + CVE metadata drift fix
install.sh never escalates to sudo. The installer defaulted to
/usr/local/bin and fell back to `sudo mv`, prompting for a password on
exactly the unprivileged accounts a privilege-escalation tool targets. It
now uses /usr/local/bin only when already writable and otherwise installs
to a per-user $HOME/.local/bin (honoring XDG_BIN_HOME), no sudo ever. An
explicit SKELETONKEY_PREFIX is honored and errors rather than escalating.
The documented one-liner prepends ~/.local/bin to PATH so it resolves on a
fresh login, and the quickstart drops the misleading sudo from --scan /
--audit / --auto.

CVE metadata drift (the failing weekly drift-check):
  - CVE-2022-0492 (cgroup_release_agent) entered CISA KEV 2026-06-02;
    corpus now 13 of 36 modules cover KEV-listed CVEs.
  - CVE-2026-46333 (ptrace_pidfd) gained CWE-269 from NVD (was unclassified
    at module-add time).
Refreshed CVE_METADATA.json, generated cve_metadata.c, and KEV_CROSSREF.md;
README + website KEV counts and version bumped to 0.9.9.
2026-06-08 10:16:24 -04:00
12 changed files with 117 additions and 48 deletions
+1 -1
View File
@@ -75,7 +75,7 @@ root on a host can upstream their kernel's offsets via PR.
| CVE-2022-2588 | net/sched cls_route4 handle-zero dead UAF | LPE (kernel UAF in cls_route4 filter remove) | mainline 5.20 / 5.19.7 (Aug 2022) | `cls_route4` | 🟡 | Userns+netns reach, tc/ip dummy interface + route4 dangling-filter add/del, msg_msg kmalloc-1k spray, UDP classify drive to follow the dangling pointer, slabinfo delta witness. Stops at empirical UAF-fired signal; no leak→cred overwrite (per-kernel offsets refused). Branch backports: 5.4.213 / 5.10.143 / 5.15.69 / 5.18.18 / 5.19.7. |
| CVE-2016-5195 | Dirty COW — COW race via /proc/self/mem + madvise | LPE (page-cache write into root-owned files) | mainline 4.9 (Oct 2016) | `dirty_cow` | 🟢 | Full detect + exploit + cleanup. **Old-systems coverage** — affects RHEL 6/7 (3.10 baseline), Ubuntu 14.04 (3.13), Ubuntu 16.04 (4.4), embedded boxes, IoT. Phil-Oester-style two-thread race: writer thread via `/proc/self/mem` vs madvise(MADV_DONTNEED) thread. Targets /etc/passwd UID flip + `su`. Ships auditd watch on /proc/self/mem + sigma rule for non-root mem-open. Pthread-linked. |
| CVE-2019-13272 | PTRACE_TRACEME → setuid execve → cred escalation | LPE (kernel ptrace race; no exotic preconditions) | mainline 5.1.17 (Jun 2019) | `ptrace_traceme` | 🟢 | Full detect + exploit. Branch backports: 4.4.182 / 4.9.182 / 4.14.131 / 4.19.58 / 5.0.20 / 5.1.17. jannh-style: fork → child `PTRACE_TRACEME` → child sleep+attach → parent `execve` setuid bin (pkexec/su/passwd auto-selected) → child wins stale-ptrace_link → POKETEXT x86_64 shellcode → root sh. x86_64-only; ARM/other return PRECOND_FAIL cleanly. |
| CVE-2022-0492 | cgroup v1 `release_agent` privilege check in wrong namespace | LPE (host root from rootless container or unprivileged userns) | mainline 5.17 (Mar 2022) | `cgroup_release_agent` | 🟢 | Universal structural exploit — no per-kernel offsets, no race. unshare(user|mount|cgroup), mount cgroup v1 RDP controller, write release_agent → ./payload, trigger via notify_on_release. Ships auditd rules covering cgroupfs mount + release_agent writes. Kept as a portable "containers misconfigured" demo. |
| CVE-2022-0492 | cgroup v1 `release_agent` privilege check in wrong namespace | LPE (host root from rootless container or unprivileged userns) | mainline 5.17 (Mar 2022) | `cgroup_release_agent` | 🟢 | Universal structural exploit — no per-kernel offsets, no race. unshare(user|mount|cgroup), mount cgroup v1 RDP controller, write release_agent → ./payload, trigger via notify_on_release. Ships auditd rules covering cgroupfs mount + release_agent writes. Kept as a portable "containers misconfigured" demo. **Added to CISA KEV 2026-06-02 — now confirmed exploited in the wild.** |
| CVE-2023-0386 | overlayfs `copy_up` preserves setuid bit across mount-ns boundary | LPE (host root via setuid carrier from unprivileged mount) | mainline 5.11 / 6.2-rc6 (Jan 2023) | `overlayfs_setuid` | 🟢 | Distro-agnostic — places a setuid binary in an overlay lower, mounts via fuse-overlayfs userns trick, executes from upper to inherit the setuid bit + root euid. Branch backports tracked for 5.10.169 / 5.15.92 / 6.1.11 / 6.2.x. |
| CVE-2021-22555 | iptables xt_compat heap-OOB → cross-cache UAF | LPE (kernel R/W via 4-byte heap OOB write + msg_msg/sk_buff groom) | mainline 5.12 / 5.11.10 (Apr 2021) | `netfilter_xtcompat` | 🟡 | Hand-rolled `ipt_replace` blob + setsockopt(IPT_SO_SET_REPLACE) fires the 4-byte OOB, msg_msg spray in kmalloc-2k + sk_buff sidecar, MSG_COPY scan for cross-cache landing + slabinfo delta. Stops before the leak → modprobe_path overwrite chain (per-kernel offsets refused). Branch backports: 5.11.10 / 5.10.27 / 5.4.110 / 4.19.185 / 4.14.230 / 4.9.266 / 4.4.266. **Bug existed since 2.6.19 (2006).** Andy Nguyen's PGZ disclosure. |
| CVE-2017-7308 | AF_PACKET TPACKET_V3 integer overflow → heap write-where | LPE (CAP_NET_RAW via userns) | mainline 4.11 / 4.10.6 (Mar 2017) | `af_packet` | 🟡 | Konovalov's TPACKET_V3 overflow + 200-skb spray + best-effort cred race. Offset table (Ubuntu 16.04/4.4 + 18.04/4.15) + `SKELETONKEY_AFPACKET_OFFSETS` env override for other kernels. x86_64-only; ARM returns PRECOND_FAIL. Branch backports: 4.10.6 / 4.9.18 / 4.4.57 / 3.18.49. |
+3 -2
View File
@@ -12,6 +12,7 @@
```bash
curl -sSL https://github.com/KaraZajac/SKELETONKEY/releases/latest/download/install.sh | sh \
&& export PATH="$HOME/.local/bin:$PATH" \
&& skeletonkey --auto --i-know
```
@@ -205,7 +206,7 @@ also compile (modules with Linux-only headers stub out gracefully).
## Status
**v0.9.8 cut 2026-06-02.** 41 modules across 36 CVEs — **every
**v0.9.9 cut 2026-06-08.** 41 modules across 36 CVEs — **every
year 2016 → 2026 now covered**. Newest: `ptrace_pidfd` (CVE-2026-46333,
Qualys's `__ptrace_may_access` / `pidfd_getfd` credential-steal) and
`sudo_host` (CVE-2025-32462, Stratascale's sudo `--host` policy bypass).
@@ -238,7 +239,7 @@ Reliability + accuracy work in v0.7.x:
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; 12 of 34 modules cover KEV-listed CVEs.
CISA KEV catalog + NVD CWE; 13 of 36 modules cover KEV-listed CVEs.
- **151 detection rules** across auditd / sigma / yara / falco; one
command exports the corpus to your SIEM.
- `--auto` upgrades: per-detect 15s timeout, fork-isolated detect +
+11 -11
View File
@@ -121,8 +121,8 @@ const struct cve_metadata cve_metadata_table[] = {
.cwe = "CWE-287",
.attack_technique = "T1611",
.attack_subtechnique = NULL,
.in_kev = false,
.kev_date_added = "",
.in_kev = true,
.kev_date_added = "2026-06-02",
},
{
.cve = "CVE-2022-0847",
@@ -236,6 +236,14 @@ const struct cve_metadata cve_metadata_table[] = {
.in_kev = false,
.kev_date_added = "",
},
{
.cve = "CVE-2025-32462",
.cwe = "CWE-863",
.attack_technique = "T1068",
.attack_subtechnique = NULL,
.in_kev = false,
.kev_date_added = "",
},
{
.cve = "CVE-2025-32463",
.cwe = "CWE-829",
@@ -286,15 +294,7 @@ const struct cve_metadata cve_metadata_table[] = {
},
{
.cve = "CVE-2026-46333",
.cwe = NULL,
.attack_technique = "T1068",
.attack_subtechnique = NULL,
.in_kev = false,
.kev_date_added = "",
},
{
.cve = "CVE-2025-32462",
.cwe = "CWE-863",
.cwe = "CWE-269",
.attack_technique = "T1068",
.attack_subtechnique = NULL,
.in_kev = false,
+12 -12
View File
@@ -122,8 +122,8 @@
"cwe": "CWE-287",
"attack_technique": "T1611",
"attack_subtechnique": null,
"in_kev": false,
"kev_date_added": ""
"in_kev": true,
"kev_date_added": "2026-06-02"
},
{
"cve": "CVE-2022-0847",
@@ -251,6 +251,15 @@
"in_kev": false,
"kev_date_added": ""
},
{
"cve": "CVE-2025-32462",
"module_dir": "sudo_host_cve_2025_32462",
"cwe": "CWE-863",
"attack_technique": "T1068",
"attack_subtechnique": null,
"in_kev": false,
"kev_date_added": ""
},
{
"cve": "CVE-2025-32463",
"module_dir": "sudo_chwoot_cve_2025_32463",
@@ -308,16 +317,7 @@
{
"cve": "CVE-2026-46333",
"module_dir": "ptrace_pidfd_cve_2026_46333",
"cwe": null,
"attack_technique": "T1068",
"attack_subtechnique": null,
"in_kev": false,
"kev_date_added": ""
},
{
"cve": "CVE-2025-32462",
"module_dir": "sudo_host_cve_2025_32462",
"cwe": "CWE-863",
"cwe": "CWE-269",
"attack_technique": "T1068",
"attack_subtechnique": null,
"in_kev": false,
+4 -2
View File
@@ -4,7 +4,7 @@ Which SKELETONKEY modules cover CVEs that CISA has observed exploited
in the wild per the Known Exploited Vulnerabilities catalog.
Refreshed via `tools/refresh-cve-metadata.py`.
**12 of 34 modules cover KEV-listed CVEs.**
**13 of 36 modules cover KEV-listed CVEs.**
## In KEV (prioritize patching)
@@ -22,6 +22,7 @@ Refreshed via `tools/refresh-cve-metadata.py`.
| CVE-2025-32463 | 2025-09-29 | CWE-829 | `sudo_chwoot_cve_2025_32463` |
| CVE-2021-22555 | 2025-10-06 | CWE-787 | `netfilter_xtcompat_cve_2021_22555` |
| CVE-2018-14634 | 2026-01-26 | CWE-190 | `mutagen_astronomy_cve_2018_14634` |
| CVE-2022-0492 | 2026-06-02 | CWE-287 | `cgroup_release_agent_cve_2022_0492` |
## Not in KEV
@@ -36,7 +37,6 @@ and are technically reachable. "Not in KEV" is not the same as
| CVE-2020-14386 | CWE-250 | `af_packet2_cve_2020_14386` |
| CVE-2020-29661 | CWE-416 | `tioscpgrp_cve_2020_29661` |
| CVE-2021-33909 | CWE-190 | `sequoia_cve_2021_33909` |
| CVE-2022-0492 | CWE-287 | `cgroup_release_agent_cve_2022_0492` |
| CVE-2022-25636 | CWE-269 | `nft_fwd_dup_cve_2022_25636` |
| CVE-2022-2588 | CWE-416 | `cls_route4_cve_2022_2588` |
| CVE-2023-0179 | CWE-190 | `nft_payload_cve_2023_0179` |
@@ -48,8 +48,10 @@ and are technically reachable. "Not in KEV" is not the same as
| CVE-2023-4622 | CWE-416 | `af_unix_gc_cve_2023_4622` |
| CVE-2024-26581 | ? | `nft_pipapo_cve_2024_26581` |
| CVE-2024-50264 | CWE-416 | `vsock_uaf_cve_2024_50264` |
| CVE-2025-32462 | CWE-863 | `sudo_host_cve_2025_32462` |
| CVE-2025-6019 | CWE-250 | `udisks_libblockdev_cve_2025_6019` |
| CVE-2026-31635 | CWE-130 | `dirtydecrypt_cve_2026_31635` |
| CVE-2026-41651 | CWE-367 | `pack2theroot_cve_2026_41651` |
| CVE-2026-43494 | ? | `pintheft_cve_2026_43494` |
| CVE-2026-46300 | CWE-787 | `fragnesia_cve_2026_46300` |
| CVE-2026-46333 | CWE-269 | `ptrace_pidfd_cve_2026_46333` |
+1
View File
@@ -26,6 +26,7 @@ haven't been maintained in years.
```bash
curl -sSL https://github.com/KaraZajac/SKELETONKEY/releases/latest/download/install.sh | sh \
&& export PATH="$HOME/.local/bin:$PATH" \
&& skeletonkey --auto --i-know
```
+41
View File
@@ -1,3 +1,44 @@
## SKELETONKEY v0.9.9 — install.sh needs no root; CVE-2022-0492 KEV drift
Two maintenance fixes, no new modules.
**`install.sh` never escalates to sudo.** SKELETONKEY is a privilege-
escalation tool — the operator by definition does *not* have root yet, so
the installer must not demand it. The old default wrote to `/usr/local/bin`
and fell back to `sudo mv` when that wasn't writable, prompting for a
password on exactly the unprivileged accounts this tool targets. It now
installs sudo-free: `/usr/local/bin` is used only when already writable,
otherwise it falls back to a per-user `$HOME/.local/bin` (honoring
`XDG_BIN_HOME`), created as needed. An explicit `SKELETONKEY_PREFIX` is
honored exactly and errors rather than escalating if unwritable. When the
chosen dir isn't on `$PATH` the installer prints the absolute path, and the
documented `curl … | sh && skeletonkey --auto --i-know` one-liner now
prepends `$HOME/.local/bin` to `$PATH` so it resolves on a fresh login. The
quickstart no longer prefixes `sudo` to `--scan`/`--audit`/`--auto`
detection and escalation run as the unprivileged user; only writing audit
rules into `/etc/audit` legitimately needs root.
**Federal metadata drift (the failing scheduled build).** The weekly
`drift-check` caught two upstream changes since v0.9.8:
- **CVE-2022-0492 entered CISA KEV (2026-06-02).** The cgroup v1
`release_agent` container-escape (`cgroup_release_agent`) is now on the
Known Exploited Vulnerabilities catalog. The corpus reports **13 of 36**
modules covering KEV-listed CVEs (was 12).
- **CVE-2026-46333 gained a CWE.** When `ptrace_pidfd` was added two weeks
after disclosure, NVD had not yet classified it; it is now **CWE-269**
(Improper Privilege Management).
A third, latent cause kept the gate red even after those two: when
`sudo_host` (CVE-2025-32462) was added in v0.9.8 its record was appended to
the *end* of `CVE_METADATA.json`, but the drift check compares the record
list in `discover_cves()`'s sorted order — so the out-of-order entry read
as drift regardless of its values. Regenerating via the script restores
sorted order.
Refreshed `CVE_METADATA.json`, the generated `cve_metadata.c` table, and
`KEV_CROSSREF.md` accordingly (README + website counts updated).
## SKELETONKEY v0.9.8 — two new LPE modules (ptrace_pidfd, sudo_host)
Adds the two most compelling recent Linux LPEs not already in the corpus,
+1 -1
View File
@@ -10,7 +10,7 @@
* 1. typed install command in the hero
* ============================================================ */
const installCmd =
'curl -sSL https://github.com/KaraZajac/SKELETONKEY/releases/latest/download/install.sh | sh \\\n && skeletonkey --auto --i-know';
'curl -sSL https://github.com/KaraZajac/SKELETONKEY/releases/latest/download/install.sh | sh \\\n && export PATH="$HOME/.local/bin:$PATH" \\\n && skeletonkey --auto --i-know';
const typedEl = document.getElementById('install-typed');
const cursorEl = document.getElementById('install-cursor');
+4 -4
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SKELETONKEY — Linux LPE corpus, VM-verified, SOC-ready detection</title>
<meta name="description" content="One binary. 41 Linux privilege-escalation modules from 2016 to 2026. 28 of 36 CVEs empirically verified in real Linux VMs. 10 KEV-listed. 151 detection rules across auditd/sigma/yara/falco. MITRE ATT&CK and CWE annotated. --explain gives operator briefings.">
<meta name="description" content="One binary. 41 Linux privilege-escalation modules from 2016 to 2026. 28 of 36 CVEs empirically verified in real Linux VMs. 13 KEV-listed. 151 detection rules across auditd/sigma/yara/falco. MITRE ATT&CK and CWE annotated. --explain gives operator briefings.">
<meta property="og:title" content="SKELETONKEY — Linux LPE corpus, VM-verified">
<meta property="og:description" content="41 Linux LPE modules; 28 of 36 CVEs empirically verified in real VMs. 151 detection rules. ATT&CK + CWE + KEV annotated.">
<meta property="og:type" content="website">
@@ -56,7 +56,7 @@
<div class="container hero-inner">
<div class="hero-eyebrow">
<span class="dot dot-pulse"></span>
v0.9.8 — released 2026-06-02
v0.9.9 — released 2026-06-08
</div>
<h1 class="hero-title">
<span class="display-wordmark">SKELETONKEY</span>
@@ -227,7 +227,7 @@ uid=0(root) gid=0(root)</pre>
<div class="bento-icon"></div>
<h3>CISA KEV prioritized</h3>
<p>
12 of 36 CVEs in the corpus are in CISA's Known Exploited
13 of 36 CVEs in the corpus are in CISA's Known Exploited
Vulnerabilities catalog — actively exploited in the wild.
Refreshed on demand via <code>tools/refresh-cve-metadata.py</code>.
</p>
@@ -600,7 +600,7 @@ uid=0(root) gid=0(root)</pre>
who found the bugs.
</p>
<p class="footer-meta">
v0.9.8 · MIT · <a href="https://github.com/KaraZajac/SKELETONKEY">github.com/KaraZajac/SKELETONKEY</a>
v0.9.9 · MIT · <a href="https://github.com/KaraZajac/SKELETONKEY">github.com/KaraZajac/SKELETONKEY</a>
</p>
</div>
</footer>
+38 -14
View File
@@ -28,7 +28,11 @@ set -eu
REPO="${SKELETONKEY_REPO:-KaraZajac/SKELETONKEY}"
VERSION="${SKELETONKEY_VERSION:-latest}"
PREFIX="${SKELETONKEY_PREFIX:-/usr/local/bin}"
# PREFIX resolution is deferred until install time so we can pick a
# sudo-free default. SKELETONKEY is a privilege-escalation tool — by
# definition the operator does NOT have root yet, so the installer must
# NEVER need sudo. Empty here means "auto-pick a writable dir below".
PREFIX="${SKELETONKEY_PREFIX:-}"
log() { printf '[\033[1;36m*\033[0m] %s\n' "$*" >&2; }
ok() { printf '[\033[1;32m+\033[0m] %s\n' "$*" >&2; }
@@ -108,29 +112,49 @@ fi
chmod +x "$tmp/skeletonkey"
# Install. Try $PREFIX directly; if not writable, sudo.
target_path="$PREFIX/skeletonkey"
if [ -w "$PREFIX" ] || [ "$(id -u)" -eq 0 ]; then
mv "$tmp/skeletonkey" "$target_path"
elif command -v sudo >/dev/null 2>&1; then
log "$PREFIX needs sudo; you may be prompted for password"
sudo mv "$tmp/skeletonkey" "$target_path"
# Choose install dir — NEVER escalate to sudo. If the user pinned
# SKELETONKEY_PREFIX we honor it exactly (creating it if needed) and
# error rather than escalate when it isn't writable. Otherwise prefer
# /usr/local/bin only when it happens to already be writable, and fall
# back to a guaranteed per-user dir ($HOME/.local/bin) that needs no
# privileges. This keeps `curl ... | sh` password-free for the exact
# users this tool is meant for: unprivileged accounts.
if [ -n "$PREFIX" ]; then
[ -d "$PREFIX" ] || mkdir -p "$PREFIX" 2>/dev/null \
|| fail "cannot create SKELETONKEY_PREFIX=$PREFIX"
[ -w "$PREFIX" ] || fail "SKELETONKEY_PREFIX=$PREFIX not writable (the installer never uses sudo — pick a writable dir)"
elif [ -w /usr/local/bin ]; then
PREFIX=/usr/local/bin
else
fail "$PREFIX not writable and sudo not available. Try SKELETONKEY_PREFIX=\$HOME/.local/bin"
PREFIX="${XDG_BIN_HOME:-$HOME/.local/bin}"
mkdir -p "$PREFIX" 2>/dev/null || fail "cannot create $PREFIX"
fi
target_path="$PREFIX/skeletonkey"
mv "$tmp/skeletonkey" "$target_path" || fail "failed to install to $target_path"
ok "installed: $target_path"
# ~/.local/bin is frequently absent from PATH on fresh accounts — tell
# the user how to invoke it rather than letting `skeletonkey` 404.
case ":$PATH:" in
*":$PREFIX:"*) : ;;
*) log "note: $PREFIX is not on \$PATH — run it as $target_path, or add the dir to PATH" ;;
esac
"$target_path" --version
cat >&2 <<EOF
[\033[1;33m!\033[0m] AUTHORIZED TESTING ONLY — see https://github.com/${REPO}/blob/main/docs/ETHICS.md
Quickstart:
sudo skeletonkey --scan # what's this box vulnerable to?
sudo skeletonkey --audit # broader system hygiene
sudo skeletonkey --detect-rules --format=auditd \\
| sudo tee /etc/audit/rules.d/99-skeletonkey.rules # deploy detection rules
Quickstart (no root required — gaining it is the point):
skeletonkey --scan # what's this box vulnerable to?
skeletonkey --audit # broader system hygiene
skeletonkey --auto --i-know # run the safest available LPE
Deploy detection rules (defensive; only the write to /etc/audit needs root):
skeletonkey --detect-rules --format=auditd \\
| sudo tee /etc/audit/rules.d/99-skeletonkey.rules
See \`skeletonkey --help\` for all commands.
EOF
+1 -1
View File
@@ -35,7 +35,7 @@
#include <string.h>
#include <unistd.h>
#define SKELETONKEY_VERSION "0.9.8"
#define SKELETONKEY_VERSION "0.9.9"
static const char BANNER[] =
"\n"