
Lab detection exercise for DirtyFrag (CVE-2026-43284) - Linux kernel privilege escalation via xfrm-ESP page cache corruption. Full write-up covering exploit execution, detection gaps, and corrected EQL rules using Elastic Stack
Classification: Internal Lab - CyberLAB
Date: 24 May 2026
Platform: Elastic Stack v9.4.1
Status: Detection Confirmed
A controlled exploitation exercise simulating a post-initial-access adversary using DirtyFrag (CVE-2026-43284), a Linux kernel privilege escalation vulnerability. The simulated attacker scenario: low-privilege user (saskia, uid=1001) obtained via phished credentials attempts to escalate to root on an internal Linux host. The lab used a deliberately vulnerable Ubuntu machine (ubuntu-vuln, kernel 6.0.0-22-generic) isolated from production networks.
The attacker achieved full root access (uid=0) within the lab environment. The Elastic Security detection stack generated 3 HIGH-severity alerts (risk score 73) correctly identifying the exploitation chain. However, detection required manual tuning - the out-of-the-box configuration had gaps that would have resulted in missed alerts.
Bottom line: Unpatched Linux hosts running kernel <= 6.x are vulnerable. If an attacker already has a low-privilege foothold, this exploit gives them full root silently and quickly, with no on-disk trace visible to file integrity tools.
auditd rules produce no evidence. Without explicit syscall rules for socket, splice, and unshare, the exploit runs silently - no logs, no alerts. Default Elastic Agent deployment does not add these rules automatically.process.parent.pid as the correlation key. In practice, DirtyFrag forks intermediate processes, breaking that correlation. The fix - correlating by auditd.session - was identified and implemented during this exercise./etc/shadow, accessed SSH authorized keys, wrote a persistence marker (/root/pwned.txt), and initiated network reconnaissance via nc. All occurred within 30 minutes of initial access.Objective: Validate detection coverage for DirtyFrag exploitation in the home lab SOC, identify detection gaps, and produce a corrected detection rule grounded in observed attacker behaviour.
Threat model: Simulated post-initial-access adversary with phished credentials for a low-privilege user account (saskia, uid=1001). Goal: privilege escalation to root via DirtyFrag using the xfrm-ESP receive path. Lab execution performed via Proxmox console - functionally equivalent to SSH post-access.
Two attacker capability tiers were considered:
This exercise covered the vanilla tier.
DirtyFrag is the third in a family of related Linux kernel bugs that all abuse the same root cause:
Root cause: IPv4/IPv6 datagram append paths did not set SKBFL_SHARED_FRAG after skb_splice_from_iter() planted a page cache page into a socket buffer (sk_buff). The ESP input path then performed in-place crypto on attacker-controlled page cache pages, treating them as ordinary uncloned non-linear skbs.
Exploit primitive: 4 or 8-byte controlled write into the page cache. Deterministic - no race condition required.
Exploit chain:
/usr/bin/su) into page cachesplice() so the file's page sits in frag[0] of struct sk_buffxfrm-ESP decrypt pathexecve() - return corrupted bytesCritical evasion property: The on-disk file is never modified. File integrity monitoring tools (AIDE, Tripwire, hash checks) all report the target file as clean.
Confirmed vulnerable distributions: Ubuntu 24.04.4, RHEL 10.1, AlmaLinux 10, openSUSE Tumbleweed, CentOS Stream 10, Fedora 44.
Active telemetry at exercise time:
Lab network isolated before exercise. Firewall alias Cyber_escape_door confirmed disabled - preventing lateral reach to other VLANs or internet.

Confirmed vulnerable kernel and low-privilege account on ubuntu-vuln:
saskia@ubuntu-vuln:~$ uname -r
6.0.0-22-generic
saskia@ubuntu-vuln:~$ whoami
saskia
saskia@ubuntu-vuln:~$ id
uid=1001(saskia) gid=1001(saskia) groups=1001(saskia),27(sudo),100(users)

Suricata: running and capturing (systemctl status)

Kibana/Elasticsearch: green/healthy (API check)

VM snapshots taken before any changes



tcpdump started on Suricata host (emp6s19, snapshot length 262144 bytes) for full packet capture of 172.66.66.27 traffic.


