Back to Intelligence

Linux Privilege Escalation Detection Framework: Why 7 of 13 Tracked CVEs Were the Same Copy-on-Write Bug — and How to Detect the Primitive, Not the Patch

SA
Security Arsenal Team
September 11, 2026
13 min read

If your vulnerability management queue looks like a firehose of Linux local privilege escalation CVEs this year, there's a reason — and it's not thirteen distinct problems. Elastic Security Labs' latest detection engineering research tracked thirteen local unauthorized privilege gain CVEs in 2026 and discovered that seven of them were the same underlying copy-on-write (COW) kernel bug pointed at different kernel interfaces. Their team executed the public proof-of-concept code for eleven of the security issues plus two common misconfigurations, and documented exactly which detection rules fired.

The defensive implication is significant: if more than half of the privilege escalation CVEs landing in your queue share a single exploitation primitive, then signature-style, per-CVE detection is a losing game. Behavioral detection aimed at the primitive — COW abuse, race conditions against kernel interfaces, and the staging behavior of exploit code — collapses seven CVEs into a handful of durable detections. This post breaks down what the research found, how the attack class works, and gives you production-ready Sigma, KQL, and Velociraptor content plus a hardening script you can run today.

Why This Matters to Your SOC Right Now

Local privilege escalation is rarely the headline breach — it's the second act. Ransomware operators, initial access brokers, and post-exploitation frameworks almost universally need root on a Linux target to disable defenses, access credential stores, deploy EDR killers, or pivot to hypervisors and container hosts. A working, public PoC for a local privesc bug converts any low-value foothold — a phished developer workstation, a compromised CI runner, a web shell on an internet-facing app server — into full host compromise.

The Elastic research is uncomfortable reading for a specific reason: public PoC code exists and executes reliably for the issues they tested. These are not theoretical. When exploit code is one git clone away, the window between disclosure and commodity use in intrusions is measured in days, not months. If your detections are waiting on per-CVE IOCs, you will lose that race eleven times out of thirteen.

Technical Analysis: The Copy-on-Write Primitive

The Vulnerability Class

Copy-on-write is a core Linux memory management optimization: when a process forks or maps a file privately, the kernel shares physical pages and only duplicates them on write. A COW vulnerability exists when an attacker can race the kernel into allowing a write to the shared (read-only) backing page instead of a private copy — typically by combining madvise(), userfaultfd, or interface-specific race windows with concurrent write attempts.

The practical result: an unprivileged user gains write access to memory or file-backed pages they should only be able to read. Depending on which kernel interface the bug is reached through, that write primitive lands on different high-value targets — page tables, setuid binary mappings, or file-backed caches — which is exactly why the same underlying bug surfaces as multiple CVEs with different entry points and different affected kernel subsystems.

What Elastic's Testing Showed

Key takeaways from the detection engineering exercise:

  • Seven of thirteen CVEs shared the COW primitive, differing only in the kernel interface abused to reach it. Per-CVE detection content would have produced seven overlapping rules; primitive-level detection produced far fewer, more durable ones.
  • Public PoCs were executed for eleven security issues and two misconfigurations, with rule-firing behavior documented. This is the correct methodology: detection content validated against real exploit execution, not against a reading of the advisory.
  • The two misconfigurations matter as much as the CVEs. World-writable staging directories, permissive sudo/setuid configurations, and unhardened namespace or ptrace settings remain reliable escalation paths that need no kernel bug at all.

Affected Platforms and Exploitation Requirements

  • Platforms: Linux systems across distributions; the affected kernel version range varies per CVE and per interface. Unpatched LTS kernels common in enterprise estates (and frequently in container host images and embedded appliances) are the population of concern.
  • Access required: Local code execution as an unprivileged user. This is a post-compromise multiplier, not an initial access vector.
  • Exploitation status: Public proof-of-concept code confirmed working. Treat this class as actively exploitable in any environment where attackers can land code execution — web-facing services, developer endpoints, CI/CD infrastructure, and multi-tenant container hosts.

The Typical Attack Chain

  1. Foothold via any vector (web shell, SSH credential theft, container escape precursor, malicious dependency).
  2. Staging: exploit source or prebuilt binary written to a world-writable path — /tmp, /dev/shm, /var/tmp.
  3. Compilation on-target (when source is dropped) using gcc, cc, or clang — a strong signal on production servers that should never compile code.
  4. Execution: the PoC spams the vulnerable interface (repeated madvise/userfaultfd/race loops), often producing high CPU bursts and thousands of rapid syscall loops.
  5. Post-exploitation: the escalated process writes to a setuid binary, drops a SUID backdoor, modifies /etc/passwd//etc/sudoers, or spawns a root shell.

