Web安全之CSRF攻擊

前言

上一篇文章中介紹了XSS,這篇文章介紹CSRFjavascript

CSRF
CSRF是什麼

Cross-site request forgery, 跨站請求僞造。是指黑客引誘用戶打開黑客的網站,在黑客的網站中,利用用戶的登陸狀態發起跨站請求。html

我本身模擬了一個例子,java

  • 首先登陸www.abc.com

在這個頁面內,我會自動向服務器發起一個get請求,服務器響應這個請求的同時會向瀏覽器發送cookie
6.pngexpress

  • 在同一瀏覽器下登陸www.haha.com

在這個頁面內,作了三個事情segmentfault

  1. 利用img標籤自動向localhost:8899發送了一個get請求
  2. 利用js自動向localhost:8899發送了一個post請求
  3. 經過引誘用戶點擊a標籤,向localhost:8899發送了一個post請求

8.png
經過截圖能夠看到,三個請求都帶了www.abc.com下的cookie發送給了localhost:8899服務器,而且localhost:8899服務器也正常響應了。
流程圖以下:
process.jpg跨域

防止CSRF

黑客發起CSRF攻擊的條件瀏覽器

  • 目標網站必定要有CSRF漏洞,黑客破解了服務器的接口
  • 用戶登陸過目標網站,而且瀏覽器保存目標網站的登陸狀態
  • 用戶在同一瀏覽器下以某種方式打開了黑客的網站或者是攻擊的連接

CSRF攻擊與XSS攻擊不一樣,CSRF攻擊不會往頁面內注入惡意腳本,所以黑客是沒法經過CSRF攻擊來獲取用戶頁面數據的,因此主要由服務器來作預防。
主要有如下幾種方式:安全

  1. 充分利用好cookie的SameSite屬性

SameSite選項一般由Strict、Lax和None三個值服務器

  • Strict最爲嚴格,若是cookie設置了Strict,那麼瀏覽器會徹底禁止第三方Cookie。
  • Lax相對寬鬆一點,在跨站點的狀況下,從第三方站點的連接打開和從第三方站點提交Get的表單都會攜帶cookie.可是若是在第三方站點中使用Post方法或者經過img、iframe等標籤加載的URL,都不會攜帶Cookie。
  • None, 任何狀況下都會發送Cookie。

可是如今大部分的網站靜態資源都放在單獨的域名下,因此經過設置Cookie的SameSite爲Strict、Lax是不能正常運行的,因此這個方法只適用靜態資源跟服務器接口在同一個站點下的網站。
我測試了一下,以下圖:
localhost:8899向www.abc.com寫入SameSite值爲Lax的cookie,寫不進去
a_cookie_lax.pngcookie

locahost:8899向該站點下的localhost:8899/get寫入SameSite值爲Lax的cookie,成功寫入。
fuwuqi_cookie_lax.png

  1. 驗證請求的來源站點

在服務器端驗證請求的來源站點。由於CSRF攻擊大多數都是來自第三方站點。

經過http請求頭中的Referer和Origin屬性

  • referer屬性

記錄了該http請求的來源地址,但有些場景不適合未來源URL暴露給服務器,因此能夠設置不用上傳,而且referer屬性是能夠修改的,因此在服務器端校驗referer屬性並無那麼可靠

  • origin屬性

經過XMLHttpRequest、Fetch發起的跨站請求或者Post方法發送請求時,都會帶上origin,因此服務器能夠優先判斷Origin屬性,再根據實際狀況判斷是否使用referer判斷。

  1. CSRF Token

除了上面兩個方法以外,還可使用CSRF Token來驗證。

  • 在瀏覽器向服務器發起請求時,服務器生成一個CSRF Token(字符串)發送給瀏覽器,而後將該字符串放入頁面中
  • 瀏覽器請求時(如表單提交)須要帶上這個CSRF Token。服務器收到請求後,驗證CSRF是否合法,若是不合法拒絕便可。
代碼

代碼很簡單

  • www.abc.com:3000
