> For the complete documentation index, see [llms.txt](https://ajaykumar-kanthi.gitbook.io/write-ups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ajaykumar-kanthi.gitbook.io/write-ups/hackthebox-cwes-track/htb-saturn.md).

# HTB - Saturn

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FZBDXHllXEcDNjviiBvsF%2Fimage.png?alt=media&amp;token=56fe20f7-17a5-4c80-87bb-a001888dcb1f" alt=""><figcaption></figcaption></figure>

Download the file to localhost and analyse the files.

the zip file is password protected, unzip it using the password file.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FPXwff8FieagpwfdeHhEk%2Fimage.png?alt=media&amp;token=d12cb440-b18a-4c9a-adde-a0a3a8b28063" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2Fw1KucW0J9JOcrm86QCfH%2Fimage.png?alt=media&amp;token=242b1de9-bfa6-4ea3-9b70-dafb4f80ed9b" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2Fna0IfYkHPj5Gfkb1Ywkc%2Fimage.png?alt=media&amp;token=bf03d511-1583-4fcd-ac70-5bcfeafe68b2" alt=""><figcaption></figcaption></figure>

### Source Code analysis&#x20;

```
from flask import Flask, request, render_template
import requests
from safeurl import safeurl
```

Flask creates the web app.\
request lets the app read incoming HTTP data like form fields.\
render\_template renders HTML pages from templates.\
requests is used to make outgoing HTTP requests from the server.\
safeurl is a library intended to validate URLs, but it is used in a weak way here.

```bash
app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
```

creates an app and allows two methods GET and POST.

```bash
def index():
    if request.method == 'POST':
        url = request.form['url']
        try:
            su = safeurl.SafeURL()
            opt = safeurl.Options()
            opt.enableFollowLocation().setFollowLocationLimit(0)
            su.setOptions(opt)
            su.execute(url)
        except:
            return render_template('index.html', error=f"Malicious input detected.")
        r = requests.get(url)
        return render_template('index.html', result=r.text)
    return render_template('index.html')
```

runs when we submit a post request and ask for a url which the user submits.

Once the user submits the url it uses the safeurl method to create a safeurl object and  create options for the url checker to validate.

If anything goes wrong in validation, it shows an error page.

The server takes a URL supplied by the user and fetches it with requests.get(). That is SSRF.

So an attacker can point the server at internal services like 127.0.0.1 or localhost.

```bash
@app.route('/secret')
def secret():
    if request.remote_addr == '127.0.0.1':
        flag = ""
        with open('./flag.txt') as f:
            flag = f.readline()
        return render_template('secret.html', SECRET=flag)
    else:
        return render_template('forbidden.html'), 403


if __name__ == '__main__':
    app.run(host="0.0.0.0", port=1337, threaded=True)// Some code
```

This block creates the protected /secret page and starts the Flask server. The secret() function checks whether the request appears to come from `127.0.0.1` (the local machine); if it does, it opens the file `./flag.txt`, reads the first line into a variable named flag, and renders the template secret.html while passing that flag as SECRET. If the request is not from localhost, it returns forbidden.html with HTTP 403. After that, the app is launched with app.run(host="0.0.0.0", port=1337, threaded=True), which makes the server listen on all network interfaces at port 1337. The security issue is that this protection is based only on a client IP check, which can be bypassed in some deployments or through SSRF-based requests that appear to originate from the server itself.

## SSRF on Saturn Proxy

Browsing to the given IP present with an Saturn Proxy application asking for an url to search.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FTbAb0O2AAccgZ0XurreV%2Fimage.png?alt=media&amp;token=92c5317a-6da8-4e2e-a5e5-001abdc07013" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FP83wI2bgiJj6M3L92nvN%2Fimage.png?alt=media&amp;token=6c7d5841-d0ff-4c2e-a956-06fe2693b575" alt=""><figcaption></figcaption></figure>

Passing the URL <http://localhost:1337/> results in malicious input detected.

```bash
su.execute(url)          # validates the URL is "safe" (not internal)
r = requests.get(url)    # then fetches it again, completely unprotected
```

Two problems combine here:

1. **`setFollowLocationLimit(0)`** — `safeurl` is configured to follow 0 redirects. So it only validates the *initial* URL you supply. If that URL points to some external, "safe" host, it passes validation.
2. **`requests.get(url)`** on the next line has no such restriction  by default `requests` **follows redirects** (up to 30). It re-fetches the URL independently of whatever `safeurl` did, with none of its protections.

So in order to bypass we need to send a URL which redirects to another ip.

### SSRF redirect bypass

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FVT6sIUqe7iS8KNODLV15%2Fimage.png?alt=media&amp;token=8d5dbe41-d145-4427-b35d-953e3f541842" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FlRxSuTVNhv0p8DuYNVBJ%2Fimage.png?alt=media&amp;token=e6840ad6-af5c-44c5-ac72-cb3c2b750320" alt=""><figcaption></figcaption></figure>

i can use a script as the box doesn’t have routes to connect to my machine

i am gonna use a public redirector for this purpose.

#### Using Shortcut URL

{% embed url="<https://shortcuturl.ai/>" %}

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FZTC0pjceIKxigXNL43JX%2Fimage.png?alt=media&amp;token=dea60b03-a053-4c37-9208-76e4eb1c6fd6" alt=""><figcaption></figcaption></figure>

now use the link to submit the form

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2F1FM9QdnKh0OZg3P8kG2d%2Fimage.png?alt=media&amp;token=ab5efee7-04ab-470c-8d36-678f9ee33e00" alt=""><figcaption></figcaption></figure>

* **`safeurl` validates the URL you submit to Saturn Proxy**  which will be `https://jeseji.shortcuturl.ai/11Yd9a3`. That's a legitimate public hostname resolving to a real public IP. Nothing about it looks internal or malicious, so it sails through validation.
* **The literal string `127.0.0.1` never appears in what you submit to Saturn**  it's stored server-side on the shortener's backend, not in the URL you're passing through `safeurl`.
* **`setFollowLocationLimit(0)`** means `safeurl` doesn't follow the redirect to discover the real destination  so it never resolves `jeseji.shortcuturl.ai`'s redirect and never sees `127.0.0.1`.
* Then `requests.get(url)` runs completely separately and **does** follow redirects by default — hits the shortener, gets a `3xx Location: http://127.0.0.1:1337/secret`, follows it, and fetches the internal `/secret` endpoint from `127.0.0.1` itself  which passes the `request.remote_addr == '127.0.0.1'` check on that route.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://ajaykumar-kanthi.gitbook.io/write-ups/hackthebox-cwes-track/htb-saturn.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
