SQL注入是PHP运用最常见的漏洞之一,很多开发人员都会时刻提防着它,防SQL注入的普遍做法是对数据输入进行过滤,以及对发送到数据库的数据进行转义。其实就是永远不要相信用户输入数据。为了更好的理解SQL注入,笔者今天自己尝试用SQL“攻击”自己了一次。以下是建立攻击的一个过程。
1、建立用户表
我用PHPMyAdmin在我test数据库建立了一张user表,表中有三个字段,分别是用户名、密码、邮箱,然后插入了一条测试数据
用户名是:周运金,密码是test(用了MD5加密)
2、建立登陆页面form.html
<html>
<head>
<title>Sql注入</title>
<meta http-equiv="content-type"content="text/html;charset=utf-8">
</head>
<body>
<form action="validate.php" method="post">
<fieldset >
<legend>Sql注入</legend>
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密 码:</td>
<td><input type="text" name="password"></td>
</tr>
<tr>
<td><input type="submit" value="提交"></td>
<td><input type="reset" value="重置"></td>
</tr>
</table>
</fieldset>
</form>
</body>
</html>
效果:
3、创建处理登陆Validate.php
<?php
//面向对象的连接方式
$mysqli =new mysqli("localhost","root","123","test");
if(!$mysqli ){
echo mysqli_connect_error();
}
$mysqli->set_charset("utf8"); $mysqli->query("set names 'utf8'");
$name=$_POST['username'];
$pwd=md5($_POST['password']);
$sql="select * from user where username='$name' and password='$pwd'";
$query=$mysqli->query($sql);
if($query == false){
echo $mysqli->error;
}
else{
$rows =$query->num_rows;
if($rows){
header("Location:manage.php");
}else{
echo "您的用户名或密码输入有误,<a href='form.html'>请重新登录!</a>";
}
}
$mysqli->close();
?>
这里说明一下,原始MySQL在PHP5.5之后已经被php抛弃,采用面像对象连接的MYSQLI。
4、建立登陆成功的页面manage.php
<?php
echo "You are a manager";
?>
这样就完成了一个有SQL注入漏洞的登陆程序了。很明显程序没有对用户输入的数据进行处理就直接放进sql语句里面了。这是很危险的做法。
接下来就开始攻击一下吧,做法其实很简单只要去拼凑sql语句就好了。
我先输入正确的用户名和密码:
为了显示密码我这里没有用密码框了。输入完之后成功登陆
接下来正式注入了,用户名输入:’ or 1=1# 密码随便输入
点击提交之后:
居然登陆成功了。哈哈哈,接下来分析一下注入后SQL语句吧:
我在sql语句那里设置了一个断点,看一下拼接后的sql
这里可以看到拼接后的sql语句是:
select * from user where username=” or 1=1#’ and password=’202cb962ac59075b964b07152d234b70’这里的#是mysql的注释符,意思就是忽略后面的sql语句,这样的话就不用验证了,而且在username后面还有一句逻辑语句or 1=1,这样的话这条语句永远成立,所以就通过了验证。
接下来就谈谈常见的方SQL注入方法:
1、最常见的就是采用mysqli_real_escape_string函数进行转义一些特殊的字符比如\n、\r、\、’、” 等(在查找mysql_real_escape_string函数的时候发现PHP文档说这个函数在php5.5之后就被抛弃了,改用mysqli_real_escape_string,看来PHP要全面使用面向对象的mysqli了),就像刚才的注入有个单引号,用了这个函数之后就会被转义,这样拼接就失败了。我们来看看再用之前的’ or 1=1#去拼接,加入这个函数之后会怎样:
$name=mysqli_real_escape_string($mysqli,$_POST['username']); //必须使用数据库连接,这样看来是专门为防sql注入准备的,比较安全
$pwd=mysqli_real_escape_string($mysqli,md5($_POST['password']));
结果不能登录了,设个断点看一下转义之后sql语句:
这个是PHP文档给出的例子
<?php
// Assume this is a simple comments form with a name and comment.
$name = mysqli_real_escape_string($conn, $_POST['name']);
$comments = mysqli_real_escape_string($conn, $_POST['comments']);
// Here is where most of the action happens. But see note below
// on dumping back out from the database
// We should use the ENT_QUOTES flag second parameter...
$name = htmlspecialchars($name);
$comments = htmlspecialchars($comments);
$insert_sql = "INSERT INTO tbl_comments ( c_id, c_name, c_comments ) VALUES ( DEFAULT, '" . $name . "', '" . $comments . "')";
$res = mysqli_query($conn, $insert_sql);
if ( $res === false ) {
// Something went wrong, handle it
}
// Now output page showing comments
?>
看到单引号‘被转义成了/’了这样的话拼接就没用了。
二、打开magic_quotes_gpc来防止SQL注入
这个原理跟第一个的原理类似,是将GET、POST、COOKIE传过来的数据进行自动转义,相当于用addslshes()函数进行转义。但是这种方式没有办法防止当参数是数字型的sql攻击,因为数字是没有单引号或者双引号的。解决的办法是用intaval()函数强制将字符数据转换成数字。如果开启了magic_quotes_gpc=on,在第一个方法中记得用stripslashes函数去掉/
三、自定义过滤函数
以下是W3C给出的一个过滤函数我将转义函数改了
function check_input($value,$con)
{
// 去除斜杠
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}
// 如果不是数字则加引号
if (!is_numeric($value))
{
$value = "'" . mysqli_real_escape_string($con,$value) . "'";
}
return $value;
}
这个函数考虑到使用mysqli_real_escape_string比使用addslshes()更加安全
最后注意了:
在写这篇文章的时候,笔者参考了一篇文章说sql注入可以绕开以上的方法了,吓得得我一身冷汗,看了之后果然觉得厉害。文章中建议使用不要用mysql_query了而是用PDO和MYSQLi来代替mysql_query,心想还好我用的是mysqli,它采用了mysqli的Prepared Statement机制可以有效解决sql注入。大家可以参考一下
文章地址:参考文章
总结:
1、sql注入的方式其实很简单,但是后果却是很致命的,所以开发人员一定记住永远不要去相信客户的数据。
2、防sql注入的方法有很多,但是一定得保持一定得技术更新,因为黑客的技术越来越厉害了,要经常更新这方面的防护知识,随时保持最新的防漏洞知识。