☰
SQL注入深度剖析:从攻击原理到防御实践
2026/10/20 20:19:15 网站建设 项目流程

这些年做安全测试,我见过太多业务系统栽在“SQL注入”这三个字上。明明OWASP Top 10年年上榜,网上防御文章一搜一大把,可现实中扫一遍内网资产,总能捞到几条裸奔的拼接SQL。更头疼的是,很多团队不是不想修,是真不知道从哪儿下手——有的以为加个过滤就够了,有的压根不清楚哪些接口容易中招。这篇内容我打算把SQL注入从头到尾捋一遍:它是怎么发生的、测试时该关注哪些点、真实场景里长什么样、修复时哪些方案才治本。不管你是刚接触安全的新人,还是被漏洞报告追着改代码的后端开发,或者要扛起安全责任的运维,这篇都值得花十分钟看完。

1. SQL注入的本质:它为什么总也杀不绝

先说个最简单的道理。SQL注入能成立,根本原因只有一个:数据和代码没有分家。你把用户输入的那段字符串,直接拼进了SQL语句里,那输入就不再是数据了,它变成了SQL语法的一部分,等于你把枪递到了别人手里。

我打个比方。就好比你做菜,配方写的是“加两勺盐”,结果你把装盐的罐子交给客人,让他自己往锅里加。客人开心起来直接倒进去半罐子,甚至把别的调料也扔进去了,这菜还能吃吗?SQL注入就是类似的事——你本来只想让用户传一个“商品ID”进来,结果他把ID后边多写了一截:1 OR 1=1,拼接出来就是SELECT * FROM products WHERE id = 1 OR 1=1。这一查,整张表都出来了。用户输入越过了“数据”的边界,成了SQL逻辑的一部分,这就是注入。

为什么会这么容易犯?因为SQL语句本身就是字符串拼接,写起来确实方便。我见过不少业务代码,早期开发图快,直接一个String sql = "SELECT * FROM user WHERE name='" + username + "'",上线跑得挺欢,直到有一天被人用' OR '1'='1这么一招呼,整个用户表就裸奔了。

另一个原因是攻击面太分散。一个大型系统里,搜索框、登录框、排序参数、分页参数、导出功能、CSV文件名……凡是能往后端传内容的入口,理论上都可能成为注入点。哪怕你防住了主站的二十个入口,某个不起眼的内部管理系统里漏了一个,照样全盘皆输。

至于为什么“杀不绝”,我觉得有个深层原因:问题永远是业务代码里那一处疏忽,而不是技术栈本身。框架再安全,总有老项目、外包代码、历史遗留接口在拖后腿。所以做安全的人得有心理准备,SQL注入这个老家伙,短时间内是退不了休的。

1.1 从一条报错看注入的形成过程

我记着早些年测试过一个后台管理系统,登录接口长这样:

SELECT * FROM admin WHERE username = '输入的用户名' AND pwd = '输入的密码'

我当时随手在用户名框里填了:

admin' --

拼出来的SQL变成:

SELECT * FROM admin WHERE username = 'admin' -- ' AND pwd = '随便密码'

--在MySQL里是注释符,后边的密码校验直接被注释掉了,登录逻辑等于只验了个用户名。这种操作在老系统里一打一个准,原理就是利用输入把原有SQL截断,再用注释吞掉后面的条件。

这里有个关键细节,你可能注意到了:闭合引号。输入里那个单引号的作用,是把SQL里原本的字符串引号提前闭合掉,这样admin成了完整字符串,后面跟的--就是纯注释内容。理解了这个“闭合-注释”的过程,你就能明白为什么几乎所有注入payload都在跟引号和注释符较劲。

顺着这个逻辑继续往下,攻击者还能玩出更多花样。比如把注释符换成OR '1'='1',构造恒真条件;或者用UNION SELECT把其他表的数据查出来;再或者用报错函数让数据库把敏感信息吐在报错里。花样很多,但核心始终是同一个:控制SQL语法结构。

1.2 拼SQL这个习惯为什么害人

说句公道话,很多开发拼SQL真不是故意的。最早学数据库的时候,教材上、培训班里教的就是字符串拼接。等意识到这个问题,代码库里已经攒了几百条拼接查询了。改?工作量太大;不改?迟早出事。

