LOADING

載入過慢請啟用快取 瀏覽器預設啟用

CGGC 2024 Writeup

2024/11/6

前言

cggc_scoreboard

這次隊名關於一群資安麻瓜為了打進決賽拿到名次而組成一隊努力練習這檔事

初賽第 43 名,其他不想寫,直接進正文

WP

Day31- 水落石出!真相大白的十一月預告信?

這題找了很久~~

前面以為是個資 Leak 一直找各種沒碼到的 Mail,找到一兩個,但沒啥資料

然後發現行不通,就開始在出題者 Kazma 的 Blog 和 iThome 用 Google Hacking 找各種 "kazma" "Day31" ,也是沒東西

最後還找到 Kazma Blog 在 Github 的倉庫,整個 Clone 下來繼續找,還是找不到

然後最後隊友和我說他有看到很像 TG Token 的東西,我看完罵髒話後大概十分鐘就用出來了。

題目給了一個標的:https://ithelp.ithome.com.tw/users/20168875/ironman/7849

而最後是在 https://ithelp.ithome.com.tw/articles/10363058 這邊文章發現 TG Bot Token Leak

裡面的程式有一行 https://api.telegram.org/bot7580842046:AAEKmOz8n3C265m2_XSv8cGFbBHg7mcnbMM/sendPhoto

洩漏了 Token 7580842046:AAEKmOz8n3C265m2_XSv8cGFbBHg7mcnbMM

然後根據 Telegram API 說明,可以知道 https://api.telegram.org/bot<token>/getUpdates 可以取得聊天內容

所以就進入 https://api.telegram.org/bot7580842046:AAEKmOz8n3C265m2_XSv8cGFbBHg7mcnbMM/getUpdates 取得 Flag

Preview Site

先上原始碼

from flask import Flask, request, redirect, render_template, session, url_for, flash
import urllib.request
import urllib.error
import urllib.parse
import os

app = Flask(__name__)
app.secret_key = os.urandom(24)

users = {'guest': 'guest'}

def send_request(url, follow=True):
    try:
        response =  urllib.request.urlopen(url)
    except urllib.error.HTTPError as e:
        response = e
    redirect_url = response.geturl()
    if redirect_url != url and follow:
        return send_request(redirect_url, follow=False)
    return response.read().decode('utf-8')


@app.route('/login', methods=['GET', 'POST'])
def login():
    next_url = request.args.get('next', url_for('index'))
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        if users.get(username) == password:
            session['username'] = username
            flash('login success')
            return redirect(next_url)
        else:
            error = 'login failed'
            return render_template('login.html', error=error, next=next_url)
    return render_template('login.html', next=next_url)

@app.route('/logout')
def logout():
    session.pop('username', None)
    next_url = request.args.get('next', url_for('index'))
    return redirect(next_url)

@app.route('/fetch', methods=['GET', 'POST'])
def fetch():
    if 'username' not in session:
        return redirect(url_for('login'))

    if request.method == 'POST':
        url = request.form.get('url')
        if not url:
            flash('Please provide a URL.')
            return render_template('fetch.html')
        try:
            if not url.startswith(os.getenv("DOMAIN", "http://previewsite/")):
                raise ValueError('badhacker')
            resp = send_request(url)
            return render_template('fetch.html', content=resp)
        except Exception as e:
            error = f'error:{e}'
            return render_template('fetch.html', error=error)
    return render_template('fetch.html')

@app.route('/')
def index():
    username = session.get('username')
    return render_template('index.html', username=username)

首先 guest/guest 登入

然後可以看到 /fetch 會透過 send_request 去戳 http://previewsite/ 的網頁,然後把內容 Show 出來

然後丟 http://previewsite/ 可以看到他又把這個題目網頁丟出來了,所以繼續看這題的原始碼。

可以看到 /loginlogout 都有:

next_url = request.args.get('next', url_for('index'))
return redirect(next_url)

可以知道可以帶一個網址進去 next_url 達到 Open Redirect,然而,/login 限制使用 HTTP POST,因此無法利用。

所以使用 /logout 做 Open Redirect

Payload http://previewsite/logout?&next=file:///flag

PS.因為機器是 Linux,所以是 file:// /flag,三個斜線。

proxy

這題沒解出來,但賽後看大家討論覺得值得筆記一下,供以後參考。

<?php

function proxy($service) {
    // $service = "switchrange";
    // $service = "previewsite";
    // $service = "越獄";
    $requestUri = $_SERVER['REQUEST_URI'];
    $parsedUrl = parse_url($requestUri);

    $port = 80;
    if (isset($_GET['port'])) {
        $port = (int)$_GET['port'];
    } else if ($_COOKIE["port"]) {
        $port = (int)$_COOKIE['port'];
    }
    setcookie("service", $service);
    setcookie("port", $port);
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    $filter = '!$%^&*()=+[]{}|;\'",<>?_-/#:.\\@';
    $fixeddomain = trim(trim($service, $filter).".cggc.chummy.tw:".$port, $filter);
    $fixeddomain = idn_to_ascii($fixeddomain);
    $fixeddomain = preg_replace('/[^0-9a-zA-Z-.:_]/', '', $fixeddomain);
    curl_setopt($ch, CURLOPT_URL, 'http://'.$fixeddomain.$parsedUrl['path'].'?'.$_SERVER['QUERY_STRING']);
    curl_exec($ch);
    curl_close($ch);
}

if (!isset($_GET['service']) && !isset($_COOKIE["service"])) {
    highlight_file(__FILE__);
} else if (isset($_GET['service'])) {
    proxy($_GET['service']);
} else {
    proxy($_COOKIE["service"]);
}

這題官方解是 xn--a ,意圖是讓 idn_to_ascii 爛掉,其他參賽者有人用一堆 a 塞爆 php 成功讓它爛掉,也有人用前一題的 open redirect 破出來。

而我們自己以為是要繞他的防護,idn_to_ascii 可以用全形字符來 Bypass 前面的兩次 trim,但最後還是卡在後面的 .cggc.chummy.tw: 和正則表達繞不掉。