浅谈 ThinkPHP 中的注入

admin 2022年6月20日10:17:47评论517 views字数 4830阅读16分6秒阅读模式

前言

在 CMS 中难免会调用到 SQL 执行(废话),百密总有一疏,今天跟师傅们分享一些平时审计时遇到的注入,可能不全面,希望和师傅们一起学习。

关于 ThinkPHP 中的 注入

这绝对是能拿来探讨探讨的,因为我看到的版本就有两三种了,这里给出两个例子。

ThinkPHP3.2.3

首先我们举个最简单的例子作为开场:

public function login(){
$User = D('User');
$map = array('username' => $_POST['username']);
$user = $User->where($map)->find();
}

首先调用了 where 函数:

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

....

if(isset($this->options['where'])){
$this->options['where'] = array_merge($this->options['where'],$where);
}else{
$this->options['where'] = $where;
}

return $this;
}

这里简单的将 where 放进了 options[where] 里。

然后第二步是调用 find 函数,find 的内部又调用了 ->db->select,这是具体的实现,可以进去看看(查询时可能会查找三个,通常是 Driver.class.php):

public function select($options=array()) {
$this->model = $options['model'];
$this->parseBind(!empty($options['bind'])?$options['bind']:array());
$sql = $this->buildSelectSql($options); // 生成 sql 语句
$result = $this->query($sql,!empty($options['fetch_sql']) ? true : false);
return $result;
}

继续跟入 buildSelectSql 函数,会发现里面调用了 parseSql 函数,继续跟:

public function parseSql($sql,$options=array()){
$sql = str_replace(
array('%TABLE%','%DISTINCT%','%FIELD%','%JOIN%','%WHERE%','%GROUP%','%HAVING%','%ORDER%','%LIMIT%','%UNION%','%LOCK%','%COMMENT%','%FORCE%'),
array(
$this->parseTable($options['table']),
$this->parseDistinct(isset($options['distinct'])?$options['distinct']:false),
$this->parseField(!empty($options['field'])?$options['field']:'*'),
$this->parseJoin(!empty($options['join'])?$options['join']:''),
$this->parseWhere(!empty($options['where'])?$options['where']:''),
$this->parseGroup(!empty($options['group'])?$options['group']:''),
$this->parseHaving(!empty($options['having'])?$options['having']:''),
$this->parseOrder(!empty($options['order'])?$options['order']:''),
$this->parseLimit(!empty($options['limit'])?$options['limit']:''),
$this->parseUnion(!empty($options['union'])?$options['union']:''),
$this->parseLock(isset($options['lock'])?$options['lock']:false),
$this->parseComment(!empty($options['comment'])?$options['comment']:''),
$this->parseForce(!empty($options['force'])?$options['force']:'')
),$sql);
return $sql;
}

这里替换了一些信息后成了最终的 sql 语句,我们可控的是 where ,所以我们跟入 parseWhere

protected function parseWhere($where) {
$whereStr = '';
if(is_string($where)) {
....
}else{ // 使用数组表达式
$operate = isset($where['_logic'])?strtoupper($where['_logic']):'';
if(in_array($operate,array('AND','OR','XOR'))){
....
}else{
....
}
foreach ($where as $key=>$val){
....
if(0===strpos($key,'_')) {
....
}else{
$multi = is_array($val) && isset($val['_multi']);
$key = trim($key);
if(strpos($key,'|')) {
...
}elseif(strpos($key,'&')){
.....
}else{
$whereStr .= $this->parseWhereItem($this->parseKey($key),$val);
}
}
$whereStr .= $operate;
}
$whereStr = substr($whereStr,0,-strlen($operate));
}
return empty($whereStr)?'':' WHERE '.$whereStr;
}

如果传入一个数组,最终会进入到,最后我留下的这个 parseWhereItem

这个函数是审计 TP 注入时的关键函数。

这里的 $val 即使是一个数组也是可以的,所以我们可以传入一个数组进 parseWhereItem 函数,继续跟进:

