目录

一、SQL常见函数

二、闭合符与注释符

三、SQL注入类型

1. 按攻击反馈机制分类(最核心的分类)

2. 按参数数据类型分类

3. 按请求提交方式分类

4. 按攻击技术/手法分类

四、联合注入

(一)联表查询的前置条件:必须具有相同列数

(二)推测列数

(三)使用联合查询步骤

1、查询表名

2、查询列名

五、盲注与普通注入的区别

六、 什么是“注入点”?

七、怎么看出来“有没有注入点”?

第一步:找(识别输入点)

第二步:试(经典单引号法)

总结

八、报错注入

(一)核心逻辑

(二)常用函数

①updatexml

②extractvalue

③floor

(三)示例

①出现问题:显示不完整

②解决问题

a>法一:逐一爆破(适用于信息内容少的情况,不常用!)

b>法二:使用截取函数分段获取(最常用)

c>法三: 使用 LIMIT 子句逐行获取(类似于:法一的逐一爆破)

d> 更换报错函数或 Payload

e>自动化工具(偷懒大法)

九、concat 与 group_concat 的区别

1. CONCAT:横向拼接(行内合并)

2. GROUP_CONCAT:纵向拼接(分组合并)

一、SQL常见函数

substr():截断函数。substring()、mid()与之功能一样

select left('字符串',数值):从左边截取数值位个字符

select ascii('字符串'):打印对应字符的ASCII码

select length(database()); :打印长度

select if(now()=sysdate(),database(),user()); :if是一个三元表达式,如果当前时间等于系统时间,就返回database(),不等于就返回user()

二、闭合符与注释符

单双引号都是成对出现的!

