web安全之攻击

转自 知乎https://www.zhihu.com/question/22953267php

做者:潘良虎
连接:https://www.zhihu.com/question/22953267/answer/80141632
来源:知乎
著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。

多年前写过一篇 「 Web安全之SQL注入攻击技巧与防范」,今天回头再看依然有价值,就偷个懒,直接贴过来了。

Web安全简史

在Web1.0时代,人们更可能是关注服务器端动态脚本语言的安全问题,好比将一个可执行脚本(俗称Webshell)经过脚本语言的漏洞上传到服务器上,从而得到服务器权限。在Web发展初期,随着动态脚本语言的发展和普及,以及早期工程师对安全问题认知不足致使不少”安全血案”的发生,至今仍然遗留下许多历史问题,好比PHP语言至今仍然没法从语言自己杜绝「文件包含漏洞」(参见这里),只能依靠工程师良好的代码规范和安全意识。html


伴随着Web2.0、社交网络、微博等一系列新型互联网产品的兴起,基于Web环境的互联网应用愈来愈普遍,Web攻击的手段也愈来愈多样,Web安全史上的一个重要里程碑是大约1999年发现的SQL注入攻击,以后的XSS,CSRF等攻击手段愈发强大,Web攻击的思路也从服务端转向了客户端,转向了浏览器和用户。mysql


在安全领域,通常用帽子的颜色来比喻黑客的善与恶,白帽子是指那些工做在反黑客领域的技术专家,这个群体是”善”的的象征;而黑帽子则是指那些利用黑客技术形成破坏甚至谋取私利形成犯罪的群体,他们是”恶”的表明。web


“白帽子”和”黑帽子”是两个彻底对立的群体。对于黑帽子而言,他们只要找到系统的一个切入点就能够达到入侵破坏的目的,而白帽子必须将本身系统全部可能被突破的地方都设防,以保证系统的安全运行。算法


这看起来好像是不公平的,可是安全世界里的规则就是这样,可能咱们的网站1000处都布防的很好,考虑的很周到,可是只要有一个地方疏忽了,攻击者就会利用这个点进行突破,让咱们另外的1000处努力白费。sql


常见攻击方式

通常说来,在Web安全领域,常见的攻击方式大概有如下几种:
一、SQL注入攻击
二、跨站脚本攻击 - XSS
三、跨站伪造请求攻击 - CSRF
四、文件上传漏洞攻击
五、分布式拒绝服务攻击 - DDOSshell



限于篇幅,本篇只讨论SQL注入攻击的技巧与防范。

SQL注入常见攻击技巧

SQL注入攻击是Web安全史上的一个重要里程碑,它从1999年首次进入人们的视线,至今已经有十几年的历史了,虽然咱们如今已经有了很全面的防范对策,可是它的威力仍然不容小觑,SQL注入攻击至今仍然是Web安全领域中的一个重要组成部分。数据库

以PHP+MySQL为例,让咱们以一个Web网站中最基本的用户系统来作实例演示,看看SQL注入到底是怎么发生的。api


一、建立一个名为demo的数据库:浏览器

CREATE DATABASE `demo` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; 

二、建立一个名为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参数,在页面打印出这个会员的详细信息,编写 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' 

注意:在MySQL中,最后连续的两个减号表示忽略此SQL减号后面的语句,我本机的MySQL版本号为5.6.12,目前几乎全部SQL注入实例都是直接采用两个减号结尾,可是实际测试,这个版本号的MySQL要求两个减号后面必需要有空格才能正常注入,而浏览器是会自动删除掉URL尾部空格的,因此咱们的注入会在两个减号后面统一添加任意一个字符或单词,本篇文章的SQL注入实例统一以 -- hack 结尾。


通过上面的SQL注入后,本来想要执行查询会员详情的SQL语句,此时还额外执行了 SHOW TABLES; 语句,这显然不是开发者的本意,此时能够在浏览器里看到页面的输出:

Array (  [0] => Array  (  [uid] => 1  [username] => plhwin  )  [1] => Array  (  [Tables_in_demo] => user  ) ) 

