1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## # auxiliary/scanner/smb/smb_ms_17_010 require 'msf/core' class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report def initialize(info = {}) super(update_info(info, 'Name' => 'MS17-010 SMB RCE Detection', 'Description'=> %q{ Uses information disclosure to determine if MS17-010 has been patched or not. Specifically, it connects to the IPC$ tree and attempts a transaction on FID 0. If the status returned is "STATUS_INSUFF_SERVER_RESOURCES", the machine does not have the MS17-010 patch. This module does not require valid SMB credentials in default server configurations. It can log on as the user "\" and connect to IPC$. }, 'Author' => [ 'Sean Dillon <sean.dillon@risksense.com>' ], 'References' => [ [ 'CVE', '2017-0143'], [ 'CVE', '2017-0144'], [ 'CVE', '2017-0145'], [ 'CVE', '2017-0146'], [ 'CVE', '2017-0147'], [ 'CVE', '2017-0148'], [ 'MSB', 'MS17-010'], [ 'URL', 'https://technet.microsoft.com/en-us/library/security/ms17-010.aspx'] ], 'License'=> MSF_LICENSE )) end def run_host(ip) begin status = do_smb_probe(ip) if status == "STATUS_INSUFF_SERVER_RESOURCES" print_warning("Host is likely VULNERABLE to MS17-010!") report_vuln( host: ip, name: self.name, refs: self.references, info: 'STATUS_INSUFF_SERVER_RESOURCES for FID 0 against IPC$' ) elsif status == "STATUS_ACCESS_DENIED" or status == "STATUS_INVALID_HANDLE" # STATUS_ACCESS_DENIED (Windows 10) and STATUS_INVALID_HANDLE (others) print_good("Host does NOT appear vulnerable.") else print_bad("Unable to properly detect if host is vulnerable.") end rescue ::Interrupt print_status("Exiting on interrupt.") raise $! rescue ::Rex::Proto::SMB::Exceptions::LoginError print_error("An SMB Login Error occurred while connecting to the IPC$ tree.") rescue ::Exception => e vprint_error("#{e.class}: #{e.message}") ensure disconnect end end def do_smb_probe(ip) connect # logon as user \ simple.login(datastore['SMBName'], datastore['SMBUser'], datastore['SMBPass'], datastore['SMBDomain']) # connect to IPC$ ipc_share = "\\\\#{ip}\\IPC$" simple.connect(ipc_share) tree_id = simple.shares[ipc_share] print_status("Connected to #{ipc_share} with TID = #{tree_id}") # request transaction with fid = 0 pkt = make_smb_trans_ms17_010(tree_id) sock.put(pkt) bytes = sock.get_once # convert packet to response struct pkt = Rex::Proto::SMB::Constants::SMB_TRANS_RES_HDR_PKT.make_struct pkt.from_s(bytes[4..-1]) # convert error code to string code = pkt['SMB'].v['ErrorClass'] smberr = Rex::Proto::SMB::Exceptions::ErrorCode.new status = smberr.get_error(code) print_status("Received #{status} with FID = 0") status end def make_smb_trans_ms17_010(tree_id) # make a raw transaction packet pkt = Rex::Proto::SMB::Constants::SMB_TRANS_PKT.make_struct simple.client.smb_defaults(pkt['Payload']['SMB']) # opcode 0x23 = PeekNamedPipe, fid = 0 setup = "\x23\x00\x00\x00" setup_count = 2 # 2 words trans = "\\PIPE\\\x00" # calculate offsets to the SetupData payload base_offset = pkt.to_s.length + (setup.length) - 4 param_offset = base_offset + trans.length data_offset = param_offset # + 0 # packet baselines pkt['Payload']['SMB'].v['Command'] = Rex::Proto::SMB::Constants::SMB_COM_TRANSACTION pkt['Payload']['SMB'].v['Flags1'] = 0x18 pkt['Payload']['SMB'].v['Flags2'] = 0x2801 # 0xc803 would unicode pkt['Payload']['SMB'].v['TreeID'] = tree_id pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count pkt['Payload'].v['ParamCountMax'] = 0xffff pkt['Payload'].v['DataCountMax'] = 0xffff pkt['Payload'].v['ParamOffset'] = param_offset pkt['Payload'].v['DataOffset'] = data_offset # actual magic: PeekNamedPipe FID=0, \PIPE\ pkt['Payload'].v['SetupCount'] = setup_count pkt['Payload'].v['SetupData'] = setup pkt['Payload'].v['Payload'] = trans pkt.to_s end end |