# TetCTF 2023 (Writeups of some challenges) CTFtime: https://ctftime.org/event/1842 Team: ⚔️TSJ⚔️ Rank: 5 [TOC] ## NewYearBot Here is a part of the source code: ```py debug = request.args.get("debug") if request.method == 'POST': greetType = request.form["type"] greetNumber = request.form["number"] if greetType == "greeting_all": greeting = random_greet(random.choice(NewYearCategoryList)) else: try: if greetType != None and greetNumber != None: greetNumber = re.sub(r'\s+', '', greetNumber) if greetType.isidentifier() == True and botValidator(greetNumber) == True: if len("%s[%s]" % (greetType, greetNumber)) > 20: greeting = fail else: greeting = eval("%s[%s]" % (greetType, greetNumber)) ``` The goal is to find the value of the variable `FL4G`. The code evals `greetType[greetNumber]` if: - `greetType` is an identifier - `greetNumber` passes `botValidator`, which does the following checks: - no ASCII characters between 58 and 122 - `int(all the 0-9 characters combined)` is at most 5 - `greetType[greetNumber]` is at most 20 characters We can set `greetType` to `FL4G` and `greetNumber` to a number to get one character of the flag at a time. Since `botValidator` only checks for ASCII characters in the range [58, 122], we can use special Unicode characters like `int(debug)` to make it use the value of `debug` as the index instead. Therefore, querying `/?debug=0` with `type=FL4G&number=int(debug)` gives the 0-th character of the flag, etc. Alternatively, we can use `exec(debug)` to make it run arbitrary code. ## Gift This is a sourceless <span style="background-color:#333">a.k.a. guessing</span> web challenge. We are given the database schema and nothing else. ### send_pic.php There is a comment in the HTML about `send_pic.php`. This endpoint takes two parameters `url` and `id`, which correspond to the `pictures` table. After a bit of testing, we can find out that it takes the row with the same `id` and sends it to the `url` we give. There is a SQL injection in the `id` parameter, and after some testing we know that it blocks the following keywords: - `,` - `(` - `)` - `key_cc` Using the payload ``` curl 'http://172.105.127.104/send_pic.php' \ --data "url=http://" \ --data "id=3 union select KEY_CC from access_key--" ``` we were able to obtain the secret access key, `messi_is_goat__!!!!`. ### get_img.php We can use the access key from the previous step to log in. We can now access the page `get_img.php`, which includes the page in the `file` parameter. We can achieve LFI using this page, but some substrings are blocked: - `sess` - `../..` - `pear` The second one can be circumvented by using `.././..`, so we can include any file outside the directory. Unfortunately, we cannot find any useful files to read, and usual LFI to RCE techniques like `php://filter`, `/tmp/sess`, `pearcmd.php`, `/proc/self/fd`, `/proc/self/environ`, etc. don't seem to work. After being stuck on this step for a while, a teammate suggested that `peclcmd.php` can be used to achieve RCE and fing the flag. ## Casino 1 This is the unintended version of casino 2. ### Challenge There are 3 main functions in this challenge: 1. We can bet any amount of money (but not more than our balance) in the casino. If we guess a random number below 2023 correctly, we win 2023 times the bet amount, otherwise the money is lost. 2. We can get our current balance with a Merkle tree hash proving we have that much money. 3. We can show our balance and proof to buy the first $\log_{256}(\text{balance})$ bytes of the flag. ### Unintended Solution 1 It is possible to bet a negative amount of money, so we can win enough money by losing a negative bet. ## Casino 2 It's the same as casino 1, except we cannot bet negative amounts. ### Unintended Solution 2 The intended solution is probably to break the hash, which I don't know how to do. However, I found another unintended solution. Let's look at how the casino's random numbers are generated: 1. At the start, it reads 64 bits from a secure random source as the seed. 2. The number is used to seed the default PRNG from Go's `math/rand` module. 3. Numbers are generated with `rand.Intn(2023)`. After reading the [source code](https://cs.opensource.google/go/go/+/master:src/math/rand/rng.go) of Go's `math/rand`, we can see that the PRNG is actually seeded with `seed % int32max`, which means that there are only $2^{31}-2$ possible seeds[^note1]. We can recover the state of the PRNG from its output (which is shown to us) by brute-forcing all seed values. Enumerating the seed only takes a few hours of CPU time. Since there is a timeout, we can preprocess a portion of possible seeds first, and then connect multiple times until we get a known seed. [^note1]: The seed 0 is replaced by 89482311 so there is 1 less possibility.