拼SQL真正恐怖的地方在于,它让“用户输入”和“SQL逻辑”之间没有边界。任何输入都成了SQL的一部分,过滤做得好不好全看运气。而参数化查询为什么安全?因为它提前把SQL结构定义好,再把输入当作纯数据传进去——数据库压根不给输入变成SQL语法的机会。这个区别,后面第4章我会细讲。

所以我对所有开发团队的建议都一样:新代码绝不拼SQL,老代码分批重构成参数化查询。这不是安全团队找茬,是给所有人省事。与其天天被扫描器报漏洞、被人刷数据、被甲方逼着写整改报告,不如直接把这个坏习惯戒了。

2. 分类与危害:同样叫SQL注入,破坏范围差得远了

很多人以为SQL注入就是把数据查出来,其实这只是冰山一角。我按攻击效果和利用难度,把它们分了几个梯队,越往下越棘手。

类型攻击原理典型危害利用难度
联合查询注入用UNION把其他查询结果并进来直接读取敏感表数据低
布尔盲注构造真假条件,靠页面差异猜数据逐字符猜出库内数据中
时间盲注用SLEEP等函数制造时间差判断条件猜数据更隐蔽,不依赖回显中
报错注入让数据库报错,错误信息里带出数据快速获取表名、库名、数据中
堆叠注入拼接多条SQL语句执行写文件、执行命令、增删改数据高

联合查询注入最好理解,也最好防。它的典型payload长这样:

1 UNION SELECT username, password FROM users

只要前面的查询列数和UNION后面的列数对得上,数据库就把两个查询结果拼一起返回。攻击者需要做的,就是不断调整ORDER BY后面的数字去试探列数。列数对上了,后面想查什么表就查什么表。

布尔盲注就阴险多了。比如某个接口会因为条件真假返回不同的页面内容,攻击者输入1' AND 1=1和1' AND 1=2,观察两次返回的差异,就能判断条件是否成立。配合SUBSTRING和ASCII函数,一个字符一个字符地猜数据库内容。效率低,但隐蔽性极强,普通WAF对这种“温柔”的试探基本没辙。

时间盲注更狠,它完全不需要页面差异。攻击者构造1' AND IF(SUBSTRING(...)=1, SLEEP(5), 0)这类语句,如果条件成立,数据库就睡5秒再响应。攻击者只要掐着秒表量响应时间,就能隔空猜数据。这种方式在自动化脚本的加持下,几百个字符分分钟就猜完了。

危害层面,我得分两个维度说。对内,数据泄露是最轻的——脱库、拖表、拿账号密码,这些事情在真实攻防演练里我见得太多了。往外走一步,如果数据库权限配置不当,攻击者还能通过INTO OUTFILE把Webshell写到服务器上去,那就是从数据泄露升级成了RCE(远程代码执行)。再严重一些,如果遇到堆叠注入并且运行在高权限账号下,整个数据库服务器就等于拱手送人了。

2.1 为什么盲注比显式注入更危险

我刚做安全那会儿,也觉得能看到查询结果的注入才有价值。直到某一次测试,遇到一个什么回显都没有的搜索接口,试了布尔差异才发现页面状态码有微妙变化——200和500交替出现。当时就意识到,盲注这东西比想象中普遍得多。

盲注危险的第一个原因是难以发现。普通扫描器查不到,代码审计时也容易漏,因为接口本身看着挺正常的,不返回任何数据库错误。第二个原因是难以阻断。请求和正常请求长得一模一样,WAF想拦截都不知道拦哪条。第三个原因是难以追踪。攻击者用脚本一点点薅数据,流量碎片化,事后回溯时很难从日志里拼出完整攻击链。

所以我后来做安全测试,从不只看显式注入的接口,反而更关注那些“没什么反应”的查询接口——安静的接口往往藏着盲注。

2.2 一张思维导图式的攻击流程

其实所有SQL注入的攻击思路都差不多,我整理了一下:

  1. 枚举入口:翻参数、翻接口、翻上传文件名、翻Cookie、翻Headers。
  2. 探测注入点:加单引号看报错,加AND 1=1/AND 1=2看差异,用时间函数看延迟。
  3. 确认数据库类型:MySQL、Oracle、SQL Server的语法差异很大,payload要跟着调整。
  4. 确定可利用方式:能回显就走联合查询,没回显就走盲注,能堆叠就尝试写文件。
  5. 提权或扩展影响面:读库名、表名、列名、数据,尝试写文件或者连提权。

