cancri-sp picoCTF 2023 Solution

Published: April 26, 2023

Description

A 500-point web challenge that went unsolved during picoCTF 2023, with no public solution since. The live instance ships an unusually large source archive (around 1 GB), which points at a multi-service / multi-tier application rather than a single small app. The page below is an honest, clearly-labeled methodology for the most likely vulnerability class (an SSRF-to-internal-service chain), not a verified step-by-step solve.

Pull the live instance's source.tar.gz (it is large, around 1 GB; budget disk and bandwidth) and map the services it bundles before touching the app.

Open the challenge URL and use Burp Suite to intercept and replay requests.

bash
# Navigate to http://<HOST>:<PORT_FROM_INSTANCE>
bash
# Download and unpack the (large) source bundle, then enumerate the internal services it defines.

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
Status: unsolved, no public solution. This challenge was not solved during the competition, so the steps below are inferred from the challenge name and the large multi-service source bundle, not confirmed against a working solve. Treat the concrete endpoints and payloads as illustrative placeholders. The most defensible single guess for the bug class is an SSRF pivot into an internal service (with a SAML/SSO service-provider bypass as a runner up, given the "sp" in the name). The SSRF for CTF post covers the full methodology: confirming the sink, sweeping internal ranges, and chaining with a secondary bug to exfiltrate.
  1. Step 1
    Identify the SSRF vector
    Observation
    I noticed the challenge ships a roughly 1 GB multi-service source archive, which suggested the app accepts user-supplied URLs or resource identifiers to fetch remote content, the classic SSRF entry point in multi-tier applications.
    Find an endpoint that fetches a URL or resource specified by user input. This is the SSRF entry point.
    Learn more

    Server-Side Request Forgery (SSRF) occurs when a server makes an HTTP (or other protocol) request to a URL controlled by the attacker. Attackers use SSRF to access internal services that are not exposed to the public internet: internal APIs, metadata services (e.g., AWS IMDSv1 at 169.254.169.254), and services bound to localhost.

    Look for parameters with names like url, endpoint, src, href, redirect, or fetch. Also look for features that fetch remote content: link previews, webhook validators, PDF generators, and image importers are classic SSRF surfaces.

    Test with a URL you control (e.g., a requestbin or webhook.site URL) to confirm the server is making outbound requests with your input.

  2. Step 2
    Pivot to internal services via SSRF
    Observation
    I noticed SSRF confirmed that the server makes outbound requests with my input, which suggested probing loopback addresses and RFC1918 ranges to find unauthenticated internal services that are invisible from the public internet.
    Use the SSRF vector to probe internal addresses (127.0.0.1, 10.0.0.0/8) and find a running internal service that exposes sensitive data.
    bash
    # Try internal endpoints:
    bash
    curl 'http://<HOST>/fetch?url=http://127.0.0.1/'
    bash
    curl 'http://<HOST>/fetch?url=http://127.0.0.1:8080/'
    bash
    curl 'http://<HOST>/fetch?url=http://169.254.169.254/latest/meta-data/'
    What didn't work first

    Tried: Sending the SSRF payload directly in the browser address bar instead of through curl or Burp Suite

    The browser issues the request from your machine, not the server, so you reach only public addresses you already have access to. The SSRF only works when the server-side backend fetches the URL; you must send the parameter to the vulnerable endpoint and let the server make the outbound connection.

    Tried: Using http://localhost/ instead of http://127.0.0.1/ when the server blocks the string 'localhost'

    Many SSRF filters block the hostname 'localhost' but not its numeric equivalents. If the raw IP is also filtered, try decimal (2130706433), hex (0x7f000001), or IPv6 ([::1]). Stopping at localhost means missing the bypass entirely when a denylist is in place.

    Learn more

    Once SSRF is confirmed, enumerate internal services by trying common ports (80, 8080, 8443, 3000, 5000, 6379 for Redis, 27017 for MongoDB, 9200 for Elasticsearch). The server's response body, status code, and response time reveal whether each port is open.

    Many internal services have no authentication because they are assumed to be unreachable from outside. An internal admin panel, debug endpoint, or metadata service may immediately disclose sensitive data when accessed through SSRF.

    SSRF filter bypasses include: using alternative IP representations (0x7f000001, 2130706433, or [::1] for 127.0.0.1), protocol switches (file://, dict://, gopher://), URL redirectors, and DNS rebinding.

  3. Step 3
    Chain with injection to read the flag
    Observation
    I noticed the internal service reachable via SSRF accepts parameters that resemble file paths or query inputs, which suggested chaining the pivot with path traversal or SQL injection to escalate from network access to direct flag disclosure.
    If the internal service is vulnerable to injection (SQLi, command injection, path traversal), chain the SSRF with injection to read the flag file.
    bash
    # Example: SSRF to internal API + path traversal:
    bash
    curl 'http://<HOST>/fetch?url=http://127.0.0.1:8080/read?file=../../flag'
    bash
    # Example: SSRF to internal API + SQLi:
    bash
    curl 'http://<HOST>/fetch?url=http://127.0.0.1:8080/users?id=1+UNION+SELECT+flag+FROM+flags--'

    Expected output

    picoCTF{...}
    What didn't work first

    Tried: Attempting path traversal directly against the external-facing host before confirming an internal service handles file reads

    The external app almost certainly sanitizes or forbids path traversal. The traversal only succeeds against the internal service reachable via SSRF, which lacks the same input validation. You need to pivot through the SSRF channel first and target the internal service's file-read or API endpoint.

    Tried: Injecting SQL directly into the external app's parameters instead of tunneling the SQLi through the SSRF payload

    The external app is the SSRF vector, not the SQLi target. The internal service at 127.0.0.1 is what exposes the vulnerable SQL query. Injecting into the outer app's parameters hits a different code path that has no SQL query to exploit; the payload must be URL-encoded inside the url= parameter so the internal service receives and evaluates it.

    Learn more

    Chained vulnerabilities - using one bug to access a second, more impactful bug - are common in real-world attacks. SSRF is particularly useful as a pivot because internal services often have weaker input validation than internet-facing ones.

    In this challenge, the SSRF lets you reach an internal service, and that service has a secondary vulnerability (injection, traversal, or a sensitive endpoint) that gives you the flag. Enumerate the internal service's routes and parameters carefully.

    SSRF was included in the OWASP Top 10 in 2021 (A10:2021). High-profile real-world SSRF incidents include the Capital One breach (2019), which used SSRF against the AWS instance metadata service to steal IAM credentials.

  4. Step 4
    Exfiltrate the flag
    Observation
    I noticed the internal service relayed its response back through the SSRF fetch endpoint, which suggested the flag would appear directly in that relayed HTTP response once the injection chain succeeded.
    Read the flag from the internal response and submit it.
    Learn more

    The flag will appear in the server's response once the chain is successful. If the internal service returns the flag directly in its HTTP response, your SSRF fetch endpoint will relay it back to you. If the internal service only writes the flag to a file, use a secondary path traversal or file-read endpoint to fetch it.

    When exfiltration through the same SSRF channel is blocked (e.g., response size limits or content filtering), consider out-of-band channels: cause the internal service to make a DNS lookup or HTTP request to a server you control, encoding the flag in the subdomain or URL path.

Interactive tools
  • Reverse Shell GeneratorGenerate reverse shell payloads (bash, nc, python, perl, ruby, php, node, powershell) and matching listeners. Set host and port once, copy any variant.

Flag

Reveal flag

picoCTF{...}

Challenge unsolved during the competition; this page is a methodology template for SSRF + injection chains, not a verified step-by-step solution. Concrete endpoints and payloads will vary.

Key takeaway

Server-Side Request Forgery exploits a server's willingness to fetch attacker-controlled URLs, turning it into a proxy that can reach internal services the attacker cannot directly address. Internal services frequently omit authentication because they assume network-level isolation, so a single SSRF vector often grants unauthenticated access to admin panels, cloud metadata endpoints (such as the AWS IMDS at 169.254.169.254), or databases. Chaining SSRF with a secondary bug on the internal service (path traversal, injection, or an exposed debug route) is how attackers escalate from network pivot to data exfiltration, as demonstrated by the 2019 Capital One breach.

Related reading

Want more picoCTF 2023 writeups?

Useful tools for Web Exploitation

What to try next