前端藏刀!XSS与CSRF的攻防博弈——Web应用安全实战(PART4)

admin 2025-12-27 02:01:34 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: Thearticleexploresfront-endsecurityvulnerabilitiesXSSandCSRF.XSSallowsattackerstoinjectmaliciousscriptstostealsessionsandhijackusers,preventableviastrictinputfiltering.CSRFforgesrequestsusingauser’sestablishedcredentials,mitigatedbyaddingrandomTokenverificationtostate-changingrequests.Thetextprovidesactionabletechnicalguidance,includingJavafiltercodeandconfigurationsteps,tohelpdevelopersimplementdefensesagainstthesehighandmedium-riskthreats,ensuringrobustprotectionforwebapplicationinteractions. 综合评分: 91 文章分类: WEB安全,漏洞分析,安全开发,解决方案,应用安全


cover_image

前端藏刀!XSS 与 CSRF 的攻防博弈——Web 应用安全实战(PART 4)

原创

耶度

野猪与安全

2025年12月26日 10:00 广东

各位开发同学,咱们的 Web 安全实战系列已经更新到第四篇啦!前几期我们陆续拆解了登录环节的核心漏洞——从可预测的凭证、错误消息枚举,到明文传输的登录请求和致命的 SQL 注入,每一个漏洞都藏着黑客入侵的突破口。

今天这篇,我们把焦点转向前端交互层。很多时候,我们以为加固了后端逻辑就万事大吉,却忽略了前端页面里可能藏着的“暗箭”——跨站点脚本攻击(XSS)和跨站点请求伪造(CSRF)。这两种漏洞专攻用户会话和身份盗用,隐蔽性极强,一不小心就会让用户和系统栽大跟头。话不多说,直接上干货!


跨站点脚本攻击(XSS)—— 页面里藏着的恶意代码

高风险漏洞

1. 漏洞描述:给网页“下毒”,用户浏览即中招

XSS(全称Cross Site Script,跨站脚本攻击),简单说就是黑客往Web页面里偷偷插入恶意 HTML 或 JavaScript 代码。当用户正常浏览这个被“下毒”的页面时,嵌入的恶意代码会被浏览器自动执行,黑客就能趁机达成恶意目的。

这里要特别说明下,早期 XSS 也被简写为 CSS,但为了和我们常用的“层叠样式表(CSS)”区分开,现在更规范的叫法是 XSS。它的攻击场景特别普遍,比如评论区、留言板、用户资料编辑页等任何允许用户输入内容的地方,都可能成为 XSS 的突破口。

安全级别:

高风险!前端页面的“隐形炸弹”,用户无感知中招,影响范围极广。

2. 核心风险:会话被盗,身份被冒充

XSS 的危害远比想象中严重,黑客通过恶意代码能实现这些操作:

  • 窃取用户的会话 Cookie 和登录态,直接冒充用户身份登录系统;
  • 篡改页面内容,比如伪造虚假公告、钓鱼链接,诱导用户输入更多敏感信息;
  • 劫持用户浏览器,强制跳转到恶意网站,或弹出垃圾广告、诈骗窗口;
  • 更严重的情况下,可通过恶意脚本获取用户设备信息,甚至控制设备。

3. 解决方案:从用户到开发者的双重防护

XSS 的防御需要“用户端+开发端”配合,双管齐下才能筑牢防线:

  • 用户端防护:在浏览器设置中禁用 JavaScript 脚本(注:此方案会影响部分网站正常功能,适合对安全性要求极高的场景);
  • 开发端防护(核心):仔细审核所有用户输入相关代码,对提交的输入数据进行严格检查和过滤——比如过滤 HTML 标签、转义特殊字符(如<、>、’、”等),避免用户输入的内容被当作代码执行。

4. 技术实现:复用过滤器,低成本落地防护

XSS 的防护可直接复用我们在“SQL 注入”漏洞中提到的过滤器技术(具体实现参见 PART 3 下的”技术实现”),核心逻辑完全一致:

通过自定义过滤器对所有用户输入的参数进行扫描,一旦检测到<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script type="6f0c4e94a628ed0c681e51ac-text/javascript">、onclick、onload 等恶意代码相关字符,直接弹出错误提示并阻断请求,从源头阻止恶意代码注入。这种方案无需大幅修改业务代码,侵入性小,适合已上线系统和开发中系统通用。


站点请求伪造(CSRF)—— 被盗的“身份凭证

