$cat avatar-app.mdWebHackultet 2026
Avatar App
A PHP avatar uploader wraps a deliberately hard deserialization chain around a much simpler bug, a file the Dockerfile has to serve just to start the container.
$man avatar-app
What this one teaches:
- Reading a Dockerfile as part of the attack surface, not just the application code.
- Why a shell redirect like
< fileneeds that file to exist before the command runs. - Checking the obvious, reachable files before chasing an elaborate exploit chain.
Task description
An avatar upload application, with its PHP source made available through
.phpssymlinks for review.
An elaborate-looking app
The app has everything in place for a hard web challenge: a JPEG-only upload form, an avatar viewer, an integrity checker that calls file_exists() on a path we control, and a chain of PHP classes (Profile, EventDispatcher, LogHandler, AvatarProcessor) that looks built for PHP object deserialization, ending in a call to ImageMagick’s convert. All the PHP source is readable via .phps files the Dockerfile exposes.
Put together, that reads like a PHAR deserialization chain into ImageMagick command execution, the kind of multi-step exploit these components are usually there to teach. We started tracing it, then checked the Dockerfile that builds the container, since it’s part of what ships with the challenge and worth reading before committing to a plan.
The file the server needs to start
The key lines:
COPY flag.txt 3
RUN chown root:root 3 && chmod 400 3
WORKDIR /var/www/html
COPY src/ /var/www/html/
RUN chown -R root:root /var/www/html && chmod -R 755 /var/www/html
ENTRYPOINT docker-php-entrypoint apache2-foreground <3
Reading it line by line:
COPY flag.txt 3places the flag at/3(the working directory is still/at that point in the build), and it’s locked tochmod 400, root-only.- The entrypoint ends in
<3, a shell redirect that feeds the file named3to Apache’s stdin. By the time this line runs,WORKDIRis/var/www/html, so the shell looks for3there, not at/3. - For that redirect to succeed at all, and for the container to start, a file named
3has to exist inside/var/www/html. That meanssrc/in the build context contains one. - The very next line,
chmod -R 755 /var/www/html, makes everything under the web root world-readable, including that3.
So a second copy of the flag, needed only so the container’s startup command has something to read, ends up sitting in the public web root with permissions that let Apache serve it as a plain static file.
The solve
curl http://chal.live.hackultet.hr:14007/3
That alone returns the flag. No upload, no deserialization gadget, no ImageMagick at all.
What the rest of the app was for
The PHAR gadget chain, ImageMagick’s relaxed @* policy, the JPEG structure validator, and the scheme filtering that blocks most PHP stream wrappers except http/https would all combine into a genuinely hard exploit, the one the challenge’s components are clearly built to teach. None of it was needed here: a build artifact meant only to keep the entrypoint’s stdin redirect from failing was left readable from the outside.
$cat flag.txt reveal
CTF2026[964734005206]