Web安全防护——SQL注入 – 作者:magic24

SQL注入是指攻击者通过把恶意SQL命令插入到Web表单的输入域或页面请求的查询字符串中,并且插入的恶意SQL命令会导致原有SQL语句作用发生改变,从而达到欺骗服务器执行恶意的SQL命令的一种攻击方式。

SQL注入攻击已经多年蝉联OWASP高位漏洞的前三名,可见SQL注入的危害程度。SQL注入会直接威胁数据库的数据安全,因为它可实现任意数据查询,如查询管理员的密码、用户高价值数据等。严重时会发生“脱库”的高危行为。更有甚者,如果数据库开启了写权限,攻击者可利用数据库的写功能及特定函数,实现木马自动部署、系统提权等后续攻击。总体来说,SQL注入的危害极为严重。

1.SQL注入攻击的原理

在网站应用中,如用户查询某个信息或者进行订单查询等业务时,用户提交相关查询参数,服务器接收到参数后进行处理,再将处理后的参数提交给数据库进行查询。之后将数据库返回的结果显示在页面上,这样就完成了一次查询过程。

标准查询过程如下图所示:

1602661437_5f86ac3d8b8aee18245b4.png

SQL注入的产生原因是用户提交参数的合法性。假设用户查询某个订单号(如8位数字12144217),服务器接收到用户提交信息后,将参数提交给数据库进行查询。但是如果用户提交的数据中,不仅仅包括订单号,而且在订单号后面拼接了查询语句,恰好服务器没有对用户输入的参数进行有效过滤,那么数据库就会根据用户提交的语句进行查询,返回更多的信息。

SQL注入的产生原因通常有以下几点:

  1. 参数处理问题:
    • 对用户参数进行了错误的类型处理
    • 转义字符处理环节产生遗漏或可被绕过
  2. 服务配置问题:
    • 不安全的数据库配置
    • Web应用对错误的处理方式不当
      • 不当的类型处理
      • 不安全的数据库配置
      • 不合理的查询集处理
      • 不当的错误处理
      • 转义字符处理不当
      • 多个提交处理不当

可见,任何环节处理不当,均可能产生SQL注入漏洞。通俗地说,计算机没有人脑那么智能,无法自动识别用户提交的SQL查询内容的真实目的。因此,只能利用以下传统手段来避免SQL注入漏洞。

  1. 采用黑名单、白名单等形式对用户提交的信息进行过滤,一旦发现用户参数中出现敏感的词或者内容,则将其删除,使得执行失败。
  2. 采用参数化查询方式,强制用户输入的数据作为参数,从而避免数据库查询语句被攻击者恶意构造。

每种防护手段的使用均需付出一定代价,表现为:影响当前系统性能、降低用户的业务体验等。无论采用哪种防护方法,都要与业务实际情况相结合,采用适合、有效的措施进行防护。

2. SQL注入攻击的分类

从攻击角度来看,SQL注入经常会根据前台的数据是否回显和后台安全配置及防护情况进行区分。先分析前台数据回显情况,主要有两种类型:

1)回显注入

用户发起查询请求,服务器将查询结果返回到页面中进行显示,典型场景为查询某篇文章、查询某个用户信息等、重点在于服务器将用户的查询请求返回到页面上进行显示。

2)盲注

盲注的特点是用户发起请求(并不一定是查询),服务器接收到请求后在数据库进行相应操作,并根据返回结果执行后续流程。在这个过程中,服务器并不会将查询结果返回到页面进行显示,这样就是盲注。典型场景为在用户注册功能中,只提示用户名是否被注册,但并不会返回数据。

回显注入与盲注在流程上没有太多差别,都是先确定注入点,然后进行查库、查表、查字段等工作,但是盲注并不会有直接的查询内容显示效果,因此在利用难度方面盲注要比回显注入大得多。

在了解SQL注入的流程之前需要知道的是:在实际使用中,Web服务器的配置情况会直接决定SQL注入的成功与否。在服务器配置及防护方面,能影响SQL注入过程的主要有以下几方面:

1)数据库是否开启报错请求。

2)服务器端是否允许数据库报错展示。

3)有过滤代码机制。

4)服务器开启了参数化查询或对查询过程预编译。

5)服务器对查询进行了限速。

3. 回显注入攻击的流程

SQL注入攻击只是在每个攻击流程上有所变化,整体流程及防护思路基本一致。

典型的攻击流程如下:

1)判断Web系统使用的脚本语言,发现注入点,并确定是否存在SQL注入漏洞。

2)判断Web系统的数据库类型。

3)判断数据库中表及相应字段的结构。

4)构造注入语句,得到表中数据内容。

5)查找网站管理员后台,用得到的管理员账号和密码登录。

6)结合其他漏洞,上传Webshell并持续连接。

7)进一步提权,得到服务器的系统权限。

以上为标准的SQL注入流程,最终的效果是获取目标站点的系统控制权限。在实际安全防护中,由于应用系统的业务特点各不相同,导致在每个阶段可获取的内容并不相同。并且5、6、7步其实与SQL注入没有直接关系,但可以归类为SQL注入后的延伸攻击手段。

3.1 SQL手工注入的思路

SQL注入攻击中常见的攻击工具有“啊D注入工具”“havji”“SQLmap”“pangolin”等,这些工具用法简单,能够提供清晰的UI界面,并自带扫描功能,可自动寻找注入点、自动查表名、列名、字段名,并可直接注入,可查到数据库数据信息。其标准流程如下:

查找注入点→查库名→查表名→查字段名→查重点数据

这里不探讨如何利用工具,毕竟工具具有良好的UI界面,有Web开发及数据库基础的使用者都能很快上手。但是面对一些复杂的环境,这些工具就不一定适用了。近年来也出现了像SQLmap这样的测试工具,其SQL注入能力强大到足以不用手动开展。但是,要分析一个注入过程及原理,必须以手工注入的方式进行。

3.2 寻找注入点

在手工注入时,基本的方法是在参数后面加单引号,观察其返回页面的内容。由于添加的单引号回到主SQL语句执行错误,因此若存在SQL注入漏洞,当前页面会报错,或者出现查询内容不显示的情况。这是手工注入的第一个步骤。下面来看一下经典的“1=1”“1=2”测试法,也叫作“恒真”“恒假”测试法。

访问以下三个链接,并观察页面的特点。

http://192.168.0.106:10000/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#

http://192.168.0.106:10000/dvwa/vulnerabilities/sqli/?id=1+%27+and+1%3D1+%23&Submit=Submit#

http://192.168.0.106:10000/dvwa/vulnerabilities/sqli/?id=1+%27+and+1%3D2+%23&Submit=Submit#

访问以上三个链接时,产生的情况可能有以下几种:

  • 页面没有发生变化:访问三个链接,显示的页面没有任何不同。这种情况说明后台针对此查询点的过滤比较严格,是否存在SQL注入漏洞还需进行后续测试。
  • 页面中少了部分内容:如访问前两个链接正常,第三个页面里有明显的内容缺失,则基本可以确定有漏洞存在。接下来就需要检测是否有union显示位,如果没有,也可以尝试进行bool注入(详情参见后续关于盲注的介绍)。
  • 错误回显:如果访问第三个链接后出现数据库报错信息,那么可以判定当前查询点存在注入,用标准的回显注入法即可实现SQL注入攻击。
  • 跳转到默认界面:如果第一个链接显示正常,第二、第三个链接直接跳转到首页或其他默认页面,那么可能是后台有验证逻辑,或者是有在线防护系统或防护软件提供实时保护。之后可尝试绕过防护工具的思路(大小写混用、编码等)。
  • 直接关闭链接:如果在访问上述第二、第三个链接时出现访问失败,那么这种情况下可尝试利用burpsuite抓取服务器的响应包,观察包头server字段内容。根据经验,这种情况通常为防护类工具直接开启在线阻断导致,后续可利用编码、换行等方式尝试绕过(极难成功)。

另外,还有一些其他的测试方法,比如id=2-1、id=(select 2)、id=2/*x*/等语句,观察其数据库是否会执行SQL语句中的运算指令。若执行成功,说明其存在SQL注入。也就是说,尝试构造错误语句触发数据库查询失败,并观察Web页面针对查询失败的显示结果,从而判断是否存在可用的注入点。

如果Web服务器关闭了错误回显或根本没有显示任何查询结果,可通过判断返回时间等手段,并观察服务器的动态等,确认注入漏洞是否存在,这就是常见的“SQL盲注”。

3.3 通过回显位确定字段数

回显位指的是数据库查询结果在前端页面中显示出来的位置,也就是查询结果返回的是数据库的哪列。在SQL注入中,一般利用order by、union select等命令获取回显位的信息来猜测表内容。具体使用方法如下:

xxx.php?id=1′ and 1=2 union select 1,2

xxx.php?id=1' and 1=2 union select 1,2

使用order by的主要目的是判断当前数据表的列数,执行效果如图所示。

1602664132_5f86b6c4335d02597d440.png

在测试过程中可修改对应的数值。如果输入的数值大于当前数据表的列数,则查询语句执行失败,由于页面没有隐藏报错信息,因此报错内容将进行显示,如下图所示。

1602664343_5f86b797c6688172db9d2.png

当获取到数据表的列数之后,可利用union select来尝试判断回显位。参考下列语句:

xxx.php?id=1' and 1=2 union select 1,2

执行效果如图所示。

1602664467_5f86b81334c05e9e35d64.png

可以看到回显的First name: adminSurname: admin变成了First name: 1Surname: 2

即可判断出回显位是1,2。

3.4注入并获取数据

接下来尝试注入获取表、字段、数据的信息。

在MySQL 5.0之后的版本中,数据库内置了一个库information_schema,用于存储当前数据库中的所有库名、表名等信息。因此,可利用SQL注入方式,通过远程注入查询语句方式实现直接读取MySQL数据库中的information_schema库的信息,从而获取感兴趣的信息。SQL注入在information_schema库中主要涉及内容可参考下图。

1602664814_5f86b96ef0bddc91cc9f2.png

在SQL注入过程中,可直接查询information_schema库来获得目标信息。这里要注意的是,如果需要对表名进行爆破,那么表名需为十六进制格式。基本注入语句可参考以下格式:

输出数据库名,数据库版本:

1' and 1=2 union select database(),@@version#

1602665090_5f86ba82a083fed9c01cd.png

输出表名:

1' and 1=2 union select 1,group_concat(table_name) from information_schema.tables where table_schema = database()#

1602665170_5f86bad25d521e971a44b.png

输出字段名:

1' and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name = 'user'#

1602665224_5f86bb08399223514d75f.png

输出字段值:

1' and 1=2 union select group_concat(user),group_concat(password) from users#

1602665465_5f86bbf9ddffe6f64b52c.png

至此,完整的注入流程已经介绍完毕,效果是可直接获取管理员的用户名及密码。真实手工注入流程远没有这么简单,这里仅给出一些关键语句,用以理解SQL注入流程。对注入流程及方式有兴趣的人可查阅相关数据库操作方式等。

当然,针对不同数据库,如Access、Oracle、MS SQL Server等,其注入方式均不相同,但SQL注入思路基本一致。

4. 盲注攻击的流程

相对于普通人来说,盲注的难点在于前台没有回显位,导致无法直接获取到有效信息。只能对注入语句执行的正确与否进行判断,也就是只有truefalse的区别,因此盲注的攻击难度较大。在实施盲注时,关键在于合理地实现对目标数据的猜测,并利用时间延迟等手段实现猜测的正确与否的证明。

盲注的攻击流程的整体思路与标准注入过程相同,只是对标准注入中的语句进行了修改,以实现相同的目的。也就是说,在回显注入语句中额外增加判断方式,是的返回结果只有true或false。以常见语句举例如下:

  • 判断当前数据库版本

left(version(),1)=5#

数据库执行方式为,从左判断当前版本的第一位是否等于5。

  • 判断数据库密码

AND ascii(substring((SELECT password FROM users where id=1),1,1))=49