Exploit source (exp.c) from V4bel/dirtyfrag PoC staged on victim host.
ubuntu-vuln@ubuntu-vuln:~/dirtyfrag$ ls
exp exp.c README.md
ubuntu-vuln@ubuntu-vuln:~/dirtyfrag$ cp exp.c /tmp/exp/
Right-hand terminal shows the staged copy visible to user saskia.

Compiled as root via sudo gcc (user saskia is in sudoers):
saskia@ubuntu-vuln:~$ sudo gcc -o /tmp/exp/clean_frag /tmp/exp/exp.c
saskia@ubuntu-vuln:~$ ls -la /tmp/exp/
-rwxr-xr-x 1 root root 62320 May 24 12:18 clean_frag
-rw-rw-r-- 1 ubuntu-vuln ubuntu-vuln 67803 May 24 12:13 exp.c
saskia@ubuntu-vuln:~$ id
uid=1001(saskia) gid=1001(saskia) groups=1001(saskia),27(sudo),100(users)
Binary: ELF 64-bit LSB executable, x86-64, dynamically linked.

Exploit executed as saskia (uid=1001). The process (clean_frag, pid=4326) opened AF_RXRPC and AF_ALG sockets, then called splice() to plant the page cache page into the network buffer. In-place crypto on the ESP path wrote attacker-controlled bytes into the page cache. A child process (pid=4327) called unshare with a0=50000000 (CLONE_NEWUSER | CLONE_NEWNET) to obtain namespace-scoped capabilities.
saskia@ubuntu-vuln:~$ /tmp/exp/clean_frag
...
root@ubuntu-vuln:~# id
uid=0(root) gid=0(root) groups=0(root)
root@ubuntu-vuln:~# ls
Full root shell obtained at 12:22 CEST (10:22 UTC), 24 May 2026.
Auditd syscall sequence captured (UTC):
All events share auditd.session=2.

All actions performed as root on ubuntu-vuln from ~12:22 CEST onward.
root@ubuntu-vuln:~# echo "Dirty Frag PoC executed at $(date)" > /root/pwned.txt
root@ubuntu-vuln:~# cat /root/pwned.txt
Dirty Frag PoC executed at Sun May 24 12:28:05 PM CEST 2026

root@ubuntu-vuln:~# cat /etc/shadow | head -2
root:*:19823:0:99999:7:::
daemon:*:19823:0:99999:7:::
root@ubuntu-vuln:~# cat /home/ubuntu-vuln/.ssh/authorized_keys
ssh-ed25519 AAAA[...]
Shadow file and SSH authorized keys accessed. In a real compromise this enables offline password cracking and persistent SSH backdoor access.

root@ubuntu-vuln:~# nc -vz 172.66.66.1 100-8000
Port scan of the lab gateway (172.66.66.1, ports 100-8000) executed from root context at 12:51 CEST. This demonstrates lateral movement capability and network mapping from an escalated session.

Suricata captured 105 events associated with 172.66.66.27 across the exercise window (88 mdns, 17 flow). At the time the screenshot was taken (during the nc scan at ~12:53 CEST), 29 events were visible in the dashboard.
The DirtyFrag exploit itself is kernel-internal and produces no network signature - the escalation primitive generates no anomalous traffic on the wire. Suricata was engaged to collect pcaps of post-exploitation traffic and enumeration attempts rather than to detect the exploit itself.
| Event Type | Count |
|---|---|
| mdns | 88 |
| flow | 17 |
| Total | 105 |
The flow events cluster around the nc port scan window (10:50-11:10 UTC / 12:50-13:10 CEST).

