前言
前些时间见到一个利用php回调函数的后门,然而当时并没有弄懂,于是尝试了解一下php回调函数的后门原理。
Here we go
什么是回调函数
回调函数:Callback (即call then back 被主函数调用运算后会返回主函数),是指通过函数参数传递到其它代码的,某一块可执行代码的引用。
回调函数就是将一个函数作为参数传入另一个函数的函数。php中有许多这样的函数,比如call_user_func
, call_user_func_array
,array_map
等等,这些函数可以将函数作为参数执行后返回主函数,方便使用,但是既然可以将函数作为参数传入执行,如果将一些危险的函数作为参数传入,那就有可能成为一个可利用且不易检测的后门,于是Google一下大佬们的骚操作。
回调函数后门
1. call_user_func 与 call_user_func_array
call_user_func
php中的一个标准回调函数,php手册中的解释为
mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $… ]] ) 。
第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。
返回值为回调函数的结果。
而call_user_func_array
与call_user_func
的功能基本相同,只不过是将一个数组参数作为回调函数的参数,返回回调函数的结果,出错则返回FALSE。
最简单的一条回调后门
将assert
作为回调函数,a的值作为参数给assert调用。
同样,它的兄弟函数的回调后门也十分简单,将回调函数的参数改为一个数组即可。。
由于这两条太过古老,大概率被查杀。
2. array_filter 与 单参数回调后门
01.array_filter等数组操作函数
php手册解释
用回调函数过滤数组中的单元
array array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )
依次将 array 数组中的每个值传递到 callback 函数。如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。
array_filter
函数会将数组中的元素遍历传递给回调函数,所以走一个。
当然菜刀也是连的上的。
同容易被查杀。
同样可以作为后门的数组操作函数有
这些数组操作函数都是将数组遍历作为参数传给回调函数,每次只传递一个参数,所以是但参数回调后门。
02.register 类函数
registregister_shutdown_function
和register_tick_function
这两个函数都可以使用回调函数。于是构造一下
|
|
|
|
这里的declare
函数是用于设定时钟周期ticks
值的函数,而register_tick_function
函数是依据ticks的值运行的。
03. filter_var 与 filter_var_array函数
这两个函数在PHP手册中讲的并不是很清楚,我还是没有理解这两个函数如何调用回调函数,所以搬运一段离别歌大牛的原文
这两个是filter_var的利用,php里用这个函数来过滤数组,只要指定过滤方法为回调(FILTER_CALLBACK),且option为assert即可。
3. assert 与 双参数回调后门
assert
在php手册中的定义十分简单
检查一个断言是否为FALSE。
assert
是一个可以用于debug的函数,会将传入的字符串作为PHP代码执行,也可以作为一个后门函数使用,同时也可以作为一个回调函数使用,于是来看看assert
作为回调函数时的参数。
回调函数应该接受三个参数。 第一个参数包括了断言失败所在的文件。 第二个参数包含了断言失败所在的行号,第三个参数包含了失败的表达式(如有任意 — 字面值例如 1 或者 “two” 将不会传递到这个参数)。 PHP 5.4.8 及更高版本的用户也可以提供第四个可选参数,如果设置了,用于将 description 指定到 assert()。
实际上在使用回调函数时只需传入两个参数即可以使assert()
成功执行PHP代码,所以使用传递两个参数的回调函数也可调用assert()
,更容易绕过waf了。
! PHP 5.4.8 版本及其以上 !
01. uasort()函数
(PHP 4, PHP 5, PHP 7)
uasort — 使用用户自定义的比较函数对数组中的值进行排序并保持索引关联
这里的回调函数可以传递两个参数,所以构造一个回调后门。
02. uksort()函数
(PHP 4, PHP 5, PHP 7)
uksort — 使用用户自定义的比较函数对数组中的键名进行排序
同样的,uksort()
函数的回调函数也是传递两个参数,继续构造。
03. usort()函数
(PHP 4, PHP 5, PHP 7)
usort — 使用用户自定义的比较函数对数组中的值进行排序
|
|
04. array_reduce函数
PHP 4 >= 4.0.5, PHP 5, PHP 7)
array_reduce — 用回调函数迭代地将数组简化为单一的值
|
|
05. array_udiff函数
(PHP 5, PHP 7)
array_udiff — 用回调函数比较数据来计算数组的差集
|
|
4. preg_replace 与三参数回调后门
惯例上php手册
(PHP 4, PHP 5, PHP 7)
preg_replace — 执行一个正则表达式的搜索和替换mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
搜索subject中匹配pattern的部分, 以replacement进行替换。
看上去一个人畜无害的函数,然而其中的pattern
函数可以使用PCRE修饰符,其中的/e
修饰符,按照PHP手册中的定义
如果设置了这个被弃用的修饰符, preg_replace() 在进行了对替换字符串的 后向引用替换之后, 将替换后的字符串作为php 代码评估执行(eval 函数方式),并使用执行结果 作为实际参与替换的字符串。单引号、双引号、反斜线()和 NULL 字符在 后向引用替换时会被用反斜线转义.
意味着当进行正则替换时使用该修饰符既可以将替换的字符串作为PHP代码执行,所以这个危险的修饰符在PHP7中被移除。
然而在PHP7之前的版本依然可以使用,所以找一个可以传递三个参数回调函数的函数。
01. array_walk() 与 array_walk_recursive
array_walk()
(PHP 4, PHP 5, PHP 7)
array_walk — 使用用户自定义函数对数组中的每个元素做回调处理
bool array_walk ( array &$array , callable $callback [, mixed $userdata = NULL ] )
callback
典型情况下 callback 接受两个参数。array 参数的值作为第一个,键名作为第二个。
userdata
如果提供了可选参数 userdata,将被作为第三个参数传递给 callback funcname。
array_walk_recursive()
(PHP 5, PHP 7)
array_walk_recursive — 对数组中的每个成员递归地应用用户函数
bool array_walk_recursive ( array &$array , callable $callback [, mixed $userdata = NULL ] )
callback
典型情况下 callback 接受两个参数。array 参数的值作为第一个,键名作为第二个。
userdata
如果提供了可选参数 userdata,将被作为第三个参数传递给 callback。
由此看来这两个函数都是可以提供三个参数给回调函数的,所以尝试构造一下。
|
|
|
|
PHP7环境下测试
由于移除了/e 标志符,所以preg_replace
函数代码执行失败。
除了preg_replace
函数外还有preg_filter
函数有同样的功能,调用的方式和preg_replace
一样。
还有一个需要四个参数的代码执行函数mb_ereg_replace
02. PHP7环境下的preg_replace_callback和mb_ereg_replace_callback
在后续的PHP版本中,preg_replace_callback
函数逐步代替preg_replace
中的回调功能,于是我们可以在PHP版本中使用preg_replace_callback
作为回调后门的函数,但是这个函数传递给回调函数的值为数组,assert
接收的是字符串,所以利用匿名函数构造后门。
mb_ereg_replace_callback
也是同样的。
5. ob_start 无回显回调函数
(PHP 4, PHP 5, PHP 7)
ob_start — 打开输出控制缓
ob_start
函数可以用来打开输出缓冲,配合ob_end_flush()
可以将存储在内部缓冲区中的内容输出给回调函数,然而这里的回调函数执行是没有回显的,但是我们可以通过这个函数写入一些后门文件,从而达到添加webshell的意图。
|
|
通过fwrite
等函数写入后门
后记
最初接触PHP回调后门是在CTF群内的讨论中见到的,随后通过学习大佬的博客和查阅超级无敌厚的PHP手册慢慢摸索,总结了一点小小的笔记,做的点微小的贡献。PHP回调函数避免了直接使用eval
之类的危险函数,方便过狗也是美滋滋了,然而菜刀本身的局限性,连接中依旧使用了eval
函数,必须配合修改过配置的过狗菜刀使用,接下来会再去研究一下菜刀的打狗棒法。
(感谢老铁这位良师益友对我的指导)
欢迎各位大佬在评论区留言指教。
参考: