취약점 개요
ejs 템플릿 엔진에서 발생하는 취약점으로, 입력 값 검증 로직이 누락되어 사용자가 템플릿 주입을 통해 RCE를 실행할 수 있다.
영향
SSTI, RCE
취약 버전
- ejs v3.1.10 이전 버전(최신 릴리즈)
환경 구성
- express v4.21.2
- ejs v3.1.9
취약점 분석
- app.js 파일을 살펴보면, express의 ejs 템플릿 엔진을 사용하여 page.ejs를 렌더링하고, GET 메서드로 전송된 값을 req.query 매개변수를 통해, ejs 템플릿에 넘겨주고 있다.


- ejs.js를 확인해보면 442번째 줄에서 렌더링된 값을 저장하고 있으며, 457번째 줄에서 req.query 매개변수로 넘겨받은 값을 data에 저장하고 있다.

- 브라우저에 /?id=aaaaa 값을 넘겨주면 data에 key, value 형식으로 저장된다.
사진은 Universe7202님의 블로그를 참고했습니다.
- 이후 ejs.js의 476번째 줄을 확인해보면, viewOpts 변수에 data.settings[‘view options’] 를 저장하고 data.settings에 [‘view options’] 값이 존재한다면 shallowCopy 함수를 실행한다.
- ejs.js의 1087번째 줄에서 shallowCopy의 실행 흐름을 보면 opts의 값이 null이나 underfined가 아니면 두번째 인자값(viewOpts)을 첫번째 인자값(opts)에 복사한다.

- ejs.js의 572번째 줄을 보면, ejs 컴파일 함수에 대한 내용이 담겨 있는데 opts.[함수명]을 임의로 덮어씌울 수 있다면 RCE가 발생한다. 이 문서에서는 581번째 줄에 있는 escapeFn 함수를 덮어씌워 RCE를 발생시킬 것이다.
- 단, escapeFn을 덮어씌우려면 선행조건으로 opts.client를 참으로 만들어야 하며, 최종적으로 escapeFn.toSting()을 통해 우리가 원하는 RCE 코드를 집어넣을 수 있다


exploit
- 도커를 이용해 취약 환경을 구성 후, http://localhost:3000 포트로 접속


- 실습에 앞서 입력한 값이 제대로 들어가는지 확인하기 위해 디버그 모드를 실행한다.
- debug 파라미터에 값을 넣고 도커 컨테이너 로그를 확인해보면 내부 디버깅이 활성화되어 값이 찍히는것을 확인할 수 있다.


- 이제 분석한 내용을 실제로 실습해보자. 사용자가 입력한 값은 req.query 매개변수를 통해 data에 저장되고, data.settings에 [‘view options’]이 존재한다면 shallowCopy를 통해 값이 opt에 저장된다.
- 우선 opt값이 변조되는지 확인하기 위해 settings[View Options][client]=true로 패킷을 전송하면 도커 컨테이너 로그에 찍히는 값이 추가된다. 이를 통해 opts.client가 참이 된 것을 확인할 수 있다.


- opts.client에 이어서 escapeFn에 값을 넣어보면 에러가 출력되고, 도커 컨테이너 로그를 확인해보면 입력한 값은 escapeFn에 저장된것을 확인할 수 있다.


- 이제 분석한 내용을 실제로 실습해보자. 우리가 입력한 값은 req.query 매개변수를 통해 data에 저장되고, data.settings에 [‘view options’]이 존재한다면 shallowCopy를 통해 값이 opt에 저장된다.
- 이를 통해, 지정한 디렉터리 경로의 파일을 읽어오는 RCE를 실행시킬 수 있다. (단, 함수에 인자값을 잘못 주면 서버가 종료되니 주의할것)


대응 방안
| 대응 방법 | 설명 |
|---|---|
| 버전 패치 | ejs v3.1.10으로 패치 |