问题:
1 | <?php |
web1
1 | <?php |
我们现在虚拟机下部署一下环境
分析
这里我们可以很容易的关注到
...$parameters
这是一个可变参数
call_user_func
这是一个回调函数
unset
这个函数去官网查查
发现他能够销毁$parameters
中的action
解法1
我们试着传参
1 | ?action=system&0=id |
成功执行了
我们下断看一下,是什么流程
我们传入的 两个参数都会被 ...$parameters
这个可变参数接收 形成了数组,
然后经过unset($parameters['action']);
会删除数组中的key是action的字段
最后只剩下了id
最后执行了我们的回调函数
解法2
1 | ?action=usort&0[0]=system&0[1]=ls&1=call_user_func |
这个解法的原理就是 因为$parameters
是可变参数,所以我们就可以利用他的特性
可变参数列表是指在函数的参数列表中使用省略号(…)来表示不定数量的参数。在函数中使用可变参数列表时,需要将该参数作为一个数组来处理
在看看官方文档usort
的定义
我们可以看到他可以接收callback
那么这个payload的利用方式就是,给action传入usort,然后给parameters 传入system,ls,all_user_func
usort先给parameters数组排序,但是里面有callback,就会调用 all_user_func,然后all_user_func 又调用 system这个函数对ls做处理,最后达到了rce
web2
1 | <?php |
这个代码我在linux上复现发现,无论我将php版本调到5.6 还是7.3,都无法访问,爆500错误
然后我们尝试在代码前加入
1 | ini_set('display_errors', 1); |
看看具体是什么原因的错误
他这里报错原因就是我们没post传值
我们在传值试试
解法1
1 | ?action=current&a=current |
这道题的难点就在于
($_POST['a'])($_POST['b']);
如何利用,因为php中根本就没有这种写法,我们就很容易想到,会不会是先执行call_user_func
然后又出现一个函数执行第一个post,最后在执行 最后一个post,这里通过payload,
我们去官方文档查查 current
是个什么函数
通过这个例子我们可以知道current就是 返回数组中的当前值
我们给action传入current,parameters数组中又是current,所以经过call_user_func把current取出来了,
就形成了current([system])(whoami)
current 又将system取出来
system(whoami)
然后执行了rce
解法2
1 | ?action=Closure::fromCallable&0=Closure&1=fromCallable |
第二个的解法思路也感觉跟解法1差不多,就是函数用的不一样,我们去查一下
我们可以看到它可以将callback 转换为闭包(closure)对象
也就是说,这里我们给cation传入的Closure::fromCallable,
数组传入的是["Closure", "fromCallable"]
经过call_user_func
我们得到一个 Closure
对象,对应的是 Closure::fromCallable
这个函数本身的封装。
然后由这个函数来执行post[‘a’] 就变成了system(ls)
下面有一些实列能帮助我们理解
将普通函数转换为闭包
1 | <?Php |
将类方法转换为闭包
1 | <?Php |
将静态方法转换为闭包
1 | <?Php |
将匿名函数转换为闭包
虽然匿名函数本身已经是闭包,但 Closure::fromCallable
可以用来创建一个新闭包对象:
1 | <?php |
使用可调用对象
1 | <?phpclass CallableClass { |
Closure::fromCallable
方法提供了一种方便的方式将不同类型的可调用(函数、类方法、可调用对象)转换为闭包对象,从而可以用统一的方式来调用这些可调用。这个特性在编写更灵活的代码时非常有用,比如在函数式编程中或需要将不同的回调传递给函数时。
web3
1 |
|
分析
这里利用的是readfile函数来读flag,但是这里判断了当前文件夹里是否有3个以上的文件,
这里我们知道,他原本的环境下应该只有两个文件,所以我们得想办法创建两个文件,
但是这里测试发现,常规的创建文件的方法都不适用,因为我们这里传入的第二个参数是一个数组
这里题目给的思路很独特,用用session,来创建文件
我们知道当用户与服务器开启对话的时候,客户端会生成ck,服务端会生成session文件,那么我们是不是可以尝试这个思路呢?
查询官方文档发现session_start
正好他可以通过session.save_path
;来指定路径,然后会创建session文件
而且这个参数是用户可控的
在写入文件之前,首先通过报错来定位当前物理路径,报错的方法有很多,大体是通过引入一个函数并传递“不合法”的参数。
那么我们很容易想到payload
1 | ?action=session_start&save_path=/var/www |
测试发现无法写入/var/www/html
但是将目录改为/tmp,却成功写入了
这里问大模型说
当时的题目环境可能设置了这个吧
另外,其实也考虑过利用 upload_progress 来控制写入文件的内容,再配合其他include 来加载shell。这里有几点需要说明:
- 需要开启 session.auto_start ,这个配置是无法动态开启的,需要环境默认开启。
- 写入文件以后,文件是以sess_ 开头并且文件名只能由这些字符构成:(0-9, a-z, A-Z, “-“, “,”) 。这也导致了不能直接写.php文件。
web4
1 |
|
本题是为了演示加载静态方法(无参类型或参数均含有默认值)
这道题就很简单了
先调用A 这个class,然后在调用f这个函数,最后post[‘a’]传入执行的代码
payload
1 | ?action=call_user_func&0=A&1=f |
web5
1 | <?php |
这道题与上一道题的改变就是我们无法直接利用post[‘a’] 传入我们的执行代码了
payload
1 | ?action=ob_start&0=A&1=f |
这里他利用了ob_start
这个函数,我们去官方文档查查看
他说他能够打开输出的缓冲区
而且他说当冲刷(发送)、清理输出缓冲区或在脚本末尾冲刷输出缓冲区时,将调用 callback
。
也就是说我们打开缓冲区后,$a 会自动的读取缓冲区的内容
所以我们可以在post[‘a’] ,实现任意的rce
比如
这里的任意命令都可以执行,我们尝试执行一下 whoami
但却发现没有回显,问问大模型
我们修改一下源代码,加入return
再次测试
成功回显,可能问题就是这里,这里我们可以发现,当我们打开缓冲区后,$a 会从缓冲区来取值,
从而实现rce
总结
在这样的代码环境下,我们能做的有这样几点:
可通过var_dump引入反射型xss(
?action=var_dump&1=<script>alert(1)</script>
);可通过引入报错来获取物理路径;
可通过session_start 写入文件,写入文件路径可自定义,文件内容是否可控要看服务器配置;
加载php内置函数或原生类/已有类的静态方法,函数参数类型需为:Array、Mixed、callable。 其中callable类型可再次加载 ,并以数组形式传入类名和静态方法名称。 如果函数有多个参数,其他参数需要有默认参数,无默认参数的那个参数需满足以上参数类型条件。若函数参数均有默认参数,首个参数类型需满足以上参数类型条件。
当有echo 配合时,可通过ob_start 加载原生类/已有类的静态函数,函数参数支持string类型。