从零开始掌握Web安全测试的核心技巧与实战经验分享

程序猿昊沅 安全 阅读 782
赞 25 收藏
二维码
手机扫码查看
反馈

又被安全测试虐了一顿,这次是XSS漏洞

最近项目上线前做安全测试,又被抓了个典型问题——XSS漏洞。说来惭愧,这种老生常谈的问题居然还能翻车。事情是这样的:我们有个评论功能,用户提交的内容直接渲染到页面上,结果就被测试人员注入了恶意脚本。

从零开始掌握Web安全测试的核心技巧与实战经验分享

折腾了半天才发现问题出在前端的数据处理不够严谨,后端虽然做了过滤,但还是被绕过了。这里我踩了个坑,以为后端已经处理干净了,前端就可以偷懒,事实证明这种想法太天真了。

排查过程:从怀疑到确认

一开始我以为是后端的锅,毕竟他们负责数据清洗和存储。但仔细看了接口返回的数据,发现后端确实对特殊字符做了转义,比如尖括号<>都变成了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 = {
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#39;',
    };
    return escapeMap[match];
  });
}

// 示例调用
const userInput = '<script>alert("XSS")</script>';
const safeContent = escapeHtml(userInput);
document.getElementById('comment').textContent = safeContent;

这个函数会对常见的危险字符进行转义,确保它们不会被浏览器解析为HTML标签或属性。

为什么还会翻车?反思一下

其实这次的问题归根结底还是心存侥幸。我一开始觉得后端已经做了过滤,前端应该没啥风险,结果忽略了几个关键点:

  • 后端的过滤规则可能被绕过,尤其是复杂场景下的多层编码。
  • 前端渲染方式直接影响安全性,像innerHTML这种API天生就不安全。
  • 安全测试的覆盖面有限,有些边缘情况可能没测到。

后来试了下发现,如果用户输入的是双重编码的payload,比如&lt;script&gt;,后端可能只解码一次,导致攻击代码顺利通过。所以前后端都需要做好防御。

还有个小问题:样式丢了

改完之后我发现评论内容里的换行符和空格都没了,页面看起来有点乱。原因是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操作方法。
  • 对接口返回的数据保持警惕,即使后端做了过滤也要再校验一遍。
  • 安全测试很重要,但也不能完全依赖它,开发者自己要有安全意识。

如果你有更好的方案或者发现了我代码中的问题,欢迎评论区交流!这个技巧的拓展用法还有很多,后续我会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论