E.Tree
3 minutos de lectura
Se nos proporciona este sitio web:
También tenemos el código fuente del proyecto en Python (Flask).
Análisis del código fuente
La aplicación tiene tres endpoints en 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)
El relevante es /search
, que llama a una función search_staff
que se define en 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
Aquí vemos que carga un documento XML (military.xml
) y usa XPATH para consultar cierta información. De hecho, podemos ver que la flag está en la parte inferior del documento XML (separado en dos trozos):
<?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>
Por ejemplo, podemos buscar por Groorg
, que es alguien que contiene parte de la flag:
Inyección XPATH
Si analizamos search_staff
con detenimiento, veremos que usa XPATH para consultar por 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.'}
El problema aquí es que concatena nuestra entrada dentro de la consulta XPATH. Por lo tanto, esto da como resultado una vulnerabilidad de inyección XPATH. Podemos encontrar algunos payloads en HackTricks para explotar la inyección XPATH. Podemos hacer una verificación simple:
Oráculo
¡Funciona! Ahora, podemos usar funciones XPATH para consultar el campo selfDestructCode
, que es el que contiene la flag:
Con esto, podemos escribir un script para encontrar la flag byte a byte usando un oráculo (exists
/ does not exist
).
También existe una función string-length
que es útil para saber la longitud del campo de antemano y hacer que la implementación de fuerza bruta sea más eficiente:
Sabiendo esto, podemos escribir un script para automatizar la extracción de flag. Decidí usar Go para esto y mejoré el rendimiento usando threads para extraer cada byte.
Flag
Si ejecutamos el script, capturaremos la flag en cuestión de segundos:
$ go run solve.go 94.237.48.205:42183
HTB{th3_3xtr4_l3v3l_4cc3s$_contr0l}
El script completo se puede encontrar aquí: solve.go
.