Detection rules › Splunk

Linux Dirty Frag Kernel Privilege Escalation

Status
production
Severity
medium
Group by
_time, auid, host, ses
Author
Axsel Riando Soplanit, Alexander Wijaya, Matthew Roytua, Muhammad Rafdi Aufar Ahmad, Brian Stevan Ambarita, Ensign Infosecurity
Source
github.com/splunk/security_content

The following analytic detects exploitation of Dirty Frag (CVE-2026-43284 and CVE-2026-43500), a Linux kernel local privilege escalation vulnerability. The exploit corrupts the kernel page cache via the IPsec ESP or RxRPC subsystems using a high-frequency splice() syscall loop, followed by a privilege transition to root in the same audit session. This analytic anchors on the exploit-specific behavioral signature, a sustained splice() spray (50 or more calls within a 60-second window) from an unprivileged process executing from a user-writable path (/home, /tmp, /dev/shm, /var/tmp, /run/user, /root), followed within 5 minutes by execution of a set-uid binary in the same auditd session.

Known false positives

  • The splice volume threshold (50 calls per 60 seconds from a single process in a user-writable path) is calibrated against the V4bel public PoC, which produces 100-460 splice calls in 90 seconds depending on system state. Legitimate user-mode workloads that may produce splice() calls (web servers, mail transfer agents, language runtimes) typically execute from system paths (/usr/sbin, /usr/bin, /usr/lib) and are excluded by the user-writable path filter. Custom-compiled applications, developer test binaries, or container runtimes executing from /home or /tmp could theoretically reach the volume threshold during legitimate I/O-heavy workloads (large file copies, log rotation, archive operations). Operators should baseline splice volume in their environment before enabling as a notable event. If false positives occur, add an allowlist of trusted binary paths via the linux_dirty_frag_kernel_privilege_escalation_filter macro, or raise the splice_count threshold based on observed legitimate-workload baseline.

MITRE ATT&CK coverage

Telemetry coverage

Rule body

name: Linux Dirty Frag Kernel Privilege Escalation
id: 517e7bf8-d0aa-46ac-80e1-f9f498944b71
version: 1
creation_date: '2026-07-06'
modification_date: '2026-07-06'
author: Axsel Riando Soplanit, Alexander Wijaya, Matthew Roytua, Muhammad Rafdi Aufar Ahmad, Brian Stevan Ambarita, Ensign Infosecurity
status: production
type: TTP
description: |-
    The following analytic detects exploitation of Dirty Frag (CVE-2026-43284 and CVE-2026-43500), a Linux kernel local privilege escalation vulnerability.
    The exploit corrupts the kernel page cache via the IPsec ESP or RxRPC subsystems using a high-frequency splice() syscall loop, followed by a privilege transition to root in the same audit session.
    This analytic anchors on the exploit-specific behavioral signature, a sustained splice() spray (50 or more calls within a 60-second window) from an unprivileged process executing from a user-writable path (/home, /tmp, /dev/shm, /var/tmp, /run/user, /root), followed within 5 minutes by execution of a set-uid binary in the same auditd session.
data_source:
    - Linux Auditd Syscall
