• 防盗链&CSRF&API接口幂等性设计


     防盗链技术

    CSRF(模拟请求)

    分析防止伪造Token请求攻击

    互联网API接口幂等性设计

    忘记密码漏洞分析

    1.Http请求防盗链

    什么是防盗链

    比如A网站有一张图片,被B网站直接通过img标签属性引入,直接盗用A网站图片展示。

     如果别人的项目频繁引用我的图片的话 别人请求放访问的是我的 服务器  也会浪费我的宽带

    如何实现防盗链

      判断http请求头Referer域中的记录来源的值,如果和当前访问的域名不一致的情况下,说明该图片可能被其他服务器盗用。

      Referer字段中记录了访问的来源(浏览器访问链接地址)

      http协议中: 请求头 请求体 请求 

      相当于限制资源(图片、文字) 只能在某个域名(限制某个服务器)来源进行访问

      在互联网本质通讯底层socket技术,socket技术里面二进制文件流传输,不论是图片还是视频都是玩的二进制文件流进行传输

         

    补充如果看不到请求头:

    从哪个访问地址过来的信息 如上图所示

    A项目(服务器A):

    maven:

      

    <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.0.RELEASE</version>
        </parent>
        <dependencies>
            <!-- SpringBoot 对lombok 支持 -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
    
            <!-- SpringBoot web 核心组件 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </dependency>
            <!-- SpringBoot 外部tomcat支持 -->
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-jasper</artifactId>
            </dependency>
    
            <!-- springboot-log4j -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-log4j</artifactId>
                <version>1.3.8.RELEASE</version>
            </dependency>
            <!-- springboot-aop 技术 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
    
        </dependencies>

     Controller:

    @Controller
    public class JspController {
        private static final Logger logger = LoggerFactory.getLogger(JspController.class);
    
        @RequestMapping("/jspIndex")
        public String jspIndex() {
    
            logger.info("springboot 集成logger 日志成功!!!");
    
            return "jspIndex";
        }
    
    }

    jsp:

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    
        <h1>我是A项目....</h1>
        <img alt="" src="http://toov5.test1.cc:8080/imgs/01.png">
    </body>
    </html>

    可以看到 静态资源访问的路径:  

    http://toov5.test1.cc:8080/imgs/01.png

    B项目(服务器B):  过滤器 + 静态资源

     maven:

    <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.0.RELEASE</version>
        </parent>
        <dependencies>
    
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.1.1</version>
            </dependency>
            <!-- mysql 依赖 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
    
            <!-- SpringBoot 对lombok 支持 -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
    
            <!-- SpringBoot web 核心组件 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </dependency>
            <!-- SpringBoot 外部tomcat支持 -->
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-jasper</artifactId>
            </dependency>
    
            <!-- springboot-log4j -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-log4j</artifactId>
                <version>1.3.8.RELEASE</version>
            </dependency>
            <!-- springboot-aop 技术 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
            <dependency>
                <groupId>commons-lang</groupId>
                <artifactId>commons-lang</artifactId>
                <version>2.6</version>
            </dependency>
        </dependencies>

    Filter:

    import java.io.IOException;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.annotation.WebFilter;
    
    import org.apache.catalina.servlet4preview.http.HttpServletRequest;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.beans.factory.annotation.Value;
    
    // 图片防盗链
    @WebFilter(filterName = "imgFilter", urlPatterns = "/imgs/*")
    public class ImgFilter implements Filter {
        @Value("${domain.name}")
        private String domainName;
    
        public void init(FilterConfig filterConfig) throws ServletException {
        
    
        }
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            System.out.println("filter");
            // 1.获取请求头中的来源字段
            HttpServletRequest req = (HttpServletRequest) request;
            String referer = req.getHeader("Referer");
            if (StringUtils.isEmpty(referer)) {
                request.getRequestDispatcher("/imgs/error.png").forward(req, response);
                return;
            }
            // 2.判断请求头中的域名是否和限制的域名一致
            String domainUrl = getDomain(referer);
            System.out.println(domainUrl);
            // 正常情况 黑名单 白名单接口  
            if (!domainUrl.equals(domainName)) {
                request.getRequestDispatcher("/imgs/error.png").forward(req, response);  //实际项目中这里是 从数据库查询出来的结果 不是写死的这样的
                return;
            }
            // 直接放行图片
            chain.doFilter(req, response);
    
        }
    
        /**
         * 获取url对应的域名
         */
        public String getDomain(String url) {
            String result = "";
            int j = 0, startIndex = 0, endIndex = 0;
            for (int i = 0; i < url.length(); i++) {
                if (url.charAt(i) == '/') {
                    j++;
                    if (j == 2)
                        startIndex = i;
                    else if (j == 3)
                        endIndex = i;
                }
    
            }
            result = url.substring(startIndex + 1, endIndex);
            return result;
        }
    
        public void destroy() {
            // TODO Auto-generated method stub
    
        }
    
    }

    需要过滤处理的域名:

    spring.mvc.view.prefix=/WEB-INF/jsp/
    spring.mvc.view.suffix=.jsp
    
    domain.name=toov5.test1.cc:8080

    过滤器打印:  此时的访问url:http://toov5.test1.cc:9090/jspIndex   与配置中的 domain.name=toov5.test1.cc:8080  不符合

     B项目实际上就是 过滤器 + 静态资源   有A服务器想要的资源

    实际开发中设置白名单  就是只能某某某服务器的才可以哈哈

    玩的就是请求头哈哈 关键词  Referer

    CSRF

    CSRF攻击产生的原因

    (Cross Site Request Forgery, 跨站域请求伪造)是一种网络的攻击方式,它在 2007 年曾被列为互联网 20 大安全隐患之一,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用也就是人们所知道的钓鱼网站。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,并且攻击方式几乎相左。XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。

     

    API接口幂等性设计


     

    会话信息,可能使用Token方式进行保存。

     API接口幂等设计 : 保证数据唯一性 不允许有重复的  (防止表单重复提交 ) 

    互联网API幂等接口设计解决方案:

      网络延迟 重复提交多次 ~~    防止

      或者恶意的攻击 重复提交~~ 防止

      所以我要防止别人模拟恶意请求啊 

    1.MVCC方案

     多版本并发控制,该策略主要使用 update with condition(更新带条件来防止)来保证多次外部请求调用对系统的影响是一致的。在系统设计的过程中,合理的使用乐观锁,通过 version 或者 updateTime(timestamp)等其他条件,来做乐观锁的判断条件,这样保证更    新操作即使在并发的情况下,也不会有太大的问题。例如

      select * from tablename where condition=#condition# // 取出要跟新的对象,带有版本 versoin

      update tableName set name=#name#,version=version+1 where version=#version#

     在更新的过程中利用 version 来防止,其他操作对对象的并发更新,导致更新丢失。为了避免失败,通常需要一定的重试机制。

     乐观锁 无锁机制,通过版本字段判断,如果多线程下。只能有一个操作成功

     but如果所有程序都这么玩儿 会影响效率的!

    2.去重表方案

    在插入数据的时候,插入去重表(额外添加一张表),利用数据库的唯一索引特性,保证唯一的逻辑。

    3.悲观锁

    select for update,整个执行过程中锁定该订单对应的记录。注意:这种在 DB 读大于写的情况下尽量少用。

     

    4.token机制,防止页面重复提交(推荐使用)

     业务要求:页面的数据只能被点击提交一次

     发生原因:由于重复点击或者网络重发,或者 nginx 重发等情况会导致数据被重复提交

        解决办法:

                         集群环境:采用 token 加 redis(redis 单线程的,处理需要排队)

                         单 JVM 环境:采用 token 加 redis 或 token 加 JVM 内存

       处理流程:

                         数据提交前要向服务的申请 token,token 放到 redis 或 jvm 内存,token 有效时间

                         提交后后台校验 token,同时删除 token,生成新的 token 返回

    token 特点:   要申请,一次有效性,可以限流

    实现步骤:

    客户端每次在调用接口的时候,需要在请求头中,传递令牌参数,每次令牌只能用一次。

    一旦使用之后,就会被删除,这样可以有效防止重复提交。

    步骤:

    1.生成令牌接口

    2. 接口中获取令牌验证

    令牌方式防止Token重复提交:

      补充下:

     1.什么Token(令牌) 表示是一个零时不允许有重复相同的值(临时且唯一)
     2.使用令牌方式防止token重复提交。

     使用场景:

      在调用第API接口的时候,需要传递令牌,该Api接口 获取到令牌之后,执行当前业务逻辑,让后把当前的令牌删除掉。
      在调用第API接口的时候,需要传递令牌 建议15-2小时
      代码步骤:
     1.获取令牌
     2.判断令牌是否在缓存中有对应的数据
     3.如果缓存没有该令牌的话,直接报错(请勿重复提交)
     4.如果缓存有该令牌的话,直接执行该业务逻辑
     5.执行完业务逻辑之后,直接删除该令牌。

     调用接口之前先生成令牌,调用接口时候把令牌存放在请求头中传递过去。然后进行处理

    首先获取token:

    不带token请求:

    再次携带token:(此时服务器端生成的token被删除了) 再次携带这个token 会执行相应的提示逻辑

     执行一次成功之后 会删除token 下次携带tonken会与服务器对应不上而错误

     tokenUtils:

    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    import org.apache.commons.lang.StringUtils;
    
    
    public class TokenUtils {
    
        private static Map<String, Object> tokenMaps = new ConcurrentHashMap<String, Object>();
    
        // 获取令牌
        public static synchronized String getToken() {
            // 如何在分布式场景下使用分布式全局ID实现
            String token = "token" + System.currentTimeMillis();
            // hashMap好处可以附带 关联值   这里没有附带其他的
            tokenMaps.put(token, token);
            //比如附带值
           //tokenMaps.put(token, "userId")
            return token;
        }
    
        public static boolean findToken(String tokenKey) {
            // 判断该令牌是否在tokenMap 是否存在
            String token = (String) tokenMaps.get(tokenKey);
            if (StringUtils.isEmpty(token)) {
                return false;
            }
            // token 获取成功后 删除掉
            tokenMaps.remove(token);
            return true;
        }
    }

    实体类:

    public class OrderEntity {
    
        private int id;
        private String orderName;
        private String orderDes;
    
        /**
         * @return the id
         */
        public int getId() {
            return id;
        }
    
        /**
         * @param id
         *            the id to set
         */
        public void setId(int id) {
            this.id = id;
        }
    
        /**
         * @return the orderName
         */
        public String getOrderName() {
            return orderName;
        }
    
        /**
         * @param orderName
         *            the orderName to set
         */
        public void setOrderName(String orderName) {
            this.orderName = orderName;
        }
    
        /**
         * @return the orderDes
         */
        public String getOrderDes() {
            return orderDes;
        }
    
        /**
         * @param orderDes
         *            the orderDes to set
         */
        public void setOrderDes(String orderDes) {
            this.orderDes = orderDes;
        }
    
    }

    Controller:

    import javax.servlet.http.HttpServletRequest;
    
    import org.apache.commons.lang.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.itmayeidu.utils.TokenUtils;
    import com.itmayiedu.entity.OrderEntity;
    import com.itmayiedu.mapper.OrderMapper;
    
    @RestController
    public class OrderController {
    
        @Autowired
        private OrderMapper orderMapper;
    
        @RequestMapping("/getToken")
        public String getToken() {
            return TokenUtils.getToken();
        }
    
        // 验证Token
        @RequestMapping(value = "/addOrder", produces = "application/json; charset=utf-8")
        public String addOrder(@RequestBody OrderEntity orderEntity, HttpServletRequest request) {
            // 代码步骤:
            // 1.获取令牌 存放在请求头中
            String token = request.getHeader("token");
            if (StringUtils.isEmpty(token)) {
                return "参数错误!";
            }
            // 2.判断令牌是否在缓存中有对应的令牌
            // 3.如何缓存没有该令牌的话,直接报错(请勿重复提交)
            // 4.如何缓存有该令牌的话,直接执行该业务逻辑
            // 5.执行完业务逻辑之后,直接删除该令牌。
            if (!TokenUtils.findToken(token)) { 
                //如果返回false 就提示 请勿操作
                return "请勿重复提交!";
            }
            orderEntity.setOrderName("黄焖鸡米饭");
            orderEntity.setOrderDes("美味");
            int result = orderMapper.addOrder(orderEntity);
            return result > 0 ? "添加成功" : "添加失败" + "";
        }
    
    }

    下次来查询时候 由于执行成功之后会删除token 所以查询的结果是false  提示"请勿重复提交"

    防御CSRF攻击手段

    如果用户提前多次生成好token 再恶意重复提交的情况,如何进行处理? 

    比如HttpClient去获取到token然后拿来使用

    使用图形验证码防止机器模拟接口请求攻击,在调用核心业务接口时,比如支付、下单、等接口,最好使用手机短信验证验证或者是人脸识别,防止其他用户使用token伪造请求。 

    也可以通过Nginx实现限流(1分钟之内接受1000个请求),配置黑名单白名单(如果发现某人恶意请求攻击,屏蔽他的IP)

    如何防止伪造token请求

     市面上没有百分百的完全识别验证码的工具~

     在实际项目中,会话信息使用令牌方式保存。如果黑客利用抓包技术分析到令牌。黑客技术使用令牌伪造支付下单等核心业务。

     绑定ip (4g网络的ip是不固定的)

     在互联网上没有绝对防止伪造请求。 但是可以在调用接口时候,确认是本人的操作。使用发送短信验证码或者图像识别的方式。

     在核心接口上,一定要确认是本人操作,比如密码修改,支付下单等等操作

    黑客使用抓包工具分析Http请求,在忘记密码找回时,需要发送一套短信验证码,如果验证码数字比较短的话,很容易使用暴力破解方式攻击破。

    防御手段:

    忘记密码验证码最好在6-8位。

    一旦频繁调用接口验证时,应该使用图形验证码拦截,防止机器模拟。

    使用黑名单和白名单机制,防御攻击。

    关于:

    1、忘记密码漏洞暴力破解找回密码

    2、使用上传文件漏洞格式化服务器硬件     注入个rm -rf * 就完蛋了

    3、常见其他攻击和漏洞(ErrorCode, Html注释流动,路径遍历漏洞)

     使用短信验证码可以被破解,在忘记密码短信找回中有一个code(短信验证码)。

       提交时候 Java程序 HttpClient技术开启多线程,暴力破解生成对应的4位数字以内验证码进行验证,如果一旦验证成功,成功破解。

       防止的话就是 找回验证码中加入 字母  字母和数字混合使用   如果找回密码接口重试五次以上还是错误(出现图形验证码)防止继续模拟

       配置防止DDOS,限制IP访问,配置黑名单白名单。

      在做值传递时候 慎重隐藏域  <input type="hidden" >

      使用上传文件漏洞格式化服务器硬盘: 对于上传的文件 要判断文件流 而不是名字后缀啥的   

      上传文件时候 没有限制格式 导致任意上传文件  如果黑客上传木马文件的(可执行程序)情况。可能会导致服务崩溃

      案例: 上传木马文件 删除某个文件 (jsp exe  bat)

      解决方案:   

       方式一: 在上传文件时候  一定要使用判断文件流的方式 确定是图片  不要判断后缀方式获取图片

       方式二: 静态资源与动态资源分开服务器  Nginx+Tomcat实现   动静分离   Nginx存放静态资源 没有tomcat环境

       方式三: 服务器硬盘上不能做删除操作

       方式四: 权限设置 对于目录的操作权限没有

       方式五: 前端做后缀限制

       方式六:服务器上不要有热部署功能。如果我上传class文件。 Java程序就能获取到了。限制 jsp  exe  等可执行程序。

      

      上传文件漏洞原理: jsp里面有操作文件的代码  我上传后 然后访问这个jsp 在tomcat环境下 执行这个文件 执行了 就完蛋了 

      用第三方工具类去判断流

    小结:


      XSS :   JS脚本注入  获取用户信息(token等等)  防御方式   使用特殊字符转换的方式 +过滤器拦截处理

      CSRF: 跨站模拟请求       

                    保证接口幂等性 : token方式             

                    防止机器识别:   token+图形识别         尽力而为 没有百分百破解验证码  图形验证码至少能有效阻止    token+图形识别+限流  有次数限制的

                    如何防止CSRF 模拟请求  最好使用图形验证码+如果调用核心业务接口 比如支付等安全相关  最好发送短信验证+人脸识别

                    如果抓包分析到toeken 可以伪造下单  

                 

    其他攻击和漏洞

    直接异常信息,会给攻击者以提示。 可以使用mvc中的工具,把错误码异常等进行封装

    HTML注释, 会暴露功能,方便攻击。 上线时去除注释

    文件上传, 如果本身功能就是上传文件去执行,那么就有可能执行非常危险的命令。 解决方式是,设置文件白名单,限制文件类型,另外还可以重新命名文件,改名为不可执行的

    路径遍历, 使用相对路径来遍历未开放的目录。 方式是将JS,CSS部署在独立的服务器,使用独立域名。 其他文件不使用静态URL访问,动态参数不包含文件路径信息。

  • 相关阅读:
    ( ) 与 { } 差在哪?-- Shell十三问<第七问>
    exec 跟 source 差在哪?-- Shell十三问<第六问>
    var=value?export前后差在哪?-- Shell十三问<第五问>
    " "( 双引号) 与 ' '( 单引号) 差在哪?-- Shell十三问<第四问>
    别人 echo 、你也 echo ,是问 echo 知多少?-- Shell十三问<第三问>
    Shell prompt(PS1) 与 Carriage Return(CR) 的关系?-- Shell十三问<第二问>
    Shell十三问更新总结版 -- 什么叫做 Shell?-- Shell十三问<第一问>
    公司项目部运维应急预案-强烈建议收藏!
    抗DDOS应急预案实践-生产环境总结-建议必看
    Ceph日常运维管理和排错 -- <7>
  • 原文地址:https://www.cnblogs.com/toov5/p/10312193.html
Copyright © 2020-2023  润新知