类别 符号/写法 说明与应用场景
引号闭 合 ' (单引号) 最常见的字符串闭合符。攻击者通常需要输入一个单引号来闭合原SQL语句中的引号,然后插入恶意代码。
" (双引号) 部分数据库(如MySQL在特定模式下)支持双引号闭合字符串。
``` (反引号) 主要用于MySQL,用来引用数据库、表或列的名称,有时用于绕过对单引号的过滤。
注释符 -- (双连字符+空格) 标准SQL注释。用于注释掉后续的合法SQL代码(如后面的引号或条件)。注意:后面通常需要跟一个空格或控制符。
# (井号) MySQL专用注释。功能同 --,但不需要像 -- 那样严格要求后面跟空格(在URL编码中更常用)。
// (C风格注释) 多行注释。常用于绕过WAF(防火墙)或关键字过滤。例如:SEL//ECT 可能会被数据库解析为 SELECT,从而绕过简单的黑名单检测。

三、SQL注入类型

1. 按攻击反馈机制分类(最核心的分类)

这是最常见、最重要的分类方式,主要取决于攻击者能否直接看到数据,或者通过什么方式推断数据。

类型 子类型 特点描述 原理简述
带内注入 (In-band) 联合查询注入 (Union-based) 攻击者通过同一信道发起攻击并立即看到结果。 利用 UNION 操作符将恶意 SELECT 语句合并到原查询中,直接从页面回显获取数据。
报错注入 (Error-based) 利用数据库的报错信息来获取敏感数据。 故意构造非法SQL语法,从数据库返回的错误提示中提取表名、字段名或数据内容。
推理注入 (Inferential/盲注) 布尔盲注 (Boolean-based) 页面无直接数据回显,但会根据条件真假显示不同内容。 通过构造逻辑判断(如 AND 1=1),根据页面返回“真”或“假”的差异,逐位推断数据。
时间盲注 (Time-based) 页面完全无差异,只能通过响应时间来判断。 构造让数据库延迟执行的语句(如 SLEEP(5)),通过响应时间的长短来确认数据是否存在。
带外注入 (Out-of-band) DNS/HTTP带外 当盲注速度太慢或不稳定时使用。 诱导数据库向攻击者控制的服务器发起DNS或HTTP请求,通过捕获这些请求来直接窃取数据。

2. 按参数数据类型分类

这种分类方式主要看注入点的参数在SQL语句中是什么类型,决定了攻击者是否需要闭合引号。

类型 特点描述 攻击构造差异
数字型注入 注入点参数为数字(如 id=1)。 通常不需要闭合引号,直接拼接逻辑语句即可(例如:1 OR 1=1)。
字符型注入 注入点参数为字符串(如用户名、密码)。 必须使用单引号 ' 或双引号 " 来闭合SQL语句,否则语法会报错。

3. 按请求提交方式分类

这种分类方式关注攻击者的数据是通过HTTP请求的哪个部分发送的。

类型 常见场景
GET注入 最常见,通过URL参数传递攻击载荷(例如:?id=1)。
POST注入 通过HTTP POST请求体发送,常见于登录、注册表单。
Cookie注入 攻击载荷包含在HTTP请求头的Cookie字段中。
HTTP头注入 攻击载荷包含在User-Agent、Referer等HTTP头字段中。

4. 按攻击技术/手法分类

这些是利用了特定SQL语法或应用逻辑缺陷的高级注入方式。

类型 特点描述
堆叠查询注入 (Stacked Queries) 利用分号 ; 分隔,一次性执行多条SQL语句(例如:查询数据后紧接着删除表)。
二次注入 (Second-order) 攻击者先提交恶意数据(看似安全)存入数据库,后续程序在读取并拼接该数据时触发注入。
宽字节注入 利用数据库字符集(如GBK)的特性,通过特定字节(如%df)吞掉转义符(\),从而绕过过滤。

防御这些攻击最有效的方法是使用预编译语句(参数化查询)输入验证

四、联合注入

(一)联表查询的前置条件:必须具有相同列数

(二)推测列数

你如何知道你要入侵的这个网站的这个数据表里有几列?

通过order by 排序的意思 也是排序,通过命令执行是否成功的结果去侧面推出 他有几列?

例如:原表只有三列,但是我们不知道,通过命令:select * from users order by 4 ;

执行后报如下错 ERROR 1054 (42S22): Unknown column '4' in 'order clause' ,可知原表不存在第四列 。

通过联合查询union可以展示出我们想展示的内容,同时也可以用来确定原表的列数

注意: 当执行 DESC 表名; 时,数据库会返回一个关于表结构的清单。 这个清单的每一行,代表表中的一个字段(列)。 要分清表格的行列和查询输出的行列,不要混淆!


但是我最终的目的是查到管理员的账号密码 : 进入后台<------就要知道管理员是哪张表,表里是什么字段才能查到我想要的

(三)使用联合查询步骤

前提:前端有回显。因为有回显才能在页面看到我想要的信息,才能实现;不回显,就算查到了也没用

1、查询表名

通过information_schema这个库逐步查询到表名

如果有100张表,要显示出来每个表名。

解决方案: ①暴力方法(数据太多不切实际!)

select table_name from information_schema.tables where table_schema='security' limit 0,1;
// 每次改动  limit0,1  中的0,直到99,100个表名就一行一行显示出来完了,不过麻烦、费时间。

burp suite爆破

group_concat函数

将多张表名组成一行内容 select group_concat(table_name) from information_schema.tables where table_schema='security' ;

2、查询列名

通过information_schema这个库查询到的表名再去查询列名(同样的问题,类似的解决方法) ①暴力

select column_name from information_schema.columns where table_schema='security' and table_name='users';
// 注意要限定表名(为的是标识要求哪个表的列名)

burp suite爆破(同理) ③group_concat函数

MySQL语句:
select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users' ;

网址:http://127.0.0.1/sqli-labs-php7-master/Less-1/?id=-1%27%20union%20select%201,(select%20group_concat(column_name)%20from%20information_schema.columns%20where%20table_schema=%27security%27%20and%20table_name=%27users%27),(select%20group_concat(table_name)%20%20from%20information_schema.tables%20where%20table_schema=%27security%27)--+

回显我想要的用户名和密码

网址:http://127.0.0.1/sqli-labs-php7-master/Less-1/?id=-1%27%20union%20select%201,(select%20group_concat(username,%27:%27,password)%20from%20users),4--+

注意:burp suite一般爆破账号和密码,日常更多是爆破密码(但几乎失败,因为密码是有策略的,一般设定错三次及以上ip就会被封掉),爆破用户名一般是较为合理的方案。

爆破用户名的好处:大概率 不会触发风控机制(大量测试的时候不会被封ip)

大公司里面可能会有人用弱口令(week passwords ),所以可通过定死一个弱口令密码,再逐一爆破用户名(其实按道理说每一个用户名与密码对应也只错了一次),而这种方式叫做 ——“密码喷洒” 。

五、盲注与普通注入的区别

特征 普通注入 (有回显) 盲注 (无回显)
服务器的态度 坦白局:直接把数据库里的数据贴在网页上给你看。 猜谜局:什么都不告诉你,或者只回答“对/错”、“快/慢”。
你的操作 输入代码 -> 数据直接出现在页面上。 输入代码 -> 页面没变化(或只报错) -> 你需要通过逻辑去猜数据。
技术手段 UNION SELECT (直接拿数据) 布尔盲注 (问对错) / 时间盲注 (看快慢)

六、 什么是“注入点”?

注入点就是“大门”。更准确地说,它是用户可以控制的输入位置,也就是你把恶意代码(Payload)塞进服务器的地方。

只要是一个带参数的动态网页,或者能提交数据的地方,都可能是注入点。

常见的“注入点”长这样:

  1. URL里的问号后面(GET请求)

    • 比如:http://example.com/news.php?id=1

    • 注入点就是id=1 这个位置。你把它改成 id=1',就是在试探这扇门锁没锁。

  2. 登录框、搜索框(POST请求)

    • 比如:你输入用户名 admin,密码 123456

    • 注入点就是usernamepassword 这两个输入框。

  3. 看不见的“暗门”(HTTP头)

    • 比如:User-Agent(浏览器身份)、Cookie(登录凭证)、Referer(来源页面)。

    • 这些虽然你看不见,但也是数据入口,也是注入点。


七、怎么看出来“有没有注入点”?

其实就两步:找地方 + 扔石头(试探)

第一步:找(识别输入点)
  • 看网址:有没有 .php?id=.asp?newsID= 这种带数字参数的?有,就是潜在注入点。

  • 看功能:有没有搜索框、登录框、分页按钮?有,就是潜在注入点。

第二步:试(经典单引号法)

找到了上面那些“点”,你就扔个单引号 ' 进去,看服务器炸不炸。

  • 操作:在URL后面加个 ',变成 http://example.com/news.php?id=1'

  • 观察结果

    • 情况A(有戏!):页面报错,出现 SQL syntax(SQL语法)MySQL Error 等字样。恭喜,这就是一个注入点,而且是有回显的。

    • 情况B(可能是盲注):页面显示“系统错误”、“内部服务器错误”,或者页面空白。这可能是一个盲注点,因为服务器虽然出错了,但不告诉你细节。

    • 情况C(没戏):页面完全正常,或者直接跳回首页。说明程序员做了过滤,这个点可能进不去。

总结
  • 不回显 = 服务器不给看结果,只能靠猜(盲注)。

  • 注入点 = 任何一个能输入数据的地方(网址参数、输入框、甚至浏览器头)。

  • 怎么确认 = 扔个单引号 ',看报错不报错。报错就是有洞(注入点),不报错可能是盲注或者没洞。

八、报错注入

(一)核心逻辑

“既然你不把数据正常显示给我看(无回显),那我就故意搞破坏,把你数据库的报错信息逼出来,在报错里藏数据!”

(二)常用函数

①updatexml
updatexml(xml_document, xpath_string, new_value)
/*xml_documen:原始xML文档;xpath_string:xPath 表达式,指定要更新的节点;new_value:新的XML内容。*/
  • 原理:这个函数是用来修改 XML 文件的。它的第二个参数 xpath_string 必须符合 XML 路径格式(比如 /root/data)。

  • 怎么搞破坏:我把第二个参数写成 concat('~', (select database()), '~')。这样一来,数据库去解析这个“路径”时,发现里面包含了一个波浪号 ,不符合 XML 规范,就会报错。

  • 结果:报错信息里会包含刚才那个不符合规范的字符串,也就是把 database() 的结果给泄露出来了。

  • 例如:

    假设后台的 SQL 语句是这样的:

    1SELECT * FROM users WHERE id = '$id';

    我输入的 Payload:

    1' AND updatexml(1, concat(0x7e, (SELECT database()), 0x7e), 1) --+
    • 0x7e 的十六进制,用来做分隔符。

    • (SELECT database()) 是我想查的内容(当前数据库名)。

②extractvalue
extractvalue(xml_document, xpath_string)
  • 原理:和上面类似,也是解析 XML 的函数,利用方式几乎一样。

③floor
floor(rand(0)*2) + group by (基于主键冲突)
  • 原理:利用数据库分组查询(GROUP BY)时不能有重复主键的机制,通过构造特殊的随机数列,人为制造“主键重复”的错误,从而在报错信息中挤出数据。

(三)示例

1' and updatexml(1,concat(0x7e,user(),0x7e),1)--+

1、如果不写concat连接字符“~”可能导致所想显示的信息显示不全

2、xpath的路径长度最多32位,如果想要显示的内容过多,可能会导致显示不完整

①出现问题:显示不完整
②解决问题
a>法一:逐一爆破(适用于信息内容少的情况,不常用!)

去逐一爆破试出每个username及其对应的password

b>法二:使用截取函数分段获取(最常用)

这是最核心、最通用的方法。既然一次显示不完,我们就把长字符串切开,一段一段地显示。

  • 核心思路: 利用字符串截取函数(如 mid()substr()substring()),指定起始位置截取长度,分多次查询。

  • 常用函数语法:

    • mid(字符串, 起始位置, 长度)

    • substr(字符串, 起始位置, 长度)

    • substring(字符串, 起始位置, 长度)

  • 操作示例:

    假设我们要获取的数据很长,我们可以这样操作:

    1. 先获取第 1 到 30 个字符:mid((select 数据), 1, 30)

    2. 再获取第 31 到 60 个字符:mid((select 数据), 31, 30)

    3. 以此类推,直到获取全部内容。

    注:起始位置通常从 1 开始计数。

c>法三: 使用 LIMIT 子句逐行获取(类似于:法一的逐一爆破)

当你查询的是多行数据(例如查询 users 表里的所有用户名)时,报错信息可能会因为包含太多行而被截断或只显示第一行。

  • 核心思路: 利用 LIMIT 子句控制查询结果的数量,一次只查一条数据。

  • 语法:

    LIMIT offset, count
    • offset:偏移量(从第几行开始,从 0 开始计数)。

    • count:返回几条记录。

  • 操作示例:

    1. 获取第 1 条数据:select username from users limit 0,1

    2. 获取第 2 条数据:select username from users limit 1,1

    3. 遍历所有数据。

    注意: 这种方法通常用于查询多行记录。如果使用了 group_concat() 将多行合并为一行,就不能再用 LIMIT 了,必须用上面的截取函数。

d> 更换报错函数或 Payload

不同的报错函数对字符长度的限制可能不同,或者对特殊字符的处理不同。

  • 原理: 比如 updatexml()extractvalue() 都有长度限制,但有时候使用 floor() 配合 rand()(报错注入的另一种形式)或者 exp() 等函数,可能会有不同的表现。

  • 建议: 如果一种报错函数显示不全,可以尝试切换另一种报错方式看看是否有改善。

e>自动化工具(偷懒大法)

如果是在 CTF 比赛或合法的渗透测试中,不想手动折腾分段,直接使用自动化工具是最省事的。

  • SQLmap: 这是最强大的 SQL 注入工具。它内置了各种绕过和分段处理机制,能够自动识别报错注入点并完整地提取出数据,无需手动拼接。

  • 优点: 一劳永逸,自动处理各种编码、截断和重试。

九、concat 与 group_concat 的区别

特性 CONCAT GROUP_CONCAT
主要作用 多个列的值连接成一个字符串 多行数据连接成一个字符串
处理方向 横向(同一行的不同字段) 纵向(同一字段的不同行)
配合子句 无需特殊配合 必须配合 GROUP BY 使用
NULL值处理 只要有一个参数为 NULL,结果即为 NULL 自动忽略 NULL 值
默认分隔符 无分隔符(直接拼接) 默认英文逗号 ,
典型用途 拼接姓和名、地址和邮编 查询每个部门的所有员工、订单的所有商品

示例

假设有一张员工表 employees

id name dept salary
1 张三 技术部 8000
2 李四 技术部 9000
3 王五 销售部 7000
1. CONCAT:横向拼接(行内合并)

它就像胶水,把同一行里的几个字段粘在一起。

  • 场景:我想把员工的姓名和部门合并显示。

    1SELECT CONCAT(name, '-', dept) AS info FROM employees;
  • 结果

    info
    张三-技术部
    李四-技术部
    王五-销售部
2. GROUP_CONCAT:纵向拼接(分组合并)

它需要配合 GROUP BY 使用。它先根据某个字段(如部门)分组,然后把每组里的所有数据(如所有员工名)挤到一行里。

  • 场景:我想知道每个部门都有哪些人。

    1SELECT dept, GROUP_CONCAT(name) AS members 
    2FROM employees 
    3GROUP BY dept;
  • 结果

    dept members
    技术部 张三,李四
    销售部 王五
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