Every step except the race itself is highly observable. That's the opportunity.

Detection Strategy: Target the Primitive and the Staging

Because seven CVEs collapse into one primitive, the durable detection strategy has three layers:

  1. Staging behavior — execution and compilation from world-writable directories.
  2. Exploit mechanics — abuse of memory-management interfaces and writes to /proc memory interfaces.
  3. Post-exploitation — unexpected SUID binary creation, root shells spawned by non-root parents, and integrity changes to authentication files.

Sigma Rules

YAML
---
title: Execution from World-Writable Directory - Possible Privilege Escalation Staging
description: Detects execution of binaries from world-writable directories commonly used to stage Linux privilege escalation PoCs such as copy-on-write kernel exploits. Public privesc PoCs are frequently dropped and run from /tmp, /dev/shm, or /var/tmp by unprivileged users.
references:
  - https://www.elastic.co/security-labs/threat-command/linux-privilege-escalation-detection-framework
  - https://attack.mitre.org/techniques/T1068/
author: Security Arsenal
status: experimental
logsource:
  product: linux
  category: process_creation
detection:
  selection_tmp:
    Image|startswith:
      - '/tmp/'
      - '/var/tmp/'
      - '/dev/shm/'
  filter_shells:
    Image|endswith:
      - '/sh'
      - '/bash'
      - '/dash'
      - '/zsh'
  condition: selection_tmp and not filter_shells
falsepositives:
  - Legitimate software installers and package managers extracting to /tmp
  - Temporary test harnesses in development environments
level: high
---
title: On-Target Compilation by Non-Privileged User - Exploit Build Activity
description: Detects invocation of C compilers with output directed to world-writable directories, a hallmark of privilege escalation PoCs compiled directly on the compromised host. Production servers should rarely, if ever, compile code.
references:
  - https://www.elastic.co/security-labs/threat-command/linux-privilege-escalation-detection-framework
  - https://attack.mitre.org/techniques/T1068/
author: Security Arsenal
status: experimental
logsource:
  product: linux
  category: process_creation
detection:
  selection_compiler:
    Image|endswith:
      - '/gcc'
      - '/cc'
      - '/clang'
      - '/g++'
      - '/make'
  selection_output:
    CommandLine|contains:
      - '/tmp/'
      - '/dev/shm/'
      - '/var/tmp/'
  condition: selection_compiler and selection_output
falsepositives:
  - Build agents and CI runners (exclude dedicated build hosts)
  - Developer workstations with legitimate compilation activity
level: high
---
title: Direct Write to Process Memory via procfs
description: Detects writes to /proc/<pid>/mem using common utilities, a technique used by memory-corruption and copy-on-write style privilege escalation exploits and injection tooling to alter process memory directly.
references:
  - https://www.elastic.co/security-labs/threat-command/linux-privilege-escalation-detection-framework
  - https://attack.mitre.org/techniques/T1055/
author: Security Arsenal
status: experimental
logsource:
  product: linux
  category: process_creation
detection:
  selection:
    CommandLine|contains:
      - '/proc/'
      - '/mem'
  selection_writers:
    Image|endswith:
      - '/dd'
      - '/tee'
      - '/cp'
      - '/python'
      - '/python3'
      - '/perl'
      - '/ruby'
      - '/php'
  condition: selection and selection_writers
falsepositives:
  - Rare legitimate debugging workflows
level: high

KQL (Microsoft Sentinel — Syslog/auditd ingestion)

This hunt query assumes Linux hosts forwarding auditd execve records or Syslog into Sentinel. It hunts for the full staging pattern: execution from world-writable paths, on-target compilation, and compiler or exploit activity by interactive non-root users.

