防盜鏈&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訪問,動態參數不包含文件路徑信息。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM