面试题答案
一键面试CSRF 攻击对 Next.js 页面路由的危害
- 数据篡改:攻击者可能利用用户已登录的会话,伪造请求向 Next.js 应用的页面路由发送请求,执行一些修改数据的操作,比如修改用户个人资料、更改订单信息等,而用户对此毫不知情。
- 状态改变:未经授权改变应用的状态,例如在电商应用中,攻击者可能伪造请求取消用户的订单,影响用户正常的业务流程。
- 权限滥用:若用户具有某些高级权限,攻击者可以通过 CSRF 攻击利用这些权限执行恶意操作,如在管理页面创建新的管理员账号等。
Next.js 项目中结合 CSRF 防护机制增强页面路由安全性的策略
服务器端防护策略
- 使用 CSRF 令牌:
- 生成令牌:在服务器端,每次用户请求页面时生成一个唯一的 CSRF 令牌。例如,在 Next.js 的 API 路由中,可以使用加密库(如
crypto
)生成一个随机字符串作为令牌。
import crypto from 'crypto'; const generateCSRFToken = () => { return crypto.randomBytes(32).toString('hex'); };
- 存储令牌:将生成的令牌存储在用户会话(session)中。在 Next.js 中,可以结合
next - iron - session
等库来管理会话。
import Iron from 'next - iron - session'; const session = Iron({ password: process.env.SESSION_PASSWORD, cookieName: 'csrf - session', cookieOptions: { secure: process.env.NODE_ENV === 'production' } }); export default async (req, res) => { await session(req, res); const csrfToken = generateCSRFToken(); req.session.csrfToken = csrfToken; await req.session.save(); res.json({ csrfToken }); };
- 验证令牌:当接收到用户的表单提交或其他可能受 CSRF 攻击影响的请求时,从请求头或表单数据中获取客户端传递的 CSRF 令牌,并与存储在会话中的令牌进行比对。
import Iron from 'next - iron - session'; const session = Iron({ password: process.env.SESSION_PASSWORD, cookieName: 'csrf - session', cookieOptions: { secure: process.env.NODE_ENV === 'production' } }); export default async (req, res) => { await session(req, res); const clientCSRFToken = req.body.csrfToken; const serverCSRFToken = req.session.csrfToken; if (clientCSRFToken === serverCSRFToken) { // 处理正常请求 } else { res.status(403).json({ error: 'CSRF token verification failed' }); } };
- 生成令牌:在服务器端,每次用户请求页面时生成一个唯一的 CSRF 令牌。例如,在 Next.js 的 API 路由中,可以使用加密库(如
- 设置合适的 HTTP 头:
- Same - Site Cookie:设置
Set - Cookie
头的Same - Site
属性,防止跨站请求时携带用户的会话 cookie。可以在 Next.js 中通过next.config.js
进行配置。
module.exports = { async headers() { return [ { source: '/(.*)', headers: [ { key: 'Set - Cookie', value: 'sessionid=abcdef; Same - Site=strict' } ] } ]; } };
- Content - Security - Policy:通过设置
Content - Security - Policy
头,限制页面可以加载的资源来源,减少攻击者注入恶意脚本进行 CSRF 攻击的机会。同样在next.config.js
中配置。
module.exports = { async headers() { return [ { source: '/(.*)', headers: [ { key: 'Content - Security - Policy', value: "default - src'self'" } ] } ]; } };
- Same - Site Cookie:设置
客户端防护策略
- 在表单和请求中包含令牌:
- 页面渲染:在 Next.js 页面中,当渲染包含表单的页面时,从服务器获取 CSRF 令牌并嵌入到表单中作为隐藏字段。
import React, { useEffect, useState } from'react'; import { useRouter } from 'next/router'; const MyForm = () => { const [csrfToken, setCSRFToken] = useState(''); const router = useRouter(); useEffect(() => { const fetchCSRFToken = async () => { const response = await fetch('/api/csrf - token'); const data = await response.json(); setCSRFToken(data.csrfToken); }; fetchCSRFToken(); }, []); const handleSubmit = (e) => { e.preventDefault(); const formData = new FormData(); formData.append('csrfToken', csrfToken); // 添加其他表单数据 fetch('/api/submit - form', { method: 'POST', body: formData }).then((response) => { if (response.ok) { router.push('/success'); } else { console.error('Form submission failed'); } }); }; return ( <form onSubmit={handleSubmit}> <input type="hidden" name="csrfToken" value={csrfToken} /> {/* 其他表单字段 */} <button type="submit">Submit</button> </form> ); }; export default MyForm;
- AJAX 请求:对于通过
fetch
或其他 AJAX 方式发送的请求,将 CSRF 令牌添加到请求头或请求体中。
const csrfToken = 'your - csrf - token - here'; fetch('/api/some - action', { method: 'POST', headers: { 'Content - Type': 'application/json', 'X - CSRF - Token': csrfToken }, body: JSON.stringify({ someData: 'value' }) });
- 避免在可跨站访问的资源中泄露敏感信息:
- 不要在 URL 中暴露敏感数据:避免在页面路由的 URL 中传递密码、令牌等敏感信息,因为这些信息可能被跨站请求获取到。
- 限制第三方脚本访问:谨慎使用第三方脚本,确保它们不会在跨站环境中获取或篡改用户数据。对第三方脚本进行严格的安全审查,并且设置合适的权限和作用域。