查询USER表中的ID=1的password数据的第一项值的ASCII码是不是49

  • 利用时间延迟判断正确与否
union select if(substring(password,1,1)='a',benchmark(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'

其中时间延时利用了BENCHMARK函数,其意义是如果判断正确,则将1济宁SHA1运算10000次,这样就产生了时间方面的滞后(由于100000次运算导致)。利用BENCHMARK函数时,如果目标服务器的数据性能不强,极可能导致目标服务器宕机。因此推荐使用sleep函数,其用法为sleep(N),N为延迟秒数。当语句执行成功时,系统会根据sleep的时限进行延时输出。因此利用出现的延时情况来判断SQL注入语句是否成功执行。

盲注时,在构造语句方面有着非常多的可能性,构造方法千变万化。总之是利用可观察到的特点来对注入的判断语句进行正确与否的显示。

相对于回显注入的漏洞环境,盲注主要表现在Web应用并不会将数据库的回显在前台进行显示,导致无法直接看到预期数据库的目标内容。基于盲注漏洞的环境,会导致在构造SQL注入语句方面比较复杂。总体来说,盲注主要特点是:将想要查询的数据作为目标,构造SQL条件判断语句,与要查询的数据进行比较,并让数据库告知当前语句执行是否正确。相比于普通注入直接获取数据,盲注要进行大量的尝试。

4.1 寻找注入点

在寻找注入点方面,盲注与回显注入基本相同,都是构造错误语句触发Web系统异常并观察。因此判断方法与回显注入相同,利用单引号或and恒假语句进行判断,观察是否触发系统异常。

1' and 1=2 #

1602667424_5f86c3a02be977c94c93b.png!

1' and 1=1#

1602667484_5f86c3dcd6aae1f6d2867.png

可以看到注入语句均成功执行,只不过当前页面的显示不同。而且,由于and 1=1的恒真性,导致执行结果与提交正常ID参数时一致。同时,可结合and 1=2返回结果,即可判断注入语句在系统内已经被成功执行,从而判断SQL注入漏洞存在。

当然,利用时间延迟函数也可观察到效果。例如,利用sleep函数在成功执行后延迟5秒回显,那么网站的响应时间也变为5秒之后(其中还有网络延时等)。这里可以发现其实是sleep函数被成功执行了,否则不会有延迟的效果产生。这样也可以判断盲注存在的可能性。

4.2 注入获取基本信息

在注入过程中,首先需获得目标数据库的版本信息、当前数据库的库名、数据库用户名及密码等信息。在构造注入语句之前,先在数据库看下具体查询思路,再进行注入语句构造。以查询当前数据库user()内容为例,思路为使用length()函数获得user的长度之后,逐字猜解每个字符。这里先以数据库中的语句执行流程为例进行演示,以便于观察结果。测试过程如下:

1)利用length判断当前user长度,如图所示。

1602672299_5f86d6abf32c213c97f94.png

可看到查询结果等于14,得到当前user的值长度为14.

2)利用mid函数查询当前用户的第一个字符的ASCII值是否大于140。如果查询成功则返回1,查询失败则返回0,如图所示。

1602672478_5f86d75e4db3ea661f84e.png

1602672539_5f86d79b733b59d93767b.png

从上例可以看出,根据返回值不同可猜测user()第一个字符的ASCII码是否大于110,由于返回值为1,则代表user()第一个字符的ASCII码在110至140之间。之后利用二分法逐步缩小查询范围,最终哦得出正确结果。在这个过程中,也可以使用ord()、substr()等相近函数实现相同的效果,如下如所示。

1602672780_5f86d88c47bf53fccc653.png

重复以上的过程14次,即可获得user()的信息。当然,获取的是ASCII码,转码后即可得到有效的内容。

注入语句可按照以上的语法进行构造。以获取当前数据库的user()信息为例,测试环境中的注入语句为:

1' and length(user())=1#

提交后效果如图所示。

1602672992_5f86d9605117facf7b9f6.png

再修改注入语句为:

1' and length(user())=14#

提交后效果如图所示。

