LibreNMS是基于PHP/MySQL/SNMP的开源网络监控工具,它可自动发现网络中的思科设备和Linux等系统,使用通用SNMP协议来监控其它系统。
该漏洞可以通过以下方式来触发:在添加新的设备时,在community(snmp配置时的一个字段)参数中添加任意命令的方式。随后该设备会向“addhost.inc.php”页面发送发送未经过滤的请求。然后调用capture.inc.php中的popen函数并执行参数,该函数的参数是你添加的任意命令。(可以通过向ajax_output.php发送请求进而调用capture.inc.php,参数为[file_name].inc.php)。
我们首先分析LibreNMS,我的目标是在该项目中找到RCE(远程代码执行),我下载了项目源代码,第一步是通过python脚本搜索一些不安全的函数,例如system, exec, shell, exec, popen等,这些函数可能导致命令执行。
对执行脚本后的结果进行深入分析和挖掘,我在该文件(html/includes/output/capture.inc.php)第67行找到了一些有趣的代码,如下图所示:
我们可以看到,该脚本使用popen函数执行$cmd中存储的命令并返回了命令执行后的结果。我们向上面继续寻找,会发现$cmd变量的赋值被包含在一个switch语句(脚本36行)中,如下图所示,可以分析出当$type(在脚本34行声明)的值等于snmpwalk时,调用gen_snmpwalk_cmd函数(脚本44行)。
显然,capture.inc.php文件对于用户传递的snmp协议请求做了处理。但现在我们把关注点放在gen_snmpwalk_cmd函数的调用上,调用后的结果会存储在$cmd变量中,然后作为参数传递给popen函数(capture.inc.php文件67行),该函数执行如下图所示的操作:
理论上讲,我们可以通过控制gen_snmp_cmd函数的返回值,并传递给popen,便可以获得想要的命令执行。我们接着进行分析,会发现该函数调用了snmp_gen_auth函数,snmp_gen_auth函数的实现在snmp.inc.php文件的768行,如下图所示:
通过分析,我发现当调用gen_snmpwalk_cmd函数时,参数是设备ID,最终会调用snmp_gen_auth函数,该函数会获取传入设备ID相关的所有设备信息并存储到$cmd变量。我想我们可以通过控制snmp中的“community”字段,进而控制执行的命令。
如上图所示,在脚本的803行,我们可以模仿这种形式把我们的命令传入($(our_command))。
接下来,我们需要找到一个允许我们控制设备信息的入口,即“SNMP community”(snmp中的一个字段,用来鉴别在管理站点和一个包含SNMP信息的代理的路由器之间的信息发送。并将被发送到在管理器和代理之间的每个数据包)。我开始在LibreNMS中添加新的主机,并观察到每当我添加一个新的设备,其配置都会被保存。然后我可以通过snmp_gen_auth函数获取信息。
接下来,使用Karma(为开发人员提供高效的测试环境)发送一个未经过滤的POST请求,并包含我们修改后的community string,如下所示:请看html/pages/addhost.inc.php line文件的第52行:
在第52行中,脚本检查输入的snmp版本是否为v1或v2c,第53行检测$_POST[‘community’]是否在POST请求中。第54行显示,如果$_POST[‘community’]存在,则传递给应用程序并添加到snmp的配置中。所以我们可以通过控制community字段进而注入任意命令。
因此,我们可以通过执行以下操作来获取RCE:
1.添加新设备,并修改community string字段,注入任意命令。==> 可以通过向addhost.inc.php发送请求来实现。
2.发送一个请求,触发popen函数。该函数会执行参数中的命令,这个参数通过其它函数获得,并会把community string传入其中。==> 可以通过向capture.inc.php发送请求来实现命令触发。
如下图所示,我们添加新设备,向addhost页面发送请求,该请求将由addhost.inc.php文件进行处理,可以看到如下内容:
然后触发popen函数,我们需要向ajax_output.php发送请求,ajax_output.php位于html/ajax_output.php下。如下图所示,该请求会调用capture.inc.php:
如果$_request [‘id’]被捕获,将调用capture.inc.php(第35行)并从Web界面发送请求。通过http://server/device/device=2/tab=capture/ ==> snmp ==> run链接,我们可以看到对应内容,如下图所示:
可以看到,请求返回200 OK。这表示我们分析的函数被成功调用,并且我们找到了修改后的community string:“test string”。我们可以成功获取RCE。
您也可以通过github获取完整的漏洞利用代码。
最终演示结果如下图: