1.EXP注入漏洞分析:
在官网下载thinkphp3.2.3
然后在ThinkPHP\Conf\convention.php配置好MySQL帐号密码
再添加一行,用来打印sql语句
'SHOW_PAGE_TRACE' =>true,
Application\Home\Controller\IndexController.class.php
漏洞代码
public function index(){
$id = $_GET['id'];
$Admin = D("admin");
$data = $Admin->where(['Id' => $id])->select();
var_dump($data);
}
exp
http://127.0.0.1/index.php?id[0]=exp&id[1]==1 or sleep(5)
下断点调试:
跟到ThinkPHP\Library\Think\Model.class.php的select函数
再跟到select函数中
再进入buildSelectSql函数中
继续进入到parseSql函数中
继续进入
$exp的值是$val[0]的值,也就是poc中的exp
赋值以后$exp='exp';这样的
经过判断以后,直接吧$key和$val[1]进行了字符串拼接
val值就是我们刚才传入进来的值,拼接以后变成
sql注入语句拼接完成
完整调用堆栈流程图
2.使用I函数为什么能防止注入?
如果你在控制器中用I函数来获取变量的话,你会发现页面会报错了
那么为什么I函数能阻止sql注入,在ThinkPHP\Common\functions.ph中的think_filter函数中
I函数会把(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOTBETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN) 这些特殊关键字加上空格
这样的话
并不会进入到if语句中,从而不会拼接sql字符。
3.那么thinkphp3对于普通的sql注入语句是如何防护的?
poc http://127.0.0.1/index.php?id=1 and 1=1
在ThinkPHP\Library\Think\Model.class.php中_parseType()函数
函数先获取了数据库中id字段的数据类型为int(11),就把变量转为整数型
1 and 1=1 被转成 1,达到防注入
那么接下来看一下对于字符型注入点是如何达到进行防注入的。
源码如下
POC
http://127.0.0.1/index.php?username=admin'
对于字符型注入,tp3会调用ThinkPHP\Library\Think\Db\Driver.class.php中的escapeString()函数对特殊符号进行转义,从而进行防注入
4.除了exp还有其他注入方法吗?
那么我们需要对parseWhereItem()这个函数进行分析
通过这些关键字的匹配,会进入到对于的if语句中,一共有5种
但是其他的三种情况都会对把语句传入parseValue()函数
parseValue函数中,又会对函数进行转义
而直接拼接的只有exp和bind两种情况
那么来试一下bind注入
bind会在语句中加入:,从而导致sql语句错误
看了一下网上的资料,bind注入只能在update语句中使用,通过报错进行注入
源码
public function index(){
$User = M("admin");
$user = I('id');
$data['username'] = I('username');
$data['password'] = I('password');
$valu = $User->where(['Id'=>$user])->save($data);
var_dump($valu);
}
POC
http://127.0.0.1/index.php?username=admin&password=123&id[0]=bind&id[1]=1%20and%20updatexml(1,concat(0x7,(select%20password%20from%20admin%20limit%201),0x7e),1)
虽然update条件比较艰难,但是也有一个好处think_filter函数中并没有对bind进行过滤
也就是说就算使用了I函数来获取参数,也是可以进行注入的
5.漏洞修复
1.获取用户输入的参数,尽量使用I函数
2.修复代码,放到index.php 应用入口文件中
function filter(&$value){
if(preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN|BIND)$/i',$value)){
$value .= ' ';
}
}
array_walk_recursive($_GET,'filter');
array_walk_recursive($_POST,'filter');
array_walk_recursive($_REQUEST,'filter');
array_walk_recursive($_COOKIE,'filter');