你能清晰的看到,除了会员的信息,数据库表的名字user也被打印在了页面上,若是做恶的黑客此时将参数换成 plhwin';DROP TABLE user-- hack,那将产生灾难性的严重结果,当你在浏览器中执行 http://localhost/test/userinfo.php?username=plhwin';DROP TABLE user-- hack 这个URL后,你会发现整个 user 数据表都消失不见了。


经过上面的例子,你们已经认识到SQL注入攻击的危害性,可是仍然会有人心存疑问,MySQL默认驱动的mysql_query方法如今已经不支持多条语句同时执行了,大部分开发者怎么可能像上面的演示程序那样又麻烦又不安全。


是的,在PHP程序中,MySQL是不容许在一个mysql_query中使用分号执行多SQL语句的,这使得不少开发者都认为MySQL自己就不容许多语句执行了,但实际上MySQL早在4.1版本就容许多语句执行,经过PHP的源代码,咱们发现其实只是PHP语言自身限制了这种用法,具体状况你们能够看看这篇文章「PHP+MySQL多语句执行」。


实例二

若是系统不容许同时执行多条SQL语句,那么SQL注入攻击是否是就再也不这么可怕呢?答案是否认的,咱们仍然以上面的user数据表,用Web网站中经常使用的会员登陆系统来作另一个场景实例,编写程序login.php,代码以下:

<?php if($_POST){ $link = mysql_connect("localhost", "root", "root"); mysql_select_db('demo', $link); $username = empty($_POST['username']) ? '' : $_POST['username']; $password = empty($_POST['password']) ? '' : $_POST['password']; $md5password = md5($password); $sql = "SELECT uid,username FROM user WHERE username='{$username}' AND password='{$md5password}'"; $query = mysql_query($sql, $link); $userinfo = mysql_fetch_array($query, MYSQL_ASSOC); if(!empty($userinfo)){ //登陆成功,打印出会员信息 echo '<pre>',print_r($userinfo, 1),'</pre>'; } else { echo "用户名不存在或密码错误!"; } } ?> <!DOCTYPE html> <html> <head>  <meta charset="utf-8">  <title>Web登陆系统SQL注入实例</title> </head> <body>  <form name="LOGIN_FORM" method="post" action="">  登陆账号: <input type="text" name="username" value="" size=30 /><br /><br />  登陆密码: <input type="text" name="password" value="" size=30 /><br /><br />  <input type="submit" value="登陆" />  </form> </body> </html> 

此时若是输入正确的用户名 plhwin 和密码 123456,执行的SQL语句为:

SELECT uid,username FROM user WHERE username='plhwin' AND password='e10adc3949ba59abbe56e057f20f883e'

上面语句没有任何问题,能够看到页面打印出了登陆成功后的会员信息,但若是有捣蛋鬼输入的用户名为 plhwin' AND 1=1-- hack,密码随意输入,好比 aaaaaa,那么拼接以后的SQL查询语句就变成了以下内容:

SELECT uid,username FROM user WHERE username='plhwin' AND 1=1-- hack' AND password='0b4e7a0e5fe84ad35fb5f95b9ceeac79'

执行上面的SQL语句,由于 1=1 是永远成立的条件,这意味着黑客只须要知作别人的会员名,无需知道密码就能顺利登陆到系统。


如何肯定SQL注入漏洞

经过以上的实例,咱们仍然还会有疑问:黑客并不知道咱们程序代码的逻辑和SQL语句的写法,他是如何肯定一个网站是否存在SQL注入漏洞呢?通常说来有如下2种途径:



一、错误提示

若是目标Web网站开启了错误显示,攻击者就能够经过反复调整发送的参数、查看页面打印的错误信息,推测出Web网站使用的数据库和开发语言等重要信息。



二、盲注

除非运维人员疏忽,不然大部分的Web运营网站应该都关闭了错误提示信息,此时攻击者通常会采用盲注的技巧来进行反复的尝试判断。 仍然以上面的数据表user为例,咱们以前的查看会员详情页面的url地址为 userinfo.php?username=plhwin,此时黑客分别访问 userinfo.php?username=plhwin' AND 1=1-- hack userinfo.php?username=plhwin' AND 1=2-- hack,若是前者访问能返回正常的信息然后者不能,就基本能够判断此网站存在SQL注入漏洞,由于后者的 1=2 这个表达式永远不成立,因此即便username传入了正确的参数也没法经过,由此能够推断这个页面存在SQL注入漏洞,而且能够经过username参数进行注入。



如何防护SQL注入

对于服务器配置层面的防范,应该保证生产环境的Webserver是关闭错误信息的,好比PHP在生产环境的配置文件php.ini中的display_errors应该设置为Off,这样就关闭了错误提示,下面咱们更多的从编码的角度来看看如何防范SQL注入。


上面用两个实例分析了SQL注入攻击的技巧,能够看到,但凡是有SQL注入漏洞的程序,都是由于程序要接受来自客户端用户输入的变量或URL传递的参数,而且这个变量或参数是组成SQL语句的一部分,对于用户输入的内容或传递的参数,咱们应该要时刻保持警戒,这是安全领域里的「外部数据不可信任」的原则,纵观Web安全领域的各类攻击方式,大多数都是由于开发者违反了这个原则而致使的,因此天然能想到的,就是从变量的检测、过滤、验证下手,确保变量是开发者所预想的。


一、检查变量数据类型和格式

若是你的SQL语句是相似where id={$id}这种形式,数据库里全部的id都是数字,那么就应该在SQL被执行前,检查确保变量id是int类型;若是是接受邮箱,那就应该检查并严格确保变量必定是邮箱的格式,其余的类型好比日期、时间等也是一个道理。总结起来:只要是有固定格式的变量,在SQL语句执行前,应该严格按照固定格式去检查,确保变量是咱们预想的格式,这样很大程度上能够避免SQL注入攻击。


好比,咱们前面接受username参数例子中,咱们的产品设计应该是在用户注册的一开始,就有一个用户名的规则,好比5-20个字符,只能由大小写字母、数字以及一些安全的符号组成,不包含特殊字符。此时咱们应该有一个check_username的函数来进行统一的检查。不过,仍然有不少例外状况并不能应用到这一准则,好比文章发布系统,评论系统等必需要容许用户提交任意字符串的场景,这就须要采用过滤等其余方案了。


二、过滤特殊符号

对于没法肯定固定格式的变量,必定要进行特殊符号过滤或转义处理。以PHP为例,一般是采用addslashes函数,它会在指定的预约义字符前添加反斜杠转义,这些预约义的字符是:单引号 (') 双引号 (") 反斜杠 (\) NULL。


来看2条SQL语句:

$uid = isset($_GET['uid']) ? $_GET['uid'] : 0; $uid = addslashes(uid); $sql = "SELECT uid,username FROM user WHERE uid='{$uid}'"; 

以及

$uid = isset($_GET['uid']) ? $_GET['uid'] : 0; $uid = addslashes(uid); $sql = "SELECT uid,username FROM user WHERE uid={$uid}"; 

上面两个查询语句都通过了php的addslashes函数过滤转义,但在安全性上却大不相同,在MySQL中,对于int类型字段的条件查询,上面个语句的查询效果彻底同样,因为第一句SQL的变量被单引号包含起来,SQL注入的时候,黑客面临的首要问题是必需要先闭合前面的单引号,这样才能使后面的语句做为SQL执行,而且还要注释掉原SQL语句中的后面的单引号,这样才能够成功注入,因为代码里使用了addslashes函数,黑客的攻击会无从下手,但第二句没有用引号包含变量,那黑客也不用考虑去闭合、注释,因此即使一样采用addslashes转义,也仍是存在SQL攻击漏洞。


对于PHP程序+MySQL构架的程序,在动态的SQL语句中,使用单引号把变量包含起来配合addslashes函数是应对SQL注入攻击的有效手段,但这作的还不够,像上面的2条SQL语句,根据「检查数据类型」的原则,uid都应该通过intval函数格式为int型,这样不只能有效避免第二条语句的SQL注入漏洞,还能使得程序看起来更天然,尤为是在NoSQL(如MongoDB)中,变量类型必定要与字段类型相匹配才能够。


从上面能够看出,第二个SQL语句是有漏洞的,不过因为使用了addslashes函数,你会发现黑客的攻击语句也存在不能使用特殊符号的条件限制,相似where username='plhwin'这样的攻击语句是无法执行的,可是黑客能够将字符串转为16进制编码数据或使用char函数进行转化,一样能达到相同的目的,若是对这部份内容感兴趣,能够点击这里查看。并且因为SQL保留关键字,如「HAVING」、「ORDER BY」的存在,即便是基于黑白名单的过滤方法仍然会有或多或少问题,那么是否还有其余方法来防护SQL注入呢?


三、绑定变量,使用预编译语句

MySQL的mysqli驱动提供了预编译语句的支持,不一样的程序语言,都分别有使用预编译语句的方法,咱们这里仍然以PHP为例,编写userinfo2.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注入攻击的发生。


数据库信息加密安全

相信你们都还对2011年爆出的CSDN拖库事件记忆犹新,这件事情致使CSDN处在风口浪尖被你们痛骂的缘由就在于他们居然明文存储用户的密码,这引起了科技界对用户信息安全尤为是密码安全的强烈关注,咱们在防范SQL注入的发生的同时,也应该未雨绸缪,说不定下一个被拖库的就是你,谁知道呢。


在Web开发中,传统的加解密大体能够分为三种:

一、对称加密:

即加密方和解密方都使用相同的加密算法和密钥,这种方案的密钥的保存很是关键,由于算法是公开的,而密钥是保密的,一旦密匙泄露,黑客仍然能够轻易解密。常见的对称加密算法有:AES、DES等。


二、非对称加密:

即便用不一样的密钥来进行加解密,密钥被分为公钥和私钥,用私钥加密的数据必须使用公钥来解密,一样用公钥加密的数据必须用对应的私钥来解密,常见的非对称加密算法有:RSA等。


三、不可逆加密:

利用哈希算法使数据加密以后没法解密回原数据,这样的哈希算法经常使用的有:md五、SHA-1等。


在咱们上面登陆系统的示例代码中,$md5password = md5($password); 从这句代码能够看到采用了md5的不可逆加密算法来存储密码,这也是多年来业界经常使用的密码加密算法,可是这仍然不安全。为何呢?


这是由于md5加密有一个特色:一样的字符串通过md5哈希计算以后生成的加密字符串也是相同的,因为业界采用这种加密的方式由来已久,黑客们也准备了本身强大的md5彩虹表来逆向匹配加密前的字符串,这种用于逆向反推MD5加密的彩虹表在互联网上随处可见,在Google里使用md5 解密做为关键词搜索,一下就能找到md5在线破解网站,把咱们插入用户数据时候的MD5加密字符串e10adc3949ba59abbe56e057f20f883e填入进去,瞬间就能获得加密前的密码:123456。固然也并非每个都能成功,但能够确定的是,这个彩虹表会愈来愈完善。


因此,咱们有迫切的需求采用更好的方法对密码数据进行不可逆加密,一般的作法是为每一个用户肯定不一样的密码加盐(salt)后,再混合用户的真实密码进行md5加密,如如下代码:

<?php //用户注册时候设置的password $password = $_POST['password']; //md5加密,传统作法直接将加密后的字符串存入数据库,但这不够,咱们继续改良 $passwordmd5 = md5($password); //为用户生成不一样的密码盐,算法能够根据本身业务的须要而不一样 $salt = substr(uniqid(rand()), -6); //新的加密字符串包含了密码盐 $passwordmd5 = md5($passwordmd5.$salt); 

小结

一、不要随意开启生产环境中Webserver的错误显示。二、永远不要信任来自用户端的变量输入,有固定格式的变量必定要严格检查对应的格式,没有固定格式的变量须要对引号等特殊字符进行必要的过滤转义。三、使用预编译绑定变量的SQL语句。四、作好数据库账号权限管理。五、严格加密处理用户的机密信息。

相关文章
相关标签/搜索