Our new creation will kill Instagram. We'll convince you in just two words:
1. Filters. New never-before-seen filters for your uploaded pictures.
2. Caching. Custom HTTP server ensures image files land in the browser cache.
Try it right now! imagehub.spb.ctf.su
Run /get_the_flag to win.
Custom server binary: dppth
This task was prepared by SPbCTF
Hint:
There are two bugs that we know of. First one gets you the web app sources, second one gets you RCE.
Still there is a check before page execution, so if we correctly guess etag value (if-none-match), than the server will serve us a 304 Not Modified status response. Using this we can bruteforce source code byte by byte.
Timestamp is easily readed from last-modified response header (string - > timestamp).
Range allows to be one byte length (so we will get hash for only one byte)
Hash can be guessed for 1 byte range (256 possible values)
Size is bruteforceable, but we need to know at least one byte from target file.
Since we would like to get source for *.php files, its a good assumption, that the file is starting with "<?php".
First step will be getting size, and the second is getting actual file contents.
With multi threaded code I reached the speed of ~1 char/sec, and dumped some files:
You can actually detect this vulnerability just by doing some fuzzing. Sending function names like "var_dump" or "debug_zval_dump" in 'filter' input will result in interesting responses from the server.
Code:
int(51)
string(10) "jsdksjdksds"
So, its not hard to guess how server side code looks like.
If we had an write permission to www root, than we could just use two functions:
While thinking of possible ways to get RCE, I noticed that function filter_input_array gives us pretty good control over $filterImage variable.
Passing filter array as second argument, will allow as to build arbitrary array on function result.
But ImageMagic is not expecting to get anything besides Imagick class.
May be we can unserialize class from input? Let's look for additional filter arguments at filter_input_array description.
It is not mentioned on the function page itself, but we can actually pass a callback for input validation. FILTER_CALLBACK example is for filter_input, but it works for filter_input_array, too!
This means that we can "validate" custom user inputs using function with one argument (eval? system?), and we have control over the argument.
*** Wooooohooo! ***
Congratulations! Your flag is:
1m_t3h_R34L_binaeb_g1mme_my_71ck37
-- SPbCTF (vk.com/spbctf)
Something was definitely feeling wrong, because why would we even need to get the source code? Just for a hint? Why uploaded files was stored on disk, isn't it more convenient not to store junk files from the challenge users?
Coincidence in naming filter=filter_input_array, text[a][filter] gave me a confidence that everything was done as expected ("never-before-seen filters", check ✓).
spl_autoload vector: LFI vector
After submitting solution I got contacted by one of the challenge authors, who said that my vector was not intended and another function can be used (spl_autoload):
It is not obvious how we can use this function because as it supposed to load a class "<class_name>" from the file named "<class_name><some_extension>". Signature is following:
Our first argument can only be number (1-512), so the class name is a ... number? ... weird. Extension argument is also looks unusable, controlled files are one level deeper than upload.php (we need to pass a prefix).
This function can actually give us an LFI if used this way:
Directory name is acquired from the leaked source code. And we got lucky, because if the first character of name was anything besides number -> we could not include files from there.
So... all we need now is to pass a "kind-of-valid" (getimagesize must accept it) *.jpg file with php code emended. Simple example (php payload in exif) is attached.
... .JFIF ... Exif MM * . " (. . .i . . D . D .. V ..
*** Wooooohooo! ***
Congratulations! Your flag is:
1m_t3h_R34L_binaeb_g1mme_my_71ck37
-- SPbCTF (vk.com/spbctf)