Two EQL sequence rules were created in the Kibana Detection Engine based on the Elastic Security Labs article.
Rule 1 - AF_RXRPC or AF_ALG Socket with Splice Followed by Execution (did not fire - see gap below)
sequence with maxspan=60s
[any where host.os.type == "linux" and
(
(event.category == "process" and auditd.data.syscall == "socket"
and auditd.data.a0 in ("26", "21")) or
(event.category == "process" and auditd.data.syscall == "splice") or
(event.category == "network" and event.action == "bound-socket"
and data_stream.dataset == "auditd_manager.auditd"
and auditd.data.socket.family == "38")
)
and user.id != "0"] by process.pid, host.id, user.id with runs=10
[process where host.os.type == "linux" and event.action == "executed" and
(
(user.effective.id == "0" and user.id != "0") or
(process.name in ("bash", "sh", "zsh", "dash", "fish", "ksh", "busybox")
and process.args in ("-c", "--command", "-ic", "-ci", "-cl", "-lc",
"-bash", "-sh", "-zsh", "-dash", "-fish", "-ksh"))
)] by process.parent.pid, host.id, user.id
Rule 2 - Namespace Manipulation Followed by Privilege Escalation (fired after fix)
sequence by host.id, auditd.session with maxspan=30s
[process where host.os.type == "linux"
and auditd.data.syscall == "unshare"
and auditd.data.a0 in ("10000000", "50000000", "70000000",
"10020000", "50020000", "70020000")
and user.id != "0" and user.id != null]
[process where host.os.type == "linux"
and user.id == "0" and user.id != null
and (process.name in ("bash", "sh", "zsh", "dash", "fish",
"ksh", "su", "sudo", "pkexec", "busybox", "mksh")
or process.name like ("python*", "perl*", "ruby*", "php*", "lua*"))]
Rule 1 returned zero results. Default auditd configuration on ubuntu-vuln captured: write, bpf, openat, unshare only.
Missing: socket, splice, bind - the core exploit primitives.
Remediation: Added to /etc/audit/rules.d/dirtyfrag.rules:
-a always,exit -F arch=b64 -S socket -k socket_syscall
-a always,exit -F arch=b32 -S socketcall -k socket_syscall
-a always,exit -F arch=b64 -S splice -k splice-syscall
-a always,exit -F arch=b32 -S splice -k splice-syscall
-a always,exit -F arch=b64 -S unshare -k unshare-syscall
-a always,exit -F arch=b32 -S unshare -k unshare-syscall
272 new auditd events landed in Elasticsearch within seconds of the exploit run.
Despite events being captured, Rule 2 returned 0 matches.
Root cause - actual process tree (from Elasticsearch):
The original rule correlated by process.parent.pid. The unshare event had ppid=4326, but the root shell had a different parent - DirtyFrag forks intermediate processes, breaking the correlation.
Fix: Correlate by auditd.session. All events in the exploit chain shared session=2, correctly linking the sequence regardless of fork depth.
3 HIGH-severity alerts, risk score 73, in Kibana Security - Alerts at 2026-05-24T10:25:57Z UTC (12:25:57 CEST).
| Alert | Timestamp (UTC) | Signal |
|---|---|---|
| Sequence match - unshare event | 10:25:57.869 | process=clean_frag, user=saskia (uid=1001) |
| Sequence match - root shell | 10:25:57.881 | process=bash, user=root (uid=0) |
DirtyFrag - Namespace Manipulation Followed by Privilege Escalationubuntu-vuln

