为什么会存在SQL注入
通俗来讲,sql作为一种解释型语言,在运行时是由一个运行时组件解释语言代码并执行其中包含的指令的语言。基于这种执行方式,产生了一系列叫做代码注入(code injection)的漏洞 。它的数据其实是由程序员编写的代码和用户提交的数据共同组成的。程序员在web开发时,没有过滤敏感字符,绑定变量,导致攻击者可以通过sql灵活多变的语法,构造精心巧妙的语句,不择手段,达成目的,或者通过系统报错,返回对自己有用的信息。
经常我们在面试中会遇到"sql中#和$的区别",都会提到注入问题:
(1)#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是id,则解析成的sql为order by "id"。
(2)$将传入的数据直接显示生成在sql中。如:order by $user_id$,如果传入的值是id,则解析成的sql为order by id。
(3)#方式在很大程度上能够防止sql注入。
(4)$方式无法防止sql注入。
SQL注入演示
以PHP+MySQL为例,以一个Web网站中最基本的用户系统来做实例演示。
1.创建一个名为demo的数据库:
CREATE DATABASE `demo` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
2.创建一个名为user的数据表,并插入1条演示数据:
CREATE TABLE `demo`.`user` (
`uid` INT( 11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '用户uid',
`username` VARCHAR( 20 ) NOT NULL COMMENT '用户名',
`password` VARCHAR( 32 ) NOT NULL COMMENT '用户密码'
) ENGINE = INNODB;
INSERT INTO `demo`.`user` (`uid`, `username`, `password`) VALUES ('1', 'plhwin', MD5('123456'));
实例一
传入username参数,在页面打印出这个user的详细信息,编写 userinfo.php 程序代码:
<?php
header('Content-type:text/html; charset=UTF-8');
$username = isset($_GET['username']) ? $_GET['username'] : '';
$userinfo = array();
if($username){
//使用mysqli驱动连接demo数据库
$mysqli = new mysqli("localhost", "root", "root", 'demo');
$sql = "SELECT uid,username FROM user WHERE username='{$username}'";
//mysqli multi_query 支持执行多条MySQL语句
$query = $mysqli->multi_query($sql);
if($query){
do {
$result = $mysqli->store_result();
while($row = $result->fetch_assoc()){
$userinfo[] = $row;
}
if(!$mysqli->more_results()){
break;
}
} while ($mysqli->next_result());
}
}
echo '<pre>',print_r($userinfo, 1),'</pre>';
上面这个程序要实现的功能是根据浏览器传入的用户名参数,在页面上打印出这个用户的详细信息,采用了mysqli的驱动,以便能使用到 multi_query 方法来支持同时执行多条SQL语句,这样能更好的说明SQL注入攻击的危害性。
假设我们可以通过 http://localhost/test/userinfo.php?username=plhwin
这个URL来访问到具体某个会员的详情,正常情况下,如果浏览器里传入的username是合法的,那么SQL语句会执行:
SELECT uid,username FROM user WHERE username='plhwin'
如果在浏览器里把传入的username参数变为 "plhwin';SHOW TABLES-- hack",也就是当URL变为http://localhost/test/userinfo.php?username=plhwin';SHOW TABLES-- hack
的时候,此时我们程序实际执行的SQL语句变成了:
SELECT uid,username FROM user WHERE username='plhwin';SHOW TABLES-- hack'
--会注释后面的内容
此时可以在浏览器里看到页面的输出,数据库的user表,此时将参数换成plhwin';DROP TABLE user-- hack
,那将产生灾难性的严重结果。
Array
(
[0] => Array
(
[uid] => 1
[username] => plhwin
)
[1] => Array
(
[Tables_in_demo] => user
)
)
SQL注入攻击的总体思路
1.寻找到SQL注入的位置
2.判断服务器类型和后台数据库类型
3.针对不通的服务器和数据库特点进行SQL注入攻击
主要讲一下如何寻找SQL注入点
寻找注入点常用的方法就是通过 不断调整参数来测试服务器的响应,进而暴露出与数据库结构有关的问题。传入SQL语句可控参数分为两类
- 数字类型,参数不用被引号括起来,如
?id=1
- 其他类型,参数要被引号扩起来,如
?name="phone"
数字类型
构造测试 | 预期结果 | 变种 |
---|---|---|
' | 触发错误,返回数据库错误 | |
1+1 | 返回原来相同的结果 | 3-1 |
1+0 | 返回原来相同的结果 | |
1 or 1=1 | 永真条件,返回所有记录 | 1) or (1=1 |
1 or 1=2 | 空条件,返回原来相同的结果 | 1) or (1=2 |
1 and 1=2 | 永假条件,不返回记录 | 1) and (1=2 |
其他类型
构造测试 | 预期结果 | 变种 |
---|---|---|
col 3 is | right-aligned | $1600 |
col 2 is | centered | $12 |
zebra stripes | are neat | $1 |
a' | 触发错误,返回数据库错误 | |
a' or '1'='1 | 永真条件,返回所有记录 | a') or ('1'=1 |
a' or '1'='2 | 空条件,返回原来相同结果 | a') or ('1'=2 |
a' and '1'='2 | 永假条件,不返回记录 | a') and ('1'='2 |
常见基本的方法有如下几种:
1.加引号法,通过在浏览器地址栏中的页面链接地址后面增加一个单引号,进而测试服务器的响应,来判断是否存在注入点。
2.“1=1和1=2”法
后续待补充
如何预防SQL注入
1. PreparedStatement预编译语句(简单最有效)
采用预编译语句集,它内置了处理SQL注入的能力,不同的程序语言,都分别有使用预编译语句的方法。
使用好处:
(1).代码的可读性和可维护性.
(2).PreparedStatement尽最大可能提高性能.
(3).最重要的一点是极大地提高了安全性.
还是以php为例:
<?php
header('Content-type:text/html; charset=UTF-8');
$username = isset($_GET['username']) ? $_GET['username'] : '';
$userinfo = array();
if($username){
//使用mysqli驱动连接demo数据库
$mysqli = new mysqli("localhost", "root", "root", 'demo');
//使用问号替代变量位置
$sql = "SELECT uid,username FROM user WHERE username=?";
$stmt = $mysqli->prepare($sql);
//绑定变量
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->bind_result($uid, $username);
while ($stmt->fetch()) {
$row = array();
$row['uid'] = $uid;
$row['username'] = $username;
$userinfo[] = $row;
}
}
echo '<pre>',print_r($userinfo, 1),'</pre>';
我们程序里并没有使用addslashes函数,但是浏览器里运行 http://localhost/test/userinfo2.php?username=plhwin' AND 1=1-- hack
里得不到任何结果,说明SQL漏洞在这个程序里并不存在。实际上,绑定变量使用预编译语句是预防SQL注入的最佳方式,使用预编译的SQL语句语义不会发生改变,在SQL语句中,变量用问号?表示,黑客即使本事再大,也无法改变SQL语句的结构,像上面例子中,username变量传递的 plhwin' AND 1=1-- hack 参数,也只会当作username字符串来解释查询,从根本上杜绝了SQL注入。
2.用正则表达式过滤传入的参数
如关键字过滤:
reg = "(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|" + "(\\b(select|update|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|into|drop|execute)\\b)";
3.字符串过滤
4.js代码预防注入(后台仍然需要预防)
5.不要随意开启生产环境中Webserver的错误显示,同时不要信任任何用户输入的数据,对请求服务器的数据需要进行一定的规则和格式校验。
附sql注入系列专题:sql注入专题
同时推荐testfire.net来模拟漏洞攻击:demo.testfire.net
看不到我 / \ 看不到我~~~~
网友评论