Skip to content

Commit 3d687dd

Browse files
committed
feat[ptr]: CSRF danger example and 5 ways to solve
1 parent 445986a commit 3d687dd

8 files changed

Lines changed: 169 additions & 0 deletions

File tree

network/cookie/CSRF/1-noCookie.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// 直接不用cookie,用 H5 的 localStorage ,但是兼容性可能较差、并且攻击者可以
2+
// XSS: alert(localStorage.getItem("access_token"));
3+
//
4+
5+
// login.js
6+
async function login(username, password) {
7+
const res = await fetch("https://api.example.com/login", {
8+
method: "POST",
9+
headers: {
10+
"Content-Type": "application/json"
11+
},
12+
body: JSON.stringify({ username, password })
13+
});
14+
15+
const data = await res.json();
16+
17+
// token 存 localStorage
18+
localStorage.setItem("access_token", data.token);
19+
20+
console.log("登录成功,token 已保存");
21+
}
22+
23+
24+
// request.js
25+
function authFetch(url, options = {}) {
26+
const token = localStorage.getItem("access_token");
27+
28+
return fetch(url, {
29+
...options,
30+
headers: {
31+
...(options.headers || {}),
32+
Authorization: `Bearer ${token}`
33+
}
34+
});
35+
}
36+
37+
authFetch("https://api.example.com/profile")
38+
.then(res => res.json())
39+
.then(data => console.log("用户信息:", data));

network/cookie/CSRF/2-samesite.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// 防御力强
2+
// 但是可能误伤跨站资源、例如图片,所以也可以开启宽松 Lax 只扼杀 post ,放过 get
3+
4+
app.post("/login", (req, res) => {
5+
const token = "session_id_abc123";
6+
7+
res.cookie("session_id", token, {
8+
httpOnly: true, // JS 无法读取,防 XSS 偷 cookie
9+
secure: true, // 只能 HTTPS
10+
sameSite: "strict", // 跨站请求不携带 cookie
11+
});
12+
13+
res.json({ message: "登录成功" });
14+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// 在用户操作时后端返回 CSRF cookie 这是一次性的,仅能用于当前操作
2+
3+
async function initCsrf() {
4+
const res = await fetch("/csrf", { credentials: "include" });
5+
const data = await res.json();
6+
sessionStorage.setItem("csrfToken", data.csrfToken);
7+
}
8+
9+
async function transfer() {
10+
const csrfToken = sessionStorage.getItem("csrfToken");
11+
12+
const res = await fetch("/transfer", {
13+
method: "POST",
14+
credentials: "include",
15+
headers: {
16+
"Content-Type": "application/json",
17+
"X-CSRF-Token": csrfToken,
18+
},
19+
body: JSON.stringify({ to: "bob", amount: 100 }),
20+
});
21+
22+
console.log(await res.json());
23+
}

network/cookie/CSRF/4-referer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// 查看请求头的 referer
2+
// 但是如果用了 base64 编码,请求头就不会携带 referer ,例如 network/cookie/CSRF/danger2Iframe.html
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# 长短期双Token
2+
3+
**短期**令牌 Access Token 放在前端例如 **localStorage**
4+
5+
**长期**令牌 Refresh Token 放在 **HttpOnly Cookie 中供服务器端**使用(防XSS),且开启 Secure
6+
7+
1. 登录
8+
1. 前端发送 POST 请求
9+
2. 服务器验证成功后返回 长期令牌 - 浏览器自动放到 Cookie 中;
10+
11+
```jsx
12+
Set-Cookie: refresh_token=xxx; HttpOnly; Secure; SameSite=Lax; Max-Age=7d
13+
```
14+
15+
并在 响应体 中返回 短期令牌 - 前端保存到内存(JS变量)或者 localStorage 中
16+
17+
```jsx
18+
{
19+
"access_token": "xxx",
20+
"expire_in": 900
21+
}
22+
```
23+
24+
2. 调用业务 API 时,请求手动携带 Access Token
25+
26+
```jsx
27+
GET /api/userinfo
28+
Authorization: Bearer ACCESS_TOKEN
29+
```
30+
31+
1. 没有过期的话直接服务器验证然后返回
32+
2. 如果服务器返回 **401** 表示短期令牌 Access Token 过期了,那么前端立即调用 POST /api/refresh_token **携带上 长期令牌 refresh_token 去请求**,服务器端验证长期令牌成功后会在响应体中返回新的 短期令牌 ,前端更新 Access Token
33+
3. 如果返回 **403** 表示长期令牌也过期了,那么就需要重新登录

network/cookie/CSRF/danger.html

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Document</title>
8+
</head>
9+
10+
<body>
11+
<img src="https://bank.com/transfer?to=ceilf6&money=1000000" alt="">
12+
</body>
13+
14+
</html>
15+
16+
<style>
17+
img {
18+
display: none;
19+
}
20+
</style>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8" />
6+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8+
<title>Document</title>
9+
</head>
10+
11+
<body>
12+
<form action="http://localhost:3000/transfer" method="post">
13+
<input type="text" name="to" value="ceilf6" />
14+
<input type="text" name="money" value="1000000" />
15+
</form>
16+
17+
<script>
18+
document.querySelector('form').submit();
19+
</script>
20+
</body>
21+
22+
</html>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8" />
6+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8+
<title>Document</title>
9+
</head>
10+
11+
<body>
12+
<!-- src="./danger2Attack.html" -->
13+
<iframe src="data:text/html;base64,Li9kYW5nZXIyQXR0YWNrLmh0bWw=" frameborder="0" style="display: none"></iframe>
14+
</body>
15+
16+
</html>

0 commit comments

Comments
 (0)