这套流程看着简单,但每一步都有很多变种。比如探测注入点时,有些系统会过滤空格,你得用/**/代替;有些系统会过滤关键字,你得大小写混着来或者用等价函数替换。道高一尺魔高一丈,过滤器永远跟不上绕过技巧的更新速度。

3. 实操复盘:一个模拟商城系统的注入点挖掘全过程

写原理写了半天,我觉得还是得拿个实例走一遍流程,这样才有“原来如此”的感觉。下面这个案例改编自之前做授权测试时的真实场景,业务方是一个模拟商城项目,数据库用的MySQL。

3.1 第一步:找入口,缩小可疑参数范围

商城系统的功能多,入口多,我不可能全部测一遍,得讲究点策略。优先盯三类接口:

  • 带有明显ID特征的:/product/info?id=1、/order/detail?orderNo=2024001
  • 带搜索功能的:关键词查询接口、筛选接口
  • 内部接口:订单导出、报表查询,这类接口往往开发时最放飞自我

当时我就是盯着商品详情页的id参数动手的。先正常请求,看响应正常;然后加上一个单引号id=1',看响应变化。这个过程叫“报错试探”,如果页面直接爆出SQL语法错误,注入点基本就实锤了。

那天的输入是这样:

GET /product/info?id=1' HTTP/1.1

响应里出现了一段报错痕迹,核心意思是“SQL语法有误,请检查与MySQL服务器版本对应的语法”——“You have an error in your SQL syntax”。到这一步,我已经能确认这是一个会拼接SQL、并且能回显报错信息的MySQL注入点。

3.2 第二步:测真假条件,锁定可操控的SQL片段

动态页面里,id=1返回商品1,id=2返回商品2。那我构造这么一组对比请求:

GET /product/info?id=1 AND 1=1 HTTP/1.1 GET /product/info?id=1 AND 1=2 HTTP/1.1

第一请求如果和正常id=1结果一样,第二请求如果页面内容明显不同(常见的表现是空页面或页面缺一块),就说明AND 1=1和AND 1=2真的影响了SQL执行结果。这意味着我能通过控制条件来控制查询逻辑,注入是实打实的,不是凑巧报错。

这一步非常关键,很多人拿到报错就直接下结论说“存在注入”,但严谨的测试必须验证条件差异,否则可能是数据库把单引号当作普通字符处理了(比如用了转义函数),压根没拼接进去。真条件与假条件的页面差异,是比“报错”更可靠的判据。

3.3 第三步:用ORDER BY列一个数,为UNION做准备

联合注入能拿到数据的前提,是前后两个查询的列数一致。列数怎么猜?用ORDER BY逐个试,从1开始往上加:

GET /product/info?id=1 ORDER BY 1 HTTP/1.1 GET /product/info?id=1 ORDER BY 2 HTTP/1.1

ORDER BY n的意思是按第n列排序,如果表里没有第n列,SQL就会报错。所以当ORDER BY 8报错而ORDER BY 7正常,就说明原查询返回7列。

那天的结果就是7列。知道了列数,我就可以构造UNION查询了。这背后的逻辑可以这样理解:UNION要求前后两个结果集列数一致,就像拼两张桌子,桌面宽度不一样就没法拼接;ORDER BY就是用来量桌面宽度的尺子。

3.4 第四步:构造UNION查询,读取业务数据

列数确定后,查询长这样:

SELECT id, name, price, stock, description, image_url, category_id FROM products WHERE id = 1

我用UNION拼一个自造结果集:

GET /product/info?id=1 UNION SELECT 1,2,3,4,5,6,7 HTTP/1.1

这步的目的是确认哪些列的数值会渲染到页面上。结果页面上出现了“2”和“5”,说明第2列和第5列是回显位。接下来把这两列替换成真正的数据查询就行:

GET /product/info?id=1 UNION SELECT 1,user(),3,4,database(),6,7 HTTP/1.1

页面第2列显示的是当前数据库用户名,第5列显示的是库名。到这一步,注入已经从“证明存在”推进到了“能拿数据”。再往下就是查information_schema元数据,把表名和列名一个个拖出来,然后定制化地读取目标数据。整个流程的核心套路就是这样:

  1. 用ORDER BY数列数。
  2. 用UNION SELECT 1,2,...定位回显位。
  3. 把回显位替换成database()、user()、表名、列名等。
  4. 逐层深入拖取数据。