1602673068_5f86d9ac156b4af61bb6d.png

注入语句的效果是获取当前id=1并且判断user()长度是否是14,只有两项都正确时方可返回正确信息。当输入测试语句时,观察页面结果,发现查询信息正常时就可判断当前user()长度。

4.3 构造语句获取数据

利用ASCII方式获取具体数据比较繁琐,在实战中可利用substr函数对所需获取数据进行定向字符获取,再逐项判断即可。在上述测试环境中,获取user()信息的注入语句为:

1' and mid(user(),1,1)='r'#

执行结果如下图所示。

1602673402_5f86dafa58827cea26154.png

1' and mid(user(),2,1)='r'#

执行结果如下图所示。

1602673532_5f86db7cc147295812fb7.png

通过返回结果可看到当前语句查询错误。不断修改查询的字符位置及对应内容,再观察执行后的结果,即可获得user()的所有信息。同理,若要获取当前数据库名、指定表数据,只需根据回显注入中的语句按照上述特征进行构造即可。

还可以利用回显所用的时间长短判断SQL注入语句执行结果正确与否。在MySQL数据库中有两种可实现时间延迟的函数,分别为benchmark()、sleep()。其用法如下:

1' union select banchmark(1000000,RANK()) #执行1000000次随机数产生

1' union select sleep(3) #延迟三秒

还是利用上述测试环境演示具体用法,测试语句为:

1' and if(length(user())=1,sleep(3),1)#

执行结果如下图所示。

1602674142_5f86ddded57ce6827c1e8.png

通过以上流程可以看到,盲注的整体流程比回显注入复杂,但注入思路基本一致。由于每一个步骤都要进行过大量的手工测试方可获得信息,因此盲注的手工过程很繁琐,大量时间耗费在重复测试过程中。这里可选择利用SQLmap等工具进行自动化测试,其测试效果及速度均优于手工注入测试。

5. 常见防护手段

SQL注入的防护方法包括参数过滤和预编译处理。参数过滤分为数据类型限制和危险字符处理。通俗的说就是:要么严防死守,细致检查;要么严格限定参数的有效范围(参数化查询)。总之就是要尽可能限制用户可提交参数的类型。

针对SQL注入设计防护体系时,一定要与真实的业务场景进行配合,很对时候用简单的方式可获得非常好的防护效果。首先需尽可能详细地限制允许用户输入的参数类型及长度;其次,需考虑用户输入内容的特点及目的,开展有针对性的关键字、词过滤;如果是新建系统,推荐利用参数化查询手段。以实现更好的防护效果。当然,在这期间,应尽可能保证中间件版本的更新频率,可有效防护各类型攻击。以下将针对每种防护场景进行探讨。再次强调,防护力较弱的方法并不一定适用,必须与实际环境相结合来选择。

5.1 参数类型检测

在设计防护方案之前先考虑业务特点。比如,针对常见的参数提交接口,可参考下图。

1602674594_5f86dfa2bf54c9b030e8d.png

可以看到,id=171为数字类型。后台在接受到用户端提交的参数后,在数据库中查询相应的页面对应信息并显示。这种业务场景非常常见,也极易出现SQL注入情况。当然,还有很多Web页面利用POST方式提交用户参数,因此推荐利用抓包工具来分析。

在设计防护方案时,首先应针对用户可控参数类型(id)进行分析。在此业务场景下,参数均为数字,并且长度均为一定值。那么,如果能对参数(id)进行过滤,避免参数中出现非数字类型字符,并且对参数长度进行限制,即可有效避免SQL注入攻击。

防护思路

本例的参数类型检测主要面向字符型的参数查询功能,可以用以下函数实现:

  • int intval(mixed $var[, int $base = 10]) :通过使用指定的进制base转换(默认是十进制),返回变量var的integer数值。
  • bool is_numeric(mixed $var ) :检测变量是否为数字或数字字符串,但次函数允许输入负数和小数。
  • ctype_digit :检测字符串中的字符是否都是数字,负数和小数无法通过检测。

