detection rules: complete sigma/yara/falco coverage across the corpus
Three parallel research agents drafted 49 detection rules grounded in
each module's source + existing .opsec_notes string + existing .detect_auditd
counterpart. A one-shot tools/inject_rules.py wrote them into the
right files and replaced the .detect_<format> = NULL placeholders.
Coverage matrix (modules with each format / 31 total):
before after
auditd 30 / 31 30 / 31 (entrybleed skipped by design)
sigma 19 / 31 31 / 31 (+12 added)
yara 11 / 31 28 / 31 (+17 added; 3 documented skips)
falco 11 / 31 30 / 31 (+19 added; entrybleed skipped)
Documented skips (kept as .detect_<format> = NULL with comment):
- entrybleed: yara + falco + auditd. Pure timing side-channel via
rdtsc + prefetchnta; no syscalls, no file artifacts, no in-memory
tags. The source comment already noted this; sigma got a 'unusual
prefetchnta loop time' rule via perf-counter logic.
- ptrace_traceme: yara. Pure in-memory race; no on-disk artifacts
or persistent strings to match. Falco + sigma + auditd cover the
PTRACE_TRACEME + setuid execve syscall sequence.
- sudo_samedit: yara. Transient heap race during sudoedit invocation;
no persistent file artifact. Falco + sigma + auditd cover the
'sudoedit -s + trailing-backslash argv' pattern.
Rule discipline (post-agent QA):
- All rules ground claims in actual exploit code paths (the agents
were instructed to read source + opsec_notes; no fabricated syscalls
or strings).
- Two falco rules were narrowed by the agent to fire only when
proc.pname is skeletonkey itself; rewrote both to fire on any
non-root caller (otherwise we'd detect only our own binary, not
real attackers).
- Sigma rule fields use canonical {type: 'SYSCALL', syscall: 'X'}
detection blocks consistent with existing rules (nf_tables,
dirty_pipe, sudo_samedit).
- YARA rules prefer rare/unique tags (SKELETONKEYU, SKELETONKEY_FWD,
SKVMWGFX, /tmp/skeletonkey-*.log) over common bytes — minimizes
false positives.
- Every rule tagged with attack.privilege_escalation + cve.YYYY.NNNN;
cgroup_release_agent additionally tagged T1611 (container escape).
skeletonkey.c: --module-info text view now dumps yara + falco rule
bodies too (was auditd + sigma only). All 4 formats visible per module.
Verification:
- macOS local: clean build, 33 kernel_range tests pass.
- Linux (docker gcc:latest): 33 + 54 = 87 passes, 0 fails.
- --module-info nf_tables / af_unix_gc / etc.: 'detect rules:'
summary correctly shows all 4 formats and the bodies print.
This commit is contained in:
@@ -669,6 +669,54 @@ static const char af_packet2_auditd[] =
|
||||
"# non-root via userns is the canonical footprint.\n"
|
||||
"-a always,exit -F arch=b64 -S socket -F a0=17 -k skeletonkey-af-packet\n";
|
||||
|
||||
static const char af_packet2_sigma[] =
|
||||
"title: Possible CVE-2020-14386 AF_PACKET VLAN underflow exploitation\n"
|
||||
"id: b83c6fa2-skeletonkey-af-packet2\n"
|
||||
"status: experimental\n"
|
||||
"description: |\n"
|
||||
" Detects the AF_PACKET TPACKET_V2 nested-VLAN frame pattern:\n"
|
||||
" unshare(CLONE_NEWUSER|CLONE_NEWNET) followed by socket(AF_PACKET),\n"
|
||||
" PACKET_RX_RING setsockopt, and a sendmmsg burst (>=64) on a unix\n"
|
||||
" socketpair spray. False positives: legitimate packet capture in\n"
|
||||
" rootless containers.\n"
|
||||
"logsource: {product: linux, service: auditd}\n"
|
||||
"detection:\n"
|
||||
" userns: {type: 'SYSCALL', syscall: 'unshare'}\n"
|
||||
" afp: {type: 'SYSCALL', syscall: 'socket', a0: 17}\n"
|
||||
" send_burst:{type: 'SYSCALL', syscall: 'sendmmsg'}\n"
|
||||
" condition: userns and afp and send_burst\n"
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2020.14386]\n";
|
||||
|
||||
static const char af_packet2_yara[] =
|
||||
"rule af_packet2_cve_2020_14386 : cve_2020_14386 heap_spray\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2020-14386\"\n"
|
||||
" description = \"AF_PACKET VLAN-underflow spray tag (skeletonkey-afp-fc-)\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $tag = \"skeletonkey-afp-fc-\" ascii\n"
|
||||
" condition:\n"
|
||||
" $tag\n"
|
||||
"}\n";
|
||||
|
||||
static const char af_packet2_falco[] =
|
||||
"- rule: AF_PACKET TPACKET_V2 nested-VLAN trigger by non-root\n"
|
||||
" desc: |\n"
|
||||
" A non-root process sets up TPACKET_V2 and sends a burst of\n"
|
||||
" sendmmsg packets carrying nested VLAN tags (CVE-2020-14386\n"
|
||||
" trigger). False positives: legitimate VLAN/network capture\n"
|
||||
" tools in unprivileged containers.\n"
|
||||
" condition: >\n"
|
||||
" evt.type = sendmmsg and fd.type = socket and\n"
|
||||
" fd.sockfamily = AF_PACKET and not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" sendmmsg burst on AF_PACKET socket by non-root\n"
|
||||
" (user=%user.name pid=%proc.pid vlen=%evt.arg.vlen)\n"
|
||||
" priority: HIGH\n"
|
||||
" tags: [network, mitre_privilege_escalation, T1068, cve.2020.14386]\n";
|
||||
|
||||
const struct skeletonkey_module af_packet2_module = {
|
||||
.name = "af_packet2",
|
||||
.cve = "CVE-2020-14386",
|
||||
@@ -680,9 +728,9 @@ const struct skeletonkey_module af_packet2_module = {
|
||||
.mitigate = NULL,
|
||||
.cleanup = NULL,
|
||||
.detect_auditd = af_packet2_auditd,
|
||||
.detect_sigma = NULL,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_sigma = af_packet2_sigma,
|
||||
.detect_yara = af_packet2_yara,
|
||||
.detect_falco = af_packet2_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET) + TPACKET_V2 ring on AF_PACKET; crafts nested-VLAN ETH_P_8021AD frames with 0x88A8/0x8100 TPIDs to trigger tpacket_rcv underflow; fires 256 frames + 64 sendmmsg via AF_UNIX socketpair spray. Tag 'skeletonkey-afp-fc-' visible in KASAN splats. Audit-visible via socket(AF_PACKET) + sendmsg/sendto from userns. No persistent artifacts; kernel cleans up on child exit.",
|
||||
};
|
||||
|
||||
|
||||
@@ -891,6 +891,55 @@ static const char af_packet_auditd[] =
|
||||
"-a always,exit -F arch=b64 -S socket -F a0=17 -k skeletonkey-af-packet\n"
|
||||
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-af-packet-userns\n";
|
||||
|
||||
static const char af_packet_sigma[] =
|
||||
"title: Possible CVE-2017-7308 AF_PACKET TPACKET_V3 exploitation\n"
|
||||
"id: a72b5e91-skeletonkey-af-packet\n"
|
||||
"status: experimental\n"
|
||||
"description: |\n"
|
||||
" Detects the AF_PACKET TPACKET_V3 integer-overflow setup pattern:\n"
|
||||
" unshare(CLONE_NEWUSER|CLONE_NEWNET) followed by socket(AF_PACKET)\n"
|
||||
" and a PACKET_RX_RING setsockopt + sendmmsg burst. False positives:\n"
|
||||
" network sandboxes / containers running raw-packet apps inside\n"
|
||||
" userns; correlate process tree to distinguish.\n"
|
||||
"logsource: {product: linux, service: auditd}\n"
|
||||
"detection:\n"
|
||||
" userns: {type: 'SYSCALL', syscall: 'unshare'}\n"
|
||||
" afp: {type: 'SYSCALL', syscall: 'socket', a0: 17}\n"
|
||||
" send_burst:{type: 'SYSCALL', syscall: 'sendmmsg'}\n"
|
||||
" condition: userns and afp and send_burst\n"
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2017.7308]\n";
|
||||
|
||||
static const char af_packet_yara[] =
|
||||
"rule af_packet_cve_2017_7308 : cve_2017_7308 heap_spray\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2017-7308\"\n"
|
||||
" description = \"AF_PACKET TPACKET_V3 spray tag from skeletonkey/iam-root tooling\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $tag1 = \"iamroot-afp-tag\" ascii\n"
|
||||
" $tag2 = \"skeletonkey-afp-fc-\" ascii\n"
|
||||
" condition:\n"
|
||||
" any of them\n"
|
||||
"}\n";
|
||||
|
||||
static const char af_packet_falco[] =
|
||||
"- rule: AF_PACKET TPACKET_V3 setup by non-root in userns\n"
|
||||
" desc: |\n"
|
||||
" A non-root process creates an AF_PACKET socket and sets up a\n"
|
||||
" TPACKET_V3 ring inside a user namespace. CVE-2017-7308 trigger\n"
|
||||
" requires CAP_NET_RAW which userns provides. False positives:\n"
|
||||
" legitimate packet-capture tools running rootless (rare).\n"
|
||||
" condition: >\n"
|
||||
" evt.type = setsockopt and evt.arg.optname contains PACKET_RX_RING\n"
|
||||
" and not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" AF_PACKET TPACKET_V3 ring setup by non-root\n"
|
||||
" (user=%user.name proc=%proc.name pid=%proc.pid)\n"
|
||||
" priority: HIGH\n"
|
||||
" tags: [network, mitre_privilege_escalation, T1068, cve.2017.7308]\n";
|
||||
|
||||
const struct skeletonkey_module af_packet_module = {
|
||||
.name = "af_packet",
|
||||
.cve = "CVE-2017-7308",
|
||||
@@ -902,9 +951,9 @@ const struct skeletonkey_module af_packet_module = {
|
||||
.mitigate = NULL,
|
||||
.cleanup = NULL,
|
||||
.detect_auditd = af_packet_auditd,
|
||||
.detect_sigma = NULL,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_sigma = af_packet_sigma,
|
||||
.detect_yara = af_packet_yara,
|
||||
.detect_falco = af_packet_falco,
|
||||
.opsec_notes = "Creates AF_PACKET socket and TPACKET_V3 ring inside unshare(CLONE_NEWUSER|CLONE_NEWNET); triggers integer overflow with crafted tp_block_size/tp_block_nr and sprays ~200 loopback frames. Audit-visible via socket(AF_PACKET) (a0=17) + sendmmsg from a userns process; KASAN tag 'iamroot-afp-tag' may appear in dmesg if enabled. No persistent files. No cleanup callback - kernel state unwinds on child exit.",
|
||||
};
|
||||
|
||||
|
||||
@@ -833,6 +833,56 @@ static const char af_unix_gc_auditd[] =
|
||||
"-a always,exit -F arch=b64 -S sendmsg -k skeletonkey-afunixgc-sendmsg\n"
|
||||
"-a always,exit -F arch=b64 -S msgsnd -k skeletonkey-afunixgc-spray\n";
|
||||
|
||||
static const char af_unix_gc_sigma[] =
|
||||
"title: Possible CVE-2023-4622 AF_UNIX GC UAF race\n"
|
||||
"id: c45d7eb3-skeletonkey-af-unix-gc\n"
|
||||
"status: experimental\n"
|
||||
"description: |\n"
|
||||
" Detects tight-loop socketpair(AF_UNIX) + sendmsg with SCM_RIGHTS\n"
|
||||
" + msgsnd grooming pattern characteristic of the AF_UNIX garbage\n"
|
||||
" collector race. False positives: legitimate IPC apps use\n"
|
||||
" SCM_RIGHTS, but the high-frequency close-and-recreate cycle is\n"
|
||||
" unusual outside fuzzing / exploit harnesses.\n"
|
||||
"logsource: {product: linux, service: auditd}\n"
|
||||
"detection:\n"
|
||||
" sp: {type: 'SYSCALL', syscall: 'socketpair', a0: 1}\n"
|
||||
" scm: {type: 'SYSCALL', syscall: 'sendmsg'}\n"
|
||||
" groom: {type: 'SYSCALL', syscall: 'msgsnd'}\n"
|
||||
" condition: sp and scm and groom\n"
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2023.4622]\n";
|
||||
|
||||
static const char af_unix_gc_yara[] =
|
||||
"rule af_unix_gc_cve_2023_4622 : cve_2023_4622 kernel_uaf\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2023-4622\"\n"
|
||||
" description = \"AF_UNIX GC race kmalloc-512 spray tag or log breadcrumb\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $tag = \"SKELETONKEYU\" ascii\n"
|
||||
" $log = \"/tmp/skeletonkey-af_unix_gc.log\" ascii\n"
|
||||
" condition:\n"
|
||||
" any of them\n"
|
||||
"}\n";
|
||||
|
||||
static const char af_unix_gc_falco[] =
|
||||
"- rule: SCM_RIGHTS cycling on AF_UNIX with msg_msg groom\n"
|
||||
" desc: |\n"
|
||||
" Tight socketpair(AF_UNIX) + sendmsg(SCM_RIGHTS) + msgsnd\n"
|
||||
" pattern characteristic of the AF_UNIX garbage collector\n"
|
||||
" race (CVE-2023-4622). False positives: IPC libraries use\n"
|
||||
" SCM_RIGHTS legitimately but rarely with the close-and-\n"
|
||||
" recreate cycle at this frequency.\n"
|
||||
" condition: >\n"
|
||||
" evt.type = sendmsg and fd.sockfamily = AF_UNIX and\n"
|
||||
" not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" SCM_RIGHTS sendmsg on AF_UNIX by non-root\n"
|
||||
" (user=%user.name pid=%proc.pid)\n"
|
||||
" priority: HIGH\n"
|
||||
" tags: [ipc, mitre_privilege_escalation, T1068, cve.2023.4622]\n";
|
||||
|
||||
const struct skeletonkey_module af_unix_gc_module = {
|
||||
.name = "af_unix_gc",
|
||||
.cve = "CVE-2023-4622",
|
||||
@@ -844,9 +894,9 @@ const struct skeletonkey_module af_unix_gc_module = {
|
||||
.mitigate = NULL,
|
||||
.cleanup = af_unix_gc_cleanup,
|
||||
.detect_auditd = af_unix_gc_auditd,
|
||||
.detect_sigma = NULL,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_sigma = af_unix_gc_sigma,
|
||||
.detect_yara = af_unix_gc_yara,
|
||||
.detect_falco = af_unix_gc_falco,
|
||||
.opsec_notes = "Two-threaded race: Thread A creates socketpair(AF_UNIX) with SCM_RIGHTS cycle then close; Thread B drives independent SCM_RIGHTS traffic on a held pair. ~5s budget (30s with --full-chain). msg_msg kmalloc-512 spray tagged 'SKELETONKEYU'. Writes /tmp/skeletonkey-af_unix_gc.log with empirical stats. Audit-visible via socketpair(AF_UNIX) + sendmsg(SCM_RIGHTS) + msgsnd triple. Dmesg may show UAF KASAN if kernel vulnerable. Cleanup callback unlinks the log.",
|
||||
};
|
||||
|
||||
|
||||
@@ -359,6 +359,36 @@ static const char cgroup_ra_sigma[] =
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1611, cve.2022.0492]\n";
|
||||
|
||||
static const char cgroup_release_agent_yara[] =
|
||||
"rule cgroup_release_agent_cve_2022_0492 : cve_2022_0492 container_escape\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2022-0492\"\n"
|
||||
" description = \"cgroup v1 release_agent payload + dropped setuid shell artifacts\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $payload = \"/tmp/skeletonkey-cgroup-payload.sh\" ascii\n"
|
||||
" $shell = \"/tmp/skeletonkey-cgroup-sh\" ascii\n"
|
||||
" $mnt = \"/tmp/skeletonkey-cgroup-mnt\" ascii\n"
|
||||
" condition:\n"
|
||||
" any of them\n"
|
||||
"}\n";
|
||||
|
||||
static const char cgroup_release_agent_falco[] =
|
||||
"- rule: cgroup v1 mount by non-root with release_agent write\n"
|
||||
" desc: |\n"
|
||||
" A non-root process inside a userns mounts cgroup v1 and\n"
|
||||
" writes to a release_agent file. CVE-2022-0492 trigger:\n"
|
||||
" release_agent runs as init-ns root when cgroup empties.\n"
|
||||
" condition: >\n"
|
||||
" evt.type = mount and evt.arg.fstype = cgroup and\n"
|
||||
" not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" cgroup v1 mount by non-root\n"
|
||||
" (user=%user.name pid=%proc.pid target=%evt.arg.name)\n"
|
||||
" priority: CRITICAL\n"
|
||||
" tags: [container, mitre_privilege_escalation, T1611, cve.2022.0492]\n";
|
||||
|
||||
const struct skeletonkey_module cgroup_release_agent_module = {
|
||||
.name = "cgroup_release_agent",
|
||||
.cve = "CVE-2022-0492",
|
||||
@@ -371,8 +401,8 @@ const struct skeletonkey_module cgroup_release_agent_module = {
|
||||
.cleanup = cgroup_ra_cleanup,
|
||||
.detect_auditd = cgroup_ra_auditd,
|
||||
.detect_sigma = cgroup_ra_sigma,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_yara = cgroup_release_agent_yara,
|
||||
.detect_falco = cgroup_release_agent_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNS), mount cgroup v1 at /tmp/skeletonkey-cgroup-mnt, write payload path to release_agent file at cgroup root, echo 1 to notify_on_release in subdir, add PID to cgroup.procs and exit. Payload at /tmp/skeletonkey-cgroup-payload.sh runs as init-namespace root when cgroup empties, dropping setuid /tmp/skeletonkey-cgroup-sh. Audit-visible via unshare + mount(cgroup) + open/write of release_agent. Cleanup callback removes /tmp/skeletonkey-cgroup-* and umounts.",
|
||||
};
|
||||
|
||||
|
||||
@@ -826,6 +826,54 @@ static const char cls_route4_auditd[] =
|
||||
"-a always,exit -F arch=b64 -S unshare -k skeletonkey-cls-route4-userns\n"
|
||||
"-a always,exit -F arch=b64 -S msgsnd -k skeletonkey-cls-route4-spray\n";
|
||||
|
||||
static const char cls_route4_sigma[] =
|
||||
"title: Possible CVE-2022-2588 cls_route4 dead-UAF\n"
|
||||
"id: d56e8fc4-skeletonkey-cls-route4\n"
|
||||
"status: experimental\n"
|
||||
"description: |\n"
|
||||
" Detects the net/sched cls_route4 dead-UAF setup: unshare userns +\n"
|
||||
" netns + tc qdisc/filter rules with handle 0 + delete + msg_msg\n"
|
||||
" spray + UDP sendto on a dummy interface. False positives:\n"
|
||||
" traffic-shaping config in rootless containers.\n"
|
||||
"logsource: {product: linux, service: auditd}\n"
|
||||
"detection:\n"
|
||||
" userns: {type: 'SYSCALL', syscall: 'unshare'}\n"
|
||||
" udp: {type: 'SYSCALL', syscall: 'sendto'}\n"
|
||||
" groom: {type: 'SYSCALL', syscall: 'msgsnd'}\n"
|
||||
" condition: userns and udp and groom\n"
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2022.2588]\n";
|
||||
|
||||
static const char cls_route4_yara[] =
|
||||
"rule cls_route4_cve_2022_2588 : cve_2022_2588 kernel_uaf\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2022-2588\"\n"
|
||||
" description = \"cls_route4 dead-UAF kmalloc-1k spray tag and log breadcrumb\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $tag = \"SKELETONKEY4\" ascii\n"
|
||||
" $log = \"/tmp/skeletonkey-cls_route4.log\" ascii\n"
|
||||
" condition:\n"
|
||||
" any of them\n"
|
||||
"}\n";
|
||||
|
||||
static const char cls_route4_falco[] =
|
||||
"- rule: tc route4 filter manipulation by non-root in userns\n"
|
||||
" desc: |\n"
|
||||
" Non-root tc qdisc + route4 filter add/delete inside a userns\n"
|
||||
" + UDP sendto trigger. CVE-2022-2588 dead-UAF pattern. False\n"
|
||||
" positives: legitimate traffic shaping inside rootless\n"
|
||||
" containers.\n"
|
||||
" condition: >\n"
|
||||
" evt.type = sendto and fd.sockfamily = AF_INET and\n"
|
||||
" not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" UDP sendto on dummy iface from non-root\n"
|
||||
" (user=%user.name pid=%proc.pid)\n"
|
||||
" priority: HIGH\n"
|
||||
" tags: [network, mitre_privilege_escalation, T1068, cve.2022.2588]\n";
|
||||
|
||||
const struct skeletonkey_module cls_route4_module = {
|
||||
.name = "cls_route4",
|
||||
.cve = "CVE-2022-2588",
|
||||
@@ -837,9 +885,9 @@ const struct skeletonkey_module cls_route4_module = {
|
||||
.mitigate = NULL, /* mitigation: blacklist cls_route4 module OR disable user_ns */
|
||||
.cleanup = cls_route4_cleanup,
|
||||
.detect_auditd = cls_route4_auditd,
|
||||
.detect_sigma = NULL,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_sigma = cls_route4_sigma,
|
||||
.detect_yara = cls_route4_yara,
|
||||
.detect_falco = cls_route4_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET); ip link/addr/route to make a dummy interface, htb qdisc + class + route4 filter with handle 0, delete filter (leaves dangling tcf_proto pointer), msg_msg spray kmalloc-1k tagged 'SKELETONKEY4', UDP sendto to trigger classify(). Writes /tmp/skeletonkey-cls_route4.log. Audit-visible via unshare + sendto(AF_INET) + msgsnd. Cleanup callback removes /tmp log + dummy interface.",
|
||||
};
|
||||
|
||||
|
||||
@@ -390,6 +390,35 @@ static const char dirty_cow_sigma[] =
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2016.5195]\n";
|
||||
|
||||
static const char dirty_cow_yara[] =
|
||||
"rule dirty_cow_cve_2016_5195 : cve_2016_5195 page_cache_write\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2016-5195\"\n"
|
||||
" description = \"Dirty COW /etc/passwd UID-flip pattern (non-root user remapped to 0000+)\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $uid_flip = /\\n[a-z_][a-z0-9_-]{0,30}:[^:]{0,8}:0{4,}:[0-9]+:/\n"
|
||||
" condition:\n"
|
||||
" $uid_flip\n"
|
||||
"}\n";
|
||||
|
||||
static const char dirty_cow_falco[] =
|
||||
"- rule: Dirty COW pwrite on /proc/self/mem by non-root\n"
|
||||
" desc: |\n"
|
||||
" Non-root pwrite() targeting /proc/self/mem at an offset that\n"
|
||||
" overlaps a private mmap of /etc/passwd. Combined with a\n"
|
||||
" racing madvise(MADV_DONTNEED) loop this is the Dirty COW\n"
|
||||
" primitive (CVE-2016-5195).\n"
|
||||
" condition: >\n"
|
||||
" evt.type = pwrite and fd.name = /proc/self/mem and\n"
|
||||
" not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" pwrite to /proc/self/mem by non-root\n"
|
||||
" (user=%user.name proc=%proc.name pid=%proc.pid)\n"
|
||||
" priority: CRITICAL\n"
|
||||
" tags: [filesystem, mitre_privilege_escalation, T1068, cve.2016.5195]\n";
|
||||
|
||||
const struct skeletonkey_module dirty_cow_module = {
|
||||
.name = "dirty_cow",
|
||||
.cve = "CVE-2016-5195",
|
||||
@@ -402,8 +431,8 @@ const struct skeletonkey_module dirty_cow_module = {
|
||||
.cleanup = dirty_cow_cleanup,
|
||||
.detect_auditd = dirty_cow_auditd,
|
||||
.detect_sigma = dirty_cow_sigma,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_yara = dirty_cow_yara,
|
||||
.detect_falco = dirty_cow_falco,
|
||||
.opsec_notes = "Two-thread race: Thread A loops pwrite(/proc/self/mem) at the user's UID offset in /etc/passwd; Thread B loops madvise(MADV_DONTNEED) on a PRIVATE mmap of /etc/passwd. Overwrites the UID field with all-zeros, then execlp('su') to claim root. UID offset is parsed from the file, not hardcoded. Audit-visible via open(/proc/self/mem) + write + madvise(MADV_DONTNEED) bursts + /etc/passwd page-cache poisoning. Cleanup callback calls posix_fadvise(POSIX_FADV_DONTNEED) on /etc/passwd and writes 3 to /proc/sys/vm/drop_caches to evict.",
|
||||
};
|
||||
|
||||
|
||||
@@ -871,6 +871,36 @@ static const char fuse_legacy_sigma[] =
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1611, cve.2022.0185]\n";
|
||||
|
||||
static const char fuse_legacy_yara[] =
|
||||
"rule fuse_legacy_cve_2022_0185 : cve_2022_0185 kernel_overflow\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2022-0185\"\n"
|
||||
" description = \"fs_context legacy_parse_param oversized-source pattern (fsopen cgroup2)\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $fsopen = \"fsopen\" ascii\n"
|
||||
" $cgrp2 = \"cgroup2\" ascii\n"
|
||||
" condition:\n"
|
||||
" all of them\n"
|
||||
"}\n";
|
||||
|
||||
static const char fuse_legacy_falco[] =
|
||||
"- rule: fsopen/fsconfig in userns (CVE-2022-0185 trigger)\n"
|
||||
" desc: |\n"
|
||||
" Non-root fsopen + fsconfig(FSCONFIG_SET_STRING) sequence\n"
|
||||
" inside a userns. legacy_parse_param() integer-underflow\n"
|
||||
" overflow into kmalloc-4k. False positives: containers may\n"
|
||||
" mount their own filesystems but FSCONFIG with oversized\n"
|
||||
" 'source' option strings is unusual.\n"
|
||||
" condition: >\n"
|
||||
" evt.type in (fsopen, fsconfig) and not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" fsopen/fsconfig by non-root\n"
|
||||
" (user=%user.name pid=%proc.pid evt=%evt.type)\n"
|
||||
" priority: HIGH\n"
|
||||
" tags: [filesystem, mitre_privilege_escalation, T1068, cve.2022.0185]\n";
|
||||
|
||||
const struct skeletonkey_module fuse_legacy_module = {
|
||||
.name = "fuse_legacy",
|
||||
.cve = "CVE-2022-0185",
|
||||
@@ -883,8 +913,8 @@ const struct skeletonkey_module fuse_legacy_module = {
|
||||
.cleanup = NULL,
|
||||
.detect_auditd = fuse_legacy_auditd,
|
||||
.detect_sigma = fuse_legacy_sigma,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_yara = fuse_legacy_yara,
|
||||
.detect_falco = fuse_legacy_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNS) for CAP_SYS_ADMIN; fsopen('cgroup2') + multiple fsconfig(FSCONFIG_SET_STRING, 'source', ...) calls to overflow legacy_parse_param's buffer. OOB write lands in kmalloc-4k adjacent to a msg_msg groom. No persistent files (msg_msg lives in the IPC namespace which disappears with the child). Dmesg silent on success; KASAN would show slab corruption if enabled. Audit-visible via unshare(CLONE_NEWUSER|CLONE_NEWNS) + fsopen + fsconfig pattern in a single process. No cleanup callback - IPC queues auto-drain on namespace exit.",
|
||||
};
|
||||
|
||||
|
||||
@@ -960,6 +960,55 @@ static const char netfilter_xtcompat_auditd[] =
|
||||
"-a always,exit -F arch=b64 -S msgsnd -k skeletonkey-xtcompat-msgmsg\n"
|
||||
"-a always,exit -F arch=b64 -S msgrcv -k skeletonkey-xtcompat-msgmsg\n";
|
||||
|
||||
static const char netfilter_xtcompat_sigma[] =
|
||||
"title: Possible CVE-2021-22555 xt_compat OOB write\n"
|
||||
"id: e67f90d5-skeletonkey-xtcompat\n"
|
||||
"status: experimental\n"
|
||||
"description: |\n"
|
||||
" Detects setsockopt(SOL_IP, IPT_SO_SET_REPLACE) from a non-root\n"
|
||||
" process inside unshare(CLONE_NEWUSER|CLONE_NEWNET) followed by\n"
|
||||
" msg_msg grooming (msgsnd/msgrcv) and sendmmsg sk_buff spray.\n"
|
||||
" False positives: iptables config inside rootless containers /\n"
|
||||
" network namespaces. Correlate with privilege escalation\n"
|
||||
" (setresuid 0,0,0) to confirm.\n"
|
||||
"logsource: {product: linux, service: auditd}\n"
|
||||
"detection:\n"
|
||||
" userns: {type: 'SYSCALL', syscall: 'unshare'}\n"
|
||||
" sso: {type: 'SYSCALL', syscall: 'setsockopt', a1: 0}\n"
|
||||
" groom: {type: 'SYSCALL', syscall: 'msgsnd'}\n"
|
||||
" condition: userns and sso and groom\n"
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2021.22555]\n";
|
||||
|
||||
static const char netfilter_xtcompat_yara[] =
|
||||
"rule netfilter_xtcompat_cve_2021_22555 : cve_2021_22555 kernel_oob_write\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2021-22555\"\n"
|
||||
" description = \"xt_compat 4-byte OOB write log breadcrumb\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $log = \"/tmp/skeletonkey-xtcompat.log\" ascii\n"
|
||||
" condition:\n"
|
||||
" $log\n"
|
||||
"}\n";
|
||||
|
||||
static const char netfilter_xtcompat_falco[] =
|
||||
"- rule: setsockopt IPT_SO_SET_REPLACE by non-root in userns\n"
|
||||
" desc: |\n"
|
||||
" Non-root process calls setsockopt(SOL_IP, IPT_SO_SET_REPLACE)\n"
|
||||
" from inside a userns with CAP_NET_ADMIN. The xt_compat\n"
|
||||
" target_to_user() handler writes past the xt_table_info\n"
|
||||
" allocation; CVE-2021-22555. False positives: iptables\n"
|
||||
" config in rootless containers.\n"
|
||||
" condition: >\n"
|
||||
" evt.type = setsockopt and not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" setsockopt SOL_IP by non-root\n"
|
||||
" (user=%user.name pid=%proc.pid)\n"
|
||||
" priority: HIGH\n"
|
||||
" tags: [network, mitre_privilege_escalation, T1068, cve.2021.22555]\n";
|
||||
|
||||
const struct skeletonkey_module netfilter_xtcompat_module = {
|
||||
.name = "netfilter_xtcompat",
|
||||
.cve = "CVE-2021-22555",
|
||||
@@ -971,9 +1020,9 @@ const struct skeletonkey_module netfilter_xtcompat_module = {
|
||||
.mitigate = NULL, /* mitigation: upgrade kernel; disable unprivileged_userns_clone */
|
||||
.cleanup = netfilter_xtcompat_cleanup,
|
||||
.detect_auditd = netfilter_xtcompat_auditd,
|
||||
.detect_sigma = NULL,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_sigma = netfilter_xtcompat_sigma,
|
||||
.detect_yara = netfilter_xtcompat_yara,
|
||||
.detect_falco = netfilter_xtcompat_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET) + setsockopt(SOL_IP, IPT_SO_SET_REPLACE) with a malformed xt_entry_target to trigger xt_compat_target_to_user 4-byte OOB into kmalloc-2k. msg_msg + sk_buff cross-cache groom. Writes /tmp/skeletonkey-xtcompat.log (breadcrumb). Audit-visible via unshare + setsockopt(IPT_SO_SET_REPLACE) + msgsnd/msgrcv + sendmmsg(sk_buff spray). Dmesg silent on success; KASAN oops if the groom misses. Cleanup callback unlinks the log; IPC auto-drains on namespace exit.",
|
||||
};
|
||||
|
||||
|
||||
@@ -1123,6 +1123,35 @@ static const char nf_tables_sigma[] =
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2024.1086]\n";
|
||||
|
||||
static const char nf_tables_yara[] =
|
||||
"rule nf_tables_cve_2024_1086 : cve_2024_1086 kernel_uaf\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2024-1086\"\n"
|
||||
" description = \"nf_tables verdict-init UAF breadcrumb log\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $log = \"/tmp/skeletonkey-nft_set_uaf.log\" ascii\n"
|
||||
" condition:\n"
|
||||
" $log\n"
|
||||
"}\n";
|
||||
|
||||
static const char nf_tables_falco[] =
|
||||
"- rule: nf_tables verdict-init UAF batch by non-root\n"
|
||||
" desc: |\n"
|
||||
" Non-root sendmsg on NETLINK_NETFILTER inside a userns,\n"
|
||||
" delivering an nfnetlink batch with NEWTABLE + NEWCHAIN +\n"
|
||||
" NEWSET (verdict-key) + NEWSETELEM with malformed NFT_GOTO\n"
|
||||
" committed twice. CVE-2024-1086 nft_verdict_init double-free.\n"
|
||||
" condition: >\n"
|
||||
" evt.type = sendmsg and fd.sockfamily = AF_NETLINK and\n"
|
||||
" not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" nfnetlink batch from non-root\n"
|
||||
" (user=%user.name pid=%proc.pid)\n"
|
||||
" priority: HIGH\n"
|
||||
" tags: [network, mitre_privilege_escalation, T1068, cve.2024.1086]\n";
|
||||
|
||||
const struct skeletonkey_module nf_tables_module = {
|
||||
.name = "nf_tables",
|
||||
.cve = "CVE-2024-1086",
|
||||
@@ -1135,8 +1164,8 @@ const struct skeletonkey_module nf_tables_module = {
|
||||
.cleanup = NULL,
|
||||
.detect_auditd = nf_tables_auditd,
|
||||
.detect_sigma = nf_tables_sigma,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_yara = nf_tables_yara,
|
||||
.detect_falco = nf_tables_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET) + nfnetlink batch (NEWTABLE + NEWCHAIN/LOCAL_OUT + NEWSET verdict-key + NEWSETELEM malformed NFT_GOTO) committed twice to trigger the nft_verdict_init double-free. msg_msg cg-96 groom with forged pipapo_elem headers; --full-chain sprays kaddr-tagged forged elems and re-fires. Writes /tmp/skeletonkey-nft_set_uaf.log (conditional). Audit-visible via unshare + socket(NETLINK_NETFILTER) + sendmsg batches + msgget/msgsnd. Dmesg: KASAN double-free panic on vulnerable kernels; silent otherwise. Cleanup is finisher-gated; no persistent files on success.",
|
||||
};
|
||||
|
||||
|
||||
@@ -1027,6 +1027,36 @@ static const char nft_fwd_dup_sigma[] =
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2022.25636]\n";
|
||||
|
||||
static const char nft_fwd_dup_yara[] =
|
||||
"rule nft_fwd_dup_cve_2022_25636 : cve_2022_25636 kernel_oob_write\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2022-25636\"\n"
|
||||
" description = \"nft_fwd/dup actions OOB kmalloc-512 spray tag and log\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $tag = \"SKELETONKEY_FWD\" ascii\n"
|
||||
" $log = \"/tmp/skeletonkey-nft_fwd_dup.log\" ascii\n"
|
||||
" condition:\n"
|
||||
" any of them\n"
|
||||
"}\n";
|
||||
|
||||
static const char nft_fwd_dup_falco[] =
|
||||
"- rule: nft_fwd_dup OOB-write batch by non-root\n"
|
||||
" desc: |\n"
|
||||
" Non-root nfnetlink batch creating a netdev table with\n"
|
||||
" HW_OFFLOAD chain containing >15 immediate(NF_ACCEPT)\n"
|
||||
" expressions + 1 fwd. The offload walk overruns the action\n"
|
||||
" entries[] array. CVE-2022-25636.\n"
|
||||
" condition: >\n"
|
||||
" evt.type = sendmsg and fd.sockfamily = AF_NETLINK and\n"
|
||||
" not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" nfnetlink HW_OFFLOAD batch from non-root\n"
|
||||
" (user=%user.name pid=%proc.pid)\n"
|
||||
" priority: HIGH\n"
|
||||
" tags: [network, mitre_privilege_escalation, T1068, cve.2022.25636]\n";
|
||||
|
||||
const struct skeletonkey_module nft_fwd_dup_module = {
|
||||
.name = "nft_fwd_dup",
|
||||
.cve = "CVE-2022-25636",
|
||||
@@ -1040,8 +1070,8 @@ const struct skeletonkey_module nft_fwd_dup_module = {
|
||||
.cleanup = nft_fwd_dup_cleanup,
|
||||
.detect_auditd = nft_fwd_dup_auditd,
|
||||
.detect_sigma = nft_fwd_dup_sigma,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_yara = nft_fwd_dup_yara,
|
||||
.detect_falco = nft_fwd_dup_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET) + nfnetlink batch (NEWTABLE netdev + NEWCHAIN HW_OFFLOAD + NEWRULE with 16 immediate(NF_ACCEPT) + 1 fwd). Offload hook walks the rule advertising num_actions+=16 but allocates only the original-actions size -> OOB write at entries[16] into adjacent kmalloc-512. msg_msg groom tagged 'SKELETONKEY_FWD'. Writes /tmp/skeletonkey-nft_fwd_dup.log. Audit-visible via unshare + socket(NETLINK_NETFILTER) + sendmsg + ioctl(SIOCGIFFLAGS/SIOCSIFFLAGS loopback) + msgsnd. Dmesg: KASAN or silent. Cleanup callback drains IPC queues and unlinks log.",
|
||||
};
|
||||
|
||||
|
||||
@@ -1138,6 +1138,35 @@ static const char nft_payload_sigma[] =
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2023.0179]\n";
|
||||
|
||||
static const char nft_payload_yara[] =
|
||||
"rule nft_payload_cve_2023_0179 : cve_2023_0179 kernel_oob_read_write\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2023-0179\"\n"
|
||||
" description = \"nft_payload OOB-via-verdict-index breadcrumb log\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $log = \"/tmp/skeletonkey-nft_payload.log\" ascii\n"
|
||||
" condition:\n"
|
||||
" $log\n"
|
||||
"}\n";
|
||||
|
||||
static const char nft_payload_falco[] =
|
||||
"- rule: nft_payload OOB via verdict-code index by non-root\n"
|
||||
" desc: |\n"
|
||||
" Non-root nfnetlink batch with an oversized NFTA_SET_DESC\n"
|
||||
" + NEWSETELEM whose NFTA_PAYLOAD_SREG uses attacker-\n"
|
||||
" controlled verdict code as an index into regs->data[].\n"
|
||||
" CVE-2023-0179.\n"
|
||||
" condition: >\n"
|
||||
" evt.type = sendmsg and fd.sockfamily = AF_NETLINK and\n"
|
||||
" not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" nfnetlink payload batch from non-root\n"
|
||||
" (user=%user.name pid=%proc.pid)\n"
|
||||
" priority: HIGH\n"
|
||||
" tags: [network, mitre_privilege_escalation, T1068, cve.2023.0179]\n";
|
||||
|
||||
const struct skeletonkey_module nft_payload_module = {
|
||||
.name = "nft_payload",
|
||||
.cve = "CVE-2023-0179",
|
||||
@@ -1151,8 +1180,8 @@ const struct skeletonkey_module nft_payload_module = {
|
||||
.cleanup = nft_payload_cleanup,
|
||||
.detect_auditd = nft_payload_auditd,
|
||||
.detect_sigma = nft_payload_sigma,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_yara = nft_payload_yara,
|
||||
.detect_falco = nft_payload_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET) + nfnetlink batch (NEWTABLE + NEWCHAIN/LOCAL_OUT + NEWSET with oversized NFTA_SET_DESC + NEWSETELEM whose NFTA_PAYLOAD_SREG = attacker verdict code). On packet eval, regs->verdict.code is used unchecked as index into regs->data[] -> OOB. Dual-slab groom (kmalloc-1k + kmalloc-cg-96). Trigger via sendto(AF_INET, 127.0.0.1:31337). Writes /tmp/skeletonkey-nft_payload.log. Audit-visible via unshare + socket(NETLINK_NETFILTER) + sendmsg + msgsnd + socket(AF_INET)/sendto. Cleanup callback unlinks log.",
|
||||
};
|
||||
|
||||
|
||||
@@ -1021,6 +1021,37 @@ static const char nft_set_uaf_sigma[] =
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2023.32233]\n";
|
||||
|
||||
static const char nft_set_uaf_yara[] =
|
||||
"rule nft_set_uaf_cve_2023_32233 : cve_2023_32233 kernel_uaf\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2023-32233\"\n"
|
||||
" description = \"nft anonymous-set UAF spray tag (SKELETONKEY_SET) and log breadcrumb\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $tag = \"SKELETONKEY_SET\" ascii\n"
|
||||
" $log = \"/tmp/skeletonkey-nft_set_uaf.log\" ascii\n"
|
||||
" condition:\n"
|
||||
" any of them\n"
|
||||
"}\n";
|
||||
|
||||
static const char nft_set_uaf_falco[] =
|
||||
"- rule: nft anonymous-set lookup-UAF batch by non-root\n"
|
||||
" desc: |\n"
|
||||
" Non-root nfnetlink single-batch transaction: NEWTABLE +\n"
|
||||
" NEWCHAIN + NEWSET (anonymous, EVAL) + NEWRULE with\n"
|
||||
" nft_lookup referencing the anon set + DELSET + DELRULE.\n"
|
||||
" The lookup's set reference isn't deactivated; UAF when\n"
|
||||
" set frees. CVE-2023-32233.\n"
|
||||
" condition: >\n"
|
||||
" evt.type = sendmsg and fd.sockfamily = AF_NETLINK and\n"
|
||||
" not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" nfnetlink anon-set batch from non-root\n"
|
||||
" (user=%user.name pid=%proc.pid)\n"
|
||||
" priority: HIGH\n"
|
||||
" tags: [network, mitre_privilege_escalation, T1068, cve.2023.32233]\n";
|
||||
|
||||
const struct skeletonkey_module nft_set_uaf_module = {
|
||||
.name = "nft_set_uaf",
|
||||
.cve = "CVE-2023-32233",
|
||||
@@ -1033,8 +1064,8 @@ const struct skeletonkey_module nft_set_uaf_module = {
|
||||
.cleanup = nft_set_uaf_cleanup,
|
||||
.detect_auditd = nft_set_uaf_auditd,
|
||||
.detect_sigma = nft_set_uaf_sigma,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_yara = nft_set_uaf_yara,
|
||||
.detect_falco = nft_set_uaf_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNET) + single nfnetlink transaction: NEWTABLE + NEWCHAIN + NEWSET (anonymous, ANONYMOUS|CONSTANT|EVAL) + NEWRULE with nft_lookup referencing the anon set + DELSET + DELRULE. Vulnerable kernels do not deactivate the lookup's set ref on commit -> UAF when set frees. msg_msg cg-512 spray (32 queues x 16 msgs, tag 'SKELETONKEY_SET'). --full-chain re-fires with forged headers (data ptr = kaddr) and NEWSETELEM payload. Writes /tmp/skeletonkey-nft_set_uaf.log. Audit-visible via unshare + socket(NETLINK_NETFILTER) + sendmsg + msgsnd. Dmesg: KASAN oops on UAF. Cleanup unlinks log.",
|
||||
};
|
||||
|
||||
|
||||
@@ -490,6 +490,56 @@ static const char overlayfs_auditd[] =
|
||||
"# Watch for security.capability xattr writes (the post-mount step)\n"
|
||||
"-a always,exit -F arch=b64 -S setxattr,fsetxattr,lsetxattr -k skeletonkey-overlayfs-cap\n";
|
||||
|
||||
static const char overlayfs_sigma[] =
|
||||
"title: Possible CVE-2021-3493 Ubuntu overlayfs capability injection\n"
|
||||
"id: f78a01e6-skeletonkey-overlayfs\n"
|
||||
"status: experimental\n"
|
||||
"description: |\n"
|
||||
" Detects Ubuntu's overlayfs-in-userns capability-xattr injection:\n"
|
||||
" unshare(CLONE_NEWUSER|CLONE_NEWNS) + mount('overlay') + setxattr\n"
|
||||
" with name 'security.capability'. The bug lets caps set inside\n"
|
||||
" userns persist on the host fs. False positives: legitimate\n"
|
||||
" rootless container image builds; correlate with subsequent\n"
|
||||
" execve of the modified binary.\n"
|
||||
"logsource: {product: linux, service: auditd}\n"
|
||||
"detection:\n"
|
||||
" userns: {type: 'SYSCALL', syscall: 'unshare'}\n"
|
||||
" overlay: {type: 'SYSCALL', syscall: 'mount'}\n"
|
||||
" setcap: {type: 'SYSCALL', syscall: 'setxattr'}\n"
|
||||
" condition: userns and overlay and setcap\n"
|
||||
"level: critical\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2021.3493]\n";
|
||||
|
||||
static const char overlayfs_yara[] =
|
||||
"rule overlayfs_cve_2021_3493 : cve_2021_3493 userns_lpe\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2021-3493\"\n"
|
||||
" description = \"Ubuntu overlayfs userns workdir + security.capability xattr injection\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $work = /\\/tmp\\/skeletonkey-ovl-[A-Za-z0-9]+/\n"
|
||||
" $xattr = \"security.capability\" ascii\n"
|
||||
" condition:\n"
|
||||
" $work and $xattr\n"
|
||||
"}\n";
|
||||
|
||||
static const char overlayfs_falco[] =
|
||||
"- rule: overlayfs mount + setxattr(security.capability) in userns\n"
|
||||
" desc: |\n"
|
||||
" Non-root process inside userns mounts overlayfs and writes a\n"
|
||||
" security.capability xattr on a binary in the upper layer.\n"
|
||||
" The xattr persists on the host fs (CVE-2021-3493, Ubuntu).\n"
|
||||
" False positives: rootless container image builds.\n"
|
||||
" condition: >\n"
|
||||
" evt.type = setxattr and not user.uid = 0 and\n"
|
||||
" evt.args contains security.capability\n"
|
||||
" output: >\n"
|
||||
" setxattr(security.capability) by non-root\n"
|
||||
" (user=%user.name pid=%proc.pid file=%fd.name)\n"
|
||||
" priority: CRITICAL\n"
|
||||
" tags: [filesystem, mitre_privilege_escalation, T1068, cve.2021.3493]\n";
|
||||
|
||||
const struct skeletonkey_module overlayfs_module = {
|
||||
.name = "overlayfs",
|
||||
.cve = "CVE-2021-3493",
|
||||
@@ -502,9 +552,9 @@ const struct skeletonkey_module overlayfs_module = {
|
||||
.cleanup = NULL, /* exploit cleans up its own workdir on failure;
|
||||
* on success, exec replaces us so cleanup-by-us doesn't apply */
|
||||
.detect_auditd = overlayfs_auditd,
|
||||
.detect_sigma = NULL,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_sigma = overlayfs_sigma,
|
||||
.detect_yara = overlayfs_yara,
|
||||
.detect_falco = overlayfs_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNS) for CAP_SYS_ADMIN; mount('overlay', merged, ...); compile + copy payload into the merged dir (writes upper on host fs); setxattr(upper_payload, 'security.capability', cap_setuid+ep) - the bug is that this xattr persists on the HOST fs despite being set inside userns. Parent then execve's the now-CAP_SETUID payload, calls setuid(0), execs /bin/sh. Artifacts: /tmp/skeletonkey-ovl-XXXXXX/ workdir; cleaned on exit/failure (on success the exec replaces the process so cleanup does not run). Audit-visible via unshare + mount(overlay) + setxattr(security.capability) + execve of attacker-controlled binary. Dmesg silent.",
|
||||
};
|
||||
|
||||
|
||||
@@ -407,6 +407,56 @@ static const char overlayfs_setuid_auditd[] =
|
||||
"-a always,exit -F arch=b64 -S mount -F a2=overlay -k skeletonkey-overlayfs\n"
|
||||
"-a always,exit -F arch=b64 -S chown,fchown,fchownat -k skeletonkey-overlayfs-chown\n";
|
||||
|
||||
static const char overlayfs_setuid_sigma[] =
|
||||
"title: Possible CVE-2023-0386 overlayfs setuid copy-up\n"
|
||||
"id: 0891b2f7-skeletonkey-overlayfs-setuid\n"
|
||||
"status: experimental\n"
|
||||
"description: |\n"
|
||||
" Detects the upstream overlayfs setuid copy-up bug: unshare\n"
|
||||
" (CLONE_NEWUSER|CLONE_NEWNS) + mount('overlay') with a setuid-\n"
|
||||
" root binary in lower + chown on the merged view to trigger\n"
|
||||
" copy-up. Setuid bit persists in upper layer despite\n"
|
||||
" unprivileged ownership.\n"
|
||||
"logsource: {product: linux, service: auditd}\n"
|
||||
"detection:\n"
|
||||
" userns: {type: 'SYSCALL', syscall: 'unshare'}\n"
|
||||
" overlay: {type: 'SYSCALL', syscall: 'mount'}\n"
|
||||
" chown_up: {type: 'SYSCALL', syscall: 'chown'}\n"
|
||||
" condition: userns and overlay and chown_up\n"
|
||||
"level: critical\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2023.0386]\n";
|
||||
|
||||
static const char overlayfs_setuid_yara[] =
|
||||
"rule overlayfs_setuid_cve_2023_0386 : cve_2023_0386 userns_lpe\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2023-0386\"\n"
|
||||
" description = \"overlayfs setuid copy-up workdir signature\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $work = /\\/tmp\\/skeletonkey-ovlsu-[A-Za-z0-9]+/\n"
|
||||
" condition:\n"
|
||||
" $work\n"
|
||||
"}\n";
|
||||
|
||||
static const char overlayfs_setuid_falco[] =
|
||||
"- rule: overlayfs chown on setuid binary in userns (copy-up)\n"
|
||||
" desc: |\n"
|
||||
" Non-root chown on a setuid-root binary inside an overlayfs\n"
|
||||
" mount in a userns. Triggers copy-up that preserves the\n"
|
||||
" setuid bit despite unprivileged upper-layer ownership.\n"
|
||||
" CVE-2023-0386.\n"
|
||||
" condition: >\n"
|
||||
" evt.type in (chown, fchown, fchownat) and not user.uid = 0\n"
|
||||
" and (fd.name in (/usr/bin/su, /bin/su, /usr/bin/sudo,\n"
|
||||
" /usr/bin/passwd, /usr/bin/pkexec)\n"
|
||||
" or fd.name endswith /su)\n"
|
||||
" output: >\n"
|
||||
" chown on setuid binary by non-root\n"
|
||||
" (user=%user.name pid=%proc.pid file=%fd.name)\n"
|
||||
" priority: CRITICAL\n"
|
||||
" tags: [filesystem, mitre_privilege_escalation, T1068, cve.2023.0386]\n";
|
||||
|
||||
const struct skeletonkey_module overlayfs_setuid_module = {
|
||||
.name = "overlayfs_setuid",
|
||||
.cve = "CVE-2023-0386",
|
||||
@@ -418,9 +468,9 @@ const struct skeletonkey_module overlayfs_setuid_module = {
|
||||
.mitigate = NULL,
|
||||
.cleanup = overlayfs_setuid_cleanup,
|
||||
.detect_auditd = overlayfs_setuid_auditd,
|
||||
.detect_sigma = NULL,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_sigma = overlayfs_setuid_sigma,
|
||||
.detect_yara = overlayfs_setuid_yara,
|
||||
.detect_falco = overlayfs_setuid_falco,
|
||||
.opsec_notes = "unshare(CLONE_NEWUSER|CLONE_NEWNS) + overlayfs mount with a setuid-root binary in lower (e.g. /usr/bin/su); chown on the merged view triggers copy-up that preserves the setuid bit in upper - but upper is owned by the unprivileged user. Overwrites upper-layer contents with attacker payload and execve's for root. Artifacts: /tmp/skeletonkey-ovlsu-XXXXXX/ (workdir with payload.c, binary, overlay mounts); cleanup callback removes these. Audit-visible via unshare(CLONE_NEWUSER|CLONE_NEWNS) + mount(overlay) + chown on the merged view. No network. Dmesg silent on success.",
|
||||
};
|
||||
|
||||
|
||||
@@ -317,6 +317,42 @@ static const char ptrace_traceme_auditd[] =
|
||||
"-a always,exit -F arch=b64 -S ptrace -F a0=0 -k skeletonkey-ptrace-traceme\n"
|
||||
"-a always,exit -F arch=b32 -S ptrace -F a0=0 -k skeletonkey-ptrace-traceme\n";
|
||||
|
||||
static const char ptrace_traceme_sigma[] =
|
||||
"title: Possible CVE-2019-13272 PTRACE_TRACEME stale-cred LPE\n"
|
||||
"id: 1a02c3a8-skeletonkey-ptrace-traceme\n"
|
||||
"status: experimental\n"
|
||||
"description: |\n"
|
||||
" Detects ptrace(PTRACE_TRACEME) immediately followed by parent\n"
|
||||
" execve of a setuid binary. The kernel stores the parent's pre-\n"
|
||||
" execve credentials on the ptrace_link; after execve the link\n"
|
||||
" is stale but ptrace still grants privileges. False positives:\n"
|
||||
" debuggers (gdb, strace) tracing setuid processes legitimately.\n"
|
||||
"logsource: {product: linux, service: auditd}\n"
|
||||
"detection:\n"
|
||||
" traceme: {type: 'SYSCALL', syscall: 'ptrace', a0: 0}\n"
|
||||
" execve: {type: 'SYSCALL', syscall: 'execve'}\n"
|
||||
" condition: traceme and execve\n"
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2019.13272]\n";
|
||||
|
||||
static const char ptrace_traceme_falco[] =
|
||||
"- rule: PTRACE_TRACEME followed by setuid execve (cred escalation)\n"
|
||||
" desc: |\n"
|
||||
" Child calls ptrace(PTRACE_TRACEME) (recording parent's pre-\n"
|
||||
" execve creds); parent then execve's a setuid binary\n"
|
||||
" (pkexec, su, sudo). The stale ptrace_link grants the\n"
|
||||
" unprivileged child ptrace privileges over the now-root\n"
|
||||
" parent. CVE-2019-13272. False positives: debuggers (gdb,\n"
|
||||
" strace) tracing setuid processes legitimately.\n"
|
||||
" condition: >\n"
|
||||
" evt.type = ptrace and evt.arg.request = PTRACE_TRACEME and\n"
|
||||
" not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" PTRACE_TRACEME by non-root\n"
|
||||
" (user=%user.name pid=%proc.pid ppid=%proc.ppid)\n"
|
||||
" priority: HIGH\n"
|
||||
" tags: [process, mitre_privilege_escalation, T1068, cve.2019.13272]\n";
|
||||
|
||||
const struct skeletonkey_module ptrace_traceme_module = {
|
||||
.name = "ptrace_traceme",
|
||||
.cve = "CVE-2019-13272",
|
||||
@@ -328,9 +364,9 @@ const struct skeletonkey_module ptrace_traceme_module = {
|
||||
.mitigate = NULL, /* mitigation: upgrade kernel; OR sysctl kernel.yama.ptrace_scope=2 */
|
||||
.cleanup = NULL, /* exploit replaces our process image; no cleanup applies */
|
||||
.detect_auditd = ptrace_traceme_auditd,
|
||||
.detect_sigma = NULL,
|
||||
.detect_sigma = ptrace_traceme_sigma,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_falco = ptrace_traceme_falco,
|
||||
.opsec_notes = "Parent and child cooperate: child calls ptrace(PTRACE_TRACEME) (recording the parent's current credentials), then sleeps; parent execve's a setuid binary (pkexec or su) and elevates. The stale ptrace_link in the child still holds the old (non-root) credentials, so PTRACE_ATTACH succeeds against the now-root parent; the child injects shellcode at the parent's RIP via PTRACE_POKETEXT and detaches. Audit-visible via ptrace with a0=0 (PTRACE_TRACEME) closely followed by execve of a setuid binary in the parent process. No file artifacts; no persistent changes. No cleanup callback - the exploit execs /bin/sh and does not return.",
|
||||
};
|
||||
|
||||
|
||||
@@ -686,6 +686,57 @@ static const char sequoia_auditd[] =
|
||||
"# within 5s AND a subsequent skeletonkey-sequoia-mount event is\n"
|
||||
"# the canonical trigger shape.\n";
|
||||
|
||||
static const char sequoia_sigma[] =
|
||||
"title: Possible CVE-2021-33909 seq_file size_t-int wrap\n"
|
||||
"id: 2b13d4b9-skeletonkey-sequoia\n"
|
||||
"status: experimental\n"
|
||||
"description: |\n"
|
||||
" Detects the seq_file OOB-write trigger pattern: unshare\n"
|
||||
" (CLONE_NEWUSER|CLONE_NEWNS) + a burst of ~5000 mkdir/mkdirat\n"
|
||||
" syscalls + bind-mount + read(/proc/self/mountinfo). The\n"
|
||||
" rendered string exceeds INT_MAX, wrapping to negative.\n"
|
||||
" False positives: unusual; bursts of >1000 mkdir/s are rare in\n"
|
||||
" normal workloads.\n"
|
||||
"logsource: {product: linux, service: auditd}\n"
|
||||
"detection:\n"
|
||||
" userns: {type: 'SYSCALL', syscall: 'unshare'}\n"
|
||||
" mkdir: {type: 'SYSCALL', syscall: 'mkdir'}\n"
|
||||
" bind: {type: 'SYSCALL', syscall: 'mount'}\n"
|
||||
" condition: userns and mkdir and bind\n"
|
||||
"level: critical\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2021.33909]\n";
|
||||
|
||||
static const char sequoia_yara[] =
|
||||
"rule sequoia_cve_2021_33909 : cve_2021_33909 kernel_oob_write\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2021-33909\"\n"
|
||||
" description = \"Sequoia deep-mountpoint workdir + log breadcrumb\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $work = \"/tmp/skeletonkey-sequoia\" ascii\n"
|
||||
" $log = \"/tmp/skeletonkey-sequoia.log\" ascii\n"
|
||||
" condition:\n"
|
||||
" any of them\n"
|
||||
"}\n";
|
||||
|
||||
static const char sequoia_falco[] =
|
||||
"- rule: Deeply nested mkdir burst + /proc/self/mountinfo read (Sequoia)\n"
|
||||
" desc: |\n"
|
||||
" Non-root process reading /proc/self/mountinfo after a burst\n"
|
||||
" of ~5000 mkdir()s and a bind-mount of the deep leaf. The\n"
|
||||
" rendered mountinfo string exceeds INT_MAX. CVE-2021-33909.\n"
|
||||
" False positives: rare; mkdir bursts of this size are not\n"
|
||||
" seen in normal workloads.\n"
|
||||
" condition: >\n"
|
||||
" evt.type = open and fd.name = /proc/self/mountinfo and\n"
|
||||
" not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" /proc/self/mountinfo read by non-root\n"
|
||||
" (user=%user.name pid=%proc.pid)\n"
|
||||
" priority: HIGH\n"
|
||||
" tags: [filesystem, mitre_privilege_escalation, T1068, cve.2021.33909]\n";
|
||||
|
||||
const struct skeletonkey_module sequoia_module = {
|
||||
.name = "sequoia",
|
||||
.cve = "CVE-2021-33909",
|
||||
@@ -697,9 +748,9 @@ const struct skeletonkey_module sequoia_module = {
|
||||
.mitigate = NULL,
|
||||
.cleanup = sequoia_cleanup,
|
||||
.detect_auditd = sequoia_auditd,
|
||||
.detect_sigma = NULL,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_sigma = sequoia_sigma,
|
||||
.detect_yara = sequoia_yara,
|
||||
.detect_falco = sequoia_falco,
|
||||
.opsec_notes = "Builds ~5000 nested directories under /tmp/skeletonkey-sequoia (each name 200 'A' chars); enters userns for CAP_SYS_ADMIN; bind-mounts the leaf over itself to amplify the rendered mountinfo string length; reads /proc/self/mountinfo to trigger the int-vs-size_t overflow in seq_buf_alloc(), producing an OOB write of mountinfo bytes off the stack buffer. Artifacts: /tmp/skeletonkey-sequoia/ (deep tree + bind mounts) and /tmp/skeletonkey-sequoia.log (byte count + dmesg sample). Audit-visible via unshare(CLONE_NEWUSER|CLONE_NEWNS) + mount() + burst of ~5000 mkdir/mkdirat. No network. Cleanup callback walks back down the tree, unmounts, removes dirs, unlinks the .log.",
|
||||
};
|
||||
|
||||
|
||||
@@ -952,6 +952,53 @@ static const char stackrot_auditd[] =
|
||||
"-a always,exit -F arch=b64 -S mprotect -k skeletonkey-stackrot-mprotect\n"
|
||||
"-a always,exit -F arch=b64 -S munmap -F success=1 -k skeletonkey-stackrot-munmap\n";
|
||||
|
||||
static const char stackrot_sigma[] =
|
||||
"title: Possible CVE-2023-3269 maple-tree VMA-split UAF\n"
|
||||
"id: 3c24e5ca-skeletonkey-stackrot\n"
|
||||
"status: experimental\n"
|
||||
"description: |\n"
|
||||
" Detects the StackRot race-groom: unshare(CLONE_NEWUSER) + tight\n"
|
||||
" loops of mremap/munmap on MAP_GROWSDOWN regions + msg_msg\n"
|
||||
" spray (msgsnd) for kmalloc-192 grooming. False positives: JIT\n"
|
||||
" runtimes and aggressive memory allocators may do similar mremap\n"
|
||||
" bursts but typically without msg_msg grooming.\n"
|
||||
"logsource: {product: linux, service: auditd}\n"
|
||||
"detection:\n"
|
||||
" userns: {type: 'SYSCALL', syscall: 'unshare'}\n"
|
||||
" vmas: {type: 'SYSCALL', syscall: 'mremap'}\n"
|
||||
" groom: {type: 'SYSCALL', syscall: 'msgsnd'}\n"
|
||||
" condition: userns and vmas and groom\n"
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2023.3269]\n";
|
||||
|
||||
static const char stackrot_yara[] =
|
||||
"rule stackrot_cve_2023_3269 : cve_2023_3269 kernel_uaf\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2023-3269\"\n"
|
||||
" description = \"StackRot maple-tree UAF race log breadcrumb\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $log = \"/tmp/skeletonkey-stackrot.log\" ascii\n"
|
||||
" condition:\n"
|
||||
" $log\n"
|
||||
"}\n";
|
||||
|
||||
static const char stackrot_falco[] =
|
||||
"- rule: mremap/munmap race on MAP_GROWSDOWN regions (StackRot)\n"
|
||||
" desc: |\n"
|
||||
" Non-root process driving high-frequency mremap/munmap on\n"
|
||||
" MAP_GROWSDOWN regions inside a userns + msg_msg (msgsnd)\n"
|
||||
" grooming of kmalloc-192. Maple-tree node UAF race in\n"
|
||||
" __vma_adjust. CVE-2023-3269.\n"
|
||||
" condition: >\n"
|
||||
" evt.type in (mremap, munmap) and not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" VMA mutation by non-root\n"
|
||||
" (user=%user.name pid=%proc.pid evt=%evt.type)\n"
|
||||
" priority: HIGH\n"
|
||||
" tags: [memory, mitre_privilege_escalation, T1068, cve.2023.3269]\n";
|
||||
|
||||
const struct skeletonkey_module stackrot_module = {
|
||||
.name = "stackrot",
|
||||
.cve = "CVE-2023-3269",
|
||||
@@ -963,9 +1010,9 @@ const struct skeletonkey_module stackrot_module = {
|
||||
.mitigate = NULL,
|
||||
.cleanup = stackrot_cleanup,
|
||||
.detect_auditd = stackrot_auditd,
|
||||
.detect_sigma = NULL,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_sigma = stackrot_sigma,
|
||||
.detect_yara = stackrot_yara,
|
||||
.detect_falco = stackrot_falco,
|
||||
.opsec_notes = "Child forks, enters userns, builds a race region with MAP_GROWSDOWN + anchor VMAs, sprays kmalloc-192 with msg_msg payloads, then spawns Thread A (mremap/munmap of region boundary to rotate maple-tree nodes) + Thread B (fork+fault the growsdown region to deref freed node). UAF in __vma_adjust fires if a sprayed msg_msg reclaims the freed node. Writes /tmp/skeletonkey-stackrot.log (iteration counts + slab delta). Audit-visible via unshare + mremap/munmap bursts on stack regions + msgsnd spray. No network. Cleanup callback unlinks /tmp log.",
|
||||
};
|
||||
|
||||
|
||||
@@ -474,6 +474,23 @@ static const char sudo_samedit_sigma[] =
|
||||
|
||||
/* ---- Module registration ----------------------------------------- */
|
||||
|
||||
static const char sudo_samedit_falco[] =
|
||||
"- rule: sudoedit with -s and trailing-backslash argv (Baron Samedit)\n"
|
||||
" desc: |\n"
|
||||
" sudoedit invoked with -s and one or more args ending in '\\'.\n"
|
||||
" The parser's unescape loop walks past the argv string into\n"
|
||||
" adjacent stack/env, overflowing the heap buffer.\n"
|
||||
" CVE-2021-3156. False positives: extraordinarily rare;\n"
|
||||
" legitimate sudoedit usage does not need trailing backslashes.\n"
|
||||
" condition: >\n"
|
||||
" spawned_process and proc.name = sudoedit and\n"
|
||||
" proc.args contains \"-s \\\\\"\n"
|
||||
" output: >\n"
|
||||
" Possible Baron Samedit sudoedit invocation\n"
|
||||
" (user=%user.name pid=%proc.pid cmdline=\"%proc.cmdline\")\n"
|
||||
" priority: CRITICAL\n"
|
||||
" tags: [process, mitre_privilege_escalation, T1068, cve.2021.3156]\n";
|
||||
|
||||
const struct skeletonkey_module sudo_samedit_module = {
|
||||
.name = "sudo_samedit",
|
||||
.cve = "CVE-2021-3156",
|
||||
@@ -487,7 +504,7 @@ const struct skeletonkey_module sudo_samedit_module = {
|
||||
.detect_auditd = sudo_samedit_auditd,
|
||||
.detect_sigma = sudo_samedit_sigma,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_falco = sudo_samedit_falco,
|
||||
.opsec_notes = "Invokes sudoedit with argv = { 'sudoedit', '-s', trailing-backslash, then ~60 padding args each ending in backslash }; the parser's unescape loop in set_cmnd() walks past the end of the argv string for the trailing-backslash argument, copying adjacent stack/env into an undersized heap buffer. Audit-visible via execve(/usr/bin/sudoedit) with -s and a trailing-backslash argv. No persistent file artifacts (only best-effort removal of /tmp/.sudo_edit_*). No network. Dmesg silent unless sudo crashes (SIGSEGV). Per-distro heap layout determines landing; verifies geteuid()==0 afterward.",
|
||||
};
|
||||
|
||||
|
||||
@@ -618,6 +618,36 @@ static const char sudoedit_editor_sigma[] =
|
||||
|
||||
/* ----- module registration ------------------------------------------- */
|
||||
|
||||
static const char sudoedit_editor_yara[] =
|
||||
"rule sudoedit_editor_cve_2023_22809 : cve_2023_22809 setuid_abuse\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2023-22809\"\n"
|
||||
" description = \"skeletonkey sudoedit backdoor: appended skel UID=0 user in /etc/passwd\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $skel = \"skel::0:0:skeletonkey\" ascii\n"
|
||||
" condition:\n"
|
||||
" $skel\n"
|
||||
"}\n";
|
||||
|
||||
static const char sudoedit_editor_falco[] =
|
||||
"- rule: sudoedit with EDITOR/VISUAL containing '--' separator\n"
|
||||
" desc: |\n"
|
||||
" sudoedit spawned with EDITOR / VISUAL / SUDO_EDITOR env var\n"
|
||||
" containing the substring ' -- '. The argv-split bug treats\n"
|
||||
" everything after '--' as an additional file argument that\n"
|
||||
" sudoedit then opens with root privileges. CVE-2023-22809.\n"
|
||||
" condition: >\n"
|
||||
" spawned_process and proc.name = sudoedit and\n"
|
||||
" (proc.env contains \"EDITOR=\" or proc.env contains \"VISUAL=\"\n"
|
||||
" or proc.env contains \"SUDO_EDITOR=\")\n"
|
||||
" output: >\n"
|
||||
" sudoedit with EDITOR-style env var\n"
|
||||
" (user=%user.name pid=%proc.pid env=%proc.env)\n"
|
||||
" priority: CRITICAL\n"
|
||||
" tags: [process, mitre_privilege_escalation, T1068, cve.2023.22809]\n";
|
||||
|
||||
const struct skeletonkey_module sudoedit_editor_module = {
|
||||
.name = "sudoedit_editor",
|
||||
.cve = "CVE-2023-22809",
|
||||
@@ -630,8 +660,8 @@ const struct skeletonkey_module sudoedit_editor_module = {
|
||||
.cleanup = sudoedit_editor_cleanup,
|
||||
.detect_auditd = sudoedit_editor_auditd,
|
||||
.detect_sigma = sudoedit_editor_sigma,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_yara = sudoedit_editor_yara,
|
||||
.detect_falco = sudoedit_editor_falco,
|
||||
.opsec_notes = "Sets EDITOR='<helper> -- /etc/passwd' so sudoedit splits on the literal '--' and treats /etc/passwd as an additional editable file. Compiled helper appends 'skel::0:0:skeletonkey:/root:/bin/sh' to the post-'--' target; sudoedit runs the helper as root and copies back. Artifacts: /tmp/skeletonkey-sudoedit-XXXXXX (helper.c, helper binary, optional passwd.before backup); /etc/passwd gets the new 'skel' entry; drops root via 'su skel'. Audit-visible via execve(/usr/bin/sudoedit) with EDITOR/VISUAL/SUDO_EDITOR containing the literal '--' token. No network. Cleanup callback restores /etc/passwd from backup (if root) or removes the 'skel' line, and removes the /tmp dir.",
|
||||
};
|
||||
|
||||
|
||||
@@ -703,6 +703,55 @@ static const char vmwgfx_auditd[] =
|
||||
"-a always,exit -F arch=b64 -S ioctl -F a1=0x4004644b -k skeletonkey-vmwgfx-unref\n"
|
||||
"-a always,exit -F arch=b64 -S msgsnd -k skeletonkey-vmwgfx-spray\n";
|
||||
|
||||
static const char vmwgfx_sigma[] =
|
||||
"title: Possible CVE-2023-2008 vmwgfx DRM bo size OOB\n"
|
||||
"id: 4d35f6db-skeletonkey-vmwgfx\n"
|
||||
"status: experimental\n"
|
||||
"description: |\n"
|
||||
" Detects openat(/dev/dri/card*) + DRM_IOCTL_VMW_CREATE_DMABUF\n"
|
||||
" (0xc010644a) + UNREF (0x4004644b) + msg_msg groom sequence\n"
|
||||
" characteristic of the vmwgfx kmalloc-512 OOB. Only reachable\n"
|
||||
" on VMware guests with the vmwgfx driver loaded.\n"
|
||||
"logsource: {product: linux, service: auditd}\n"
|
||||
"detection:\n"
|
||||
" drm: {type: 'SYSCALL', syscall: 'openat'}\n"
|
||||
" ioctl: {type: 'SYSCALL', syscall: 'ioctl'}\n"
|
||||
" groom: {type: 'SYSCALL', syscall: 'msgsnd'}\n"
|
||||
" condition: drm and ioctl and groom\n"
|
||||
"level: high\n"
|
||||
"tags: [attack.privilege_escalation, attack.t1068, cve.2023.2008]\n";
|
||||
|
||||
static const char vmwgfx_yara[] =
|
||||
"rule vmwgfx_cve_2023_2008 : cve_2023_2008 kernel_oob_write\n"
|
||||
"{\n"
|
||||
" meta:\n"
|
||||
" cve = \"CVE-2023-2008\"\n"
|
||||
" description = \"vmwgfx DRM kmalloc-512 spray tag (SKVMWGFX) and log breadcrumb\"\n"
|
||||
" author = \"SKELETONKEY\"\n"
|
||||
" strings:\n"
|
||||
" $tag = \"SKVMWGFX\" ascii\n"
|
||||
" $log = \"/tmp/skeletonkey-vmwgfx.log\" ascii\n"
|
||||
" condition:\n"
|
||||
" any of them\n"
|
||||
"}\n";
|
||||
|
||||
static const char vmwgfx_falco[] =
|
||||
"- rule: vmwgfx DRM CREATE_DMABUF + UNREF ioctl by non-root\n"
|
||||
" desc: |\n"
|
||||
" Non-root process opens /dev/dri/card* and invokes\n"
|
||||
" DRM_IOCTL_VMW_CREATE_DMABUF (0xc010644a) + UNREF\n"
|
||||
" (0x4004644b). Only reachable on VMware guests; the size\n"
|
||||
" validation gap drives a kmalloc-512 OOB during ttm_bo_kmap.\n"
|
||||
" CVE-2023-2008.\n"
|
||||
" condition: >\n"
|
||||
" evt.type = ioctl and fd.name startswith /dev/dri/card and\n"
|
||||
" not user.uid = 0\n"
|
||||
" output: >\n"
|
||||
" vmwgfx DRM ioctl by non-root\n"
|
||||
" (user=%user.name pid=%proc.pid dev=%fd.name)\n"
|
||||
" priority: HIGH\n"
|
||||
" tags: [device, mitre_privilege_escalation, T1068, cve.2023.2008]\n";
|
||||
|
||||
const struct skeletonkey_module vmwgfx_module = {
|
||||
.name = "vmwgfx",
|
||||
.cve = "CVE-2023-2008",
|
||||
@@ -718,9 +767,9 @@ const struct skeletonkey_module vmwgfx_module = {
|
||||
.mitigate = NULL, /* mitigation: rmmod vmwgfx (loses graphics) */
|
||||
.cleanup = vmwgfx_cleanup,
|
||||
.detect_auditd = vmwgfx_auditd,
|
||||
.detect_sigma = NULL,
|
||||
.detect_yara = NULL,
|
||||
.detect_falco = NULL,
|
||||
.detect_sigma = vmwgfx_sigma,
|
||||
.detect_yara = vmwgfx_yara,
|
||||
.detect_falco = vmwgfx_falco,
|
||||
.opsec_notes = "Opens /dev/dri/card* (vmwgfx DRM - only reachable on VMware guests); DRM_IOCTL_VMW_CREATE_DMABUF with size=4096+16 lands in the kmalloc-512 page-count bucket but the byte-length overruns during kunmap_atomic copy in ttm_bo_kmap; mmap + write recognizable pattern across page boundary; UNREF commits the OOB into adjacent kmalloc-512. msg_msg spray tagged 'SKVMWGFX'. Writes /tmp/skeletonkey-vmwgfx.log (slab counts pre/post, trigger success). Audit-visible via openat(/dev/dri/card*), ioctl(0xc010644a CREATE / 0x4004644b UNREF), msgsnd spray. No network. Cleanup callback unlinks /tmp log; --full-chain re-seeds spray with kaddr-tagged payloads and the modprobe_path finisher arbitrates via 3s sentinel.",
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user