这个流程现在很多工具能全自动跑,但我建议每个人都手动走一遍,因为手工测试的经验能帮你在自动化工具失灵时,快速判断问题出在哪一步——是参数被过滤了,还是列数不对,还是数据库类型不匹配。

3.5 实操心得:手动与工具怎么配合

自动化工具我常用sqlmap,这玩意确实省力。但我想强调一句:工具永远替代不了思路。扫描器能告诉你“这里可能有注入”,但它不会帮你判断这个注入值多少钱——是能拖全库,还是只能挤出一个数据库版本号。

我一般这样配合:先手工验证入口,确定注入类型和可利用深度;再用工具做大规模数据提取和后续利用;最后手工整理测试报告,把漏洞链路和修复建议写清楚。两件事分开做,既高效又靠谱。

另外,类似于id=1这种数字型参数和name='admin'这种字符型参数,注入payload的构造差很多。数字型参数不需要闭合引号,字符型参数必须先闭合引号再注入。初学者最容易在这里翻车:对着一个WHERE name='admin'的接口,直接拿id=1 UNION SELECT去怼,结果SQL语法错误,还一头雾水。记住,先看参数是数字还是字符串,再决定payload怎么构造。

4. 治本方案:参数化查询为什么能根治SQL注入

我修过很多漏洞,但“SQL注入的修复方案”这块,翻来覆去其实就那么几招,真能治本的更是只有一个核心原则:永远不让用户输入成为SQL语法的一部分。

4.1 参数化查询(预编译)的正确姿势

先说使用预编译语句(PreparedStatement)的做法。Java里的典型写法:

String sql = "SELECT * FROM products WHERE id = ?"; PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setInt(1, productId); ResultSet rs = pstmt.executeQuery();

Python里用?占位符:

cursor.execute("SELECT * FROM products WHERE id = %s", (product_id,))

Go里的写法:

row := db.QueryRow("SELECT * FROM products WHERE id = ?", productId)

关键点都一样:SQL骨架先发给数据库编译,用户输入作为参数传进去。数据库处理时,传入值只会被当作字面量数据(字符串、数字),绝不会被解释成新的SQL指令。哪怕输入是1 OR 1=1,它在数据库眼里也只是一个普通字符串,用去匹配id字段的内容,语法上无从注入。

我见过有人以为参数化就是“把单引号转义一下”或者“用正则过滤危险字符”,然后拿这些土办法塞给客户交差。这里必须说清楚:转义和过滤是“消毒”思路,堵一个算一个;参数化是“隔离”思路,釜底抽薪。前者总有遗漏和绕过的空间,后者才是结构性的解法。只要是能写到SQL里的变量,一律走参数化,连排序字段这种看似安全的字段名也别用拼接。

4.2 后端必须加的白名单校验

有些场景没法用参数化。比如排序字段ORDER BY ?,你用占位符传字段名,数据库会发现字段名是字符串,直接报错。这种连SQL结构中的“关键字”都由外部决定的场景,正确做法是白名单校验。

我常用的思路是:提前在代码里定义好允许参与排序的字段集合,用户传的排序值必须在集合里,否则就用默认值:

String[] allowedSort = {"id", "price", "created_time"}; if (!Arrays.asList(allowedSort).contains(sortParam)) { sortParam = "id"; }

有人会用黑名单过滤INTO、UNION、SELECT这些词,我劝你别。黑名单永远不可靠,大小写混写、编码绕过、注释符拆分,黑客有几十种方法让过滤器失效。白名单才是正道:不是“不让坏东西进来”,而是“只让好东西通过”。

4.3 数据库权限最小化

前面说过的INTO OUTFILE写Webshell,为什么能成功?因为数据库账号有FILE权限。很多团队图省事,代码里用的都是root账号连库,等于把整个数据库的管理员权限交给了业务代码。一旦注入,攻击者直接拿到了数据库的最高权限。

我在安全整改建议里永远会加这么一条:应用连库账号只给SELECT、INSERT、UPDATE、DELETE权限,能不给DDL就不给DDL。没有FILE权限,写文件这条路就断了;没有DELETE权限,拖库之外想删库也得掂量掂量。减少权限没法根除注入,但能把攻击者利用注入造成的破坏力按在地上摩擦。

4.4 一个完整的修复示例对比

拿前面那个商城系统的漏洞做个前后对比。

