Merge branch 'goliath' of github.com:clee-r7/metasploit-framework into goliath
commit
505f1fd547
|
@ -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.
|
|
@ -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
|
||||
|
||||
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
|
||||
|
||||
2. `use exploit/windows/misc/commvault_cmd_exec`
|
||||
|
||||
3. `set RHOST [ip]`
|
||||
|
||||
4. `exploit`
|
||||
|
||||
5. shellz :)
|
||||
|
||||
|
||||
## References
|
||||
|
||||
* https://en.wikipedia.org/wiki/Commvault
|
||||
* https://www.securifera.com/advisories/sec-2017-0001/
|
|
@ -13,6 +13,8 @@ module HostDataProxy
|
|||
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)
|
||||
puts 'Calling find host'
|
||||
report_host(opts)
|
||||
|
|
|
@ -3,13 +3,22 @@ require 'metasploit/framework/data_service/remote/http/response_data_helper'
|
|||
module RemoteCredentialDataService
|
||||
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 = {})
|
||||
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
|
||||
|
||||
def create_credential(opts)
|
||||
self.post_data_async(CREDENTIAL_PATH, opts)
|
||||
self.post_data_async(CREDENTIAL_API_PATH, opts)
|
||||
end
|
||||
end
|
|
@ -3,27 +3,28 @@ require 'metasploit/framework/data_service/remote/http/response_data_helper'
|
|||
module RemoteHostDataService
|
||||
include ResponseDataHelper
|
||||
|
||||
HOST_PATH = '/api/1/msf/host'
|
||||
HOST_SEARCH_PATH = HOST_PATH + "/search"
|
||||
HOST_API_PATH = '/api/1/msf/host'
|
||||
HOST_SEARCH_PATH = HOST_API_PATH + "/search"
|
||||
HOST_MDM_CLASS = 'Mdm::Host'
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
def report_hosts(hosts)
|
||||
self.post_data(HOST_PATH, hosts)
|
||||
self.post_data(HOST_API_PATH, hosts)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
# TODO: Remove? What is the purpose of this method?
|
||||
|
|
|
@ -3,11 +3,12 @@ require 'metasploit/framework/data_service/remote/http/response_data_helper'
|
|||
module RemoteLootDataService
|
||||
include ResponseDataHelper
|
||||
|
||||
LOOT_PATH = '/api/1/msf/loot'
|
||||
LOOT_API_PATH = '/api/1/msf/loot'
|
||||
LOOT_MDM_CLASS = 'Mdm::Loot'
|
||||
|
||||
def loot(opts = {})
|
||||
# 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
|
||||
loots.each do |loot|
|
||||
if loot.data
|
||||
|
@ -19,14 +20,14 @@ module RemoteLootDataService
|
|||
end
|
||||
|
||||
def report_loot(opts)
|
||||
self.post_data_async(LOOT_PATH, opts)
|
||||
self.post_data_async(LOOT_API_PATH, opts)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def report_loots(loot)
|
||||
self.post_data(LOOT_PATH, loot)
|
||||
self.post_data(LOOT_API_PATH, loot)
|
||||
end
|
||||
end
|
|
@ -1,6 +1,7 @@
|
|||
module RemoteSessionDataService
|
||||
|
||||
SESSION_API_PATH = '/api/1/msf/session'
|
||||
SESSION_MDM_CLASS = 'Mdm::Session'
|
||||
|
||||
def report_session(opts)
|
||||
session = opts[:session]
|
||||
|
@ -12,7 +13,7 @@ module RemoteSessionDataService
|
|||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
|
|
|
@ -3,14 +3,15 @@ require 'metasploit/framework/data_service/remote/http/response_data_helper'
|
|||
module RemoteSessionEventDataService
|
||||
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 = {})
|
||||
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
|
||||
|
||||
def report_session_event(opts)
|
||||
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
|
|
@ -5,19 +5,20 @@ module RemoteWorkspaceDataService
|
|||
|
||||
WORKSPACE_COUNTS_API_PATH = '/api/1/msf/workspace/counts'
|
||||
WORKSPACE_API_PATH = '/api/1/msf/workspace'
|
||||
WORKSPACE_MDM_CLASS = 'Mdm::Workspace'
|
||||
DEFAULT_WORKSPACE_NAME = 'default'
|
||||
|
||||
def find_workspace(workspace_name)
|
||||
workspace = workspace_cache[workspace_name]
|
||||
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
|
||||
end
|
||||
|
||||
def add_workspace(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
|
||||
|
||||
def default_workspace
|
||||
|
@ -33,11 +34,11 @@ module RemoteWorkspaceDataService
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
#########
|
||||
|
|
|
@ -10,10 +10,10 @@ module ResponseDataHelper
|
|||
# Converts an HTTP response to an OpenStruct object
|
||||
#
|
||||
def json_to_open_struct_object(response_wrapper, returns_on_error = nil)
|
||||
if (response_wrapper.expected)
|
||||
if response_wrapper.expected
|
||||
begin
|
||||
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)
|
||||
end
|
||||
rescue Exception => e
|
||||
|
@ -24,6 +24,34 @@ module ResponseDataHelper
|
|||
return returns_on_error
|
||||
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.
|
||||
# Saves the file in the location specified in the parameter.
|
||||
#
|
||||
|
@ -45,6 +73,60 @@ module ResponseDataHelper
|
|||
save_path
|
||||
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
|
||||
#
|
||||
|
|
|
@ -31,16 +31,14 @@ module Msf::DBManager::Host
|
|||
deleted = []
|
||||
hosts.each do |host|
|
||||
begin
|
||||
host.destroy
|
||||
deleted << host.address.to_s
|
||||
deleted << host.destroy
|
||||
rescue # refs suck
|
||||
elog("Forcibly deleting #{host.address}")
|
||||
host.delete
|
||||
deleted << host.address.to_s
|
||||
deleted << host.delete
|
||||
end
|
||||
end
|
||||
|
||||
return { deleted: deleted }
|
||||
return deleted
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -18,12 +18,12 @@ module CredentialServlet
|
|||
begin
|
||||
opts = parse_json_request(request, false)
|
||||
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
|
||||
# This is normally pulled from a class method from the MetasploitCredential class
|
||||
response = []
|
||||
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
|
||||
end
|
||||
set_json_response(response)
|
||||
|
|
|
@ -424,16 +424,7 @@ class Creds
|
|||
public_val = core.public ? core.public.username : ""
|
||||
private_val = core.private ? core.private.data : ""
|
||||
realm_val = core.realm ? core.realm.value : ""
|
||||
human_val = ""
|
||||
# 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
|
||||
human_val = core.private ? core.private.class.model_name.human : ""
|
||||
|
||||
tbl << [
|
||||
"", # host
|
||||
|
@ -442,7 +433,7 @@ class Creds
|
|||
public_val,
|
||||
private_val,
|
||||
realm_val,
|
||||
human_val,
|
||||
human_val
|
||||
]
|
||||
else
|
||||
core.logins.each do |login|
|
||||
|
@ -466,22 +457,13 @@ class Creds
|
|||
public_val = core.public ? core.public.username : ""
|
||||
private_val = core.private ? core.private.data : ""
|
||||
realm_val = core.realm ? core.realm.value : ""
|
||||
human_val = ""
|
||||
# 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
|
||||
human_val = core.private ? core.private.class.model_name.human : ""
|
||||
|
||||
row += [
|
||||
public_val,
|
||||
private_val,
|
||||
realm_val,
|
||||
human_val,
|
||||
human_val
|
||||
]
|
||||
tbl << row
|
||||
end
|
||||
|
|
|
@ -659,7 +659,7 @@ module Msf
|
|||
|
||||
if mode == [:delete]
|
||||
result = framework.db.delete_host(workspace: framework.db.workspace, addresses: host_search)
|
||||
delete_count += result[:deleted].size
|
||||
delete_count += result.size
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue