该仓库包含一篇博客文章,内容是我对 CVE-2018-19987 的分析,这是一个影响多款 D-Link 路由器的认证后操作系统命令注入漏洞。
大家好!
在这篇简短的博客文章中,我将详细拆解我所进行的一项快速分析,以更好地理解 CVE-2018-19987 背后的细节。起初我追踪这个 CVE 是因为我以为没有任何公开信息或漏洞利用。在进行初步分析后不久,我在 GitHub 上找到了两个公开的 PoC,你可以在这里和在这里找到它们。
由于我已经完成了大部分工作,并且没有找到完整的分析,我决定无论如何都要写这篇博客文章。
希望你喜欢!
更新: 好吧……当我在这篇博客文章中遇到困难并进行谷歌搜索时,我发现了下面这篇文章,它描述了 D-Link DIR-818 中一个非常相似的漏洞(CVE-2018-19986)。路由器漏洞分析系列(五):CVE-2018-19986 DIR-818LW&828 命令注入漏洞分析与复现(谷歌翻译)作者:Ogur1。
我开始了我的分析,尽可能收集了关于该设备的所有信息:
注意:我所有的分析都是使用固件 v3.01B02 进行的。
该漏洞的公开信息可在 Mitre 的页面 上获取,提供了足够的信息来开始:
D-Link DIR-822 Rev.B 202KRb06, DIR-822 Rev.C 3.10B06, DIR-860L Rev.B 2.03.B03, DIR-868L Rev.B 2.05B02, DIR-880L Rev.A 1.20B01_01_i3se_BETA, and DIR-890L Rev.A 1.21B02_BETA devices mishandle IsAccessPoint in /HNAP1/SetAccessPointMode. In the SetAccessPointMode.php source code, the IsAccessPoint parameter is saved in the ShellPath script file without any regex checking. After the script file is executed, the command injection occurs. A vulnerable /HNAP1/SetAccessPointMode XML message could have shell metacharacters in the IsAccessPoint element such as the `telnetd` string.
为了开始分析,我需要一些可以查看的东西,所以我继续提取了路由器的文件系统。
binwalk -eM DIR822C1_FW301WWb02.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 DLOB firmware header, boot partition: "dev=/dev/mtdblock/1"
10380 0x288C LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 4213444 bytes
1376372 0x150074 PackImg section delimiter tag, little endian size: 10505216 bytes; big endian size: 5021696 bytes
1376404 0x150094 Squashfs filesystem, little endian, version 4.0, compression:lzma, size: 5019773 bytes, 2282 inodes, blocksize: 131072 bytes, created: 2016-03-18 09:35:31
正如我们所看到的,Binwalk 确实检测到了一个 Linux 文件系统。如果我们更深入地查看现有的信息,可以看到公告中提到了 /HNAP1 路径。因此,我们可以开始研究路由器如何处理这些 URL。
正如优秀的 /dev/tty0 博客文章 中所解释的,这些 URL 最终由一个名为 cgibin 的二进制文件处理,该文件位于 htdocs/cgibin。但我想更好地理解这是如何配置的,最终这让我不得不去理解 HTTP 服务器是如何配置来处理请求的。我继续查找 httpd.conf 这个文件名,假设它是用来配置 HTTP 服务器的,并在 /etc/services/ 文件夹下找到了一个名为 HTTP.php 的文件,其中除其他内容外,还包含以下几行有趣的代码:
$httpd_conf = "/var/run/httpd.conf";
fwrite("a",$START, "xmldbc -P /etc/services/HTTP/httpcfg.php > ".$httpd_conf."\n");
fwrite("a",$START, "event PREFWUPDATE add /etc/scripts/prefwupdate.sh\n");
fwrite("a",$START, "httpd -f ".$httpd_conf."\n");
fwrite("a",$START, "event HTTP.UP\n");
fwrite("a",$START, "exit 0\n");
我假设这个文件在某个时刻会被执行并写入 /var/run/httpd.conf 文件。为了了解 Web 服务器是如何配置的,我继续分析了 /etc/services/HTTP/httpcfg.php 文件(我只包含了相关部分):
if ($hnap > 0)
{
echo
" Control". "\n".
" {". "\n".
" Alias /HNAP1". "\n".
" Location /htdocs/HNAP1". "\n".
" External". "\n".
" {". "\n".
" /usr/sbin/hnap { hnap }". "\n".
" }". "\n".
" IndexNames { index.hnap }". "\n".
" }". "\n";
}
现在我们拥有了所有的拼图!我们可以假设,Web 服务器被配置为使用 /usr/sbin/hnap 处理对 /HNAP1 的 HTTP 请求,并且根据 /dev/tty0 博客文章,我们知道它将是指向 htdocs/cgibin 二进制文件的链接。
下一步是查看 cgibin 二进制文件,以了解它如何处理 HNAP 请求。在下图中,我们可以看到反编译后 main 函数的一部分,其中传递给它的 URL 路径会与不同的路径进行比较(例如 session.cgi、authentication.cgi、captcha.cgi 等等),直到找到 hnap 并调用我们的函数 hnap_main。
注意:在分析 main 函数时,我在识别 hnap 字符串时遇到了一些问题,因为 Ghidra 在初始分析期间没有将其检测为字符串。
下一步是理解这个函数做了什么。对于相关的部分,我将只提供有助于更好地理解该函数功能的代码片段,同时我也会用一些伪代码来补充其他相关部分——我重命名了一些变量以阐明其用途。
...
HTTP_SOAPACTION = getenv("HTTP_SOAPACTION");
REQUEST_METHOD = getenv("REQUEST_METHOD");
HNAP_AUTH = getenv("HTTP_HNAP_AUTH");
__haystack = getenv("HTTP_COOKIE");
pcVar1 = getenv("HTTP_REFERER");
...
if (HTTP_SOAPACTION != "") {
if (HTTP_SOAPACTION == GetDeviceSettings) {
...
} else {
// These actions will occur during auth. Process
if ("GetCAPTCHAsetting" in HTTP_SOAPACTION) {
sess_generate_captcha();
} else {
if ("Login" in HTTP_SOAPACTION) {
perform_login();
}
// We'll land here once auth.
if (HNAP_AUTH != "") {
if ("uid=" in HTTP_COOKIE){
is_valid_auth = perform_auth_process()
if (is_valid_auth) {
if("logout" in HTTP_SOAPACTION){
perform_logout();
// If we are auth. and NOT trying to logout
// the code will try to perform the action
// we requested
} else {
// If we arrive here we win
// interesting code below
goto LAB_004141d4;
}
}
}
}
// If we are not authenticated we can't do anything
Return "You need proper authorization to use this resource"
}
}
} else {
...
}
LAB_004141d4:
hnap_action = get_hnap_operation(HTTP_SOAPACTION);
if (HTTP_SOAPACTION != "") {
hnap_action_len = strlen(hnap_action);
}
snprintf(path_to_hnap_php_file,0x100,"%s/%s.php","/etc/templates/hnap/",hnap_action);
if (!check_file_access(path_to_hnap_php_file)){
return "HNAP ACTION DOES NOT EXIST (FAIL)"
}
if (REQUEST_METHOD == "POST") {
// Here arguments for the PHP are extracted
parse_request_and_extract_xml()
if (hnap_action == "GetFirmwareStatus") {
system("sh /etc/events/checkfw.sh > /dev/console");
}
// Here the final arguments for the xmldbc_ephp are crafted
snprintf(ARGS_FOR_XMLDBC_PHP,0x100,"%s%s.php\nShellPath=%s%s.sh\nPrivateKey=%s\n",
"/etc/templates/hnap/", hnap_action, &ShellPath, hnap_action, &PRIVATE_KEY);
// PHP is executed (in our case SetAccessPointMode.php) and the shell
// script is written to ShellPath
xmldbc_ephp(0,0,ARGS_FOR_XMLDBC_PHP,stdout);
snprintf(hnap_action, 0x100, "%s", hnap_action);
// Shell command is built to run the previously written shell file
// (File written by the PHP script)
shell_command = "sh %s%s.sh > /dev/console &";
}
snprintf(cmd_to_execute, 0x100, shell_command, &PATH, hnap_action);
// File is executed containing the command injection
system(cmd_to_execute);
}
...
完成对这个函数的分析后,我决定查看 PHP 文件 SetAccessPointMode.php——它最终就是包含缺陷的那个文件——并尝试将所有部分拼凑在一起:
...
$IsAccessPoint = query("/runtime/hnap/SetAccessPointMode/IsAccessPoint");
...
fwrite("w",$ShellPath, "#!/bin/sh\n");
fwrite("a",$ShellPath, "echo [$0] $1 ... > /dev/console\n");
fwrite("a",$ShellPath, "echo IsAccessPoint = ".$IsAccessPoint." > /dev/console\n");
fwrite("a",$ShellPath, "echo Result = ".$Result."\n");
...
正如我们所看到的,$ShellPath 变量由函数 hnap_main 填充,而 $IsAccessPoint 变量是用户可控的,并在发送的 XML 请求中传递。通过这一点,我们可以证实由 PHP 文件写入的变量中是如何执行操作系统命令注入的。这里你可以找到一个由 pr0v3rbs 开发的完整 PoC 来利用此问题。
稍后我们将进一步讨论受影响和已修复的版本。正如你在下表中看到的,补丁似乎存在某种回归,在那些本应已被修复的版本中重新引入了该漏洞。
(*)我没有检查这些固件版本,因为它们未加密的对应版本存在漏洞。
正如我们在上表中所看到的,该漏洞在某些固件版本中已通过移除受影响的 PHP 文件 SetAccessPointMode.php 得到修复。一旦该文件不存在,函数中执行的检查将会失败,并且不会执行任何操作。我通过分析固件版本 FW v3.131 和 3.15B02 WW 中的 hnap_function 证实了这一点;hnap_main 的代码与此问题相关的部分没有变化,但 PHP 文件已不再存在。
更有趣的是,在版本 3.11B01_icjg_WW 中被修复之后,这个 bug 不知何故又在固件版本 FW v3.11 到 FW v3.12B04 中被重新引入。
作为第一个结论,我想说,永远不要把这些设备当作真正安全的设备来信任——更新过程中发生的事情就是一个明确的例子,说明了为什么不应该这样做。此外,经过上述分析,我可以得出结论:这篇博客文章中列出的第一个 PoC 对这个特定版本无效,因为正如我们所看到的,要执行易受攻击的 PHP 文件需要满足一些条件。
而最重要的结论是:在根据 CVSS 评分来判断漏洞的严重程度时,要非常谨慎。我决定分析这个 CVE 是因为它的评分为 9.8(CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H),而且很明显,至少对于这个版本,攻击者需要凭据才能利用它。
作为后续步骤,我将致力于在无法直接接触路由器的情况下模拟这个二进制文件以利用该漏洞,但这将是另一篇文章的内容!
感谢阅读。
| 固件 | 版本 | MD5 哈希 | 发布日期 | 是否易受攻击 |
|---|
| DIR822C1_FW315WWb02.bin | 3.15B02 WW | 7121771c3e1706ba76fbf244023efad3 | 06/11/2019 | 否 |
| DIR822C1_FW313WWb01.bin | FW v3.13 | aa16c7016f67be384e0784e439ce26d2 | 10/07/2019 | 否 |
| DIR822C1_FW303WWb04_i4sa_middle.bin | FW v3.12B04 | c3b9a3f115c02e739690616aba2f2d99 | 26/04/2019 | 是 |
| DIR822C1_FW312WWb04.bin | FW v3.12B04 | eb11afbd136a5b29cea18141f727bfa8 | 26/04/2019 | 是(*) |
| DIR822C1_FW311WWb01.bin | FW v3.11 | 6d7c90eaaae835667faea65c862b3c82 | 01/01/2019 | 是 |
| DIR822C1_FW311bWWb01_icjg.bin | 3.11B01_icjg_WW | 75e361e1465604aeda5d5dbcaecca977 | 21/12/2018 | 否 |
| DIR822C1_FW303WWb04_i4sa_middle.bin | FW v3.10B06 | c3b9a3f115c02e739690616aba2f2d99 | 17/08/2018 | 是 |
| DIR822C1_FW310WWb06.bin | FW v3.10B06 | e33db75d0801fddb1c90308982e69fe5 | 17/08/2018 | 是(*) |
| DIR-822_C1_FW302WWb05.bin | FW v3.02 | 0dbf840c0ff5d3a5b593d33690e15d82 | 14/09/2017 | 是 |
| DIR822C1_FW301WWb02.bin | FW v3.01B02 | 1bff7ec8b4da0643f65b4d44c630e92b | 27/04/2016 | 是 |