search: |-
    `linux_auditd`
    type=SYSCALL
    key=splice_user
    exe IN (
        "/home/*",
        "/tmp/*",
        "/var/tmp/*",
        "/dev/shm/*",
        "/run/user/*",
        "/root/*"
    )
    
    | bin _time span=60s
    | eval user = coalesce(user, auid, AUID)
    
    | stats count AS splice_count
            values(exe) AS spray_binary
            values(pid) AS spray_pid
            values(user) AS user
            earliest(_time) AS spray_start
            latest(_time) AS spray_end
      BY auid host ses _time
    
    | where splice_count >= 50
    
    | join type=inner auid host ses [
        search `linux_auditd`
        type=SYSCALL
        key=process_creation
        exe IN (
            "/usr/bin/chfn",
            "/usr/bin/chsh",
            "/usr/bin/fusermount3",
            "/usr/bin/gpasswd",
            "/usr/bin/mount",
            "/usr/bin/newgrp",
            "/usr/bin/passwd",
            "/usr/bin/su",
            "/usr/bin/sudo",
            "/usr/bin/umount",
            "/usr/lib/dbus-1.0/dbus-daemon-launch-helper",
            "/usr/lib/landscape/apt-update",
            "/usr/lib/openssh/ssh-keysign",
            "/usr/lib/polkit-1/polkit-agent-helper-1"
        )
    
        | stats earliest(_time) AS privesc_time
                values(exe) AS privesc_binary
                values(comm) AS privesc_comm
                values(ppid) AS privesc_ppid
          BY auid host ses
    ]
    
    | eval window_secs = privesc_time - spray_end
    | where window_secs >= 0 AND window_secs <= 300
    
    | `security_content_ctime(spray_start)`
    | `security_content_ctime(spray_end)`
    | `security_content_ctime(privesc_time)`
    
    | rename host AS dest
    
    | table dest user auid ses spray_binary spray_pid
            splice_count spray_start spray_end privesc_binary
            privesc_comm privesc_ppid privesc_time window_secs
    
    | `linux_dirty_frag_kernel_privilege_escalation_filter`
how_to_implement: |-
    This analytic requires Linux auditd telemetry forwarded to Splunk
    via the Splunk_TA_nix add-on with the linux_audit sourcetype (referenced through
    the linux_auditd macro).

    The detection depends on two audit rule keys, both of which
    are standard in the Neo23x0 recommended ruleset (https://github.com/Neo23x0/auditd)
    and the existing ESCU Copy Fail analytic (Linux Auditd Copy Fail Privilege Escalation),
    so no detection-specific custom keys are introduced.


    PREREQUISITE 1; splice_user key.

    -a always,exit -F arch=b64 -S splice -S vmsplice -F success=1 -F auid>=1000 -F auid!=unset
    -k splice_user
    -a always,exit -F arch=b32 -S splice -S vmsplice -F success=1 -F auid>=1000 -F auid!=unset
    -k splice_user

    Operators should baseline splice volume in their environment before enabling.
    Hosts running heavy zero-copy I/O workloads (specific: web servers, mail transfer agents, container runtimes) may need additional filtering.

    PREREQUISITE 2; process_creation key.

    -a always,exit -F arch=b64 -S execve -S execveat -F auid>=1000 -F auid!=unset -k
    process_creation
    -a always,exit -F arch=b32 -S execve -S execveat -F auid>=1000 -F auid!=unset -k
    process_creation
known_false_positives: |-
    The splice volume threshold (50 calls per 60 seconds from a single process in a user-writable path) is calibrated against the V4bel public PoC, which produces 100-460 splice calls in 90 seconds depending on system state.
    Legitimate user-mode workloads that may produce splice() calls (web servers, mail transfer agents, language runtimes) typically execute from system paths (/usr/sbin, /usr/bin, /usr/lib) and are excluded by the user-writable path filter.
    Custom-compiled applications, developer test binaries, or container runtimes executing from /home or /tmp could theoretically reach the volume threshold during legitimate I/O-heavy workloads (large file copies, log rotation, archive operations).
    Operators should baseline splice volume in their environment before enabling as a notable event. If false positives occur, add an allowlist of trusted binary paths via the linux_dirty_frag_kernel_privilege_escalation_filter macro, or raise the splice_count threshold based on observed legitimate-workload baseline.
references:
    - https://www.cve.org/CVERecord?id=CVE-2026-43284
    - https://www.cve.org/CVERecord?id=CVE-2026-43500
    - https://www.cve.org/CVERecord?id=CVE-2026-31431
    - https://github.com/V4bel/dirtyfrag
    - https://www.openwall.com/lists/oss-security/2026/05/07/8
    - https://www.microsoft.com/en-us/security/blog/2026/05/08/active-attack-dirty-frag-linux-vulnerability-expands-post-compromise-risk/
    - https://attack.mitre.org/techniques/T1068/
    - https://attack.mitre.org/techniques/T1548/001/
finding:
    title: Local privilege escalation attempt detected on $dest$ executed by $user$ using $spray_binary$
    entity:
        field: dest
        type: system
        score: 50
intermediate_findings:
    entities:
        - field: user
          type: user
          score: 50
          message: Local privilege escalation attempt detected on $dest$ executed by $user$ using $spray_binary$
threat_objects:
    - field: spray_binary
      type: process_name
analytic_story:
    - Linux Privilege Escalation
asset_type: Endpoint
cve:
    - CVE-2026-43284
    - CVE-2026-43500
    - CVE-2026-31431
mitre_attack_id:
    - T1548.001
    - T1068
product:
    - Splunk Enterprise
    - Splunk Enterprise Security
    - Splunk Cloud
category: endpoint
security_domain: endpoint

Stages and Predicates

Stage 1: search

`linux_auditd`
type=SYSCALL
key=splice_user
exe IN (
    "/home/*",
    "/tmp/*",
    "/var/tmp/*",
    "/dev/shm/*",
    "/run/user/*",
    "/root/*"
)

Stage 2: bucket

| bin _time span=60s

Stage 3: eval

| eval user = coalesce(user, auid, AUID)

Stage 4: stats

| stats count AS splice_count
        values(exe) AS spray_binary
        values(pid) AS spray_pid
        values(user) AS user
        earliest(_time) AS spray_start
        latest(_time) AS spray_end
  BY auid host ses _time

Stage 5: where

| where splice_count >= 50

Stage 6: join

| join type=inner auid host ses [
    search `linux_auditd`
    type=SYSCALL
    key=process_creation
    exe IN (
        "/usr/bin/chfn",
        "/usr/bin/chsh",
        "/usr/bin/fusermount3",
        "/usr/bin/gpasswd",
        "/usr/bin/mount",
        "/usr/bin/newgrp",
        "/usr/bin/passwd",
        "/usr/bin/su",
        "/usr/bin/sudo",
        "/usr/bin/umount",
        "/usr/lib/dbus-1.0/dbus-daemon-launch-helper",
        "/usr/lib/landscape/apt-update",
        "/usr/lib/openssh/ssh-keysign",
        "/usr/lib/polkit-1/polkit-agent-helper-1"
    )

    | stats earliest(_time) AS privesc_time
            values(exe) AS privesc_binary
            values(comm) AS privesc_comm
            values(ppid) AS privesc_ppid
      BY auid host ses
]

Stage 7: eval

| eval window_secs = privesc_time - spray_end

Stage 8: where

| where window_secs >= 0 AND window_secs <= 300

Stage 9: search

| `security_content_ctime(spray_start)`

Stage 10: search

| `security_content_ctime(spray_end)`

Stage 11: search

| `security_content_ctime(privesc_time)`

Stage 12: rename

| rename host AS dest

Stage 13: table

| table dest user auid ses spray_binary spray_pid
        splice_count spray_start spray_end privesc_binary
        privesc_comm privesc_ppid privesc_time window_secs

Stage 14: search

| `linux_dirty_frag_kernel_privilege_escalation_filter`

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
exein
  • "/dev/shm/*"
  • "/home/*"
  • "/root/*"
  • "/run/user/*"
  • "/tmp/*"
  • "/usr/bin/chfn"
  • "/usr/bin/chsh"
  • "/usr/bin/fusermount3"
  • "/usr/bin/gpasswd"
  • "/usr/bin/mount"
  • "/usr/bin/newgrp"
  • "/usr/bin/passwd"
  • "/usr/bin/su"
  • "/usr/bin/sudo"
  • "/usr/bin/umount"
  • "/usr/lib/dbus-1.0/dbus-daemon-launch-helper"
  • "/usr/lib/landscape/apt-update"
  • "/usr/lib/openssh/ssh-keysign"
  • "/usr/lib/polkit-1/polkit-agent-helper-1"
  • "/var/tmp/*"
field:"exe" kind:in
keyeq
  • process_creation
  • splice_user
field:"key" kind:eq
sourcetypeeq
  • auditd
field:"sourcetype" kind:eq value:"auditd"
splice_countge
  • 50
field:"splice_count" kind:ge value:"50"
typeeq
  • SYSCALL
field:"type" kind:eq value:"SYSCALL"
window_secsge
  • 0
field:"window_secs" kind:ge value:"0"
window_secsle
  • 300
field:"window_secs" kind:le value:"300"