文档章节

防CSRF攻击:一场由重复提交的问题引发的前端后端测试口水战

风间影月
 风间影月
发布于 2017/04/20 11:15
字数 959
阅读 10
收藏 1

重复提交,这是一直以来都会存在的问题,当在网站某个接口调用缓慢的时候就会有可能引起表单重复提交的问题,不论form提交,还是ajax提交都会有这样的问题,最近在某社交app上看到这么一幕,这个团队没有做重复提交的验证,从而导致了数据有很多的重复提交,在这里我们不讨论谁对谁错,问题解决即可。

 

 首先的一种方式,在前端加入loading,或者是blockUI,在ios以及安卓上也是类似,效果如下: 

这个时候整个页面不能再用鼠标点击,只能等待请求响应以后才能操作

具体可以参考blockUI这个插件

此外就是后端了,其实后端在一定程度上也要进行防止重复提交的验证,某些无所谓的情况下可以在前端加,某些重要的场景下比如订单等业务就必须再前后端都要做,为了测试方便,blockUI就直接注释

在后台我们线程sleep5秒

 

@RequestMapping("/CSRFDemo/save")
    @ResponseBody
    public LeeJSONResult saveCSRF(Feedback feedback) {
        
        log.info("保存用户反馈成功, 入参为 Feedback.title: {}", feedback.getTitle());
        
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        return LeeJSONResult.ok();
    }

多次点击,效果如下

 

 

步骤1:页面生成token,每次进入都需要重新生成

设置自定义标签

package com.javasxy.web.tag;

import java.io.IOException;
import java.io.StringWriter;
import java.util.UUID;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.SimpleTagSupport;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;

import com.javasxy.components.JedisClient;
import com.javasxy.pojo.ActiveUser;
import com.javasxy.web.utils.SpringUtils;

/**
 * 
 * @Title: TokenTag.java
 * @Package com.javasxy.web.tag
 * @Description: 生成页面token
 * Copyright: Copyright (c) 2016
 * Company:DINGLI.SCIENCE.AND.TECHNOLOGY
 * 
 * @author leechenxiang
 * @date 2017年4月11日 下午3:29:13
 * @version V1.0
 */
public class TokenTag extends SimpleTagSupport {

    private String id;

    private String name;
    
    private static final String CACHE_MAKE_CSRF_TOKEN_ = "cache_make_csrf_token_";

    StringWriter sw = new StringWriter();
    
    private JedisClient jedis = SpringUtils.getContext().getBean(JedisClient.class);
    
    public void doTag() throws JspException, IOException {
        
        // 生成token
        String token = UUID.randomUUID().toString();
        
        // 构建token隐藏框
        String tokenHtml = "<input type='hidden' ";
        if (StringUtils.isNotEmpty(id)) {
            tokenHtml += " id='" + id + "' ";
        } 
        if (StringUtils.isNotEmpty(name)) {
            tokenHtml += " name='" + name + "' ";
        } 
        tokenHtml += " value='" + token + "' ";
        tokenHtml += " />";
        
        // 获取当前登录用户信息
        ActiveUser activeUser = (ActiveUser)SecurityUtils.getSubject().getPrincipal();
        // 设置token到redis(如果是单应用项目设置到session中即可)
        jedis.set(CACHE_MAKE_CSRF_TOKEN_ + ":" + activeUser.getUsername(), token);
        jedis.expire(CACHE_MAKE_CSRF_TOKEN_ + ":" + activeUser.getUsername(), 1800);
        
        JspWriter out = getJspContext().getOut();
        out.println(tokenHtml);
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
}

页面生成

 

 查看redis

 拦截器代码:

 

package com.javasxy.web.controller.interceptor;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.javasxy.common.utils.JsonUtils;
import com.javasxy.common.utils.LeeJSONResult;
import com.javasxy.common.utils.NetworkUtil;
import com.javasxy.components.JedisClient;
import com.javasxy.pojo.ActiveUser;
import com.javasxy.web.controller.filter.ShiroFilterUtils;
import com.javasxy.web.utils.SpringUtils;

public class CSRFTokenInterceptor extends HandlerInterceptorAdapter {
    
    final static Logger log = LoggerFactory.getLogger(CSRFTokenInterceptor.class);
    
    private static final String CACHE_MAKE_CSRF_TOKEN_ = "cache_make_csrf_token_";

    private JedisClient jedis = SpringUtils.getContext().getBean(JedisClient.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        
        // 获取从页面过来的token
        String pageCSRFToken = request.getHeader("pageCSRFToken");
        
        // 获取IP
        String ip = NetworkUtil.getIpAddress(request);
        if (StringUtils.isEmpty(pageCSRFToken)) {
            String msg = "禁止访问";
            log.error("ip: {}, errorMessage: {}", ip, msg);
            returnErrorResponse(response, LeeJSONResult.errorTokenMsg(msg));
            return false;
        }
        
        // 当前登录用户
        ActiveUser activeUser = (ActiveUser)SecurityUtils.getSubject().getPrincipal();
        
        String CSRFToken = jedis.get(CACHE_MAKE_CSRF_TOKEN_ + ":" + activeUser.getUsername());
        if (StringUtils.isEmpty(CSRFToken)) {
            String msg = "操作频繁,请稍后再试";
            log.error("ip: {}, errorMessage: {}", ip, msg);
            returnErrorResponse(response, LeeJSONResult.errorTokenMsg(msg));
            return false;
        }
        
        if (!pageCSRFToken.equals(CSRFToken)) {
            String msg = "禁止访问";
            log.error("ip: {}, errorMessage: {}", ip, msg);
            returnErrorResponse(response, LeeJSONResult.errorTokenMsg(msg));
            return false;
        }
        
        // 清除token
        jedis.del(CACHE_MAKE_CSRF_TOKEN_ + ":" + activeUser.getUsername());
        return true;
    }
    