中风险漏洞

1. 漏洞描述:冒充你发请求,钱没了都可能不知情

CSRF(全称Cross-site request forgery,跨站请求伪造),通俗理解就是“黑客盗用你的身份,以你的名义发送恶意请求”。它还有个更形象的名字——“一键攻击”,因为很多时候用户只需点击一个链接、打开一封邮件,就会在不知情的情况下触发攻击。

举个直观的例子:你刚登录银行网站完成转账,没退出登录就点开了一封垃圾邮件。邮件里藏着一个隐藏的转账请求链接,浏览器会自动携带你银行网站的登录 Cookie 发起请求,银行验证 Cookie 合法后,就会执行这笔“被伪造”的转账操作,而你全程毫不知情。

安全级别:

中风险!依赖用户已登录的前提,但攻击成功后可能造成财产损失和隐私泄露。

2. 核心风险:隐私泄露+财产安全危机

CSRF 能做的坏事远超你的想象,包括但不限于:

  • 以你的名义发送邮件、发布不良言论,败坏你的声誉;
  • 盗用你的社交账号、购物账号,发送垃圾信息或恶意下单;
  • 针对金融类应用,发起转账、支付等操作,直接造成你的财产损失;
  • 修改你的用户资料(如密码、绑定手机号),彻底掌控你的账号。

3. 解决方案:给请求加“验证码”,拒绝非法请求

CSRF 的核心漏洞是“服务器仅通过 Cookie 验证身份,无法区分请求是用户主动发起还是黑客伪造”,因此防御的关键是给合法请求加“唯一标识”:

  • 严格区分请求类型:仅允许 GET 请求用于检索数据(如查看订单),绝不允许用 GET 请求执行修改数据、转账、删除等核心操作——这能直接防止黑客通过标签等方式触发GET型恶意请求;
  • 给 POST 请求加“随机 Token”:要求所有涉及数据修改的POST请求,都必须携带一个服务器生成的随机 Token(相当于请求的“验证码”),服务器仅接收 Token 合法的请求。

4. 技术实现:Token+过滤器,全方位拦截伪造请求

CSRF 防护的核心是“Token 验证机制”,具体实现分为 4 个步骤,含完整代码可直接复用:

(1)核心逻辑说明

在用户登录时,服务器生成一个随机且唯一的 Token,存储在 Session 中;前端页面发起请求时,需携带这个 Token(POST 通过隐藏域、GET 通过 URL 参数);服务器通过过滤器拦截所有请求,验证请求携带的 Token 与 Session 中的 Token 是否一致,一致则放行,不一致则阻断。

(2)具体落地步骤

  1. 添加 Token 隐藏域 /URL 参数
  • POST 请求页面:添加固定名称的隐藏域,值从 Session 中获取
<input&nbsp;name="CSRF_TOKEN"&nbsp;type="hidden"&nbsp;id="CSRF_TOKEN"&nbsp;value="<%=session.getAttribute("CSRF_TOKEN") %>"/>
  • GET 请求页面:在 URL 后拼接 Token 参
window.location='loginProcess.jsp?CSRF_TOKEN=<%=session.getAttribute("CSRF_TOKEN") %>'

*2. 编写 CSRF 过滤器(核心代码)*:

