1. 项目概述:从两个“拦路虎”说起
搞Web开发,尤其是前后端分离的项目,有两个名字听起来很像、但本质完全不同的“拦路虎”经常把人绕晕:CORS和CSRF。一个是浏览器为了安全给你设的“路障”,另一个是攻击者为了窃取你身份而设的“陷阱”。很多文章要么只讲概念,要么只给配置,看完还是云里雾里,真遇到浏览器控制台飘红报错“has been blocked by CORS policy”,或者安全测试报告里出现“CSRF漏洞”时,依然手足无措。
这个项目的目的,就是彻底终结这种模糊感。我们不满足于看文档,而是要亲手搭建一个微型的、可复现的实验环境。通过这个环境,你将能像调试普通业务逻辑一样,亲眼看到CORS策略是如何在浏览器层面拦截请求的,也能直观地理解一次成功的CSRF攻击是如何在用户毫无察觉的情况下完成的。更重要的是,我们会剖析它们之间的联系:为什么解决了CORS不等于解决了CSRF?为什么某些场景下它们会“联手”出现?理解了这些,你不仅能正确配置 Access-Control-Allow-Origin ,更能从架构层面设计出更安全的Web应用。
2. 实验环境搭建与核心概念澄清
在动手之前,我们必须把两个概念的基础定义和实验的边界划清楚。这是后续所有实验能够正确进行的前提。
2.1 核心角色定位:CORS vs CSRF
首先,我们必须明确它们各自针对的问题域和“舞台”在哪里。
CORS 的全称是“跨源资源共享”。它的核心矛盾是 “浏览器”与“服务器”之间的信任问题 。当你的前端应用(比如运行在 http://localhost:3000 )试图通过JavaScript的 fetch 或 XMLHttpRequest 去请求另一个源(比如 http://api.example.com )的资源时,浏览器会主动介入检查。它会先询问目标服务器:“嘿, localhost:3000 这个源来的小弟,你允许他访问你的资源吗?”这个询问可能是一个简单的请求头检查,也可能是一个正式的 OPTIONS 预检请求。如果服务器响应头里没有明确说“我允许”,浏览器就会毫不留情地阻断这个请求,并在控制台抛出我们熟悉的CORS错误。 所以,CORS是浏览器强制执行的一套安全策略,目的是防止恶意网站通过脚本随意读取另一个域下的敏感数据。
CSRF 的全称是“跨站请求伪造”。它的核心矛盾是 “用户浏览器”与“服务器”之间的身份验证问题 。假设你已经登录了银行网站 bank.com ,浏览器里存着有效的登录凭证(比如Session Cookie)。此时,你不小心访问了一个恶意网站 evil.com 。这个恶意网站里隐藏着一个自动提交的表单,其 action 指向 bank.com/transfer ,并填好了转账参数。由于浏览器会自动携带 bank.com 的Cookie,这个伪造的请求在服务器看来,完全就是一个来自已认证用户的合法请求,从而可能成功执行转账操作。 所以,CSRF攻击利用了浏览器对特定网站(如银行)的自动身份认证机制,诱骗用户的浏览器向目标网站发起一个非预期的请求。
简单类比:CORS像是小区门禁,检查的是“来访者”(前端JS)有没有得到“业主”(目标服务器)的访问许可;而CSRF像是有人伪造了你的门禁卡,大摇大摆地进入了你家(你的登录态)。
2.2 实验环境设计与工具选型
为了直观展示,我们需要模拟出三个关键角色:
- 受害者网站(Vulnerable Site) :一个存在CSRF漏洞的简单Web应用,用户在此登录。
- 恶意网站(Evil Site) :一个诱导用户访问的第三方网站,用于发起CSRF攻击。
- API服务器(API Server) :为受害者网站提供后端接口,我们将在此观察CORS策略的影响。
为了让实验足够轻量且聚焦,我们选择 Node.js + Express 作为后端框架。它足够简单,能让我们快速搭建多个服务,并精细控制HTTP响应头。前端我们直接用最原始的HTML和JavaScript,避免框架带来的复杂度。
你需要准备:
- Node.js环境(建议v16+)。
- 一个代码编辑器。
- 一个现代浏览器(Chrome/Firefox),用于观察开发者工具中的网络请求和报错。
项目结构大致如下:
csrf-cors-demo/
├── vulnerable-server/ # 受害者网站服务器
│ ├── server.js
│ └── views/
├── api-server/ # API服务器
│ └── server.js
└── evil-site/ # 恶意网站
└── index.html
我们将分别启动两个Node.js服务(不同端口模拟不同源),并用浏览器直接打开HTML文件来模拟恶意网站。
3. 第一阶段:亲手触发一个CORS错误
让我们先让CORS错误“现身”。这个阶段的目标是,不配置任何CORS头,亲眼看到跨域请求被浏览器阻断。
3.1 搭建无CORS配置的API服务器
在 api-server 目录下,创建 server.js :
const express = require('express');
const app = express();
const PORT = 3001;
// 一个非常简单的API,返回一些数据
app.get('/api/data', (req, res) => {
// 注意:这里故意不设置任何CORS相关的响应头
res.json({ message: '敏感数据来自 API Server 3001' });
});
// 一个接受POST请求的API,模拟修改操作
app.post('/api/update', express.json(), (req, res) => {
console.log(`API Server: 收到更新请求,数据为:`, req.body);
// 同样不设置CORS头
res.json({ status: 'success', data: req.body });
});
app.listen(PORT, () => {
console.log(`API Server 运行在 http://localhost:${PORT}`);
});
这个服务器监听3001端口,提供了两个端点,但关键点在于: 它没有设置 Access-Control-Allow-Origin 等任何CORS响应头 。
3.2 创建前端页面发起跨域请求
在 vulnerable-server 目录下,我们创建一个简单的服务器和前端页面。先创建 server.js :
const express = require('express');
const path = require('path');
const app = express();
const PORT = 3000;
app.use(express.static('views')); // 托管静态文件
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'views', 'index.html'));
});
app.listen(PORT, () => {
console.log(`受害者网站运行在 http://localhost:${PORT}`);
});
然后在 views 目录下创建 index.html :
<!DOCTYPE html>
<html>
<head>
<title>受害者网站 (localhost:3000)</title>
</head>
<body>
<h1>我是受害者网站 (源: http://localhost:3000)</h1>
<button onclick="fetchData()">获取API数据 (GET)</button>
<button onclick="updateData()">更新数据 (POST)</button>
<div id="result"></div>
<script>
const apiBase = 'http://localhost:3001'; // 不同端口,不同源!
const resultDiv = document.getElementById('result');
function fetchData() {
resultDiv.innerHTML = '请求中...';
fetch(`${apiBase}/api/data`)
.then(response => response.json())
.then(data => {


2324

被折叠的 条评论
为什么被折叠?