All dirtyfrag-related auditd events:
data_stream.dataset: "auditd_manager.auditd" and auditd.summary.how: *dirtyfrag*
Namespace manipulation by non-root:
data_stream.dataset: "auditd_manager.auditd" and auditd.data.syscall: "unshare" and auditd.data.a0: "50000000"
Post-escalation root activity in same session:
data_stream.dataset: "auditd_manager.auditd" and user.id: "0" and auditd.session: "2"
All auditd events during exploit window:
data_stream.dataset: "auditd_manager.auditd" and @timestamp >= "2026-05-24T10:20:00Z" and @timestamp <= "2026-05-24T10:30:00Z"
Socket and splice syscalls from exploit process:
data_stream.dataset: "auditd_manager.auditd" and auditd.summary.how: "/tmp/exp/clean_frag"
| Item | Detail |
|---|
| Vulnerability | DirtyFrag - CVE-2026-43284 (xfrm-ESP path), CVE-2026-43500 (RxRPC path) |
| CVSS impact | Full local privilege escalation to root |
| Affected kernels | Linux kernel >= January 2017 (9-year lifetime) |
| Affected distros | Ubuntu 24.04.4, RHEL 10.1, AlmaLinux 10, openSUSE, CentOS Stream 10, Fedora 44 |
| File integrity bypass | On-disk files unchanged - AIDE, Tripwire, hash monitoring all report clean |
| Public PoC | Available (V4bel/dirtyfrag on GitHub) |
| Time to root | < 10 minutes from low-privilege foothold |
| Priority | Action |
|---|
| Immediate | Patch Linux kernel to a fixed version on all hosts. Apply vendor security advisories for Ubuntu, RHEL, AlmaLinux, openSUSE, CentOS, Fedora. |
| Short-term | Deploy explicit auditd syscall rules for socket, splice, and unshare on all Linux hosts monitored by Elastic Agent. |
| Short-term | Update EQL detection rules to correlate by auditd.session rather than process.parent.pid for namespace-manipulation sequences. |
| Ongoing | Treat privilege escalation alerts with risk score >= 73 as high-priority triage. Current rule fires within seconds of escalation. |
| CVE | Nickname | Introduced | Path |
|---|
| CVE-2022-0847 | Dirty Pipe | - | pipe |
| CVE-2026-31431 | Copy Fail | - | splice |
| CVE-2026-43284 | DirtyFrag | Jan 2017 (commit cac2661c53f3) | xfrm-ESP |
| CVE-2026-43500 | DirtyFrag | Jun 2023 (commit 2dc334f1a63a) | RxRPC |
| Component | Details |
|---|
| Fleet Server / ELK host | ubuntu-tm / elk-docker - 172.66.66.30 (lab-internal) |
| Elastic Stack | v9.4.1 (Elasticsearch, Kibana, Logstash - Docker) |
| Target (victim) host | ubuntu-vuln - 172.66.66.27 |
| Victim kernel | 6.0.0-22-generic (vulnerable) |
| Victim user | saskia - uid=1001, groups=1001(saskia),27(sudo),100(users) |
| Elastic Agent | v9.4.1 with auditbeat |
| Network IDS | Suricata 8.0.5 on dedicated sensor |
| Kibana | http://172.66.66.30:5601 (lab-internal) |
| Dataset | Share |
|---|
suricata.eve | 75.2% |
elastic_agent.fleet_server | 10.8% |
elastic_agent | 9.1% |
elastic_agent.filebeat | 1.9% |
elastic_agent.auditbeat | 1.4% |
auditd_manager.auditd | 0.5% |
system.auth | 0.2% |
system.syslog | 0.1% |
| Timestamp (UTC) | Syscall | Argument | Process (pid) | User |
|---|
| 10:21:11.054 | unshare | a0=50000000 | clean_frag (4327) | saskia (1001) |
| 10:21:11.055 | socket | a0=21 (AF_RXRPC) | clean_frag (4326) | saskia (1001) |
| 10:21:17.403 | socket | a0=26 (AF_ALG) | clean_frag (4326) | saskia (1001) |
| 10:21:17.419 | splice | a0=4 | clean_frag (4326) | saskia (1001) |
| 10:21:17.419 | splice | a0=7 | clean_frag (4326) | saskia (1001) |
| Process | PID | PPID | UID | auditd.session |
|---|
| shell (saskia) | 4182 | - | 1001 | 2 |
clean_frag (main) | 4326 | 4182 | 1001 | 2 |
clean_frag (unshare child) | 4327 | 4326 | 1001 | 2 |
bash (root) | - | 4451 | 0 | 2 |
| Composite sequence alert |
| 10:25:57.893 |
| host=ubuntu-vuln |
| Tactic | Technique | ID |
|---|
| Privilege Escalation | Exploitation for Privilege Escalation | T1068 |
| Privilege Escalation | Escape to Host | T1611 |
| Credential Access | OS Credential Dumping: /etc/passwd and /etc/shadow | T1003.008 |
| Discovery | Network Service Discovery | T1046 |
| Syscall | Argument | Meaning |
|---|
unshare | a0=50000000 | CLONE_NEWUSER | CLONE_NEWNET |
socket | a0=26 | AF_ALG (kernel crypto subsystem) |
socket | a0=21 | AF_RXRPC (RxRPC path) |
bind | socket.family=38 | AF_ALG bind |
splice | - | Page injection into network buffers |
| # | Finding | Severity | Remediated |
|---|
| 1 | Default auditd rules miss socket, splice, unshare - no evidence without explicit rules | Critical | Yes - custom rules deployed to /etc/audit/rules.d/dirtyfrag.rules |
| 2 | EQL correlation by process.parent.pid fails for forked exploit chains | High | Yes - changed to auditd.session |
| 3 | File integrity monitoring blind to DirtyFrag - on-disk files unchanged throughout | High | No in-scope remediation - patching the kernel is the fix |
| 4 | Post-exploitation: shadow file, SSH keys, and network recon all executed as root before any alert-driven response | High | Detected via auditd logs; no dedicated rule fired for credential access or recon phase |