# Hauntmart

This challenge was about a SSRF with a filter bypass to create a privileged user at an api endpoint.

* At first glance I could see that the flag is copied to /flag in the dockerfile. The file entrypoint.sh is loaded, which sets up a database like so:

```sql
DROP DATABASE IF EXISTS hauntmart;
CREATE DATABASE hauntmart;
CREATE TABLE hauntmart.users (
    id INTEGER PRIMARY KEY AUTO_INCREMENT,
    username varchar(255) NOT NULL UNIQUE,
    password varchar(255) NOT NULL,
    role varchar(255) NOT NULL DEFAULT 'user'
);

CREATE TABLE hauntmart.products (
    id INTEGER PRIMARY KEY AUTO_INCREMENT,
    name varchar(255) NOT NULL,
    price varchar(255) NOT NULL,
    description TEXT NOT NULL
);

CREATE USER 'xclow3n'@'localhost' IDENTIFIED BY 'xclow3n';
GRANT SELECT, INSERT, UPDATE ON hauntmart.users TO 'xclow3n'@'localhost';
GRANT SELECT, INSERT, UPDATE ON hauntmart.products TO 'xclow3n'@'localhost';

FLUSH PRIVILEGES;
```

* The file web\_hauntmart/challenge/application/blueprints/routes.py is interesting. It contains route definitions for web and api endpoints. At the web endpoint /home the flag is defined as flag=current\_app.config\['FLAG'] with the template index.html
* For /home and /product it seems like you need to be authenticated
* The /register API seems interesting too. You can possibly register a user from an unauthenticated standpoint:

<figure><img src="https://746814813-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fe1HXVppEt3OHWIFqtAXT%2Fuploads%2Fdd4DYq4v6S30wwU2cUPC%2Fimage.png?alt=media&#x26;token=08f7e810-00ba-4641-a001-e806ae91f710" alt=""><figcaption></figcaption></figure>

* On the live website I could register an account immediately and access the site.
* Under Sell Product it seems like you can send data to the server. This site really stands out, it says that you can send a manual url which will be visited by the admins. My first Idea is to host a javascript that triggers another request to me, containing the admin cookie.
* The idea of reflected blind XSS did not work as I could not find a working injection. But what we state in the url field is indeed accessed by the server, which is interesting.
* This is written in the index.html:

```
{% if user['role'] == 'admin' %}
{{flag}}
{% endif %}
```

* This code in util.py might also be interesting as it seems to download what we give it in the manual url field under certain circumstances:

```python
def downloadManual(url):
    safeUrl = isSafeUrl(url)
    if safeUrl:
        try:
            local_filename = url.split("/")[-1]
            r = requests.get(url)
            
            with open(f"/opt/manualFiles/{local_filename}", "wb") as f:
                for chunk in r.iter_content(chunk_size=1024):
                    if chunk:
                        f.write(chunk)
            return True
        except:
            return False
    
    return False
```

* This might be the way in. I found out before that /api is prepended to the url of api calls. so we should be able to directly access /api/addAdmin. Here is the code from routes.py that shows that:

```python
@api.route('/addAdmin', methods=['GET'])
@isFromLocalhost
def addAdmin():
    username = request.args.get('username')
    
    if not username:
        return response('Invalid username'), 400
    
    result = makeUserAdmin(username)

    if result:
        return response('User updated!')
    return response('Invalid username'), 400
```

* Trying to access it shows a 403, which is expected because of @isFromLocalhost:

<figure><img src="https://746814813-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fe1HXVppEt3OHWIFqtAXT%2Fuploads%2F4KtaHygUvoxkXVCQOeNX%2Fimage.png?alt=media&#x26;token=377f7c74-2bb3-4317-ae31-eb090d60ee29" alt=""><figcaption></figcaption></figure>

* Lets check the isFromLocalhost function; Maybe we can bypass that. It is defined in util.py:

```python
def isFromLocalhost(func):
    @wraps(func)
    def check_ip(*args, **kwargs):
        if request.remote_addr != "127.0.0.1":
            return abort(403)
        return func(*args, **kwargs)

    return check_ip
```

Well, we have a chance to perform a localhost request. Here:

<figure><img src="https://746814813-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fe1HXVppEt3OHWIFqtAXT%2Fuploads%2Fn0iw0cRRV554aaX5Vukp%2Fimage.png?alt=media&#x26;token=68d4a647-2e06-4142-b095-3e3acec0e9d3" alt=""><figcaption></figcaption></figure>

* According to the addAdmin code, the payload <http://127.0.0.1:12345/api/addAdmin?username=hacker> should work. But no, we receive “Invalid URL”. We see that there is a blacklist for manual url requests defined in util.py:

```python
blocked_host = ["127.0.0.1", "localhost", "0.0.0.0"]

def isSafeUrl(url):
    for hosts in blocked_host:
        if hosts in url:
            return False
    
    return True
```

* There are many ways to bypass this as you can see in this reference: <https://book.hacktricks.xyz/pentesting-web/ssrf-server-side-request-forgery/url-format-bypass>
* Using 0 as the IP did work to bypass the filter and get the flag