public&nbsp;class&nbsp;CsrfFilter&nbsp;implements&nbsp;Filter&nbsp;{&nbsp;private&nbsp;String notProtect;&nbsp;// 不受保护的资源集合(如登录页)&nbsp;private&nbsp;String tokenTimeout;&nbsp;// token 过期时间(默认10分钟)&nbsp;public&nbsp;void&nbsp;init(FilterConfig filterConfig)&nbsp;throws&nbsp;ServletException {&nbsp; notProtect = filterConfig.getInitParameter("notProtect");&nbsp; tokenTimeout = filterConfig.getInitParameter("TokenTimeOut");&nbsp;&nbsp;if&nbsp;(null&nbsp;== tokenTimeout) {&nbsp; &nbsp;tokenTimeout =&nbsp;"10";&nbsp; }&nbsp;}&nbsp;public&nbsp;void&nbsp;doFilter(ServletRequest request, ServletResponse response,&nbsp; &nbsp;FilterChain filterChain)&nbsp;throws&nbsp;IOException, ServletException {&nbsp;&nbsp;HttpServletRequest&nbsp;req&nbsp;=&nbsp;(HttpServletRequest) request;&nbsp;&nbsp;HttpSession&nbsp;session&nbsp;=&nbsp;req.getSession(true);&nbsp;&nbsp;// 排除无需保护的资源&nbsp;&nbsp;if&nbsp;(!FilterUtils.isContainUrl(notProtect, FilterUtils.getFullUrlFromRequest(req))) {&nbsp; &nbsp;String&nbsp;requestToken&nbsp;=&nbsp;req.getParameter(TokenBuilder.CSRF_NONCE_REQUEST_PARAM);&nbsp; &nbsp;Object&nbsp;sessionTokenObj&nbsp;=&nbsp;session.getAttribute(TokenBuilder.CSRF_NONCE_REQUEST_PARAM) ; &nbsp;&nbsp;&nbsp; &nbsp;String&nbsp;sessionToken&nbsp;=&nbsp;sessionTokenObj ==&nbsp;null&nbsp;?"":(String)sessionTokenObj;&nbsp;&nbsp; &nbsp;// 若Session中无Token,生成新Token并存储&nbsp; &nbsp;if&nbsp;(sessionToken.equals("")) {&nbsp; &nbsp; sessionToken = TokenBuilder.generateToken();&nbsp; &nbsp; session.setAttribute(TokenBuilder.CSRF_NONCE_REQUEST_PARAM, sessionToken);&nbsp; &nbsp;}&nbsp; &nbsp;// 验证请求Token的有效性&nbsp; &nbsp;if&nbsp;(null&nbsp;!= requestToken) {&nbsp; &nbsp;&nbsp;// 检查Token是否过期&nbsp; &nbsp;&nbsp;if(TokenBuilder.checkTokenTimeout(requestToken, tokenTimeout) ){ &nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp;sessionToken = TokenBuilder.generateToken();&nbsp; &nbsp; &nbsp;session.setAttribute(TokenBuilder.CSRF_NONCE_REQUEST_PARAM, sessionToken); &nbsp; &nbsp;&nbsp;&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;// 验证Token是否匹配,不匹配则阻断&nbsp; &nbsp;&nbsp;if(!TokenBuilder.isTokenValid(sessionToken,requestToken)){&nbsp; &nbsp; &nbsp;response.setContentType("text/html; charset=GBK");&nbsp; &nbsp; &nbsp;response.getWriter().write("<script language='javascript'>alert('无效的安全标识符,请重新刷新当前页面!');history.go(-1);</script>");&nbsp; &nbsp; &nbsp;return;&nbsp; &nbsp; }&nbsp; &nbsp;}&nbsp; }&nbsp; filterChain.doFilter(request, response);&nbsp;}&nbsp;public&nbsp;void&nbsp;destroy()&nbsp;{}}
  1. 编写Token生成与验证工具类(TokenBuilder.java):
public&nbsp;class&nbsp;TokenBuilder&nbsp;{ &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;private&nbsp;static&nbsp;Map&nbsp;map&nbsp;=&nbsp;new&nbsp;HashMap(); &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;public&nbsp;static&nbsp;final&nbsp;String&nbsp;CSRF_NONCE_REQUEST_PARAM&nbsp;=&nbsp;"CSRF_TOKEN";&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 生成随机Token(含时间戳,确保唯一性)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;public&nbsp;synchronized&nbsp;static&nbsp;String&nbsp;generateToken(){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;byte&nbsp;random[] =&nbsp;new&nbsp;byte[16];&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;StringBuffer&nbsp;buffer&nbsp;=&nbsp;new&nbsp;StringBuffer();&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;SecureRandom&nbsp;randomSource&nbsp;=&nbsp;new&nbsp;SecureRandom(); &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; randomSource.nextBytes(random); &nbsp; &nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;(int&nbsp;j&nbsp;=&nbsp;0; j< random.length; j++) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;byte&nbsp;b1&nbsp;=&nbsp;(byte) ((random[j] &&nbsp;0xf0) >>&nbsp;4);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;byte&nbsp;b2&nbsp;=&nbsp;(byte) (random[j] &&nbsp;0x0f);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(b1 <&nbsp;10)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; buffer.append((char) ('0'&nbsp;+ b1));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; buffer.append((char) ('A'&nbsp;+ (b1 -&nbsp;10)));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(b2 <&nbsp;10)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; buffer.append((char) ('0'&nbsp;+ b2));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; buffer.append((char) ('A'&nbsp;+ (b2 -&nbsp;10)));&nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;long&nbsp;nowTime&nbsp;=&nbsp;System.currentTimeMillis(); &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;String&nbsp;token&nbsp;=&nbsp;nowTime +":"+ buffer.toString(); &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; map.put(token,&nbsp;null);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;token;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 检查Token是否过期&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;public&nbsp;synchronized&nbsp;static&nbsp;boolean&nbsp;checkTokenTimeout(String token,String tokenTimeout)&nbsp;{ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;boolean&nbsp;isTimeout&nbsp;=&nbsp;false; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;long&nbsp;term&nbsp;=&nbsp;Long.parseLong(tokenTimeout) *&nbsp;60&nbsp;*&nbsp;1000;&nbsp;// 转换为毫秒&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Iterator&nbsp;it&nbsp;=&nbsp;map.keySet().iterator();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;long&nbsp;nowTime&nbsp;=&nbsp;System.currentTimeMillis(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;long&nbsp;tokenTime&nbsp;=&nbsp;Long.valueOf(token.split(":")[0]).longValue();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if((nowTime - tokenTime) >= term){&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; isTimeout =&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }catch(Exception e){&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; isTimeout =&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 清理过期Token,避免内存溢出&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;while&nbsp;(it.hasNext()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;String&nbsp;key&nbsp;=&nbsp;(String) it.next();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;long&nbsp;oldTokenTime&nbsp;=&nbsp;Long.valueOf(key.split(":")[0]).longValue();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;((nowTime - oldTokenTime) >= term) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; it.remove();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;isTimeout;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 验证Token是否有效(匹配+存在)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;public&nbsp;synchronized&nbsp;static&nbsp;boolean&nbsp;isTokenValid(String savedToken,String token)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;boolean&nbsp;isTokenValid&nbsp;=&nbsp;map.containsKey(token);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if(savedToken.equals(token)){&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(isTokenValid) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; map.remove(token);&nbsp;// 一次性Token,使用后删除&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; isTokenValid =&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }else{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(isTokenValid) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; map.remove(token);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;isTokenValid;&nbsp; &nbsp; &nbsp; &nbsp; }}
  1. 配置 web.xml(注册过滤器):
<filter>&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<filter-name>CsrfFilter</filter-name>&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<filter-class>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cn.com.victorysoft.devcenter.component.usrmgr.security.filter.CsrfFilter&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</filter-class>&nbsp; &nbsp;&nbsp;<init-param>&nbsp; &nbsp; &nbsp;&nbsp;<param-name>TokenTimeOut</param-name>&nbsp; &nbsp; &nbsp;&nbsp;<param-value>15</param-value>&nbsp;<!-- Token过期时间:15分钟 -->&nbsp; &nbsp;&nbsp;</init-param></filter><filter-mapping>&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<filter-name>CsrfFilter</filter-name>&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<url-pattern>*.jsp</url-pattern>&nbsp;<!-- 拦截所有JSP请求 --></filter-mapping>

重要说明:

FilterUtil 类代码可直接复用 PART 3 下“SQL 注入”中的辅助类代码,无需重复编写;

此方案是目前防御 CSRF 的唯一可行方案,但会增加一定开发工作量,且对系统效率有轻微影响。是否落地需结合业务安全性需求综合评估——金融、电商等涉及资金和核心数据的系统建议优先部署。


安全攻击及防范手册

今天我们搞定了前端交互层的两大漏洞——XSS 和 CSRF。

总结一下核心要点:XSS 是“给页面下毒”,防御关键是过滤用户输入;CSRF 是“盗用身份发请求”,防御核心是给请求加 Token 验证。这两种漏洞都瞄准了用户会话和身份,前端同学一定要重点关注!

下一篇(PART 5),我们将把视线拉回后端,拆解两个容易被忽视的漏洞:发现数据库错误模式应用程序错误。很多时候,黑客就是通过系统返回的错误提示,摸清了数据库结构和代码逻辑,进而发起精准攻击。这部分内容关乎后端安全的“底线”,千万别错过!

你在项目中有没有遇到过 XSS 或 CSRF 攻击?或者在部署 Token 验证时遇到过难题?欢迎在评论区留言讨论,我们将在后续文章中针对性解答!


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:野猪与安全 耶度《前端藏刀!XSS 与 CSRF 的攻防博弈——Web 应用安全实战(PART 4)》

评论:0   参与:  1