Merge branch 'goliath' of github.com:clee-r7/metasploit-framework into goliath

GSoC/Meterpreter_Web_Console
christopher lee 2018-01-19 13:40:38 -06:00
commit 505f1fd547
14 changed files with 347 additions and 62 deletions

20
CURRENT.md Normal file
View File

@ -0,0 +1,20 @@
Active Metasploit 5 development will sometimes push aggressive changes.
Integrations with 3rd-party tools, as well as general usage, may change quickly
from day to day. Some of the steps for dealing with major changes will be
documented here. We will continue to maintain the Metasploit 4.x branch until
Metasploit 5.0 is released.
**2018/01/17 - [internal] module cache reworked to not store metadata in PostgreSQL**
Metasploit no longer stores module metadata in a PostgreSQL database, instead
storing it in a cache file in your local ~/.msf4 config directory. This has a
number of advantages:
* Fast searches whether you have the database enabled or not (no more slow search mode)
* Faster load time for msfconsole, the cache loads more quickly
* Private module data is not uploaded to a shared database, no collisions
* Adding or deleting modules no longer displays file-not-found error messages on start in msfconsole
* Reduced memory consumption
Code that reads directly from the Metasploit database for module data will need
to use the new module search API.

View File

@ -1,21 +1,208 @@
# Commvault Communications Service execCmd Vulnerability
## Introduction
Commvault is a data protection and information management software; an enterprise-level data
platform that contains modules to back up, restore, archive, replicate, and search data.
According to public documentation, the data is protected by installing agent software on the
physical or virtual hosts, which use the OS or application native APIs to protect data in a
consistent state. Production data is processed by the agent on client computers and backed
up through a data manager (the MediaAgent) to disk, tape, or cloud storage. All data
management activity in the environment is tracked by a centralized server (called CommServe),
and can be managed by administrators through a central user interface. End users can access
protected data using web browsers or mobile devices.
One of the base services of Commvault is vulnerable to a remote command injection attack,
specifically the cvd service.
## Vulnerable Application ## Vulnerable Application
Commvault v11 SP5 or prior are vulnerable to this vulnerability. The specific vulnerable
version I tested was 11.0.80.0.
This module exploits a remote command injection vulnerability in the Commvault Communications service (cvd.exe). Exploitation of this vulnerability can allow for remote command execution as SYSTEM. The version of the vulnerable DLL is:
```
Image path: C:\Program Files\Commvault\ContentStore\Base\CVDataPipe.dll
Image name: CVDataPipe.dll
Timestamp: Wed Dec 21 11:59:21 2016 (585AC2F9)
CheckSum: 002ED404
ImageSize: 002F0000
File version: 11.80.50.60437
Product version: 11.0.0.0
File flags: 1 (Mask 3F) Debug
File OS: 40004 NT Win32
File type: 1.0 App
File date: 00000000.00000000
Translations: 0409.04b0
CompanyName: Commvault
ProductName: Commvault
InternalName: CVDataPipe
OriginalFilename: CVDataPipe.dll
ProductVersion: 11.0.0.0
FileVersion: 11.80.50.60437
PrivateBuild:
SpecialBuild:
FileDescription:
LegalCopyright: Copyright (c) 2000-2016
LegalTrademarks:
Comments:
```
## Root Cause Analysis
Usually, there are two ways to execute a command in a C/C++ application, one of them is ```WinExec()```,
and the other one is ```CreateProcess()```:
```
BOOL WINAPI CreateProcess(
_In_opt_ LPCTSTR lpApplicationName,
_Inout_opt_ LPTSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCTSTR lpCurrentDirectory,
_In_ LPSTARTUPINFO lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation
);
```
Since ```CreateProcess()``` is meant to replace ```WinExec()``` according to Microsoft, we can create a
breakpoint there first in our debugger (WinDBG), and we hit it:
```
0:044> g
Breakpoint 3 hit
kernel32!CreateProcessA:
00000000`76fe8730 4c8bdc mov r11,rsp
```
Looking at the callstack of this ```kernel32!CreateProcessA```, we already have a pretty good idea
locating the vulnerability:
```
0:044> k
Child-SP RetAddr Call Site
00000000`11a36b78 000007fe`f378a40f kernel32!CreateProcessA
00000000`11a36b80 000007fe`f377714e CVDataPipe!execCmd+0x7af
00000000`11a3f340 000007fe`f3777a69 CVDataPipe!CVDMessageHandler+0x78e
00000000`11a3fbd0 000007fe`f9cdc58d CVDataPipe!CVDMessageHandler+0x10a9
00000000`11a3fd40 000007fe`f9cdc1b1 CvBasicLib!CvThreadPool::th_defaultWorkerObj+0x3cd
00000000`11a3fe40 000007fe`f9cd2073 CvBasicLib!CvThreadPool::th_defaultWorker+0x51
00000000`11a3fe90 000007fe`f9a84f7f CvBasicLib!CvThread::~CvThread+0x63
00000000`11a3fee0 000007fe`f9a85126 MSVCR120!_callthreadstartex+0x17 [f:\dd\vctools\crt\crtw32\startup\threadex.c @ 376]
00000000`11a3ff10 00000000`76f6f56d MSVCR120!_threadstartex+0x102 [f:\dd\vctools\crt\crtw32\startup\threadex.c @ 354]
00000000`11a3ff40 00000000`770a3281 kernel32!BaseThreadInitThunk+0xd
00000000`11a3ff70 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
```
There are two things that are interesting. One of them is ```CVDataPipe!CVDMessageHandler```, and the
other one is ```CVDataPipe!execCmd```.
```CVDataPipe!CVDMessageHandler``` is basically a function that handles our packet's message type.
The Metasploit exploit specifically sends a code of ```9h```, which is the message type for ```execCmd```:
```
.text:0000000180147103 loc_180147103: ; CODE XREF: CVDMessageHandler(int,selectStruct_t *,CQiSocket,void *):loc_180146D78j
.text:0000000180147103 lea rax, [rsp+888h+var_220] ; jumptable 0000000180146D78 case 9
.text:000000018014710B mov [rsp+888h+var_600], rax
.text:0000000180147113 mov rdx, [rsp+888h+sock]
.text:000000018014711B mov rcx, [rsp+888h+var_600]
.text:0000000180147123 call cs:??0CQiSocket@@QEAA@AEBV0@@Z ; CQiSocket::CQiSocket(CQiSocket const &)
.text:0000000180147129 mov [rsp+888h+var_5F0], rax
.text:0000000180147131 mov r8, [rsp+888h+arg_18]
.text:0000000180147139 mov rdx, [rsp+888h+var_5F0]
.text:0000000180147141 mov rcx, [rsp+888h+structSelect]
.text:0000000180147149 call ?execCmd@@YAXPEAUselectStruct_t@@VCQiSocket@@PEAX@Z ; execCmd(selectStruct_t *,CQiSocket,void *)
```
If we take a closer look at the ```execCmd``` function, we can tell the purpose of it is for processes such as:
* ifind (For restoring purposes)
* BackupShadow.exe (For archiving)
* Pub (Map file)
* createIndex (A Commvault process for building index)
Additional information can be found [here](https://www.securifera.com/advisories/sec-2017-0001/) ```
.text:0000000180159F1B loc_180159F1B: ; CODE XREF: execCmd(selectStruct_t *,CQiSocket,void *)+261j
.text:0000000180159F1B ; DATA XREF: .rdata:0000000180286258o
.text:0000000180159F1B lea rdx, aIfind ; "ifind"
.text:0000000180159F22 lea rcx, [rsp+87B8h+ApplicationName] ; Str
.text:0000000180159F2A call cs:strstr
.text:0000000180159F30 test rax, rax
.text:0000000180159F33 jnz short loc_180159F6D
.text:0000000180159F35 lea rdx, aBackupshadow_e ; "BackupShadow.exe"
.text:0000000180159F3C lea rcx, [rsp+87B8h+ApplicationName] ; Str
.text:0000000180159F44 call cs:strstr
.text:0000000180159F4A test rax, rax
.text:0000000180159F4D jnz short loc_180159F6D
.text:0000000180159F4F lea rdx, aPub ; "Pub"
.text:0000000180159F56 lea rcx, [rsp+87B8h+ApplicationName] ; Str
.text:0000000180159F5E call cs:strstr
...
.text:000000018015A0BA loc_18015A0BA: ; CODE XREF: execCmd(selectStruct_t *,CQiSocket,void *)+307j
.text:000000018015A0BA lea rdx, aCreateindex ; "createIndex"
.text:000000018015A0C1 lea rcx, [rsp+87B8h+ApplicationName] ; Str
.text:000000018015A0C9 call cs:strstr
.text:000000018015A0CF test rax, rax
.text:000000018015A0D2 jz loc_18015A220
```
However, if you don't call one of these processes, the ```execCmd``` will assume you want to run your
custom process, and pass it to ```CreateProcess``` anyway:
```
.text:000000018015A361 loc_18015A361: ; CODE XREF: execCmd(selectStruct_t *,CQiSocket,void *)+675j
.text:000000018015A361 call cs:GetEnvironmentStrings
.text:000000018015A367 mov [rsp+87B8h+var_86A8], rax
.text:000000018015A36F lea rax, [rsp+87B8h+StartupInfo]
.text:000000018015A377 mov rdi, rax
.text:000000018015A37A xor eax, eax
.text:000000018015A37C mov ecx, 68h
.text:000000018015A381 rep stosb
.text:000000018015A383 mov [rsp+87B8h+StartupInfo.cb], 68h
.text:000000018015A38E lea rax, [rsp+87B8h+ProcessInformation]
.text:000000018015A396 mov rdi, rax
.text:000000018015A399 xor eax, eax
.text:000000018015A39B mov ecx, 18h
.text:000000018015A3A0 rep stosb
.text:000000018015A3A2 mov [rsp+87B8h+StartupInfo.dwFlags], 1
.text:000000018015A3AD xor eax, eax
.text:000000018015A3AF mov [rsp+87B8h+StartupInfo.wShowWindow], ax
.text:000000018015A3B7 lea rax, [rsp+87B8h+ProcessInformation]
.text:000000018015A3BF mov [rsp+87B8h+lpProcessInformation], rax ; lpProcessInformation
.text:000000018015A3C4 lea rax, [rsp+87B8h+StartupInfo]
.text:000000018015A3CC mov [rsp+87B8h+lpStartupInfo], rax ; lpStartupInfo
.text:000000018015A3D1 mov [rsp+87B8h+lpCurrentDirectory], 0 ; lpCurrentDirectory
.text:000000018015A3DA mov [rsp+87B8h+lpEnvironment], 0 ; lpEnvironment
.text:000000018015A3E3 mov [rsp+87B8h+dwCreationFlags], 10h ; dwCreationFlags
.text:000000018015A3EB mov [rsp+87B8h+bInheritHandles], 0 ; bInheritHandles
.text:000000018015A3F3 xor r9d, r9d ; lpThreadAttributes
.text:000000018015A3F6 xor r8d, r8d ; lpProcessAttributes
.text:000000018015A3F9 lea rdx, [rsp+87B8h+CommandLine] ; lpCommandLine
.text:000000018015A401 lea rcx, [rsp+87B8h+ApplicationName] ; lpApplicationName
.text:000000018015A409 call cs:CreateProcessA
```
## Verification Steps It is unclear whether allowing an arbitrary custom process is intentional or not, it is unsafe
anyway considering the cvd process binds to 0.0.0.0, so anybody can gain access to it under the
context of SYSTEM.
## Using the Metasploit Module
1. Start msfconsole 1. Start msfconsole
2. `use exploit/windows/misc/commvault_cmd_exec` 2. `use exploit/windows/misc/commvault_cmd_exec`
3. `set RHOST [ip]` 3. `set RHOST [ip]`
4. `exploit` 4. `exploit`
5. shellz :) 5. shellz :)
## References
* https://en.wikipedia.org/wiki/Commvault
* https://www.securifera.com/advisories/sec-2017-0001/

View File

@ -13,6 +13,8 @@ module HostDataProxy
end end
end end
# TODO: Shouldn't this proxy to RemoteHostDataService#find_or_create_host ?
# It's currently skipping the "find" part
def find_or_create_host(opts) def find_or_create_host(opts)
puts 'Calling find host' puts 'Calling find host'
report_host(opts) report_host(opts)

View File

@ -3,13 +3,22 @@ require 'metasploit/framework/data_service/remote/http/response_data_helper'
module RemoteCredentialDataService module RemoteCredentialDataService
include ResponseDataHelper include ResponseDataHelper
CREDENTIAL_PATH = '/api/1/msf/credential' CREDENTIAL_API_PATH = '/api/1/msf/credential'
# "MDM_CLASS" is a little misleading since it is not in that repo but trying to keep naming consistent across DataServices
CREDENTIAL_MDM_CLASS = 'Metasploit::Credential::Core'
def creds(opts = {}) def creds(opts = {})
json_to_open_struct_object(self.get_data(CREDENTIAL_PATH, opts), []) data = self.get_data(CREDENTIAL_API_PATH, opts)
rv = json_to_mdm_object(data, CREDENTIAL_MDM_CLASS, [])
parsed_body = JSON.parse(data.response.body)
parsed_body.each do |cred|
private_object = to_ar(cred['private_class'].constantize, cred['private'])
rv[parsed_body.index(cred)].private = private_object
end
rv
end end
def create_credential(opts) def create_credential(opts)
self.post_data_async(CREDENTIAL_PATH, opts) self.post_data_async(CREDENTIAL_API_PATH, opts)
end end
end end

View File

@ -3,27 +3,28 @@ require 'metasploit/framework/data_service/remote/http/response_data_helper'
module RemoteHostDataService module RemoteHostDataService
include ResponseDataHelper include ResponseDataHelper
HOST_PATH = '/api/1/msf/host' HOST_API_PATH = '/api/1/msf/host'
HOST_SEARCH_PATH = HOST_PATH + "/search" HOST_SEARCH_PATH = HOST_API_PATH + "/search"
HOST_MDM_CLASS = 'Mdm::Host'
def hosts(opts) def hosts(opts)
json_to_open_struct_object(self.get_data(HOST_PATH, opts), []) json_to_mdm_object(self.get_data(HOST_API_PATH, opts), HOST_MDM_CLASS, [])
end end
def report_host(opts) def report_host(opts)
json_to_open_struct_object(self.post_data(HOST_PATH, opts)) json_to_mdm_object(self.post_data(HOST_API_PATH, opts), HOST_MDM_CLASS, []).first
end end
def find_or_create_host(opts) def find_or_create_host(opts)
json_to_open_struct_object(self.post_data(HOST_PATH, opts)) json_to_mdm_object(self.post_data(HOST_API_PATH, opts), HOST_MDM_CLASS, []).first
end end
def report_hosts(hosts) def report_hosts(hosts)
self.post_data(HOST_PATH, hosts) self.post_data(HOST_API_PATH, hosts)
end end
def delete_host(opts) def delete_host(opts)
json_to_open_struct_object(self.delete_data(HOST_PATH, opts)) json_to_mdm_object(self.delete_data(HOST_API_PATH, opts), HOST_MDM_CLASS, [])
end end
# TODO: Remove? What is the purpose of this method? # TODO: Remove? What is the purpose of this method?

View File

@ -3,11 +3,12 @@ require 'metasploit/framework/data_service/remote/http/response_data_helper'
module RemoteLootDataService module RemoteLootDataService
include ResponseDataHelper include ResponseDataHelper
LOOT_PATH = '/api/1/msf/loot' LOOT_API_PATH = '/api/1/msf/loot'
LOOT_MDM_CLASS = 'Mdm::Loot'
def loot(opts = {}) def loot(opts = {})
# TODO: Add an option to toggle whether the file data is returned or not # TODO: Add an option to toggle whether the file data is returned or not
loots = json_to_open_struct_object(self.get_data(LOOT_PATH, opts), []) loots = json_to_mdm_object(self.get_data(LOOT_API_PATH, opts), LOOT_MDM_CLASS, [])
# Save a local copy of the file # Save a local copy of the file
loots.each do |loot| loots.each do |loot|
if loot.data if loot.data
@ -19,14 +20,14 @@ module RemoteLootDataService
end end
def report_loot(opts) def report_loot(opts)
self.post_data_async(LOOT_PATH, opts) self.post_data_async(LOOT_API_PATH, opts)
end end
def find_or_create_loot(opts) def find_or_create_loot(opts)
json_to_open_struct_object(self.post_data(LOOT_PATH, opts)) json_to_mdm_object(self.post_data(LOOT_API_PATH, opts), LOOT_MDM_CLASS, [])
end end
def report_loots(loot) def report_loots(loot)
self.post_data(LOOT_PATH, loot) self.post_data(LOOT_API_PATH, loot)
end end
end end

View File

@ -1,6 +1,7 @@
module RemoteSessionDataService module RemoteSessionDataService
SESSION_API_PATH = '/api/1/msf/session' SESSION_API_PATH = '/api/1/msf/session'
SESSION_MDM_CLASS = 'Mdm::Session'
def report_session(opts) def report_session(opts)
session = opts[:session] session = opts[:session]
@ -12,7 +13,7 @@ module RemoteSessionDataService
end end
opts[:time_stamp] = Time.now.utc opts[:time_stamp] = Time.now.utc
sess_db = json_to_open_struct_object(self.post_data(SESSION_API_PATH, opts)) sess_db = json_to_mdm_object(self.post_data(SESSION_API_PATH, opts), SESSION_MDM_CLASS, []).first
session.db_record = sess_db session.db_record = sess_db
end end

View File

@ -3,14 +3,15 @@ require 'metasploit/framework/data_service/remote/http/response_data_helper'
module RemoteSessionEventDataService module RemoteSessionEventDataService
include ResponseDataHelper include ResponseDataHelper
SESSION_EVENT_PATH = '/api/1/msf/session_event' SESSION_EVENT_API_PATH = '/api/1/msf/session_event'
SESSION_EVENT_MDM_CLASS = 'Mdm::SessionEvent'
def session_events(opts = {}) def session_events(opts = {})
json_to_open_struct_object(self.get_data(SESSION_EVENT_PATH, opts), []) json_to_mdm_object(self.get_data(SESSION_EVENT_API_PATH, opts), SESSION_EVENT_MDM_CLASS, [])
end end
def report_session_event(opts) def report_session_event(opts)
opts[:session] = opts[:session].db_record opts[:session] = opts[:session].db_record
self.post_data_async(SESSION_EVENT_PATH, opts) self.post_data_async(SESSION_EVENT_API_PATH, opts)
end end
end end

View File

@ -5,19 +5,20 @@ module RemoteWorkspaceDataService
WORKSPACE_COUNTS_API_PATH = '/api/1/msf/workspace/counts' WORKSPACE_COUNTS_API_PATH = '/api/1/msf/workspace/counts'
WORKSPACE_API_PATH = '/api/1/msf/workspace' WORKSPACE_API_PATH = '/api/1/msf/workspace'
WORKSPACE_MDM_CLASS = 'Mdm::Workspace'
DEFAULT_WORKSPACE_NAME = 'default' DEFAULT_WORKSPACE_NAME = 'default'
def find_workspace(workspace_name) def find_workspace(workspace_name)
workspace = workspace_cache[workspace_name] workspace = workspace_cache[workspace_name]
return workspace unless (workspace.nil?) return workspace unless (workspace.nil?)
workspace = json_to_open_struct_object(self.get_data(WORKSPACE_API_PATH, {:workspace_name => workspace_name})) workspace = json_to_mdm_object(self.get_data(WORKSPACE_API_PATH, {:workspace_name => workspace_name}), WORKSPACE_MDM_CLASS).first
workspace_cache[workspace_name] = workspace workspace_cache[workspace_name] = workspace
end end
def add_workspace(workspace_name) def add_workspace(workspace_name)
response = self.post_data(WORKSPACE_API_PATH, {:workspace_name => workspace_name}) response = self.post_data(WORKSPACE_API_PATH, {:workspace_name => workspace_name})
json_to_open_struct_object(response, nil) json_to_mdm_object(response, WORKSPACE_MDM_CLASS, nil)
end end
def default_workspace def default_workspace
@ -33,11 +34,11 @@ module RemoteWorkspaceDataService
end end
def workspaces def workspaces
json_to_open_struct_object(self.get_data(WORKSPACE_API_PATH, {:all => true}), []) json_to_mdm_object(self.get_data(WORKSPACE_API_PATH, {:all => true}), WORKSPACE_MDM_CLASS, [])
end end
def workspace_associations_counts() def workspace_associations_counts()
json_to_open_struct_object(self.get_data(WORKSPACE_COUNTS_API_PATH), []) json_to_mdm_object(self.get_data(WORKSPACE_API_PATH, []), WORKSPACE_MDM_CLASS, [])
end end
######### #########

View File

@ -10,10 +10,10 @@ module ResponseDataHelper
# Converts an HTTP response to an OpenStruct object # Converts an HTTP response to an OpenStruct object
# #
def json_to_open_struct_object(response_wrapper, returns_on_error = nil) def json_to_open_struct_object(response_wrapper, returns_on_error = nil)
if (response_wrapper.expected) if response_wrapper.expected
begin begin
body = response_wrapper.response.body body = response_wrapper.response.body
if (not body.nil? and not body.empty?) if not body.nil? and not body.empty?
return JSON.parse(body, object_class: OpenStruct) return JSON.parse(body, object_class: OpenStruct)
end end
rescue Exception => e rescue Exception => e
@ -24,6 +24,34 @@ module ResponseDataHelper
return returns_on_error return returns_on_error
end end
#
# Converts an HTTP response to an Mdm Object
#
# @param [ResponseWrapper] A wrapped HTTP response containing a JSON body.
# @param [String] The Mdm class to convert the JSON to.
# @param [Anything] A failsafe response to return if no objects are found.
# @return [ActiveRecord::Base] An object of type mdm_class, which inherits from ActiveRecord::Base
def json_to_mdm_object(response_wrapper, mdm_class, returns_on_error = nil)
if response_wrapper.expected
begin
body = response_wrapper.response.body
if not body.nil? and not body.empty?
parsed_body = Array.wrap(JSON.parse(body))
rv = []
parsed_body.each do |json_object|
rv << to_ar(mdm_class.constantize, json_object)
end
return rv
end
rescue Exception => e
puts "Mdm Object conversion failed #{e.message}"
e.backtrace.each { |line| puts "#{line}\n" }
end
end
return returns_on_error
end
# Processes a Base64 encoded file included in a JSON request. # Processes a Base64 encoded file included in a JSON request.
# Saves the file in the location specified in the parameter. # Saves the file in the location specified in the parameter.
# #
@ -45,6 +73,60 @@ module ResponseDataHelper
save_path save_path
end end
# Converts a Hash or JSON string to an ActiveRecord object.
# Importantly, this retains associated objects if they are in the JSON string.
#
# Modified from https://github.com/swdyh/toar/
# Credit to https://github.com/swdyh
#
# @param [String] klass The ActiveRecord class to convert the JSON/Hash to.
# @param [String] val The JSON string, or Hash, to convert.
# @param [Class] base_class The base class to build back to. Used for recursion.
# @return [ActiveRecord::Base] A klass object, which inherits from ActiveRecord::Base.
def to_ar(klass, val, base_object = nil)
data = val.class == Hash ? val.dup : JSON.parse(val)
obj = base_object || klass.new
obj_associations = klass.reflect_on_all_associations(:has_many).reduce({}) do |reflection, i|
reflection[i.options[:through]] = i if i.options[:through]
reflection
end
data.except(*obj.attributes.keys).each do |k, v|
association = klass.reflect_on_association(k)
next unless association
case association.macro
when :belongs_to
data.delete("#{k}_id")
to_ar(association.klass, v, obj.send("build_#{k}"))
obj.class_eval do
define_method("#{k}_id") { obj.send(k).id }
end
when :has_one
to_ar(association.klass, v, obj.send("build_#{k}"))
when :has_many
obj.send(k).proxy_association.target =
v.map { |i| to_ar(association.klass, i) }
as_th = obj_associations[k.to_sym]
if as_th
obj.send(as_th.name).proxy_association.target =
v.map { |i| to_ar(as_th.klass, i[as_th.source_reflection_name.to_s]) }
end
end
end
obj.assign_attributes(data.slice(*obj.attributes.keys))
obj.instance_eval do
# prevent save
def valid?(_context = nil)
false
end
end
obj
end
# #
# Converts a hash to an open struct # Converts a hash to an open struct
# #

View File

@ -31,16 +31,14 @@ module Msf::DBManager::Host
deleted = [] deleted = []
hosts.each do |host| hosts.each do |host|
begin begin
host.destroy deleted << host.destroy
deleted << host.address.to_s
rescue # refs suck rescue # refs suck
elog("Forcibly deleting #{host.address}") elog("Forcibly deleting #{host.address}")
host.delete deleted << host.delete
deleted << host.address.to_s
end end
end end
return { deleted: deleted } return deleted
} }
end end

View File

@ -18,12 +18,12 @@ module CredentialServlet
begin begin
opts = parse_json_request(request, false) opts = parse_json_request(request, false)
data = get_db().creds(opts) data = get_db().creds(opts)
includes = [:logins, :public, :private, :origin, :realm] includes = [:logins, :public, :private, :realm]
# Need to append the human attribute into the private sub-object before converting to json # Need to append the human attribute into the private sub-object before converting to json
# This is normally pulled from a class method from the MetasploitCredential class # This is normally pulled from a class method from the MetasploitCredential class
response = [] response = []
data.each do |cred| data.each do |cred|
json = cred.as_json(include: includes).merge('human' => cred.private.class.model_name.human) json = cred.as_json(include: includes).merge('private_class' => cred.private.class.to_s)
response << json response << json
end end
set_json_response(response) set_json_response(response)

View File

@ -424,16 +424,7 @@ class Creds
public_val = core.public ? core.public.username : "" public_val = core.public ? core.public.username : ""
private_val = core.private ? core.private.data : "" private_val = core.private ? core.private.data : ""
realm_val = core.realm ? core.realm.value : "" realm_val = core.realm ? core.realm.value : ""
human_val = "" human_val = core.private ? core.private.class.model_name.human : ""
# TODO: We shouldn't have separate code paths depending on the model we're working with
# This should always expect an OpenStruct.
if core.private
if core.private.is_a?(OpenStruct)
human_val = core.human
else
human_val = core.private.class.model_name.human
end
end
tbl << [ tbl << [
"", # host "", # host
@ -442,7 +433,7 @@ class Creds
public_val, public_val,
private_val, private_val,
realm_val, realm_val,
human_val, human_val
] ]
else else
core.logins.each do |login| core.logins.each do |login|
@ -466,22 +457,13 @@ class Creds
public_val = core.public ? core.public.username : "" public_val = core.public ? core.public.username : ""
private_val = core.private ? core.private.data : "" private_val = core.private ? core.private.data : ""
realm_val = core.realm ? core.realm.value : "" realm_val = core.realm ? core.realm.value : ""
human_val = "" human_val = core.private ? core.private.class.model_name.human : ""
# TODO: We shouldn't have separate code paths depending on the model we're working with
# This should always expect an OpenStruct.
if core.private
if core.private.is_a?(OpenStruct)
human_val = core.human
else
human_val = core.private.class.model_name.human
end
end
row += [ row += [
public_val, public_val,
private_val, private_val,
realm_val, realm_val,
human_val, human_val
] ]
tbl << row tbl << row
end end

View File

@ -659,7 +659,7 @@ module Msf
if mode == [:delete] if mode == [:delete]
result = framework.db.delete_host(workspace: framework.db.workspace, addresses: host_search) result = framework.db.delete_host(workspace: framework.db.workspace, addresses: host_search)
delete_count += result[:deleted].size delete_count += result.size
end end
end end