1. 背景介绍
网站中存在一种常见的安全漏洞——跨站请求伪造(CSRF)漏洞。简单来说,就是攻击者利用用户已登录的身份向网站发送恶意请求,以此进行攻击或者获取用户的敏感信息。
本文将主要介绍Java中的跨站请求伪造漏洞以及如何进行防御。
2. CSRF漏洞的原理与实现
2.1 原理
CSRF漏洞是一种通过伪造用户身份来利用用户在某个网站上的信任,实现非法操作或者获取用户信息的攻击方式。攻击者通过诱骗用户单击恶意链接或是在用户登录的情况下访问恶意网站,使用户在不知情的情况下实施恶意操作。
具体来说,攻击者将一个恶意请求发送给目标站点,该请求中携带着攻击者欲实现的非法操作。由于用户在上一次登录目标站点时浏览器中保存着登录凭证(例如Cookie),因此当用户再次访问目标站点时,浏览器会自动将该登录凭证携带在请求头中向目标站点发送请求,而攻击者便利用了这一点。
2.2 实现
下面是一个简单的CSRF攻击示例。假设目标网站有一个修改个人信息的功能,且其请求接口如下:
POST: http://example.com/modify_person_info
Content-Type: application/json
{
"username": "Tom",
"age": 20,
"phone": "123456789"
}
攻击者可以建立一个网页,通过JavaScript发出这样一个恶意的POST请求:
<html>
<head>
<script>
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://example.com/modify_person_info', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({
"username": "Tom",
"age": 30,
"phone": "987654321"
}));
</script>
</head>
<body>
<form action="http://example.com/modify_person_info" method="POST">
<input type="hidden" name="username" value="Tom" />
<input type="hidden" name="age" value="30" />
<input type="hidden" name="phone" value="987654321" />
<input type="submit" />
</form>
</body>
</html>
当用户在浏览器中访问这个网页时,就会发送一个修改个人信息的POST请求,由于该请求中携带了用户的登录凭证,因此目标网站将把该请求当作是用户正常的操作,并完成了一个非法的个人信息修改。
3. CSRF漏洞的防御
3.1 防御措施
为了防止CSRF漏洞,我们需要对用户发送的请求进行验证,确保该请求确实是用户自己所发出的。
常见的防范措施有以下几种:
使用验证码:在用户进行敏感操作时要求输入验证码,以此防止非人类请求的发生。
检查Referer字段:在服务端验证收到的请求头Referer字段是否是本网站的域名或者本网站的子域名。
使用Token验证:服务端为每个表单页面生成一个Token,在提交表单时将Token一并提交,服务端验证Token的正确性再进行相应操作。这样,在CSRF攻击时,攻击者无法获取到Token,因而无法伪造请求。
3.2 Token验证的具体实现
public class TokenInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(request.getMethod().equalsIgnoreCase("POST")){
HttpSession session = request.getSession();
synchronized (session) {
String csrfToken = (String) session.getAttribute("csrfToken");
String requestCsrfToken = request.getParameter("csrfToken");
if(csrfToken==null || !csrfToken.equals(requestCsrfToken)) {
throw new RuntimeException("CSRF Token Invalid!");
}
}
} else {
String csrfToken = UUID.randomUUID().toString();
request.setAttribute("csrfToken", csrfToken);
HttpSession session = request.getSession();
synchronized (session) {
session.setAttribute("csrfToken",csrfToken);
}
}
return true;
}
}
上述代码中,我们创建了一个TokenInterceptor类,每当有请求进来时,便会检查请求方法是否为POST以及是否上传了一个名为csrfToken的字段。如果是POST请求,那么我们就会去请求中寻找csrfToken字段以及用户Session中保存的Token进行比对。如果两者不同,说明该请求来自于CSRF攻击,我们便会抛出一个异常来阻止请求执行。如果是GET请求,则会为该请求添加一个新的Token并存入Session中,以便于后续提交表单时使用。
在前端代码中,我们还需要为每个表单添加一个csrfToken字段,并且在提交表单时将csrfToken字段一并提交:
<form action="/update" method="post">
<input type="hidden" name="csrfToken" value="{{csrfToken}}"/>
<input type="text" name="username"/>
<input type="submit" value="submit"/>
</form>
这样,在服务端和客户端都进行Token验证后,我们就可以很好地防范CSRF攻击了。
4. 小结
CSRF漏洞是一种利用用户信任实现非法操作或者获取敏感信息的攻击方式。为了防范CSRF,我们可以使用验证码、检查Referer字段以及使用Token验证等方式进行防御。其中,使用Token验证是一种简单有效的防御方式,可以很好地增强网站的安全性。