在特定情况下,使用这三个函数可限制用户输入数字型参数,这在一些仅允许用户参数为数字的情况下非常适用,如查询ID、学号、电话号码等业务场景。

5.2 参数长度检测

1)检测思路

当攻击者构造SQL语句进行注入攻击时,其SQL注入语句一般会有一定长度,并且成功执行的SQL注入语句的字符数量通常会非常多,远大于正常业务中有效参数的长度。因此,如果某处提交的内容具有固定的长度(如密码、用户名、邮箱、手机号等),那么严格控制这些提交点的字符长度,大部分注入语句就没办法成功执行。这样可以实现很好的防护效果。

2)检测代码

在PHP下,可用strlen函数检查输入长度,并进行长度判断,如果参数长度在限制范围内即通过,超过限制范围则终止当前流程。示例代码如下:

if($_GET['id']){
     $id = $_GET['id'];
     if(strlen($id)<4)
     {
          $sql = queryStr($id);
          $res = $db->getOneRow($sql);
          sql_print($res);
     }
}

直接检查参数长度的方法简单有效。其主要思想是注入语句必须依附在正常参数之后,并添加多个字符以实现原有查询语义的改变,因此SQL注入语句会比正常参数多很多字符。但使用场景较为苛刻,要求Web业务需针对参数长度有明确限制,才可以利用这种方式进行检测过滤。

5.3 危险参数过滤

单纯地进行参数长度检测适用于严格满足参数为单一类型的场景,且在特定场景下也会存在一定的安全隐患。因此针对一些复杂场景,如参数类型必须包含字符或长度无法直接控制,那么只利用参数长度限制及类型检测进行防护就非常不适用了。在复杂场景中,有效的防护手段还包括对参数中的敏感信息进行检测及过滤,避免危险字符被系统重构成查询语句,导致SQL注入执行成功。可见,过滤危险参数的工作非常必要。

1)防护思路

常见的危险参数过滤方法包括关键字、内置函数、敏感字符的过滤,其过滤方法主要有以下三种:

  1. 黑名单过滤:将一些可能用于注入的敏感字符写入黑名单中,如(单引号) 、union、select等,也可能使用正则表达式做过滤,但黑名单可能会有疏漏。
  2. 白名单过滤:例如:用数据库中已知值校对,通常对参数结果进行合法性校验,符合白名单的数据方可显示。
  3. 参数转义:对变量默认进行addsalashes(在预定义字符前添加反斜杠),使得SQL注入语句构造失败。

由于白名单方式要求输出参数有着非常明显的特点,因此适用的业务场景非常有限。总体来说,防护手段仍建议以黑名单+参数转义方式为主,这也是目前针对SQL敏感参数处理的主要方式,以下逐项进行分析。

2)防护代码

上述各类防护思路的关键代码如下

黑名单过滤

针对参数中的敏感字符进行过滤,如果发现敏感字符则直接删除。这里利用str_replace()函数进行过滤,过滤的关键字为union、\、exec、select。需要注意的是,真实业务场景中需过滤的敏感字符远远不止这些。参考源码如下:

if($_GET['level']&& $_GET['name']){
     $name = $_GET['name'];
     //strtolower($name)     //如果后台对用户名不区分大小写,可将字符串转换为消息,避免大小写绕过
     $level=$_GET['level'];
     //将敏感字符用空格替换
     $name = str_replace('union','',$name);       //如果存在union则替换为空
     $name = str_replace('\','',$name);
     $name = str_replace('exec','',$name);
     $name = str_replace('select','',$name);
     $sql = queryStr($name);
     $res = $db->getOneRow($sql);
     sql_print($res);
          break;
}

②白名单过滤

白名单过滤是为了避免黑名单出现的过滤遗漏的情况。一下是一个标准的利用场景。首先设置杯名单为当前用户名,之后对由GET方式传入的用户名进行对比,若相同则进行查询,若不同这提示输入有误。参考以下代码:

if($_GET['name']){
     $name = $_GET['name'];
     $conn = mysql_connect($DB_HOST,$DB_USER,$DB_PASS) or die("connect failed" .mysql_error());
     mysql_select_db($DB_DATABASENAME,$conn);
     $sql = "select * from user ";
     $result = mysql_query($sql, $conn);
     $isWhiteName = is_in_white_list($result,$name);
     if($isWhiteName){
     (输出)
     } else  {
          echo "输入有误";
     }
     mysql_free_result($result);
     mysql_close($conn);
}
function is_in_white_list($result,$username){
     while  ($row=mysql_fetch_array($result))
     {
          $username2 = $row['user'];
          if  ($username2 == $username){
          return TRUE;
     }
}

③GPC过滤

GPC过滤是GET、POST、COOKIE三种数据接收方式的合称。在PHP中,如果利用$_REQUEST接受用户参数,那么这三种方式均可被接收。在早期PHP中,GPC过滤是内置的一种安全过滤函数,若用户提交的参数中存在敏感字符单引号( ‘ )、双引号 ( ” )、反斜杠( \ )与NULL(NULL字符),就在其前端添加反斜杠。这样,如果用户参数存在SQL注入语句,则会由于前端的反斜杠导致语义失效,从而起到防护的作用。可参考以下代码:

mysql_query("SET NAMES 'GBK' ");
mysql_select_db("XX",$conn);
$user = addslashes($user);
$pass = addslashes($pass);
$sql="select * from user where user='$user' and password= '$pass' ";
$result = mysql_query($sql,$conn) or die(mysql_error());
$row = mysql_fetch_array($result,MYSQL_ASSOC);
echo "<p>{$row['user']}<p><p>{$row['password']}<p>\n\n";

5.4 参数化查询

1)防护思路

参数化查询是指数据库服务器在数据库完成SQL指令的编译后,才套用参数运行,因此就算参数中含有有损的指令,也不会被数据库所运行,仅认为它是一个参数。在实际开发中,前面提到的入口处的安全检查是必要的的,参数化查询一般作为最后一道防线。

PHP中有三种常见的框架:访问MySQL数据库的mysqli包、PEAR::MDB2包和Data Object框架。但并不是所有数据库都支持参数化查询。目前Access、SQL Server、MySQL、SQLite、Oracle等常用数据库支持参数化查询。

2)防护代码

PHP+MySQL环境中标准的参数化查询方式的源码如下:

<?php 
echo '<br/>'; 
error_reporting(E_ERROR); 
$mysqli = new mysqli("localhost","root","","sqli");
     if($_GET['user'] && $_GET['password']){
          $username = $_GET['user'];
          $password = $_GET['password'];
          $query = "SELECT filename,filesize FROM preuser WHERE (name = ?) and (password = ?)";
          $stmt = $mysqli->stmt_init();
          if($stmt->prepare($query)){
               $stmt->bind_param("ss", $username, $password);
               $stmt->execute();
               $stmt->bind_result($filename, $filesize);
               while ($stmt->fetch()){
                    printf("%s : %d\n",$filename,$filesize);
               }
               $stmt->close();
          }
}
$mysqli->close();
>

5.5 常见防护手段总结

根据上述内容,针对SQL注入的防护方式依然是以过滤为主,或者从开发阶段即采用参数化查询的方式来解决SQL注入的隐患。SQL注入环境下的安全防护流程图如下图所示。

1602696422_5f8734e65835bc73baa89.png

6. 总结

在对抗SQL注入攻击方面,有效的措施是过滤/转义,或者将参数进行预编译或进行参数化查询。在实际Web系统中,推荐从潜在的SQL注入漏洞点对数据的限制进行入手,尽可能限制数据类型(如强制转义为数字),限制提交查询的字符类型;在这对各类注入中的特殊字符及敏感函数进行严格过滤。这种方法适合中小站点,开发成本小且易实现。针对大型站点,推荐利用预编译方法或参数化查询,可有效避免SQL注入漏洞的产生。在防护方法设计方面,需综合关注添加防护的代价与业务开展的正常与否,切不可过度防御,以免对业务产生影响。漏洞修复适度即可,防护手段没有绝对的好与坏。

来源:freebuf.com 2020-10-15 01:36:29 by: magic24

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论