KQL — Microsoft Sentinel / Defender
// Hunt for Linux privilege escalation staging and execution behavior
// Source: Elastic Security Labs COW privesc detection framework research
let TmpExec = Syslog
| where TimeGenerated > ago(24h)
| where SyslogMessage has_any ("/tmp/", "/dev/shm/", "/var/tmp/")
| where SyslogMessage has_any ("EXECVE", "execve", "type=SYSCALL")
| extend CommandLine = tostring(SyslogMessage)
| project TimeGenerated, Computer, HostIP, ProcessName, CommandLine;
let Compile = Syslog
| where TimeGenerated > ago(24h)
| where SyslogMessage has_any ("gcc", "/usr/bin/cc", "clang", "g++", "make")
| where SyslogMessage has_any ("/tmp/", "/dev/shm/", "/var/tmp/")
| project TimeGenerated, Computer, HostIP, ProcessName, SyslogMessage;
union TmpExec, Compile
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), ActivityCount=count(), SampleCommand=any(CommandLine) by Computer, ProcessName
| order by ActivityCount desc

If you collect auditd auid/euid transitions, extend this hunt by correlating processes whose effective UID changed to 0 while the parent process ran from a world-writable path — that is the highest-fidelity indicator of a successful escalation.

Velociraptor VQL

This artifact hunts live systems for (a) processes currently executing from world-writable directories and (b) recently created SUID-root files, which are the two most common artifacts of a successful COW-class escalation.

VQL — Velociraptor
-- Hunt: COW privesc artifacts - tmp execution and new SUID-root binaries
SELECT Pid, Ppid, Name, CommandLine, Exe, Username, CreateTime
FROM pslist()
WHERE Exe =~ '^/(tmp|var/tmp|dev/shm)/'
   OR CommandLine =~ '/dev/shm/'

-- Companion: SUID-root files modified in the last 7 days in user-writable paths
SELECT FullPath, Size, Mtime, Mode
FROM glob(globs=['/tmp/**', '/var/tmp/**', '/dev/shm/**', '/home/*/**'])
WHERE Mode.String =~ 's'
  AND Mtime > now() - 604800

For fleet-wide sweeps, deploy these as separate artifacts (Linux.Hunt.TmpExecution and Linux.Hunt.NewSuidBinaries) so the SUID sweep's heavier glob doesn't slow the process hunt. Note the second query uses Velociraptor's Mode string match for the setuid bit — validate against your client version's glob output fields.

Remediation and Hardening Script

The following Bash script verifies kernel patch posture, enumerates the misconfiguration surface Elastic flagged, applies auditd coverage for escalation behaviors, and sets kernel hardening sysctls. Run it on a representative host first; auditd rule changes require an auditd restart or reboot to persist via /etc/audit/rules.d/.

Bash / Shell
#!/bin/bash
# Security Arsenal - Linux privesc hardening & audit script (2026 COW class)
set -euo pipefail

REPORT=/var/log/privesc_harden_$(date +%Y%m%d).log
exec > >(tee -a "$REPORT") 2>&1

echo "=== [1] Kernel version check ==="
uname -r
echo "[!] Verify running kernel is patched per your distro's 2026 security advisories:"
echo "    Ubuntu : https://ubuntu.com/security/notices (usn-tool or ubuntu-advantage-tools)"
echo "    RHEL   : https://access.redhat.com/security/security-updates/"
echo "    Debian : https://security-tracker.debian.org/tracker/"

echo "=== [2] Pending kernel security updates ==="
if command -v apt >/dev/null; then
  apt list --upgradable 2>/dev/null | grep -i linux-image || echo "No pending kernel updates via apt"
elif command -v dnf >/dev/null; then
  dnf check-update --security 2>/dev/null | grep -i kernel || echo "No pending kernel updates via dnf"
fi

echo "=== [3] SUID/SGID inventory (baseline and alert on drift) ==="
find / -xdev -type f \( -perm -4000 -o -perm -2000 \) -printf '%M %u %g %p\n' 2>/dev/null | sort

echo "=== [4] World-writable mounts missing noexec/nosuid ==="
for d in /tmp /var/tmp /dev/shm; do
  mount | grep " $d " | grep -Eq 'noexec.*nosuid|nosuid.*noexec' \
    && echo "[OK] $d mounted noexec,nosuid" \
    || echo "[!!] $d is NOT mounted with noexec,nosuid - add to /etc/fstab: tmpfs $d tmpfs defaults,noexec,nosuid,nodev 0 0"
done