<div>
    <h6>CSRF測試</h2>
      <a href="http://www.haha.com:9999">點我點我</a>
      <script>
        fetch('http://localhost:8899', {
          method: 'get',
          mode: 'cors',
          credentials: 'include' //很重要,容許跨域訪問傳輸cookie
        }).then((res) => {
          console.log(res)
        })
      </script>
  </div>
  • 服務器 localhost:8899
const express = require('express')
const app = express()
const port = 8899

//allow custom header and CORS
app.all('*',function (req, res, next) {
    // res.header('Access-Control-Allow-Origin', '*');
    // res.header('Access-Control-Allow-Origin', req.hostname);
    res.header('Access-Control-Allow-Origin', req.headers.origin);
    res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
    res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
    res.header('Access-Control-Allow-Credentials', true); // 很重要,容許跨域訪問傳輸cookie
  
    if (req.method == 'OPTIONS') {
      res.send(200); /讓options請求快速返回/
    }
    else {
      next();
    }
  });

app.get('/', (req, res) => {
    // res.cookie('name', 'hey', { domain: req.hostname, path: '/'});
    res.cookie('name', 'hey', { domain: req.hostname, path: '/', sameSite: 'None'});
    // res.cookie('name', 'hey', { secure: true });
    res.send('Hello World!a')
})

app.get('/get/test', (req, res) => {
  res.cookie('username', 'hahah', { domain: req.hostname, path: '/', sameSite: 'Lax'});
  // res.cookie('name', 'hey', { secure: true });
  res.send('Hello World!a')
})

app.get('/get', (req, res) => {
  res.set('Content-Type', 'text/html')
  console.log(req.headers, 'header')
  const html = `
  <div>
    <div>服務器同站點下的頁面</div>
    <script>
      function fetchUrl(url, method='get') {
        fetch(url, {
        method,
        mode: 'cors',
        credentials: 'include' //很重要,容許跨域訪問傳輸cookie
      }).then((res) => {
        console.log(res)
      })

      }

      fetchUrl('http://localhost:8899/get/test')
    </script>
  </div>
  `
  res.send(html)
})

app.post('/post', (req, res) => {
  console.log(req.headers, 'header')
    // res.cookie('name', 'hey', { secure: true });
    res.send('I am post')
})

app.listen(port, () => console.log(`Example app listening on port ${port}!`))
  • 服務器 www.haha.com:9999
const express = require('express')
const app = express()
const port = 9999

//allow custom header and CORS
app.all('*',function (req, res, next) {
    // res.header('Access-Control-Allow-Origin', '*');
    // res.header('Access-Control-Allow-Origin', req.hostname);
    console.log(req.headers.origin)
    res.header('Access-Control-Allow-Origin', req.headers.origin);
    res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
    res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
    res.header('Access-Control-Allow-Credentials', true); // 很重要,容許跨域訪問傳輸cookie
  
    if (req.method == 'OPTIONS') {
      res.send(200); /讓options請求快速返回/
    }
    else {
      next();
    }
  });

app.get('/', (req, res) => {
    res.set('Content-Type', 'text/html')
    console.log(req.headers, 'header')
    const html = `
    <div>
      <div>nihao</div>
      <img src="http://localhost:8899" />
      <a href="javascript: fetchUrl('http://localhost:8899/post', 'post');">你好,交個朋友吧</a>
      <script>
        function fetchUrl(url, method='get') {
          fetch(url, {
          method,
          mode: 'cors',
          credentials: 'include' //很重要,容許跨域訪問傳輸cookie
        }).then((res) => {
          console.log(res)
        })

        }

        fetchUrl('http://localhost:8899/post', 'post')
      </script>
    </div>
    `
    res.send(html)
})

app.listen(port, () => console.log(`Example app listening on port ${port}!`))
最後

以前雖然知道CSRF的原理,可是沒有實際模擬過,實際模擬以後,感受,原來真的那麼簡單。也算是一個小小收穫

Web安全之XSS攻擊

歡迎跟我一塊兒挖坑、填坑。
image

相關文章
相關標籤/搜索