CSRF XSS(XSRF) CORS OPTIONS(HTTP)概念理解

本文探讨了XSS(跨站脚本)和CSRF(跨站请求伪造)两种常见的网络安全威胁。介绍了XSS的工作原理及其防御措施,如启用模版引擎的默认转义功能。此外还讲解了CSRF的基本概念及如何通过请求方法、令牌验证等手段来防止此类攻击。

XSS

XSS 全称“跨站脚本”,是注入攻击的一种。其特点是不对服务器端造成任何伤害,而是通过一些正常的站内交互途径,例如发布评论,提交含有 JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本,作为内容发布到了页面上,其他用户访问这个页面的时候就会运行这些脚本。

我们不需要用户输入 HTML 而只想让他们输入纯文本,那么把所有用户输入进行 HTML 转义输出是个不错的做法。似乎很多 Web 开发框架、模版引擎的开发者也发现了这一点,Django 内置模版和 Jinja2 模版总是默认转义输出变量的。

大多数 Web 开发者都了解 XSS 并知道如何防范,往往大型的 XSS 攻击都是由于疏漏。我个人建议在使用模版引擎的 Web 项目中,开启(或不要关闭)类似 Django Template、Jinja2 中“默认转义”(Auto Escape)的功能。在不需要转义的场合,我们可以用类似 的方式取消转义。这种白名单式的做法,有助于降低我们由于疏漏留下 XSS 漏洞的风险。

XSS 是实现 CSRF 的诸多途径中的一条,但绝对不是唯一的一条。一般习惯上把通过 XSS 来实现的 CSRF 称为 XSRF

CSRF

CSRF 并不一定要有站内的输入,因为它并不属于注入攻击,而是请求伪造。被伪造的请求可以是任何来源,而非一定是站内。所以我们唯有一条路可行,就是过滤请求的 处理者。

对于发布帖子这一类创建资源的操作,应该只接受 POST 请求,而 GET 请求应该只浏览而不改变服务器端资源。当然,最理想的做法是使用 REST 风格 的 API 设计,GET、POST、PUT、DELETE 四种请求方法对应资源的读取、创建、修改、删除。现在的浏览器基本不支持在表单中使用 PUT 和 DELETE 请求方法,我们可以使用 ajax 提交请求。也可以使用隐藏域指定请求方法,然后用 POST 模拟 PUT 和 DELETE (Ruby on Rails 的做法)。这么一来,不同的资源操作区分的非常清楚

令牌方法
首先服务器端要以某种策略生成随机字符串,作为令牌(token), 保存在 Session 里。然后在发出请求的页面,把该令牌以隐藏域一类的形式,与其他信息一并发出。在接收请求的页面,把接收到的信息中的令牌与 Session 中的令牌比较,只有一致的时候才处理请求,否则返回 HTTP 403 拒绝请求或者要求用户重新登陆验证身份。

CORS

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

简单的说即为浏览器限制访问A站点下的js代码对B站点下的url进行ajax请求。比如说,前端域名是www.abc.com,那么在当前环境中运行的js代码,出于安全考虑,访问www.xyz.com域名下的资源,是受到限制的。现代浏览器默认都会基于安全原因而阻止跨域的ajax请求,这是现代浏览器中必备的功能,但是往往给开发带来不便。

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要同时满足以下两大条件,就属于简单请求。

1) 请求方法是以下三种方法之一:
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

如果Origin指定的源,**不在许可范围内**,服务器会返回一个正常的HTTP回应。如果这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200


如果Origin指定的域名**在许可范围内**,服务器返回的响应,会多出几个头信息字段。
1)Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
(2)Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
(3)Access-Control-Expose-Headers
该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。

浏览器发现,这是一个非简单请求,就自动发出一个”预检”请求,要求服务器确认可以这样请求。

“预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。
除了Origin字段,”预检”请求的头信息包括两个特殊字段。
(1)Access-Control-Request-Method
该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。
(2)Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,如:X-Custom-Header。

HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。

否定了”预检”请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。

<think> 我们继续讨论Java程序中防御CSRF攻击的解决方案。根据之前的讨论,我们已经了解了CSRF攻击的原理和一般的防御策略。现在,我们重点关注Java环境下的具体实现方式。 ### Java程序防御CSRF攻击的解决方案 #### 1. 使用框架内置的CSRF防护 现代Java Web框架通常内置了CSRF防护机制,以下以Spring Security为例: **Spring Security配置** ```java @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() // 启用CSRF防护 .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); // 将Token存储在Cookie中(前端可读取) } } ``` - **工作原理**:Spring Security会自动生成CSRF Token,存储在`XSRF-TOKEN` Cookie中,并要求所有修改状态的请求(POST/PUT/DELETE)必须在请求头或参数中包含该Token(默认头名`X-XSRF-TOKEN`)[^1]。 **前端集成示例**(Angular): ```javascript // Angular自动识别XSRF-TOKEN Cookie并添加到请求头 this.http.post('/transfer', data).subscribe(); ``` #### 2. 手动实现CSRF Token验证 对于非Spring框架的应用,可手动实现: **服务端生成Token** ```java public class CsrfUtil { public static String generateToken() { return UUID.randomUUID().toString(); } } // 在会话中存储Token request.getSession().setAttribute("csrfToken", CsrfUtil.generateToken()); ``` **JSP表单嵌入Token** ```jsp <form action="/transfer" method="post"> <input type="hidden" name="csrfToken" value="${sessionScope.csrfToken}"> <input type="text" name="amount"> </form> ``` **过滤器验证Token** ```java public class CsrfFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpReq = (HttpServletRequest) req; HttpSession session = httpReq.getSession(false); // 仅验证非安全方法(POST/PUT/DELETE) if ("POST".equalsIgnoreCase(httpReq.getMethod())) { String sessionToken = (String) session.getAttribute("csrfToken"); String requestToken = httpReq.getParameter("csrfToken"); if (sessionToken == null || !sessionToken.equals(requestToken)) { throw new ServletException("CSRF token验证失败"); } } chain.doFilter(req, res); } } ``` #### 3. SameSite Cookie防御(Tomcat 8.5+) 在`context.xml`中配置: ```xml <Context> <CookieProcessor sameSiteCookies="strict" /> </Context> ``` - **效果**:浏览器发送请求时不会携带第三方Cookie,彻底阻断CSRF利用途径[^2]。 #### 4. 关键操作二次验证 对于敏感操作强制二次认证: ```java // 转账前的密码确认 public class TransferController { public void confirmTransfer(HttpServletRequest request) { if (!checkPassword(request.getParameter("password"))) { throw new SecurityException("二次验证失败"); } // 执行转账 } } ``` #### 防御策略组合建议 | 安全等级 | 推荐方案组合 | |----------|---------------------------------------| | 基础防护 | SameSite Cookie + CSRF Token | | 增强防护 | 基础防护 + 关键操作二次验证 | | 金融级 | 增强防护 + 请求签名 + 行为风控系统 | ### 部署注意事项 1. **Token存储安全**: - 避免将Token存储在LocalStorage(易受XSS攻击) - 推荐使用HttpOnly Cookie存储,前端通过脚本读取 2. **Ajax请求处理**: ```javascript // 手动为AJAX请求添加Token $.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } }); ``` 3. **Token更新策略**: - 每次验证后刷新Token(防止重放攻击) - 或在会话周期内保持固定(平衡安全性与用户体验) ### 测试验证方法 1. 使用Burp Suite的CSRF PoC生成器 2. 自动化测试脚本: ```python # 模拟CSRF攻击测试 requests.post("http://victim/transfer", data={"amount":1000}, headers={"Referer":"http://attacker.com"}) ``` --- ### 相关问题 1. 如何在Spring Boot中自定义CSRF Token存储策略? 2. 分布式Session环境下如何同步CSRF Token? 3. 使用JWT认证时如何防御CSRF攻击? 4. 如何为RESTful API设计无状态的CSRF防护? 5. CSRF防护与CORS策略有何关联与冲突? [^1]: 参见Spring Security官方文档:https://docs.spring.io/spring-security/reference/servlet/exploits/index.html [^2]: Tomcat SameSite配置指南:https://tomcat.apache.org/tomcat-8.5-doc/config/cookie-processor.html </think> ### Java程序部署中CSRF/XSRF攻击解决方案 #### 一、核心防御机制 Java生态中防御CSRF攻击的核心是基于**不可伪造令牌验证**,其数学本质可表示为: $$ \text{请求合法性} = \begin{cases} 1 & \text{当 } \exists \text{ CSRF Token} \land \text{Token}_\text{req} = \text{Token}_\text{sess} \\ 0 & \text{其他情况} \end{cases} $$ #### 二、主流框架解决方案 ##### 1. Spring Security(推荐方案) ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() // 启用CSRF防护 .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .and() // 其他配置... } } ``` - **工作流程**: ```mermaid graph LR A[客户端请求] --> B[服务端生成Token] B --> C[Token存入Cookie/X-Header] C --> D[客户端提交包含Token] D --> E{Token验证} E -->|有效| F[执行操作] E -->|无效| G[返回403] ``` - 前端集成示例: ```html <form action="/transfer" method="post"> <input type="hidden" name="_csrf" th:value="${_csrf.token}"/> <input type="number" name="amount"> </form> ``` ##### 2. Jakarta EE (JAX-RS) 方案 ```java @Provider public class CsrfFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext ctx) { if ("POST".equals(ctx.getMethod())) { String headerToken = ctx.getHeaderString("X-CSRF-TOKEN"); String sessionToken = (String) session.getAttribute("csrfToken"); if (!Objects.equals(headerToken, sessionToken)) { ctx.abortWith(Response.status(Response.Status.FORBIDDEN).build()); } } } } ``` #### 三、关键配置实践 1. **SameSite Cookie防护**(Tomcat 配置) ```xml <!-- conf/context.xml --> <Context> <CookieProcessor sameSiteCookies="strict" /> </Context> ``` 2. **敏感操作二次验证** ```java // 银行转账示例 @PostMapping("/transfer") public ResponseEntity<?> transferFunds( @RequestParam("amount") BigDecimal amount, @RequestParam("otp") String otp) { // 强制二次验证 if (!otpService.validateOTP(otp)) { throw new SecurityException("二次验证失败"); } // 执行转账 } ``` 3. **安全头加固**(Spring Boot配置) ```properties # application.properties server.servlet.session.cookie.http-only=true server.servlet.session.cookie.secure=true security.headers.content-security-policy=default-src 'self' ``` #### 四、防御层级矩阵 | 防护层级 | 技术措施 | 适用场景 | |----------|----------------------------|-----------------------| | 基础层 | CSRF Token验证 | 所有表单提交操作 | | 增强层 | SameSite Cookie + Token同步 | 高安全要求系统 | | 终极层 | 关键操作二次认证 | 金融/账户变更操作 | #### 五、特殊场景处理 1. **JSONP防御方案**(针对引用[1]的漏洞) ```java @RestController public class ApiController { @GetMapping(value = "/data", produces = "application/javascript") public String getData(@RequestParam String callback, HttpServletResponse res) { res.setHeader("X-Content-Type-Options", "nosniff"); // 校验callback参数合法性(防XSS) return callback + "({data: '安全数据'});"; } } ``` 2. **无状态API防护** - 使用JWT签名验证:`$$ \text{Signature} = \text{HMAC-SHA256}(\text{header.payload}, \text{secret}) $$` - 请求头添加时间戳防重放 #### 六、测试验证方法 1. 使用OWASP ZAP进行CSRF扫描 2. 手动测试脚本: ```html <!-- 攻击模拟页面 --> <body onload="document.forms[0].submit()"> <form action="https://victim.com/transfer" method="POST"> <input type="hidden" name="amount" value="10000"> </form> </body> ``` --- ### 相关问题 1. 如何在Spring Boot中自定义CSRF Token存储策略? 2. 微服务架构下如何实现跨服务的CSRF防护? 3. 使用JWT认证时如何防御CSRF攻击? 4. 如何在Java中实现CSRF攻击的实时监控? 5. 对于历史遗留的Struts应用如何加固CSRF防护? [^1]: 粗略的 JSONP 部署很容易受到跨网站的伪造要求(CSRF/XSRF)的攻击... [^2]: 跨站点请求伪造(Cross-Site Request Forgery )是一种攻击,强制用户在他们当前登录的应用程序中执行不需要的操作...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值