修复前:

sql = "SELECT * FROM products WHERE id = " + request.GET.get("id")

修复后:

cursor.execute("SELECT * FROM products WHERE id = %s", (request.GET.get("id"),))

三行字的差异,背后是整个安全思维的转变。以前是把输入拼进SQL再执行,现在是SQL固定、数据分离传递。任何想注入的尝试,在数据库编译阶段就失去了存活的空间。

4.5 别忽视ORM和框架自带能力

现在很多团队在用MyBatis、Hibernate、Django ORM这类框架。框架用得好,确实能规避大部分拼接问题,但前提是别在框架里写裸SQL。

MyBatis的${}和#{}是两个完全不同的东西,前者是字符串替换,后者是预编译参数。一个真实的坑是:开发图省事写了ORDER BY ${sortColumn},认为和#{}差不多,结果就是妥妥的注入点。ORM的价值在于帮你把“参数化”默认做了,但你偏要绕开它去拼字符串,那谁也救不了。

所以我在给开发团队培训时反复强调:框架是防御的第一道防线,但你不能把命都交给它。凡是用了${}、字符串拼接、原生SQL的地方,都值得单独过一遍安全测试。

5. 防御进阶:多层防线下的WAF与过滤不是挡箭牌

参数化查询是治本,但在真实系统里,总会有一批老代码短期改不动。这个过渡期怎么办?我的建议是分两层走:先用WAF缓解外部攻击,再把敏感入口的代码固化掉。

5.1 WAF的定位:应急和缓解,不是“修好”

