你的网站真的安全吗?XSS攻击可能正在窃取用户数据
别急着关页面——你刚上线的活动页、用户评论区、甚至那个“欢迎回来,张三”的小提示,都可能是XSS的后门。真实情况是:90%的XSS漏洞,不是出在黑客多高明,而是我们把用户输入原封不动塞进了HTML里,还信誓旦旦点了个“提交”。
为什么你的输入框会成为攻击入口?
浏览器不会质疑它渲染的内容。它只认一件事:你给它什么标签,它就照单执行。
当用户在搜索框里输入 <script>fetch('/api/me')</script>,而你又把它直接拼进HTML返回给下一个人——那这个脚本就会在对方浏览器里跑起来。
就像你让前台帮贴个便签:“张经理,速来3楼”,结果便签背面用隐形墨水写着“把保险柜密码发我”。所有路过的人,都照做了。
某招聘平台曾出过类似问题:求职者在“自我介绍”栏里藏了一段JS,只要HR点开简历预览,脚本就悄悄把当前页面的登录态发到外部服务器。后续账号被批量盗用,直到有员工发现自己的后台在没操作的情况下自动点了“导出全部候选人”。
处理用户输入,仅仅过滤够吗?
别再写 str.replace(/<script>/gi, '') 了。
攻击者早把 <ScRiPt>、<img/src="x"onerror=alert()>、甚至 <script> 玩得比你还熟。
黑名单式过滤,等于在防盗门上画一把锁。
真正管用的是两步:
- 输入时验证:手机号字段,就只收数字、括号和短横线;邮箱字段,用标准正则校验格式;其他字段,明确长度和字符集。验证必须放在后端,前端校验只是省用户一次刷新。
- 输出时编码:这才是防XSS的主战场。数据安安静静躺在数据库里不危险,危险的是它被塞进HTML那一刻。
输出到页面时,到底该怎么做?
记住一句话:数据放哪儿,就按那儿的规则编码。
如果插在 HTML 标签中间(比如
<p>用户写了:{content}</p>),就做 HTML 实体编码:<→<,>→>,&→&,"→",'→'
这样<script>就老老实实显示成文字,不会被解析。如果塞进 HTML 属性里(比如
<input value="{content}">),除了实体编码,还得确保属性值用引号包裹。不加引号?编码可能直接失效。如果要放进 JS 字符串(比如
var name = "{content}";),就得用 JavaScript 字符串编码,比如把双引号转成\x22;放进 URL 参数?那就用encodeURIComponent()。
别硬编,用框架自带的输出函数(如 React 的{content}、Vue 的{{ content }}、Django 的{{ content|escape }}),它们默认做了上下文感知编码。
有哪些现成的安全机制可以直接用?
别重复造轮子。你天天用的工具,早就给你备好了盾牌:
CSP(内容安全策略):在 Nginx 或后端加一行响应头:
Content-Security-Policy: script-src 'self';
这句话的意思是:“浏览器,只准执行同源的JS,别的脚本,不管藏得多深,一律不许运行。”
即使 XSS payload 成功注入,也会被拦在执行前。Cookie 加个
HttpOnly:
设置Set-Cookie: sessionid=xxx; HttpOnly; Secure;
这样document.cookie就读不到它——XSS 拿不到 session,基本就废了一半。响应头补两行保底:
X-Content-Type-Options: nosniff(防MIME类型混淆)X-Frame-Options: DENY(防点击劫持)
你的富文本编辑器是不是安全盲区?
论坛、知识库、客服消息……这些地方不能简单地全量转义,否则加粗变纯文本,链接变死链。
这时候,白名单是唯一靠谱的路:
- 只允许
<p><b><i><ul><li><a>这类基础标签; <a>标签只放href,且强制检查协议必须是https://或http://,砍掉javascript:和data:;- 所有
style属性一概拒绝,CSS 能做的恶意事,远超你想象。
别手写正则去“修”HTML。用成熟的库:
- 后端推荐
DOMPurify(Node)、bleach(Python)、jsoup(Java); - 前端直接上
DOMPurify.sanitize(html),一行代码搞定。
另外,少用 .innerHTML = xxx。能用 .textContent 显示纯文本,就别碰 HTML;非得动态加节点?用 document.createElement('div') + appendChild(),安全又干净。
如何建立持续的安全防御习惯?
安全不是上线前突击检查,而是每天写代码时的肌肉记忆:
- 每次写模板,问自己一句:“这段变量,是从哪来的?我要把它放在 HTML 的哪个位置?”
- 代码审查时,重点盯三处:表单接收、数据库读取、模板渲染。这三个环节串起来,就是一条完整的XSS流水线。
- 用你 already 在用的工具扫漏洞:
- VS Code 装上 “ESLint + eslint-plugin-security” 插件,保存即报
dangerous innerHTML; - Jenkins 或 GitHub Actions 里加个
npm run audit或pip install --upgrade pip && pip install safety && safety check;
- VS Code 装上 “ESLint + eslint-plugin-security” 插件,保存即报
- 每季度快速过一遍依赖:
npm outdated/pip list --outdated,重点关注express、lodash、moment这类高频漏洞库。
今天下班前就能做的一件事
打开你正在维护的项目,在本地或测试环境里,找一个用户可编辑、且内容会直接展示的页面——比如「个人简介」编辑页、「评论列表」页、「消息通知」弹窗。
然后,在对应输入框里,粘贴这三行,逐个提交:
<script>alert(1)</script><img src=x onerror=alert(1)>"><svg/onload=alert(1)>
如果页面弹出警告框,或者排版崩了、按钮消失了、控制台报错——说明漏洞就在那儿。
不用等明天,现在就打开你项目的模板文件(比如 profile.html 或 comment.vue),找到渲染用户内容的那一行,把它改成带上下文编码的写法:
- Vue:把
{{ user.bio }}改成v-html="user.bio | safeHtml"(并配好过滤器); - React:把
{user.bio}改成{DOMPurify.sanitize(user.bio)}; - Django:把
{{ user.bio }}改成{{ user.bio|escape }}或更严格的{{ user.bio|urlize|linebreaks }}。
改完,重新提交测试。弹窗没了,样式稳了——你刚刚亲手堵住了一个真实存在的XSS入口。