首页 / 资讯中心 / 文章详情

Web安全实战:SQL注入与XSS漏洞原理、攻击路径及防护排查指南

Web安全实战:SQL注入与XSS漏洞原理、攻击路径及防护排查指南 ★ FEATURED ARTICLE
Web安全这个问题几乎每个做开发的人都绕不开。我早几年带过的一个电商模拟项目上线不到一个月后台管理接口就被脚本小子拿SQL注入直接打穿几万条用户数据被拖库当时迫不得已全站紧急下线整改。那次之后我才真正明白SQL注入和XSS这类所谓“老漏洞”远不是网上说的“只要是个框架就自动免疫”那么简单——只要你某个接口没走ORM、某个渲染地方忘了转义该出事照样出。这篇博文我就结合多年踩坑经历围绕SQL注入、跨站脚本攻击XSS这类最常见又最致命的Web漏洞把原理、攻击路径、防护手段、排查技巧一次讲透。文章适合这几类人看刚入行的后端开发、写了几年业务代码但没系统梳理过安全知识的全栈工程师、以及需要给团队落地安全规范的组长或技术负责人。我尽量不用教科书式的语言所有结论都从实际攻击和修复案例中来能让你直接对着自己的代码排查不只是看个热闹。1. 内容整体设计与思路拆解很多开发对Web安全有个误区以为装了WAF、用了HTTPS、上了云厂商的安全组就万事大吉。实际上这些只是外围防御真正决定漏洞是否存在的是你写的每一行业务代码。SQL注入的本质是“数据和代码没有分开”XSS的本质是“不可信数据被当成代码执行了”只要理解这两句话安全编码的绝大多数思路都能推导出来。1.1 为什么经典攻击手法至今仍有效我处理过的安全事件里SQL注入和XSS占了差不多七成而且大部分都不是什么高深变种就是最基础的Payload。原因不外乎三点第一历史遗留代码太多。业务迭代快的时候早期为赶进度手写的拼接SQL、直接渲染用户输入的老代码都会留下来很少有人专门回头做安全改造。第二开发框架的安全能力没被用全。比如很多项目虽然用了ORM但遇到复杂查询就写原生SQL又图省事没用参数化查询等于绕过了框架的防护机制。XSS这边前端框架普遍有自动转义可一旦用了v-html、dangerouslySetInnerHTML、innerHTML这类“后门”接口防线就直接破了。第三业务逻辑漏洞比技术漏洞更隐蔽。比如越权访问只要服务端没校验资源归属你技术防护做得再严也没用。这属于设计层面的缺陷工具扫不出来只能靠威胁建模和代码审查。1.2 漏洞的分类逻辑与攻击面梳理统一定个坐标系凡是有数据输入的地方就有攻击面。我把常见的Web漏洞按入口分成了四类排查时可以对着清单逐个过数据存储与查询层典型是SQL注入、NoSQL注入入口是用户输入拼接进查询语句。内容展示层典型是XSS入口是用户内容被注入到HTML、JavaScript、CSS上下文中。会话与请求层典型是CSRF跨站请求伪造、点击劫持入口是浏览器自动携带Cookie的机制被滥用。服务端信任层典型是文件上传漏洞、越权漏洞IDOR、反序列化漏洞入口是服务端没有验证或越权执行。每类漏洞的防护重点完全不同。SQL注入靠参数化查询和最小权限数据库账号XSS靠输出编码和内容安全策略CSPCSRF靠Token和SameSite Cookie文件上传靠白名单校验和隔离存储越权靠请求级鉴权。下面我按这个框架逐个展开。2. 核心细节解析与实操要点接下来是最硬核的部分逐个漏洞拆解。每个漏洞我都会讲清楚“攻击者怎么打进来”、“为什么防护手段有效”、“代码里到底怎么写才对”这些都是可以直接拿去用的。2.1 SQL注入从拼接字符串到参数化查询的彻底改造先看一种最典型的错误写法我见过无数个生产事故都是从这种代码开始的// 错误示例存在严重SQL注入风险 String sql SELECT * FROM users WHERE username username AND password password ; Statement stmt connection.createStatement(); ResultSet rs stmt.executeQuery(sql);攻击者在username输入框填admin --拼出来的SQL变成SELECT * FROM users WHERE username admin -- AND password ...这里的--把后面的密码校验注释掉了攻击者连密码都不用知道就能登录管理员账号。更狠的可以用UNION SELECT把全表数据拖走甚至调用xp_cmdshell直接拿下服务器。正确做法非常明确任何情况下都不允许用字符串拼接的方式构造SQL语句一律使用参数化查询PreparedStatement。参数化查询的核心原理是让数据库先编译SQL骨架再把参数作为纯数据传进去从根本上让用户输入“无法改变SQL结构”。// 正确示例使用预编译语句参数安全传递 String sql SELECT * FROM users WHERE username ? AND password ?; PreparedStatement pstmt connection.prepareStatement(sql); pstmt.setString(1, username); pstmt.setString(2, password); ResultSet rs pstmt.executeQuery();Java的PreparedStatement、Python的cursor.execute(sql, params)、Go的db.QueryContext带占位符、Node.js的mysql2的?占位符——这些全部都是参数化查询的正确形态。实际改造过程中我总结了几条经验能用ORM框架就尽量用ORM比如Java的MyBatis-Plus、Python的SQLAlchemy它们默认就用预编译机制但注意MyBatis的${}取值是直接拼接的这个符号禁止用在SQL语句中只能用来动态传表名、列名这类结构信息且必须白名单校验。存储过程如果内部用了拼接SQL同样有注入风险不能说用了存储过程就安全。数据库账号权限必须单独收敛应用连接数据库的账号只赋予增删改查DML权限严禁使用db_owner或管理员角色这样即使SQL注入发生了也无法执行xp_cmdshell这类高权限操作。参数化查询不是万能符如果有动态表名、列名、排序字段参数化查询帮不了你必须用白名单映射。比如前端传orderByusername后端只允许映射到预先定义好的几个合法字段名。注意一点NoSQL注入的原理虽然不同但思路完全一致——MongoDB查询语句里如果直接拼进用户输入攻击者传{$gt: }就能绕过条件。正确的做法是把用户输入放到操作符的值那一侧而不是拼进查询结构。2.2 XSS跨站脚本三种类型、三条防线XSS的杀伤力在很多人认知里被低估了。攻击者的脚本一旦在受害者浏览器里执行能拿到的东西包括本地存储里的Token、Cookie中的会话标识、页面上的所有敏感数据、键盘记录、甚至摄像头权限在HTTPS授权场景下。它攻击的是用户而不是服务器但因为很难溯源实际造成的损失往往比拖库还大。按触发机制分三种反射型XSS用户点击了构造好的恶意链接服务端把URL参数原样拼进返回页面脚本立即执行。常见于搜索页、错误提示页存储型XSS攻击者把脚本存进数据库比如评论、昵称、签名其他用户浏览页面时触发。这是危害最大的一种因为不需要诱导点击DOM型XSS纯前端问题脚本通过document.write、innerHTML、eval等接口把不可信数据写入DOM触发的服务端响应里根本没有恶意内容。核心防护手段是“输出编码”。安全界有一句话叫“输出上下文决定编码方式”——同一个字符串放到HTML标签里、放进JavaScript字符串里、放进CSS里、放进URL参数里需要的编码规则完全不同。所谓富文本场景如果确实要允许用户输入部分HTML那就必须用白名单过滤库比如前端的DOMPurify、Java的OWASP Java HTML Sanitizer逐标签逐属性地配置允许列表而不是简单replace掉几个敏感词。我给初学者的建议是建立这样三层防线第一层所有用户输入默认不可信展示时统一做输出编码。主流前端框架自带这个能力React默认转义变量Vue的{{}}插值也是转义的。但要特别注意两个危险接口dangerouslySetInnerHTML和v-html业务代码里能不用就不用第二层Cookie里的会话标识要加HttpOnly属性让JavaScript拿不到XSS就算执行了也偷不走会话。同App应用建议给会话Cookie加SameSiteLax或Strict顺带把CSRF的一部分问题也解决掉第三层整个站点开启内容安全策略CSP用Content-Security-Policy响应头限制浏览器只能加载白名单内的脚本、样式、图片来源。这一层哪怕前两层都失守也能极大程度限制恶意脚本的生存能力。DOM型XSS的排查最容易遗漏。因为服务端日志里看不到攻击Payload问题只在浏览器端。排查思路是全局搜索前端代码里的innerHTML、outerHTML、document.write、eval、setTimeout第一个参数、jQuery的$()传变量等危险写法逐个确认传入的数据是否可能被用户控制。2.3 CSRF与会话安全借助浏览器机制发动的攻击CSRF的攻击场景用一句话概括你在登录状态下访问了攻击者精心构造的页面那个页面自动向你的应用发了一个合法请求应用因为分不清请求到底是不是你本人在操作就照单全收了。原理在于浏览器会自动把Cookie随同请求发送给目标站点服务器只要验证了Cookie中的会话就误以为请求是用户授权发出的。经典的攻击方式是在恶意网站上放一个隐藏表单自动提交或者放一张src指向转账接口的图片!-- 攻击者页面上的代码诱导受害者访问即可触发转账 -- img srchttps://bank.example/transfer?toattackeramount10000 /服务器收到这个请求时Cookie一验是有效的转账就完成了。传统CSRF防护最通用的一招是同步令牌模式Synchronizer Token服务器在渲染页面时生成一个随机Token放进表单隐藏字段提交时校验这个Token。攻击者构造的跨站请求读不到这个Token自然就过不了校验。现在的浏览器生态下处理这道题的姿势丰富了很多。我强烈建议大家把Cookie的SameSite属性设为Lax甚至Strict——这是现代浏览器原生支持的防护比纯靠Token更底层的保障。但注意两个坑第一SameSite只对部分跨站请求生效不同浏览器的行为有细微差异所以它不能完全替代Token校验第二大量内部系统会对WebSocket、Sentry、监控上报等加白名单白名单一多防护就容易出现缝隙务必定时审查。这类问题有一种很难被工具自动发现的变体——JSONP劫持和跨站窗口泄露。前者是老API设计遗留问题后者的本质是window.name和postMessage滥用。做前端或者网关层的同学对这两块要特别敏感。2.4 文件上传和越权访问业务代码里的隐形炸弹文件上传漏洞属于那种“平时不出事出事就是大事”的类型。攻击者靠上传一个带木马的文件拿到Webshell整个服务器就沦陷了。防护措施我按重要性排序文件类型校验必须用服务端白名单包括扩展名和MIME类型双层校验图片类还需要重新渲染一次以剥离伪装成图片的脚本内容文件名不能由用户提供服务端应该用UUID重新生成特殊字符和路径跳转../必须直接拒绝上传目录和执行目录严格分离文件的存储路径放到Web应用目录之外并通过独立的静态文件服务映射避免与后端代码同目录执行安全检测不能少条件允许就接入第三方的恶意文件扫描服务针对压缩包解压这种场景还要防Zip炸弹和路径穿越限制解压总大小和文件数量。越权漏洞则是Web安全里最容易反复出现的一类。典型场景是单页应用把用户ID写在URL或前端路由里/order/detail?id1001后端只根据这个id查订单却不校验“这个订单到底是不是当前登录用户的”。用户手一滑改成id1002如果后端没校验归属别人的订单信息就泄露了。这种漏洞我统称“对象级越权问题”OWASP在2019年之后的Top 10里专门给了它一个重要位置——因为它本质上属于逻辑错误静态扫描工具很难发现只能靠业务团队在开发时建立“每次获取资源都要验证归属”的条件反射式规范以及代码审查时专门去查。3. 实操过程与核心环节实现理论讲完上实战。这一章我用一个模拟项目X的实际改造记录展示从“漏洞百出”到“基础加固”的完整链路包含详细的操作步骤和验证方法。这个项目用Java Spring Boot Vue做典型增删改查系统存在所有刚才提到的典型问题我们按顺序逐个解决。3.1 模拟项目X的漏洞排查实操从攻击面到代码修复项目背景这是一个带用户登录、商品查询、订单管理和后台日志的小型电商模拟系统表结构约20张后端接口约60个。排查开始前我先把所有接口和数据入口列了一张数据流清单标注出哪些数据来源于用户输入、会流经哪些代码路径、最终落在哪里。这一步等于画了一张“攻击面地图”后面所有工作都对着这张地图来。排查顺序上我建议从高危往下走先处理数据库访问层再处理文件上传和权限校验最后处理前端渲染。数据库访问层的排查方法是全局搜索DAO层代码重点找这几类特征Statement、字符串拼接SQL、${}取值、动态拼查询条件。模拟项目X里很典型的一个漏洞是商品搜索接口// 修复前的代码模糊搜索用字符串拼接SQL注入风险极高 String sql SELECT * FROM products WHERE name LIKE % keyword %; Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql);修复方式改为预编译查询用CONCAT(%, ?, %)来拼模糊查询参数完全避免直接拼接String sql SELECT * FROM products WHERE name LIKE CONCAT(%, ?, %); PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, keyword); ResultSet rs ps.executeQuery();同一轮排查中再把数据库账号从root降级为只读和一个专用只增改查账号权限最小化。这一步往往是最容易忽略的但真出事时就是最后的底牌。接着处理文件上传。模拟项目X里有头像上传功能修复前几乎没有校验接收文件后直接存在/static/uploads目录文件名用的是用户原文件名。修复后我做了三件事// 校验扩展名白名单 String ext FilenameUtils.getExtension(originalFilename); if (!allowedExtensions.contains(ext.toLowerCase())) { throw new BizException(不支持的文件类型); } // 重命名文件后缀保持白名单内合法扩展名 String newFilename UUID.randomUUID().toString() . ext; // 存储目录改到 Web 应用外部通过独立静态资源服务映射 storageService.save(file, newFilename);注意光校验扩展名肯定不够还需要用工具读取文件头判断真实类型防止攻击者把PHP一句话木马伪装成图片上传有条件的话对图片调用图像处理库重新压缩一次这是很有效的剥离手段。3.2 安全编码规范落地把防线从“个人能力”变成“团队机制”修复完已有漏洞之后下一步是把安全要求固化成团队的编码规范。我在团队内部推的一套规则很简单就六条但因为每条都是血泪教训换来的执行力度一直很高数据库访问禁止字符串拼接SQL禁用MyBatis${}传参。用户数据展示时默认走模板引擎或前端框架的转义机制确需渲染HTML时使用白名单过滤库并过安全评审。每个HTTP请求都要经过身份认证每个资源请求都要做归属校验不允许直接信任前端传来的ID。文件上传必须满足“白名单扩展名、UUID重命名、隔离目录存储”三条底线。第三方开源组件使用前必须查一次已知漏洞库新版本发布后要在两周内评估升级。敏感信息数据库账号、密钥严禁写入代码仓库一律走环境变量或专门的密钥管理服务。配套的机制是代码评审环节的安全检查项。我要求评审人在点评Diff时重点看这几个点这次改动是否涉及SQL访问传参是参数化还是拼接是否涉及页面渲染用了哪些危险前端接口是否有新增文件上传接口接口鉴权逻辑是否被改动不要把这些当成流程负担真正做到每条都过一遍大概只要多花十分钟但能拦掉绝大多数“随手写出来的漏洞”。3.3 自动化检测工具与自建验证脚本的组合应用人工审查之外必须上自动化工具。我目前用的工具组合是SAST DAST SCA三件套每条信息我都从实际使用体验讲SAST静态应用安全测试工具商用和开源的都有比较成熟的选型Java项目常用SpotBugs配合Find Security Bugs插件Go项目有gosecPython项目有banditNode.js项目有用NodeJsScan的。它们能在编码阶段扫描出明显的问题模式比如字符串拼接SQL、危险函数调用。优点是快、覆盖面广缺点是误报率高需要人去判定。DAST动态应用安全测试工具最有名的是OWASP ZAP免费开源可编程。它能模拟真实攻击对运行中的站点发Payload检测SQL注入、XSS、CSRF等漏洞的实弹效果。我习惯把它接入CI流水线每次测试环境部署完自动跑一轮完整扫描。SCA软件成分分析用于检查第三方开源组件的已知漏洞工程项目里依赖的组件多那些CVE编号的漏洞公告其实离我们不远。工具可以识别出有已知CVE的组件版本并给出升级建议。工具虽然好用但我不建议一种定终身。我的一次真实遭遇是某系统所有SAST、DAST报告都是绿的结果在安全测试时还是被人手工打穿了——因为工具扫不出“订单接口缺少归属校验”这一类的逻辑漏洞。工具是防守的第一道网但绝不是最后一道。自建验证脚本极有价值。比如针对XSS我写过一个简单的Payload自动探测脚本核心逻辑就是往目标参数里注入各种编码变体的Payload然后在浏览器自动化工具里查看是否弹出执行框scriptalert(1)script img srcx onerroralert(1) svg onloadalert(1) javascript:alert(1)针对SQL注入的探测则更简单往参数里加和观察响应是否出现数据库报错信息或者用 AND SLEEP(5) -- -测时间盲注。我强烈建议每个项目团队都维护一套这样的内测用例在每次发布前对着关键接口跑一遍成本极低但效果很好。3.4 常见问题与排查技巧实录含速查表最后分享一些我在排查和应急响应中遇到的高频问题列成速查表每一条都是实际操作中的真实记录。症状很可能的原因排查与解决方案搜索框输入特殊字符就报错SQL语句用了拼接报错信息泄露数据库结构全文搜索DAO层拼接SQL全部改为参数化查询顺手关掉生产环境报错回显页面偶尔弹出奇怪弹窗存储型XSS比如评论或昵称没转义检查所有渲染用户内容的模板将过滤手段改为输出编码并用DOMPurify白名单清理存量数据接口被扫出存在大量SQL注入点用了通用查询工具且直接拼参对动态查询做字段白名单映射禁止用户输入直接进入SQL结构更新头像后访问URL直接打开PHP文件上传校验绕过Webshell已植入立即隔离该存储目录排查WebShell收紧上传校验逻辑并清理存量文件用户反馈“我没操作却改了密码”CSRF检查敏感操作是否校验来源与Token给Cookie加上SameSite属性系统突然大量请求集中在某几个接口被扫描器扫描攻击面分析Access Log来源对异常IP做限制优先修复扫描器爆出的漏洞第三方组件的CVE爆出后不知道怎么处理SCA漏检或未接入引入SCA工具制定紧急升级RCA机制高危CVE要求在3天内完成评估这里分享一个排查时的独家经验遇到疑似SQL注入的流量不要只盯着应用日志把数据库的慢查询日志开起来攻击者做时间盲注时一定会留下明显的规律性请求痕迹。有一次我就是根据慢查询日志里大量SLEEP(5)的查询特征反向定位到了具体接口比一个个接口测试高效得多。再补充一个前端XSS排查的小技巧在浏览器开发者工具的控制台跑一段脚本可以统计当前页面里危险DOM接口的数量比如用document.querySelectorAll(*)过滤出带事件属性的节点。对定位DOM型XSS触发点很有帮助。4. 安全验收与上线前检查清单安全加固做得再彻底上线前也必须有最后一道验收工序。我把多年总结的检查清单收敛成了20条左右分四组团队内统一执行。为了实用我直接以“验收操作”的视角写出来团队成员对照执行即可4.1 接口入口验收所有写操作接口默认走POST并用框架拦截器统一校验CSRF Token。登录接口和短信接口做了防暴力破解的频控限制校验了国家/地区维度。越权类接口逐个验证过切换当前登录用户后仅靠修改请求参数访问不了不属于自己的资源。分页参数、排序字段做了白名单校验不允许用户任意传列名或SQL关键字。4.2 数据输入输出验收全文搜索代码仓库中已经不存在字符串拼接SQL和${}动态传入的情况。所有用户输入都对长度、格式、枚举值做了服务端校验前端校验一律视为无效。所有展示环节的上下文编码已核对HTML上下文的转HTML实体属性上下文转属性编码JS字符串上下文转JS编码。富文本输入已配置白名单标签与属性按需禁用script、style、iframe等标签。4.3 基础设施验收数据库账号遵循最小权限原则应用连接不使用管理员权限且网络隔离。服务器开启Web防火墙或安全组只开放业务所需端口。日志系统脱敏密码、身份证号、银行卡号、会话Token、支付数据禁止明文落盘。线上HTTPS配置正确证书到期时间提前设好提醒且已加上HSTS响应头。4.4 上线前安全测试用OWASP ZAP或同类工具对测试环境全站扫描一遍高危漏洞清零。对核心用户路径登录、支付、修改资料人工走一遍攻击用例。安全测试中发现的每个中高危问题都有编号、负责人和修复期限不允许悬空备案。这份清单不是一次性工作建议每隔一段时间就拿出来重跑一次尤其是每次大版本迭代后。有些问题当时修了后来重构、换人、赶工又会悄悄回来。“安全验收”四个字不是一道流程而是需要贯穿整个开发生命周期的反复确认。我个人的体会是Web应用安全做得好的团队不是那些买了最贵安全设备的团队而是那些把“安全编码”当成默认开发的团队。安全意识和编码习惯一旦养成了防范SQL注入和XSS这类问题甚至不需要额外花太多时间——写代码的时候顺手就把防护做进去了。希望这篇文章能给你提供一个完整的排查思路以及可以直接抄作业的操作清单。最后再分享一个小技巧把你修复过的每个漏洞都整理成一页纸的复盘包括触发条件、利用方式、修复前后代码对比、如何避免再次引入坚持积累一年你会发现自己比市面上大多数安全工程师见过的真实案例都要多。
阅读完成 · 觉得有帮助?
咨询建站