echo "=== [5] Kernel hardening sysctls ==="
cat >/etc/sysctl.d/99-privesc-hardening.conf <<'EOF'
kernel.yama.ptrace_scope = 2
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
kernel.perf_event_paranoid = 3
kernel.unprivileged_bpf_disabled = 1
net.core.bpf_jit_harden = 2
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
fs.suid_dumpable = 0
# Disable unprivileged user namespaces if not required by containers on this host
kernel.unprivileged_userns_clone = 1
user.max_user_namespaces = 0
EOF
sysctl --system >/dev/null 2>&1 || echo "[!] Review sysctl output manually"
echo "[OK] Sysctls written to /etc/sysctl.d/99-privesc-hardening.conf"
echo "[!] NOTE: set user.max_user_namespaces=0 ONLY if this host does not run rootless containers"

echo "=== [6] auditd rules for escalation telemetry ==="
cat >/etc/audit/rules.d/privesc-detection.rules <<'EOF'
# Detect execution from world-writable dirs
-a always,exit -F arch=b64 -F dir=/tmp -F perm=x -k tmp_exec
-a always,exit -F arch=b64 -F dir=/dev/shm -F perm=x -k tmp_exec
# Detect SUID/SGID file creation and permission changes
-a always,exit -F arch=b64 -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod
# Detect writes to authentication & privilege config files
-w /etc/passwd -p wa -k auth_files
-w /etc/shadow -p wa -k auth_files
-w /etc/sudoers -p wa -k auth_files
-w /etc/sudoers.d/ -p wa -k auth_files
# Detect setuid/setgid syscall usage by unprivileged users
-a always,exit -F arch=b64 -S setuid -S setgid -S setreuid -S setregid -F auid>=1000 -F auid!=4294967295 -k priv_esc
EOF
command -v augenrules >/dev/null && augenrules --load || echo "[!] Load rules with: auditctl -R /etc/audit/rules.d/privesc-detection.rules"
echo "[OK] auditd rules staged; forward audit logs to your SIEM and alert on keys: tmp_exec, priv_esc, auth_files"

echo "=== [7] Compiler presence on production hosts ==="
for c in gcc cc clang g++; do
  command -v $c >/dev/null && echo "[!!] Compiler present: $(command -v $c) - remove on production servers or restrict via file ACLs"
done

echo "=== Done. Report: $REPORT ==="

Remediation Priorities

1. Patch the kernel — all thirteen, not seven. Even though seven CVEs share a primitive, the patch surface differs by interface. Pull your distribution's current kernel security updates:

2. Break the staging pattern. Mount /tmp, /var/tmp, and /dev/shm with noexec,nosuid,nodev. This single control forces attackers to bring statically linked binaries and find alternative staging paths — it doesn't stop a determined attacker, but it eliminates the most common public PoC workflow wholesale and creates a clean detection boundary.

3. Remove compilers from production. There is no legitimate reason for gcc on a production web server. Removing it converts "compile on target" into a noisier, more detectable "drop prebuilt binary" — and pairs directly with the Sigma compilation rule above.

4. Restrict unprivileged attack surface. Disable unprivileged user namespaces (user.max_user_namespaces=0) on hosts that don't need rootless containers, set kernel.unprivileged_bpf_disabled=1, and enforce kernel.yama.ptrace_scope=2. Several escalation interfaces and post-exploitation techniques depend on these being permissive — and permissive is the default on many distributions.

5. Baseline SUID and alert on drift. The two misconfigurations in Elastic's testing are a reminder that SUID binary drift (a new SUID file is a classic escalation persistence artifact) is a cheap, high-fidelity alert. Baseline once, alert on any addition.

6. Validate detections the way Elastic did. Run the public PoCs in an isolated lab against your own rule set. Detection content that has never seen the actual exploit execute is a hypothesis, not a control. If you don't have lab capacity for this, this is precisely the kind of adversary-emulation validation a purple team engagement should cover.

The Bottom Line

The headline isn't that Linux had thirteen privilege escalation CVEs — it's that vulnerability-count-based risk prioritization is misleading when the majority of those CVEs are one bug wearing different clothes. Defenders who detect the primitive (COW abuse, memory-interface racing, tmp staging, on-target compilation) get coverage for this entire CVE family and the next one. Defenders who wait for per-CVE signatures will be writing detection number eight while attackers are already on number thirteen.

Patch the kernels, harden the mounts and sysctls, deploy behavioral detections, and validate them against real exploit execution. That's the whole playbook — and it's executable this week.

Related Resources

Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub

Is your security operations ready?

Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.