一次团队内部比赛经历
经过这次登录框被多数人打爆的经历,我反思了很多。由于当时为了快点写好登录框,没有考虑对登录框的SQL注入进行预防。在写登录框制作总结时,也发现了自己登录框存在的漏洞,但是没有去改。。。最终导致这一惨剧的发生!!!
经过不断的修改,最终把我已知漏洞修复完毕。在这个过程中,我收获了很多,所以总结了一下。。
SQL注入介绍
所谓SQL注入式攻击,就是输入域或页面请求的查询字符串,欺骗服务器执行恶意的SQL命令。在某些表单中,用户输入的内容直接用来构造(或者影响)动态SQL命令(也就是所谓构造payload),或作为存储过程的输入参数,这类表单特别容易受到SQL注入式攻击
SQL注入发生
当应用程序使用输入内容来构造payload以访问数据库时,会发生sql注入攻击。
如果代码使用存储过程,而这些存储过程作为包含未筛选的用户输入的 字符串来传递,也会发生sql注入。
sql注入可能导致攻击者使用应用程序登陆在数据库中执行命令。如果应用程序使用特权过高的帐户连接到数据库,这种问题会变得很严重。在某些表单中,用户输入的内容直接用来构造payload,或者作为存储过程的输入参数,这些表单特别容易受到sql注入的攻击。而许多 网站程序在编写时,没有对用户输入的合法性进行判断或者程序中本身的变量处理不当,使应用程序存在安全隐患。这样,用户就可以提交一段数据库查询的代码, 根据程序返回的结果,获得一些敏感的信息或者控制整个服务器,于是sql注入就发生了。
SQL注入的预防
思路
1.永远不要信任用户的输入。对用户的输入进行校验,可以通过正则表达式,或限制长度;对单引号和双"-"进行转换等。
2.永远不要使用动态拼装sql,可以使用参数化的sql或者直接使用存储过程进行数据查询存取。
3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
4.不要把机密信息直接存放,加密或者hash掉密码和敏感的信息。
5.应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装
6.sql注入的检测方法一般采取辅助软件或网站平台来检测,软件一般采用sql注入检测工具jsky,网站平台就有亿思网站安全平台检测工具
正文
SQL注入漏洞
首先,先看一下我的登录验证的源代码
<?php
header('Content-type:text/html;charset=utf-8');
$db = mysql_connect("localhost","root","root")//mysql_connect()建立数据库连接//localhost:连接MySQL地址//root:连接MySQL用户名//root:连接MySQL密码
or die("连接数据库失败!");
mysql_select_db("demo",$db)//mysql_select_db()选择数据库,这里选择的是数据库名为demo的数据库
or die("您要选择的数据库不存在".mysql_error());
$username=$_POST['username'];//用$username存放从login.html表单提交过来的username
$password=$_POST['password'];//用$password存放从login.html表单提交过来的password
$sql="select * from users where username='$username' and password='$password'";/*用$sql存放sql查询判断命令*/
$result = mysql_query($sql);//mysql_query()规定要发送的SQL查询,查询MySQL,存放于$result。注意:查询字符串不应以分号结束
$colum= mysql_fetch_array($result);//mysql_fetch_array()获取和显示数据
if(is_array($colum)){//is_array()检测变量是否是数组
if(setcookie('username',$_POST['username'],time()+3600)){//设置cookie
header("Location:index.php");
}
else{
echo 'Cookie设置失败';
}
}
else{
echo "您的用户名或密码输入有误,<a href=login.html>请重新登录!</a>";
}
?>
漏洞1
上面的这一部分应该放在另放到一个PHP文件,并把这个PHP文件重点保护。在登录验证时,可以使用require_once() 语句,在脚本执行期间包括并运行这个PHP文件。因为如果攻击者获得了登录验证的源码,就会得到数据库的用户名和密码。
当phpstudy根目录(WWW目录)下的phpMyAdmin文件未删除时,攻击者会通过获得的数据库的用户名和密码,登进phpMyAdmin,查看管理员的数据库。(当然也可以考虑将phpMyAdmin文件删除,这样上面和下面的操作就可以忽略。当然,我没有删除)。
**注意:**若没有删除phpMyAdmin文件,还需要更改一下phpMyAdmin的登录用户名和密码,这个百度、谷歌上都有教程,可以参考一下。改好后,就可以防止直接从phpMyAdmin进入数据库啦!!
漏洞2
观察上面的SQL查询命令,发现了使用动态拼装SQL语句。直接将用户提交过来的数据(用户名和密码)直接拿去执行,并没有实现进行特殊字符过滤,这是十分危险的。
当进行SQL注入攻击时,在用户名输入框中输入:’ or 1=1#
,密码随便输入,这时候的合成后的SQL查询语句为:
$sql="select * from users where username='' or 1=1#' and password='$password'";
分析:“#”在mysql中是注释符,这样井号后面的内容将被mysql视为注释内容,这样就不会去执行了,与以下sql语句等价:select * from users where username='' or 1=1
#
可以注释掉后面的一行SQL代码 相当于去掉了一个where条件。MySQL 注释, 过滤掉后面的SQL语句,使其不起作用。因为1=1永远是都是成立的,即where子句总是为真。
应对方法:
方法一:
对于这个语句,可以使用下面的技术对用户输入的内容进行过滤:
(1)替换单引号,即把所有单独出现的单引号改成两个单引号,防止攻击者修改SQL命令的含义。再来看这个payload语句,select * from users where username=''' or ''1''=''1' and password=''' or ''1''=''1'
显然会得到与select * from users where username='' or '1'='1' and password='' or '1'='1'
不同的结果
(2)删除用户输入内容中的所有连字符,防止攻击者构造出类如select * from users where username= 'mas' —— and password =''
之类的查询,因为这类查询的后半部分已经被注释掉,不再有效,攻击者只要知道一个合法的用户登录名称,根本不需要知道用户的密码就可以顺利获得访问权限。
(3)对于用来执行查询的数据库帐户,限制其权限。用不同的用户帐户执行查询、插入、更新、删除操作。由于隔离了不同帐户可执行的操作,因而也就防止了原本用于执行select命令的地方却被用于执行insert、update或delete命令。
方法二:
先通过SQL语句查询数据库,取出相关数据
function collect_data(){
require_once ("mysql_connect.php"); //在脚本执行期间包括并运行mysql_connect.php
$sql = "select * from users"; //查询数据表语句,并用变量$sql存放
$result = mysql_query($sql);//执行MySQL语句,并将结果用变量$result存放
$colum= mysql_fetch_array($result);//获取和显示$result的数据
return $colum;//返回变量$colum的数据
}
然后再将用户提交过来的数据(用户名和密码)和返回的数据进行比对,成功比对才能登录成功。
方法三:
PHP有一个特制的功能以防止这些攻击。所有你需要做的就是使用一个函数mysql_real_escape_string()。 mysql_real_escape_string所做的是把一个输入的字符串,在MySQL查询时将它处理为用户输入的真实字符串,来防止SQL注入。就是将用户输入可能引起Mysql安全隐患的字符串比如单引号(’),用逃脱引用来表示\ '。
将这个函数应用到上面那个可能被注入的例子中:
$username = mysql_real_escape_string($_POST['username']);
$password= mysql_real_escape_string($_POST['password']);
$sql = "select*from users where username= '$username' and password='$password'";
这里要十分小心的是,mysql_real_escape_string要先成功地通过mysql_connect连接到mysql server上以后才能正常使用,如果数据库 还没连接直接使用这个函数会报错。上面经过函数转化后,$sql最后打印出来的语句为:
select*from users where username='$username\' OR 1=1 -- and password='$password\' OR 1=1 --'
也就是说变量 u s e r n a m e 和 username和 username和password所存放的数据后面的单引号(’)被转义为真实的输入字符,不再和 u s e r n a m e 和 username和 username和password前的字符进行匹配, u s e r n a m e 和 username和 username和password前的单引号将和字符串–后面的单引号进行匹配。
让我们创建一个通用的函数,你可以用任何名字来命名它,在这里,我要将它命名为"mres":
function mres($var){
if (get_magic_quotes_gpc()){/*magic_quotes_gpc作用类似addslashes(),就是对输入的字符创中的字符进行转义处理,所有的 ‘ (单引号), ” (双引号), (反斜线) and 空字符会自动转为含有反斜线的溢出字符*/
$var = stripslashes(trim($var));//stripslashes删除由 addslashes() 函数添加的反斜杠
}
return mysql_real_escape_string(trim($var));
}
现在,可以把函数简化成下面这个样子:
$username = mres($_POST['username']);
$password = mres($_POST['password']);
$sql = "select*from users where username= '$username' and password='$password'";
漏洞3
is_array() 函数用于检测变量是否是一个数组,如果检测的变量是数组,则返回 TRUE,否则返回 FALSE。
很明显当SQL语句直接在数据库执行后。没有对结果进行有无特殊字符的判断和过滤。
当然,为了配合漏洞2的第二种应对方法。这里可以进行是否全等的判断
if(($colum['username']===$username) && ($colum['password']===$password)){
if($i==1&&(setcookie('username',$_POST['username'],time()+3600))){
echo"<script type='text/javascript'>alert('登陆成功');location='index.php';</script>";
}
}
这样就解决了这个问题。
漏洞貌似总结的差不多了耶!开心!!用常规SQL注入方法已经注入不进去了。但本人能力有限,若有其他漏洞,敬请大佬告知啊。本小白感激不尽!!!
小白进阶ing