Questcon Writeup

# はじめに

久しぶりにCTFに参加してきました。
結果は204位でした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510659/6c80fbe5-aeb9-698e-564f-8f2d364b6655.png)
今回はsknbとしての初CTFでした。
やはりWeb以外の分野に特化している方がチームに居ると心強いです。
Webのジャンルに関してもお互いに情報共有ができるのは大きいです。
今回私は2問解いたので、その解説をします。

# Direction

配布されたURLに行ってみると、以下のようなページが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510659/1dcd0cc7-1b27-47f2-e27c-efad044caed7.png)
今回は自動ツールの使用が禁止されているので、gobusterやffufを使用したサブディレクトリの列挙が行えません。
どうしようかと困っていたら、チームメンバのくままぬいさんが
robots.txtにパスが書いてあったと情報を共有してくれました。
robots.txtの中身を以下に示します。
```
User-agent: * Disallow: /start
```
/startというパスがあるようです。
ですがGETリクエストを送信しても何も反応がありません。
OPTIONSリクエストを送信し、どのメソッドが使用できるか確認してみます。
```
curl -X OPTIONS https://questcon-misdirect.chals.io/start

POST
```
POSTメソッドが使用可能と分かったので、POSTリクエストを送信してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510659/6f17a4a0-f0cc-4f12-0efd-76bf3e85ce24.png)
すると/startの他に5つのパスにリダイレクトしました。
レスポンスをよく見てみると、それぞれにX-Flag-Partというヘッダにフラグの一部が付いていました。
```
HTTP/1.1 302 Found
X-Powered-By: Express
Locaiton: /redirect0
X-Flag-Part: QUESTC
Date: Wed, 23 Oct 2024 19:42:13 GMT
Connection: close
Content-Length: 0
```
これらを繋ぎ合わせるとフラグになります。
QUESTCON{mi3d1r3ct10n_15_4n_4r}

# Twice The Trouble
以下のPythonファイルが配布されます。
```python
import math
import random

def xor_encrypt_decrypt(input_str, key):
    return ''.join(chr(ord(c) ^ key) for c in input_str)

def get_flag():
    # XOR-encoded flag
    encoded_flag = [92, 88, 72, 94, 89, 78, 66, 67, 118, 105, 61, 120, 111, 97, 62, 82, 121, 127, 61, 120, 111, 97, 62, 112]
    key = 13  # XOR key used for encoding
    flag = ''.join(chr(c ^ key) for c in encoded_flag)
    return flag

# Function to compare the magnitude of two numbers
def compare_numbers(num1, num2):
    if math.sqrt(num1**2) == 2 * abs(num2):
        print("The magnitude of the first number is exactly twice the magnitude of the second!")
        return True
    else:
        print("One of the numbers has a larger magnitude.")
        return False

def main():
    junk = [random.randint(1, 100) for _ in range(10)]
    
    try:
        num1 = float(input("Enter the first number: "))
        num2 = float(input("Enter the second number: "))

        # Compare numbers and reveal flag if the condition is met
        if compare_numbers(num1, num2):
            print(f"Congratulations! Here's the flag: {get_flag()}")
        else:
            print("Try again with different numbers.")
    
    except ValueError:
        print("Please enter valid numbers.")

if __name__ == "__main__":
    main()
```
これはencode_flag変数の文字コードとkeyである13とをxorした結果がフラグになっています。
スクリプトの中にget_flagという関数がありますが、これがそのまま暗号文を複合するソルバーになっています。
なので以下がソルバーになります。
```python
def get_flag():
    # XOR-encoded flag
    encoded_flag = [92, 88, 72, 94, 89, 78, 66, 67, 118, 105, 61, 120, 111, 97, 62, 82, 121, 127, 61, 120, 111, 97, 62, 112]
    key = 13  # XOR key used for encoding
    flag = ''.join(chr(c ^ key) for c in encoded_flag)
    return flag


if __name__ == "__main__":
    get_flag()
```
QUESTCON{d0ubl3_tr0ubl3}

# おわりに

Questcon CTFのWeb問題は全ての問題においてソースコードが配布されません。
さらにはサブディレクトリの総当たりやサブドメイン列挙を含む総当たりツールの使用が禁止されています。
このような環境下でのCTFは初めてだったので、かなり苦戦しました。
これからもsknbでCTFに参加していきます。