【csrf防御】springboot项目如何防御csrf

csrf介绍以及原理

CSRF(Cross-Site Request Forgery) ,通过单词简单理解就是跨站点请求伪造,用通俗简单点就是攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的操作

1、用户打开浏览器访问受信任网站A,输入用户名和密码请求登录网站A;

2、在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;

3、用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;

4、网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;

5、浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。

CSRF 攻击的三个条件 :

1 . 用户已经登录了站点 A,并在本地记录了 cookie

2 . 在用户没有登出站点 A 的情况下(也就是 cookie 生效的情况下),访问了恶意攻击者提供的引诱危险站点 B (B 站点要求访问站点A)。

3 . 站点 A 没有做任何 CSRF 防御

如果想详细了解,可以参考看下这些文章

什么是CSRF?如何防御CSRF攻击?知了堂告诉你 - 知乎

Web漏洞之CSRF(跨站请求伪造漏洞)详解 - 知乎

https://juejin.cn/post/7008171429845811207#comment

springboot项目怎么实现防御

这里我们没有使用spring security框架来实现,如果使用该框架来实现,可以略过此文

后端代码添加

pom文件引入依赖

     <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.18</version>
        </dependency>

启动类添加filter扫描

@ServletComponentScan(basePackages = {"com.filter.Referer"})
public class MyWebApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyWebApplication.class, args);
    }

}

新增过滤器

工具类RedisEngine

@Component
public class RedisEngine {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    public void put(String key, String value, int timeout, TimeUnit unit) {
        stringRedisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

  public boolean containsKey(String key) {
        return stringRedisTemplate.redisTemplate.hasKey(key);
    }

}

1.新增header头部添加token机制校验

@Slf4j
@Order(value = 99)
@WebFilter(filterName = "csrfTokenFilter", urlPatterns = {"/*"})
public class CSRFTokenFilter implements Filter {

    private static final String CSRF_TOKEN_NAME = "csrfToken";
    private static final String CSRF_TOKEN_ = "_C_T_";
    @Autowired
    private RedisEngine redisEngine;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
 
            String csrfToken = httpRequest.getHeader(CSRF_TOKEN_NAME);
        
            if (csrfToken == null || !csrfToken.equals(redisEngine.get(CSRF_TOKEN_))) {
                Map<String, Object> rsp = new HashMap<>(2);
                rsp.put("code", 403);
                rsp.put("msg", "非法伪造访问");
                httpResponse.setCharacterEncoding("UTF-8");
                httpResponse.setContentType("application/json; charset=utf-8");
                final PrintWriter writer = httpResponse.getWriter();
                writer.write(JSON.toJSONString(rsp));
                writer.flush();
                writer.close();
                return;
            }

        chain.doFilter(httpRequest, httpResponse);
    }


}

2.新增登录生成token来验证,当然也可以通过新增获取token的接口,每次请求动态生成token

@Slf4j
@Order(value = 98)
@WebFilter(filterName = "loginSetTokenFilter", urlPatterns = {"/login"})
public class LoginSetTokenFilter implements Filter {

    private static final String CSRF_TOKEN_ = "_C_T_";

    @Autowired
    private RedisEngine redisEngine;

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        if (!redisEngine.containsKey(CSRF_TOKEN_)) {
            //设置请求头信息
            // 生成新的CSRF Token
            String csrfToken = UUID.randomUUID().toString();
            ((HttpServletRequest) req).getSession().setAttribute(CSRF_TOKEN_, csrfToken);
            redisEngine.put(CSRF_TOKEN_, csrfToken, 350, TimeUnit.MINUTES);
        }
        filterChain.doFilter(request, response);
    }
}

前端代码添加

// 获取Cookie的值
function getCookie(name) {
    var value = "; " + document.cookie;
    var parts = value.split("; " + name + "=");
    if (parts.length == 2) {
        return parts.pop().split(";").shift();
    }
}
    
 $.ajax({
             url : enableUrl,
                async : true,
                dataType : 'json',
                type : 'POST',
                headers:{
                    "csrfToken":getCookie('_C_T_')
                },
                data : {
                    'userNames' : "xxxxx"                },
                success : function(data) {
                    //todo 
                },
                error : function() {
                   //todo 
                },
    });

使用Burpsuite工具来复测

 

 看到这里,说明我们已经实现了CSRF的防御功能

如果对于使用durpsuite工具不熟悉的可以看上一篇文章

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值