This was a challenge to demonstrate how the content-type header can be used to fool the browser into treating the HTTP response body in unexpected ways.
As the harder solution works for both, heres source:
<?php
/*
FROM php:7.0-apache
RUN a2dismod status
COPY ./files/index.php /var/www/html
COPY ./files/harder.php /var/www/html
EXPOSE 80
*/
$message = isset($_GET['message']) ? $_GET['message'] : 'hello, world';
$type = isset($_GET['type']) ? $_GET['type'] : die(highlight_file(__FILE__));
header("Content-Type: text/$type");
header("X-Frame-Options: DENY");
if($type == "plain"){
die("the message is: $message");
}
?>
<html>
<h1>The message is:</h1>
<hr/>
<pre>
<input type="text" value="<?php echo preg_replace('/([^\s\w!-~]|")/','',$message);?>">
</pre>
<br>
solved by:
<li> nobody yet!</li>
</html>
The first part: it was made to demonstrate the fact that modern browsers still support multiple-byte (or other) encodings that dont treat standard ASCII characters as their ASCII counterpart.
For example, in a UTF-16
world, the byte \x3c
is not equivalent to ASCII <
, but \x00\x3c
is. No surprise there, but this means that if an attacker controls even just the end of the Content-Type
(or meta http-equiv, in some cases, exercise for the reader), they can have the browser treat the document in one way, while the server treats it in another.
The second part: Multiple HTTP response headers with the same name should be treated as concatenated with the ,
/\x2c
character. Browsers support the sequence ,
/\x2c\x20
too, but that's just for convenience, or whitespaces are stripped, or something, I think.
This means that if we add x,image/gif
to our parameter, the header sent by the server will be:
Content-Type: text/x,image/gif
As text/x
is not a recognized MIME type, the user agent will use image/gif
which is recognized and supported.
So, we can control "the entire content-type header". So what? Well, there are some older (see O'Reilly's HTML The Definitive Guide vol2 from 1997, chapter 14.3) MIME types that can make the browser treat the document in other ways too!
The multipart/mixed
MIME type acts like a standard multipart message where the server can supply multiple parts with different Content-Types (and other headers), and the user agent should render whichever part it has support for:
HTTP/1.1 200 OK
Content-type: multipart/mixed;boundary="8ormorebytes"
ignored_first_part_before_boundary
--8ormorebytes
Content-Type: text/html
<img src=x onerror=alert(domain)>
--8ormorebytes
ignored_last_part
As you can imagine, this allows us to make the browser ignore parts of the document, as it scans for the multipart boundary, specified in the Content-Type
header. That means we don't need any "
character to solve the challenge (firefox):
There are other ways to confuse the user agent to start treating the "start of body" later in the message, via information in the HTTP Response first line or headers, such as Content-Encoding
, HTTP/1.1 100 Continue
and Content-Range
. Try it out!
Also, fun stuff happens if the server doesn't close the connection but instead keeps sending parts :)
"HTTP/1.1 had support for HTTP Push before it was cool"