怎么做一考试网站不踩坑?建站报价背后的安全真相
改个需求建站公司拖一周,这种憋屈感做过项目的人太懂。很多老板找外包做怎么做一考试网站,拿到报价单只盯着“建站报价”里的数字,却没人提安全成本。结果上线三天,题库被拖库,或者有人通过接口批量刷分,最后赔的钱比当初省下的开发费多十倍。
今天不聊虚的,直接拆解一个真实案例:某教育机构花3.5万做了个在线考试系统,因为后端校验逻辑缺失,被黑客利用SQL注入直接读取了数据库。这个教训很惨痛,但更惨的是,90%的开发者在写考试系统时,都在重复犯同样的错误。
威胁场景:考试系统到底怕什么
考试网站和普通的展示型官网完全不同,它的核心资产是“公平性”和“数据完整性”。在设计安全架构前,你得先明白黑客盯着你什么。
第一,题目泄露。这是最大的痛点。如果前端把题目数据全量渲染,或者接口没有做权限校验,用户只要按F12,或者抓包工具一开,所有考题就躺在响应体里了。对于付费题库或者保密性高的认证考试,这等于直接崩盘。
第二,成绩篡改与刷分。考试系统的后端逻辑往往比前端复杂得多。如果只在前端JS里判断“是否交卷”或者“答案是否正确”,黑客可以直接修改请求参数,把答案改成对的,或者跳过考试时间限制。这种“前端信任”是安全大忌。
第三,DDoS攻击导致服务中断。考试通常有统一的时间窗口,比如上午9:00整开始。这种瞬时高并发极易引发DDoS。如果服务器配置不当,或者没有做限流,几百个并发就能把系统搞挂,导致大量考生无法进入考场。
第四,用户隐私泄露。考生账号、手机号、身份证号,这些敏感数据如果明文存储在数据库,或者在传输过程中没有加密,一旦数据库泄露,就是巨大的法律风险。
漏洞原理:为什么你的代码在裸奔
很多设计师转前端的开发者,习惯用UI思维做安全,觉得“看不见”就是“安全”。其实,安全是后端和架构的事,前端只是最后一道防线。
这里有两个最典型的漏洞原理,必须搞懂。
漏洞一:越权访问(IDOR)
这是考试系统最常见的漏洞。假设你的接口是 /api/getQuestion?id=101。
如果不校验当前登录用户是否有权查看这道题,或者是否处于考试时间窗口内,任何知道或猜测到 id=101 的人都能获取题目。
很多开发者以为加了JWT Token就安全了,但JWT只验证了“你是谁”,没验证“你能看什么”。如果Token里没包含题目权限,或者后端没做二次校验,IDOR漏洞就存在。
漏洞二:SQL注入与命令注入
考试系统涉及大量的动态查询,比如按难度、按章节查题。如果拼接SQL时没有参数化,用户就可以在输入框里输入 ' OR 1=1 --,直接绕过条件,拖出整个题库。
更隐蔽的是,如果系统允许导出成绩单为Excel或PDF,且后端直接拼接了文件名或路径,攻击者可以构造 ../../etc/passwd 这样的路径遍历,读取服务器系统文件。
根据 MDN Web Docs 的安全最佳实践,客户端(浏览器端)永远不能被信任。所有涉及业务逻辑的判断(如答案正确性、考试时间、用户权限),必须在服务端完成。前端做的任何校验,只是为了提升用户体验,绝不能作为安全边界。
防护方案:代码层面的硬核防御
光讲原理没用,直接上代码。对比一下“不安全”和“安全”的写法,你就能明白差距在哪。
场景1:获取题目接口
❌ 错误写法(不安全)
// Node.js Express 示例
app.get('/api/question', (req, res) = {const { id } = req.query;// 直接从数据库查询,没有校验用户权限,没有校验考试时间const question = db.query(`SELECT * FROM questions WHERE id = ${id}`);res.json(question);
});风险点:SQL拼接,存在注入风险。
没有检查 req.user 是否有权限看这道题。
没有检查当前时间是否在考试窗口内。✅ 正确写法(安全)
app.get('/api/question', authMiddleware, (req, res) = {const { id } = req.query;const userId = req.user.id;const now = new Date();// 1. 参数化查询,防SQL注入const question = db.query('SELECT * FROM questions WHERE id = ?', [id]);if (!question) {return res.status(404).json({ error: 'Question not found' });}// 2. 权限校验:确保该题目对当前用户可见// 3. 时间校验:确保在考试时间内if (!isUserAllowed(userId, question.examId) || !isInExamWindow(question.examId, now)) {return res.status(403).json({ error: 'Access denied' });}// 4. 数据脱敏:只返回必要字段,不返回答案解析(如果是正式考试)const safeData = {id: question.id,title: question.title,options: question.options};res.json(safeData);
});关键点:使用参数化查询(? 占位符)。
强制通过中间件获取 userId,不信任前端传递的ID。
后端双重校验权限和时间。
最小化返回数据,不泄露答案解析。场景2:提交答案接口
❌ 错误写法(不安全)
app.post('/api/submit', (req, res) = {const { questionId, answer } = req.body;// 前端传什么就是什么,直接存库或计算分数const isCorrect = (answer === db.getCorrectAnswer(questionId));db.saveScore(req.user.id, questionId, isCorrect);res.json({ score: isCorrect ? 100 : 0 });
});风险点:攻击者可以伪造 answer 参数,或者直接修改 isCorrect 的值(如果前端返回了判断逻辑)。
没有防重放攻击,同一个请求可以无限次提交。✅ 正确写法(安全)
app.post('/api/submit', authMiddleware, (req, res) = {const { questionId, answer } = req.body;const userId = req.user.id;// 1. 防重放:检查该题是否已经提交过const existingScore = db.getScore(userId, questionId);if (existingScore) {return res.status(400).json({ error: 'Already submitted' });}// 2. 服务端判题:绝不信任前端传来的结果const correctAnswer = db.getCorrectAnswer(questionId);const isCorrect = (answer === correctAnswer);// 3. 记录日志与时间戳,便于审计db.saveScore(userId, questionId, isCorrect, new Date());// 4. 只返回提交状态,不返回具体得分细节(防止前端探测)res.json({ status: 'success' });
});关键点:服务端重新获取正确答案进行比对。
增加防重放逻辑,每题只能提交一次。
不向前端暴露具体的得分细节,减少信息泄露面。检测与修复:上线前的“找茬”环节
代码写完了,别急着上线。自己测一遍,或者找同行帮你看一眼,重点检查这三个地方。
1. 接口权限遍历测试
拿一个普通考生的账号,登录系统。
打开浏览器开发者工具,找到获取题目的接口。
尝试修改 id 参数,从 101 改成 102、103,直到 200。
如果能看到不该看的题目,说明存在越权漏洞。
修复:后端必须校验 userId 与 examId 的关联关系。
2. 时间戳篡改测试
在浏览器控制台执行 new Date(),查看当前时间。
然后尝试通过修改系统时间,或者使用代理工具(如Burp Suite)修改请求头中的 X-Client-Date(如果后端依赖此头)。
看后端是否以服务器时间为准。
修复:后端所有时间判断,必须使用 server.getTime(),严禁使用 client.getTime()。
3. SQL注入基础扫描
在任意输入框(如搜索题目关键词)输入 ' OR 1=1 --。
如果返回了所有数据,或者报错信息泄露了SQL语句,说明存在注入风险。
修复:全面检查ORM框架的使用,确保所有动态查询都使用参数化绑定。
安全加固清单:给设计师转前端的避坑指南
如果你是设计师转前端,或者负责项目管理的非技术背景人员,这份清单能帮你把关建站公司是否靠谱。不要只看UI美不美,要问以下问题:
1. 关于数据加密问:用户密码是明文存储还是哈希存储?
答:必须是 Bcrypt 或 Argon2 哈希存储,且加盐。如果说是 MD5,直接 Pass。
问:传输过程是否全站 HTTPS?
答:必须配置 SSL 证书,且 HSTS 头已开启。2. 关于并发与限流问:考试高峰期预计有多少并发?服务器做了限流吗?
答:应该配置 Nginx 的 limit_req 模块,或者应用层的 Rate Limiting。例如,每个IP每分钟最多发起 10 次请求。3. 关于日志与审计问:有没有记录操作日志?
答:必须记录“谁”在“什么时间”“提交了什么答案”“IP地址是多少”。日志要保留至少 6 个月,以便事后追溯。4. 关于第三方依赖问:用了哪些开源库?有没有检查过安全漏洞?
答:应该定期运行 npm audit 或 pip check。如果建站公司连依赖包版本都不管,说明运维能力极差。5. 关于备份问:数据库多久备份一次?
答:至少每日全量备份,实时增量备份。并且要测试过“恢复”流程,而不是只备份不测试恢复。结尾互动
做考试网站,技术只是基础,安全意识才是护城河。很多低价的建站报价之所以便宜,是因为他们把安全成本全砍了,留给你的是无穷无尽的麻烦。
建站花了多少钱?留言说说真实价格,顺便吐槽一下你遇到过最离谱的安全事故,大家一起避坑。