    public void returnErrorResponse(HttpServletResponse response, LeeJSONResult result) throws IOException, UnsupportedEncodingException {
        OutputStream out=null;
        try{
            response.setCharacterEncoding("utf-8");
            response.setContentType("text/json");
            out = response.getOutputStream();
            out.write(JsonUtils.objectToJson(result).getBytes("utf-8"));
            out.flush();
        } finally{
            if(out!=null){
                out.close();
            }
        }
    }
    
    /*public boolean returnError(HttpServletRequest request, HttpServletResponse response, String errorMsg) throws IOException, UnsupportedEncodingException {
        if (ShiroFilterUtils.isAjax(request)) {
            returnErrorResponse(response, LeeJSONResult.errorTokenMsg(errorMsg));
            return false;
        } else {
            // TODO 跳转页面
            return false;
        }
    }*/

}

测试:

 

这样重复提交的问题就解决了,同时也解决了CSRF攻击的问题,关于什么是CSRF可以自行百度

注意:

1、token生成也可以在异步调用的时候生成,也就是一次请求一个token,而不是一个页面一个token,但是这样做可能会被第三方获取

2、这里使用了springmvc的拦截器,当然在shiro中也可以自定义过滤器来实现,代码略

 

本文转载自:http://www.cnblogs.com/leechenxiang/p/6694960.html

风间影月
粉丝 4
博文 126
码字总数 252
作品 0
无锡
技术主管
私信 提问
Flask模拟实现CSRF攻击

CSRF 全拼为,译为跨站请求伪造。 指攻击者盗用了你的身份,以你的名义发送恶意请求。 包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账...... 造成的问题:个人...

汪凡
2018/07/13
0
0
Web 漏洞分析与防御之 CSRF(二)

原文地址:Web 漏洞分析与防御之 CSRF(二) 博客地址:www.extlight.com 一、全称 跨站请求伪造(Cross-site Request Forgery) 二、原理 在用户登陆目标网站后,后端会返回用户登陆的凭证到...

moonlightL
2017/10/12
0
0
【基本功】 前端安全系列之二:如何防止 CSRF 攻击?

本文转自美团技术团队(订阅号id:meituantech),经平台同意授权转载。 背景 随着互联网的高速发展,信息安全问题已经成为企业最为关注的焦点之一,而前端又是引发企业安全问题的高危据点。...

Qunar技术沙龙
2018/10/19
0
0
前端面试查漏补缺--(七) XSS攻击与CSRF攻击

前言 本系列最开始是为了自己面试准备的.后来发现整理越来越多,差不多有十二万字符,最后决定还是分享出来给大家. 为了分享整理出来,花费了自己大量的时间,起码是只自己用的三倍时间.如果喜欢...

shotCat
02/25
0
0
Cookie-Form型CSRF防御机制的不足与反思

Cookie-Form型CSRF防御机制的不足与反思 离别歌2016-09-2650 阅读 cookieformcsrf 今天看了 https://hackerone.com/reports/26647 有感。这个漏洞很漂亮,另外让我联想到很多之前自己挖过的漏...

离别歌
2016/09/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

分享一个 pycharm 专业版的永久使用方法

刚开始接触Python,首先要解决的就是Python开发环境的搭建。 目前比较好用的Python开发工具是PyCharm,他有社区办和专业版两个版本,但是社区版支持有限,我们既然想好好学python,那肯定得用...

上海小胖
35分钟前
5
0
Spring Cloud Alibaba 实战(二) - 关于Spring Boot你不可不知道的实情

0 相关源码 1 什么是Spring Boot 一个快速开发的脚手架 作用 快速创建独立的、生产级的基于Spring的应用程序 特性 无需部署WAR文件 提供starter简化配置 尽可能自动配置Spring以及第三方库 ...

JavaEdge
今天
7
0
TensorFlow 机器学习秘籍中文第二版(初稿)

TensorFlow 入门 介绍 TensorFlow 如何工作 声明变量和张量 使用占位符和变量 使用矩阵 声明操作符 实现激活函数 使用数据源 其他资源 TensorFlow 的方式 介绍 计算图中的操作 对嵌套操作分层...

ApacheCN_飞龙
今天
8
0
五、Java设计模式之迪米特原则

定义:一个对象应该对其他对象保持最小的了解,又叫最小知道原则 尽量降低类与类之间的耦合 优点:降低类之间的耦合 强调只和朋友交流,不和陌生人说话 朋友:出现在成员变量、方法的输入、输...

东风破2019
昨天
25
0
jvm虚拟机结构

1:jvm可操作数据类型分为原始类型和引用类型,因此存在原始值和引用值被应用在赋值,参数,返回和运算操作中,jvm希望在运行时 明确变量的类型,即编译器编译成class文件需要对变量进行类型...

xpp_ba
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部