Cacti, a web-based device monitoring tool, is vulnerable to a critical command injection vulnerability. Tracked as CVE-2022-46169, this vulnerability requires no authentication for exploitation. On successful exploitation, this could allow an unauthenticated attacker to execute arbitrary code if a specific data source was selected for any monitored device.
Cacti is a network monitoring and graphing tool that provides RRDTool’s data storage and graphing functionality. This open-source, web-based tool offers robust and adaptable operational monitoring and fault management framework for users worldwide.
Description
To exploit the vulnerability, an attacker needs to chain two different vulnerabilities to perform code execution. The first vulnerability is Authentication Bypass, which allows an attacker to bypass authentication and execute code by exploiting the Command Injection vulnerability.
Authentication Bypass Vulnerability
The file remote_agent.php ideally should only be accessible to authorized users. Cacti uses the following authorization check applied at the beginning of the file to implement this:
cacti/remote_agent.php
if (!remote_client_authorized()) {
print 'FATAL: You are not authorized to use this service';
exit;
}
The function remote_client_authorized retrieves the IP address of the client ($client_addr) and resolves it to the corresponding hostname ($client_name). Once the IP address has been resolved to the hostname, it checks if the “poller” table in the database contains an entry with this hostname. If a similar entry is found in the table, the function returns true, and the client is authorized:
cacti/lib/html_utility.php
function remote_client_authorized() {
// ...
$client_addr = get_client_addr();
// ...
$client_name = gethostbyaddr($client_addr);
// ...
$pollers = db_fetch_assoc('SELECT * FROM poller', true, $poller_db_cnn_id);
foreach($pollers as $poller) {
if (remote_agent_strip_domain($poller['hostname']) == $client_name) {
return true;
//
The function get_client_addr accepts a variety of attacker-controllable HTTP headers for determining the IP address of the client:
cacti/lib/functions.php
function get_client_addr($client_addr = false) {
$http_addr_headers = array(
// ...
'HTTP_X_FORWARDED',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'HTTP_CLIENT_IP',
'REMOTE_ADDR',
);
$client_addr = false;
foreach ($http_addr_headers as $header) {
// ...
$header_ips = explode(',', $_SERVER[$header]);
foreach ($header_ips as $header_ip) {
// ...
$client_addr = $header_ip;
break 2;
}
}
return $client_addr;
}
The above code snippet in cacti/lib/functions.php works by storing the value of the corresponding HTTP headers into the $http_addr_headers variable and then storing each header into a list of different variables. The REMOTE_ADDR variable is set to the source IP address connected to the webserver. Attackers can fully control these values if there is no reverse proxy between the client and the webserver filtering these HTTP headers.
A default entry exists in the poller table in the database with the server’s hostname running Cacti. An attacker can bypass the remote_client_authorized check exploiting this flaw, for example, by using the HTTP header X-Forwarded:.
In this process, the function get_client_addr returns the IP address of the server running Cacti. After that, the function gethostbyaddr will resolve this IP address to the server’s hostname. Because of the default entry, this will pass the poller hostname check.
This will allow unauthenticated attackers to access the functionality of remote_agent.php without providing legitimate credentials.
Command Injection Vulnerability
The user-provided parameter poller_id is transmitted to the first parameter of proc_open without any sanitization or escaping.
Attackers can trigger the vulnerable function by setting the action parameter to polldata:
cacti/remote_agent.php:
switch (get_request_var('action')) {
case 'polldata':
poll_for_data();
The poll_for_data function retrieves the parameters host_id and poller_id. The host_id parameter comes from get_filter_request_var, while the poller_id parameter comes from get_nfilter_request_var.
cacti/remote_agent.php:
function poll_for_data() {
// ...
$host_id = get_filter_request_var('host_id');
$poller_id = get_nfilter_request_var('poller_id');
The function get_filter_request_var function checks if the retrieved parameter is an integer. The function get_nfilter_request_var, which retrieves the poller_id parameter accepts data of string data type.
If the action of one of the poller items is set to POLLER_ACTION_SCRIPT_PHP, the vulnerable call to proc_open is issued:
cacti/remote_agent.php:
foreach($items as $item) {
switch ($item['action']) {
// ...
case POLLER_ACTION_SCRIPT_PHP: /* script (php script server) */
// ...
$cactiphp = proc_open(read_config_option('path_php_binary') . ' -q ' . $config['base_path'] . '/script_server.php realtime ' . $poller_id, $cactides, $pipes);
This indicates that when an item with the POLLER_ACTION_SCRIPT_PHP action exists, attackers can use the poller_id parameter to inject an arbitrary command. The poller_id parameter is numbered in ascending order, and an attacker can bruteforce this parameter to perform command injection.
Affected versions
As per vendor advisory, the vulnerability affects Cacti version 1.2.22.
Mitigation
Customers must upgrade to Cacti versions 1.2.23, 1.3.0, or later to patch the vulnerability. The advisory states, “For instances of 1.2.x running under PHP < 7.0, a further change a8d59e8 is also required.”
For more information, please refer to the Vendor Advisory (GHSA-6p93-p743-35gf).
Qualys Detection
Qualys customers can scan their devices with QIDs 283607, 283606, 691017, 181453, 181311, and 730703 to detect vulnerable assets.
Please continue to follow Qualys Threat Protection for more coverage of the latest vulnerabilities.
References
https://github.com/Cacti/cacti/security/advisories/GHSA-6p93-p743-35gf
https://www.sonarsource.com/blog/cacti-unauthenticated-remote-code-execution/