E.Tree
3 minutes to read
We are given this website:
We are also given the source code of the project in Python (Flask).
Source code analysis
The application has three endpoints in blueprints/routes.py
:
from flask import Blueprint, render_template, request
from application.util import leaderboard, search_staff
web = Blueprint('web', __name__)
api = Blueprint('api', __name__)
@web.route('/')
def index():
return render_template('index.html')
@web.route('/leaderboard')
def web_leaderboard():
return render_template('leaderboard.html', leaderboard=leaderboard('DSC-N-1547'))
@api.route('/search', methods=['POST'])
def api_search():
name = request.json.get('search', '')
return search_staff(name)
The relevant one is /search
, which calls a function search_staff
that is defined in util.py
:
from lxml import etree
tree = etree.parse('military.xml')
def leaderboard(district_id):
# this whole function is irellevant
leaderboard = {
district_id: []
}
top_staff = tree.xpath(f'//district[@id="{district_id}"]/staff')
top_staff.sort(key=lambda x: int(x.find('kills').text), reverse=True)
top_staff = top_staff[:5]
for staff in top_staff:
staff_info = {
'name': staff.find('name').text,
'age': int(staff.find('age').text),
'rank': staff.find('rank').text,
'kills': int(staff.find('kills').text)
}
leaderboard[district_id].append(staff_info)
return sorted(leaderboard[district_id], key=lambda x: x['kills'], reverse=True)
def search_staff(name):
# who cares about parameterization
query = f"/military/district/staff[name='{name}']"
if tree.xpath(query):
return {'success': 1, 'message': 'This millitary staff member exists.'}
return {'failure': 1, 'message': 'This millitary staff member does not exist.'}
# ocd
Here we see that it loads an XML document (military.xml
) and uses XPATH to query some information. In fact, we can see that the flag is at the bottom of the XML document (separated in two strings):
<?xml version="1.0" encoding="utf-8"?>
<military>
<!-- ... -->
<district id="DSC-N-1548">
<!-- ... -->
<staff>
<name>Groorg</name>
<age>52420</age>
<rank>Colonel</rank>
<kills>4112825</kills>
<selfDestructCode>HTB{f4k3_fl4g_</selfDestructCode>
</staff>
</district>
<district id="DSC-N-1549">
<!-- ... -->
<staff>
<name>Bobhura</name>
<age>61792</age>
<rank>Magor</rank>
<kills>5076298</kills>
<selfDestructCode>f0r_t3st1ng}</selfDestructCode>
</staff>
<!-- ... -->
</district>
</military>
For instance, we can search by Groorg
, that is someone that holds part of the flag:
XPATH injection
If we look closely at search_staff
, we will see that it uses XPATH to query by name
:
def search_staff(name):
# who cares about parameterization
query = f"/military/district/staff[name='{name}']"
if tree.xpath(query):
return {'success': 1, 'message': 'This millitary staff member exists.'}
return {'failure': 1, 'message': 'This millitary staff member does not exist.'}
The issue here is that it concatenates our input inside the XPATH query. Therefore, this results in an XPATH injection vulnerability. We can find some payloads in HackTricks to exploit XPATH injection. We can do a simple check:
Oracle
It works! Now, we can use XPATH functions to query the selfDestructCode
field, which contains the flag:
With this, we can write a script to find the flag byte by byte using an oracle (exists
/ does not exist
).
There is also a string-length
function that is useful to know the length of the field beforehand, and make the brute force implementation more efficient:
Knowing this, we can write a script to automate the flag extraction. I used Go for this, and improved performance using threads to extract each byte.
Flag
If we run the script, we will capture the flag in a matter of seconds:
$ go run solve.go 94.237.48.205:42183
HTB{th3_3xtr4_l3v3l_4cc3s$_contr0l}
The full script can be found in here: solve.go
.