WAF能拦截常见的注入特征,比如UNION SELECT、information_schema、SLEEP(这些关键词模式。但WAF的关键局限是:它认的是特征,不是语义。攻击者只要把payload做一下变形,编码、注释、大小写、函数替换,就能绕过很多规则。更麻烦的是,盲注的请求和普通请求长得几乎一样,WAF很难精确识别。

所以WAF在我的防御框架里是“临时工”角色:给修复争取时间、挡掉大量脚本小子的扫描,但绝不能作为唯一防线。真正要做的,还是代码层面把注入点堵死。

5.2 过滤函数为什么靠不住

我确实见过不少代码拿preg_replace或者replace把SQL关键字替换成空字符串,然后觉得“过滤了危险词就安全了”。下面给你看个经典的绕过演示:

输入:SELSELECTECT,过滤逻辑把SELECT删掉,剩下的还是SELECT。

这叫“递归过滤不足”,写过滤器的人只考虑了一次替换,没想到攻击者可以把词拆开嵌套。类似的手法还有把空格替换成/**/、把OR替换成||(MySQL管道符可当逻辑或用),以及用十六进制编码字符串。过滤器越复杂,攻击者的绕过花样越多,这条路走不通。

5.3 纵深防御的正确姿势

安全圈有个老经验:靠一层防线不如靠多层。就算某层被绕过,后面还有兜底。我建议按下面这个顺序搭:

  1. 代码层面:参数化查询、白名单校验、ORM规范。
  2. 权限层面:数据库账号最小权限,避免root连库。
  3. 网络层面:数据库不对公网开放,只允许应用服务器连接。很多人直接让数据库监听0.0.0.0,等于给攻击者敞开了大门。
  4. 应用层面:对敏感错误信息做统一拦截,别把SQL报错原样返回给用户。报错信息会帮攻击者确认注入点,暴露SQL结构和数据库类型。
  5. 监控层面:在日志里标记常见的异常查询特征,比如UNION、SLEEP、INTO OUTFILE,持续监控告警。

这五层不是多选题,是叠buff。每一层都可能被单独绕过,但想连续绕过五层,成本会大到攻击者怀疑人生。

5.4 关于安全加固,我最想提醒的一件小事

做防御时最容易忽略的是低危端点。大家会盯主站的购物接口、登录接口,却忽略了后台的某个历史导出功能、报表接口。很多被攻破的案例,入口都是从一个不起眼的边缘功能撕开的。安全测试的资源有限,但至少要保证所有对外接口都统一走了参数化,别让“某个内部接口”成为漏网之鱼。

另外,报错处理这关一定要守好。线上系统我见过直接返回数据库异常堆栈的——库名、表名、SQL版本全暴露了。哪怕代码里拼SQL,只要报错不暴露,攻击者也得靠盲注慢慢猜,效率能拖慢几十倍。反过来,如果报错直接把敏感信息显示在页面上,等于给攻击者递地图。

6. 问题排查与检测:从应急响应到常态化防御

前面聊的都是怎么防,但现实是,就算防得再好,你也得具备发现问题的能力。我单独把问题排查和检测手段拿出来写,是因为这块内容在常规文档里讲得少,但实际工作中非常救命。

6.1 怀疑被注入后的排查步骤

假设你收到告警,说某台应用服务器访问异常,或者数据库有大量慢查询。这时候别慌,按顺序来:

  1. 看数据库慢查询日志。如果出现大量执行时间特别长的SELECT,尤其里面带着SLEEP、BENCHMARK、IF这类函数,基本可以判定有人在跑时间盲注。
  2. 看Web访问日志。找找接口请求里有没有带'、OR 1=1、UNION SELECT、/*这类特征的参数。注意大小写混合、URL编码版的特征也要搜。
  3. 看数据库连接数趋势。注入攻击通常伴随着大量并发请求,连接数会突然飙升。
  4. 查最近的用户表变更记录。如果有人拖走了数据,大概率会留下一些异常查询记录。

排查过程我建议从“脏”入手,先看数据库日志,再看应用日志。很多时候安全事件是运维先发现的——因为数据库慢了,或者磁盘满了(备份被拖走),而不是安全团队的设备先报警。

6.2 常见问题速查表

我在下面整理一个表,开发和安全测试都能用:

现场现象可能原因排查方向
页面报SQL语法错误疑似存在注入,报错信息裸奔检查该接口是否拼接SQL,修复参数化
输入单引号后页面空白可能代码里吞掉了异常,但仍可能注入改用盲注思路测试,观察真假条件差异
数据库CPU飙升可能有人在跑时间盲注或大量UNION查询抓慢查询日志,定位异常SQL来源
某账号被异地登录账号数据可能被脱库检查用户表是否被异常读取
请求日志出现大量编码后的参数可能是自动化工具在扫注入点分析攻击源IP,封禁+加固

6.3 应急响应中的止损与取证

如果确认被注入了,止损是第一优先级。我建议按这个顺序落地:

  1. 切断入口:临时在WAF上封禁攻击源IP,或者下线被攻击的接口。
  2. 锁权限:立刻更改数据库账号密码,同时在数据库层收紧该账号权限。
  3. 备份与取证:先备份当前数据库和日志,别急着删东西。很多团队一慌就清日志,结果丢失了追踪攻击者的关键证据。
  4. 改口令:如果用户表可能泄露,全员改密,必要时强制会话失效。
  5. 溯源:通过Web日志反查攻击者的入口路径,确认是哪个接口、哪条链路进的。

整个过程里最不该做的事是“偷偷把漏洞改了然后当无事发生”。出了安全事件不可怕,可怕的是修了A面漏了B面,或者事后连攻击途径都说不清楚。

6.4 如何把SQL注入检测融入日常

“常态化防御”听起来高大上,落到操作层面,我建议做三件事:

第一,每次版本上线前必须过一遍注入自测清单。就是拿一些典型的payload测一下新增接口,比如'、OR 1=1、UNION SELECT、SLEEP(5),跑完看有没有异常。成本不高,收益很高。

第二,定期用扫描工具对测试环境做自动化漏洞扫描。扫描器能帮你发现手工测试容易漏掉的点,尤其是边缘功能。

第三,给开发团队做安全意识培训。很多注入漏洞是因为年轻开发不知道${}和#{}的区别,不知道排序参数也需要白名单。花半天时间把注入和修复讲透,比事后应急省心十倍。

写在最后的个人经验

SQL注入这个议题,我讲了这么多年,最深的感触是:它很难靠某个“神器”彻底消灭,但完全可以靠一套规范化的习惯把它压到极低的水平。参数化查询、白名单、最小权限、报错收敛、日常检测,这些单拎出来哪个都不复杂,难的是团队里每个人都愿意遵守并坚持。

最后再分享一个小技巧:在代码评审阶段,专门盯“用户输入流向SQL语句”的数据链路。凡是用户输入经过处理最终拼进SQL的,一律打回重写。这个方法看起来朴素,但真的能挡住绝大多数新增注入漏洞。以后你在代码里看到字符串拼接查库的场景,也都能本能地停下来多想一步——这一步,就是安全意识的开始。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询