Chall: kittyconvert
Difficulty: medium
Category: web
Points: 204
Solves: 25
Description: Need to convert a file? Our kittens have got you covered!
Attachments: kittyconvert.tar.gz
From reading the title and description we know that this chall will involve some sort of file conversion. As it turns out it's a PNG to ICO converter.
After running the docker container and visiting localhost we are presented with a page containing a button to select a file to convert. After sending it we get our converted file. Let's see what's under the hood. Our goal is to get the flag.txt file. Let's check where it gets copied by looking into the Dockerfile
COPY ./flag.txt /flag.txt
COPY src/ /var/www/html/
From the fact that we have to read a file that's outside of the webroot we can guess that this chall will probably involve either code execution
or local file inclusion.Below is the php from index:
// Disable annoying warnings
error_reporting(E_ERROR | E_PARSE);
$success = false;
if (isset($_FILES['file'])) {
$base_dir = "/var/www/html/";
$ico_file = "uploads/" . preg_replace("/^(.+)\\..+$/", "$1.ico", basename($_FILES["file"]["name"]));
if ($_FILES["file"]["size"] > 8000) {
echo "<p>Sorry, your file is too large you need to buy Nitro.</p>";
} else {
require( dirname( __FILE__ ) . '/class-php-ico.php' );
$ico_lib = new PHP_ICO( $_FILES["file"]["tmp_name"], array( array( 32, 32 ), array( 64, 64 ) ) );
$ico_lib->save_ico( $base_dir . $ico_file );
$success = true;
}
}
How can we get local file inclusion or code execution in the above code?Local file inclusion seems unlikely as the above code only handles uploading files and saves the output to $base_dir. Maybe some vulnerability could be hiding in class-php-ico.php, but after closer inspection the code is from an open source project and is up to date. Searching "php-ico cve" also yields no results. Last commit is from 2016, but I also doubt we'd have to exploit a zero day in an open source project.
How about code execution? RCE in php often involves either evals or uploading your own php file. There are no evals, but maybe we could upload a php file? The site tries to change the extension to ico using preg_replace, but maybe we can somehow evade the filter?
Below you can see how the filter works by typing in a filename and observing results.
→
So now that we can upload php files this challenge is trivial, right? Not so fast, we can't just straight up upload a php file, as what ends up being saved is our converted ico file. How can we make it so that the ico file is also a valid php file? The answer is to smuggle our payload in the colors (and alpha).
This works because the ico files produced by the site are uncompressed, so our payload doesn't get mangled. They're also stored in 32x32 and 64x64 resolutions. We'll be targetting the 64x64 resolution. Why? In short, resizing in either direction will result in our payload getting mangled, but in 32x32 -> 64x64 conversion we get invalid php code, while in 64x64 -> 32x32 case we get something that's not even recognised as php code. It's important because the php interpreter won't even run our code if it sees invalid php (we could in theory use __halt_compiler(), but we can't encode it in the payload, you'll later see why).
Another thing to consider is the order of colors, as they're stored in the BGRA order.
Below you can generate your own payload hidden in an image. To download it just use "Save Image As...".
→
This pretty much means that every fourth character can't have an ascii number that's odd.
There's still plenty of ways you can write a payload. One way to try to circumvent this is to add spaces between brackets and in other places where they don't matter. Using this you can move the characters a bit, so that the ones that get mangled end up in color bytes and not in the alpha bytes. Spaces in the alpha bytes aren't a problem as they're 32 in decimal, which is an even number.
Some functions like __halt_compiler can't be encoded due to this reason, but you can solve this chall using pretty short function names that can be encoded without too much of a hassle.
Here's a couple of payloads that work (hover to reveal):
<?php echo(exec("cat /flag.txt" )) ; ?>
<?php echo(exec($_GET["c"])) ; ?>
<?= `/flag.txt` ?>
Now let's put all of this into practice. Change the name of the crafted image to ".php"
and upload it to the site. After downloading the result and running strings on it you should see:
x3c{b1tm4p5_4r3_s1mpl3_6u7_7h3_4lph4_1s_w31rd}
Bonus fun fact:Writing the payload generator in js was a PITA because of alpha premultiplication, which results in putImageData on canvas being lossy. For this to work I had to use the UPNG.js library for PNG encoding. Every time the payload input changes it gets encoded as PNG, converted to base64 and put into the src of an img element.