从零开始掌握Web安全测试的核心技巧与实战经验分享
又被安全测试虐了一顿,这次是XSS漏洞
最近项目上线前做安全测试,又被抓了个典型问题——XSS漏洞。说来惭愧,这种老生常谈的问题居然还能翻车。事情是这样的:我们有个评论功能,用户提交的内容直接渲染到页面上,结果就被测试人员注入了恶意脚本。
折腾了半天才发现问题出在前端的数据处理不够严谨,后端虽然做了过滤,但还是被绕过了。这里我踩了个坑,以为后端已经处理干净了,前端就可以偷懒,事实证明这种想法太天真了。
排查过程:从怀疑到确认
一开始我以为是后端的锅,毕竟他们负责数据清洗和存储。但仔细看了接口返回的数据,发现后端确实对特殊字符做了转义,比如尖括号<和>都变成了HTML实体。那问题肯定出在前端。
然后我开始检查前端代码,发现我们在渲染评论时用了innerHTML,这简直就是给XSS开了大门。试了几个简单的payload,比如:
const userInput = '<script>alert("XSS")</script>';
document.getElementById('comment').innerHTML = userInput;
果然页面直接弹窗了。这里我踩了个大坑:innerHTML真的不能随便用,尤其是在用户输入场景下。
核心代码就这几行:解决XSS问题
最后解决方案其实很简单,就是不用innerHTML,改用更安全的DOM操作方法。比如这样:
function safeRender(containerId, content) {
const container = document.getElementById(containerId);
if (!container) return;
// 创建文本节点,避免解析HTML
const textNode = document.createTextNode(content);
container.appendChild(textNode);
}
// 示例调用
safeRender('comment', '<script>alert("XSS")</script>');
这段代码的核心思想是:通过createTextNode创建纯文本节点,浏览器会自动对特殊字符进行转义,从而避免了XSS攻击。
另外我还加了个兜底方案,在接收数据时再做一次转义:
function escapeHtml(str) {
return str.replace(/[&<>"']/g, function (match) {
const escapeMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
};
return escapeMap[match];
});
}
// 示例调用
const userInput = '<script>alert("XSS")</script>';
const safeContent = escapeHtml(userInput);
document.getElementById('comment').textContent = safeContent;
这个函数会对常见的危险字符进行转义,确保它们不会被浏览器解析为HTML标签或属性。
为什么还会翻车?反思一下
其实这次的问题归根结底还是心存侥幸。我一开始觉得后端已经做了过滤,前端应该没啥风险,结果忽略了几个关键点:
- 后端的过滤规则可能被绕过,尤其是复杂场景下的多层编码。
- 前端渲染方式直接影响安全性,像
innerHTML这种API天生就不安全。 - 安全测试的覆盖面有限,有些边缘情况可能没测到。
后来试了下发现,如果用户输入的是双重编码的payload,比如<script>,后端可能只解码一次,导致攻击代码顺利通过。所以前后端都需要做好防御。
还有个小问题:样式丢了
改完之后我发现评论内容里的换行符和空格都没了,页面看起来有点乱。原因是textContent会把所有空白字符都当成普通文本,而原来的innerHTML会保留一些格式。
这里我简单处理了一下,用正则替换换行符为标签:
function formatContent(content) {
return content.replace(/n/g, '<br>');
}
const userInput = '第一行n第二行';
const formattedContent = formatContent(escapeHtml(userInput));
document.getElementById('comment').innerHTML = formattedContent;
不过这里又引入了一个小隐患:如果换行符前后有其他特殊字符,可能会导致样式异常。暂时还没想到更好的办法,先这么用着吧。
总结一下我的踩坑经验
以上是我踩坑后的总结,简单回顾一下重点:
- 不要依赖单一防线,前后端都需要做安全防护。
- 尽量避免使用
innerHTML,改用更安全的DOM操作方法。 - 对接口返回的数据保持警惕,即使后端做了过滤也要再校验一遍。
- 安全测试很重要,但也不能完全依赖它,开发者自己要有安全意识。
如果你有更好的方案或者发现了我代码中的问题,欢迎评论区交流!这个技巧的拓展用法还有很多,后续我会继续分享这类博客。

暂无评论