SQL注入攻击的一般方法
以下代码是一个简单的数据输出页面,该页面通过从
浏览器
地址栏获得参数来显示相应的内容。
<?php
$id = $_GET['id']; //获得参数
$sql = "SELECT * FROM posts WHERE postid = $id"; //定义SQL
mysql_connect("localhost", "root"); //连接服务器
mysql_select_db("guestbook"); //连接数据库
$rs = mysql_query($sql); //执行SQL
if(mysql_numrows($rs)) //如果记录存在,则输出
{
$row_rs = mysql_fetch_assoc($rs);
echo "TOPIC: ".$row_rs['topic']."<br>";
}
else //否则提示错误
{
echo "Record not found!";
}
?>
上面的代码通过获得id参数的值来进行数据查询,并在页面上显示相应的数据信息。如果数据无法从数据库中找到,则显示相应的错误信息。例如,从浏览器上访问http://127.0.0.1/ bugs/eg2.php?id=9可以获得如下结果。
Topic: This is a test!!
这里,SQL注入的方法是通过对id参数的赋值来构造一个用户自定义的SQL语句给
程序
执行来实现的。例如,从浏览器上访问http://127.0.0.1/bugs/eg2.php?id=9 and 1=1,仍然可以看到上面的结果。但是,此时程序实际上执行的SQL语句如下所示。
SELECT * FROM posts WHERE postid = 9 and 1=1
可以看出,由于程序简单地将id参数放置在SQL语句中,实际上程序已经允许用户自由地运行SQL语句了。从浏览器上访问http://127.0.0.1/bugs/eg2.php?id=9 and 1=2,可以看到页面的运行结果如下所示。
Record not found!
这是因为程序执行了如下的SQL语句致使记录没有成功返回而造成的。
SELECT * FROM posts WHERE postid = 9 and 1=2
有了这一基础,就可以通过猜测存储管理员的用户名和密码的表名和列名来对网站进行SQL注入攻击了。例如,当前用于存储管理员用户的数据表名为admin,在浏览器上访问http://127.0.0.1/bugs/eg2.php?id=9 and (select length(adminname) from admin limit 0,1)>0可以使PHP程序对admin表进行查询并获得当前用于存储管理员账号和密码的列名。如果上面的访问可以使页面内容正常显示,则说明猜测正确。实际上,上面的访问执行了下面的SQL语句。
SELECT * FROM posts WHERE postid = 9 and (select length(adminname) from adminlimit 0,1)>0
就这样,通过对数据库字段长度的逻辑判断获得了存储管理员账号和密码的表名和列名。下一步,就可以通过对管理员账号和密码的每个字符的ASCII码的判断来获得管理员账号了。例如,当前管理员的账号为admin,在浏览器上访问http://127.0.0.1/bugs/eg2.php?id=9 and (select ascii(substr(adminname,1,1)) from admin limit 0,1)=97可以使网页正常显示,这是因为当前管理员账号为admin,通过使用ascii函数和substr函数来获取第一个字母的ASCII码为 97。当SQL中的逻辑成立时,页面就可以正常地显示了。
漏洞防护措施
前面所述的攻击方法虽然很烦琐,但是只要有充足的时间,完全可以通过前面所示的PHP代码来获取管理员的账号和密码,并对网站数据进行修改。解决这一问题的方法很简单,就是对通过地址栏传入的参数的值进行判断或者格式化处理,如以下代码所示。
<?php
$id = (int)$_GET['id']; //通过对id参数进行数据类型转换过滤掉非法SQL字符
$sql = "SELECT * FROM posts WHERE postid = $id";//定义SQL
echo "SQL: ".$sql."<br>"; //输出SQL
mysql_connect("localhost", "root"); //连接服务器
mysql_select_db("guestbook"); //连接数据库
$rs = mysql_query($sql); //执行SQL
if(mysql_numrows($rs)) //判断是否存在记录
{
$row_rs = mysql_fetch_assoc($rs);
echo "TOPIC: ".$row_rs['topic']."<br>";
}
else //如果记录不存在,则输出错误信息
{
echo "Record not found!";
}
?>
上面的代码首先将传入的参数的值转换为整型数据,然后再放入SQL语句中执行。这一简单的举措可以将地址栏中的非数字字符全部过滤掉,大大增强了系统的安全性。