protected function parseWhereItem($key,$val) {
$whereStr = '';
if(is_array($val)) {
if(is_string($val[0])) {
$exp = strtolower($val[0]);
if(...) {
....
}elseif(....){
.....
}elseif('bind' == $exp ){
...
}elseif('exp' == $exp ){ //看这里~~~~
$whereStr .= $key.' '.$val[1];
}elseif(preg_match('/^(notin|not in|in)$/',$exp)){
.....
}elseif(preg_match('/^(notbetween|not between|between)$/',$exp)){
.....
}else{
E(L('_EXPRESS_ERROR_').':'.$val[0]);
}
}else {
...
}
}else {
....
}
return $whereStr;
}

这里省略了大部分代码,在留下的一个逻辑中可以看到,当 $val 为数组且第一位是 exp 时,直接将 $val 的第二位拼接进去了,没有过滤。。。我们可以传入个 payload 试试。

我们可以在 parseSql 函数 return 前加入一行 print_r($sql);

然后传入:


浅谈 ThinkPHP 中的注入



成功注入了,至于利用就不过多叙述了。

ThinkPHP5.0

在 TP5 中 parseWhereItem 在 Db.class.php,但其实也有一段和 3.2 中差不多的代码:

if(isset($val[2]) && 'exp'==$val[2]) {
$whereStr .= $key.' '.strtoupper($val[0]).' '.$val[1];
}

但是在 TP5.0 却加入了一个全局过滤:

浅谈 ThinkPHP 中的注入

他在我们的 EXP 后面给多加了一个空格,导致这里不能用了,贴一下这个在 5.0 中这个函数的代码:

protected function parseWhereItem($key,$val) {
$whereStr = '';
if(is_array($val)) {
if(is_string($val[0])) {
if(preg_match('/^(EQ|NEQ|GT|EGT|LT|ELT|NOTLIKE|LIKE)$/i',$val[0])) {
...
}elseif('exp'==strtolower($val[0])){
...
}elseif(preg_match('/^(NOTIN|NOT IN|IN)$/i',$val[0])){
...
}elseif(preg_match('/(NOTBETWEEN|NOT BETWEEN|BETWEEN)/i',$val[0])){ // 看这里~~~
$data = is_string($val[1])? explode(',',$val[1]):$val[1];
$whereStr .= ' ('.$key.' '.strtoupper($val[0]).' '.$this->parseValue($data[0]).' AND '.$this->parseValue($data[1]).' )';
}else{
throw_exception(L('_EXPRESS_ERROR_').':'.$val[0]);
}
}else {
...
}
}else {
...
}
return $whereStr;
}

可以前面两个正则都使用了 ^ 和 $ 来限定正则表达式的开始和结束。但是 BETWEEN 这里却没有,只要 $val[0] 里带有 between 即可,在下面中也没有对 val[0] 带入 parseValue 进行过滤,那么这里我们能就可以插入单引号。

举个例子:

我们传入:id[0]=BETWEEN '1234&id[1]=abcd

调试图:


浅谈 ThinkPHP 中的注入



可以看到 abcd 被我们逃逸出来了。到这里注入就算完成了。具体的利用得看实际情况中。

ThinkPHP5.1

在 TP5.1 中全部参数都被绑定了,弟弟没有 0day 在手,所以无法分享这个版本。。233

关于 TP 的总结

其实其他函数可能还有注入,但是这里就不再去深入了,只分析了三个不同版本的 parseWhereItem 函数。其中可能有什么有误或者不清楚的地方,欢迎师傅们提出问题一起探讨。

这里就不得不提一手 红日团队 的:https://github.com/hongriSec/PHP-Audit-Labs

里面也有一些关于 TP 的


来源:先知

注:如有侵权请联系删除


推荐阅读

XSS 实战思路总结

内网信息收集总结

xss攻击、绕过最全总结

一些webshell免杀的技巧

命令执行写webshell总结

SQL手工注入总结 必须收藏

后台getshell常用技巧总结

web渗透之发现内网有大鱼

蚁剑特征性信息修改简单过WAF


查看更多精彩内容,还请关注橘猫学安全



每日坚持学习与分享,觉得文章对你有帮助可在底部给点个“再看浅谈 ThinkPHP 中的注入

原文始发于微信公众号(橘猫学安全):浅谈 ThinkPHP 中的注入

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月20日10:17:47
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   浅谈 ThinkPHP 中的注入http://cn-sec.com/archives/1128830.html

发表评论

匿名网友 填写信息