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