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 a 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
.