1 CSRF漏洞
1.1 漏洞原理
跨站请求伪造(Cross-site request forgery)CSRF,是一种使已登录用户在不知情的情况下执行某种动作的攻击。因为攻击者看不到伪造请求的响应结果,所以CSRF攻击主要用来执行动作,而非窃取用户数据。 当受害者是一个普通用户时,CSRF可以实现在其不知情的情况下转移用户资金、发送邮件等操作;但是如果受害者是一个具有管理员权限的用户时CSRF则可能威胁到整个Web系统的安全。

CSRF攻击攻击原理及过程如下:
-
用户user打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
-
在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
-
用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
-
网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点 A;
-
浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带 Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的 Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。
1.2 常见存在场景及漏洞审计点
CSRF 可能出现的场景: 更改个人信息 添加/修改资料 关注/取关用户 发布主题或信息 与交易相关的操作
常见漏洞点:
代码审计中的思路是检查是否校验Referer、是否给cookie设置SameSite属性、敏感操作是否会生成
CSRF token,如果都不存在再查看请求参数中是否存在不可被攻击者猜测的字段,比如验证码等参数。
例如: 后台修改密码的代码即存在 CSRF 攻击风险:
@RequestMapping("/doUpdate")
public String doUpdate(HttpServletRequest request, Users user, Model model) {
HttpSession session = request.getSession();
user.setUsername((String)session.getAttribute("user"));
System.out.println(session.getAttribute("user"));
usersService.update(user); // 更新用户信息
model.addAttribute("success", "修改成功");
return "sqli/update";
}
攻击者可在其服务器上创建钓鱼页面,其中包含构造的发送修改用户密码请求的代码, 当用户在已登录网站的情况下点击攻击者发送的钓鱼链接时,密码将被修改。
例如: 如下是一段更新个人信息的代码,存在CSRF漏洞。
String email=request.getParameter("email");
String tel=request.getParameter("password");
String realname=request.getParameter("realname");
Object[] params = new Object[4];
params[0] = email;
params[1] = password;
params[2] = realname;params[3] = userid;
final String sql = "update user set email=?,password=?,realname=? where userid=?";
conn.execUpdate(sql,params);
在代码中,攻击者可在恶意站点中构造如下表单,诱使登陆者点击。
<script> document.form1.submit(); </script> <div style="display:'none'"> <form name="form1" action="http://A.com/modify.jsp" method="POST"> <input name="email" value="test@test.com"> <input name="password" value="test"> <input name="realname" value="test"> <input type="submit"> </form> </div>
例如:Referer校验不严:
// 定义一个名为RefererInterceptor的类,该类继承自HandlerInterceptorAdapter接口
public class RefererInterceptor extends HandlerInterceptorAdapter {
// 定义一个私有布尔变量check,默认值为true
// 如果check为true,则执行preHandle方法中的逻辑
private Boolean check = true;
// 重写HandlerInterceptorAdapter中的preHandle方法
// 此方法会在请求处理之前被调用
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 如果check为false,则直接返回true,继续执行后续流程
if (!check) {
return true;
}
// 获取请求头中的Referer信息
String referer = request.getHeader("Referer");
// 如果Referer不为空,并且以"www.testdomain.com"开头
if ((referer != null) && (referer.trim().startsWith("www.testdomain.com"))) {
// 继续执行后续的请求处理流程(包括其他Interceptor和Controller)
chain.doFilter(request, response);
} else {
// 如果Referer不符合条件,则重定向到"index.jsp"页面
request.getRequestDispatcher("index.jsp").forward(request, response);
}
// 通常在这里返回false,表示当前Interceptor已经处理完请求,并且不继续执行后续的Interceptor或Controller
// 注意:如果上面的chain.doFilter(request, response)被调用,那么返回false可能会导致请求被截断
// 根据实际情况,可能需要在这里返回true或false
return false;
}
}
1.3 修复方案
Referer校验,对HTTP请求的Referer校验,如果请求Referer的地址不在允许的列表中,则拦截请求。Token校验,服务端生成随机token,并保存在本次会话cookie中,用户发起请求时 附带token参数,服务端对该随机数进行校验。如果不正确则认为该请求为伪造请求 拒绝该请求。
对于高安全性操作则可使用验证码、短信、密码等二次校验措施 如下给出在 SpringBoot 项目中,使用 Spring-Security 组件来进行 Token 校验的示例
代码:
-
首先注册过滤器如下,本示例代码中使用的过滤器为Spring-Security提供的HttpSessionCsrfTokenRepository 过滤器
@Configuration
public class CSRFFilterConfig {
public FilterRegistrationBean csrfFilterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
// 使用 HttpSessionCsrfTokenRepository 过滤器进行 token 校验时, token值存储在session中
filterRegistrationBean.setFilter(new CsrfFilter(new HttpSessionCsrfTokenRepository()));
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
-
使用${csrf.parameterName}和${csrf.token}表达式分别获取Token参数名和 Token 参数值,并记录到 hidden 字段中
<div class="layui-form-item">
<label class="layui-form-label">新密码:</label>
<div class="layui-input-block">
<input type="text" name="password" class="layui-input"/>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="demo1">修改 </button>
1</div>
</div>
<!-- 存放 token-->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>



















