2020年7月6日,我在Twitter上宣布了XSS挑战,到目前为止,只有四个人能够解决它,每个人都告诉我他们以前从未听说过挑战中使用这种"奇特"的方式,因此,这里有一篇文章解释了这个"奇特"以及一些背景故事。
挑战的核心在于以下几行JavaScript:
document.addEventListener("DOMContentLoaded", () => {
for (let attr of document.body.attributes) {
eval(attr.value);
}
});
该代码仅对<body>
元素的所有属性进行迭代,并将所有这些属性的值作为JavaScript执行。由于挑战中没有其他源,这意味着要解决该问题,需要找到一种方法来将任意属性值注入document.body。那怎么可能呢?
当我注意到HTML规范中一个有趣的代码片段时,一切就开始了。规范的第14部分称为“渲染”,介绍了某些元素的默认样式。例如,它说<style>
or <script>
元素默认情况下不显示(即 display:none)。有趣的是<body>
的边距是如何确定的?
该表表示,如果<body>
标签拥有一个名为marginheight的属性,那么它将映射到该元素的CSS属性——margin-top上。如果不存在该属性,则检查topmargin属性。如果也不存在,那么(接下来就很有意思了),如果当前页面位于嵌套的浏览器上下文(就是说在<frame>
或<iframe>
)中,浏览器将使用容器元素的marginwidth属性。这种现象在跨域的时候也一样,这是规范中直接允许的:
起初,我认为这是一个历史遗留问题,并且没有哪个现代浏览器实际以这种方式实现它。
为了测试浏览器的行为,我有一个简单的代码,可让我检查是否考虑了该marginwidth属性。
<iframe src="https://sekurak.pl/.htaccess" marginwidth="100px"></iframe>
在Chromium中,该marginwidth属性反映在<body>
元素中,但在赋值之前已被强制转换为整数。有趣的是Chromium会监听此值的更改,因此,如果您动态更改它,它也会反映在iframe中。这是一个例子:
<style>
iframe, input {
width:400px;
}
</style>
<iframe id=ifr src="https://sekurak.pl/.htaccess" marginwidth="0"></iframe>
<br>
<input type=range
min=0
max=500
value=0
oninput="ifr.setAttribute('marginwidth', this.value)">
在Firefox中,的值<iframe marginwidth>
根本不会反映在嵌套文档DOM树中。但是已将其考虑在内,可以通过进行检索getComputedStyle()。因此,带有滑块的示例的工作方式与Chromium中的完全相同。
在Safari中,的值无需任何修改即可<iframe marginwidth>
反映在嵌套<body>
元素中。
与Firefox和Chromium相反,Safari不会监听属性的更改,因此滑块示例无法使用
因此,挑战的解决方案非常简单:
<iframe src="https://securitymb.github.io/xss/3"
marginwidth="alert(document.domain)">
祝贺@terjanq,@shafigullin,@BenHayak和@steike找到了预期的解决方案!
对于那些试图找到解决方案但没有设法解决的人;提示中说:"使用Safari 可能会稍微好一点"
marginwidth/marginheight的"副作用"就在于让跨域有了可能性,在不同的浏览器中有不同的实现方式:
<body>
的marginwidth。<body marginwidth>
子级中的属性改变我认为本文的主要收获是HTML规范仍然包含一些隐藏的"宝石",这在某些晦涩的攻击中是有可能存在的。
另外,我认为这marginwidth特别适合XS泄漏漏洞,但我找不到可行的方案。
作者: Michał Bentkowski
原文地址:https://research.securitum.com/marginwidth-marginheight-the-unexpected-cross-origin-communication-channel/