Skip to main content

Fetch the Flag CTF 2022 解説:Juggalo Central

著者

Alexander Kraev

feature ctf juggalo central

2022年11月10日

0 分で読めます

Fetchに参加してくれてありがとう! 一緒に参加してくれた何千人ものプレイヤーの皆さん、Fetch the Flag CTFへのご参加ありがとうございました。また、チャレンジの作成、テスト、解説記事の執筆に携わったSnykerの皆さんにも心から感謝します!

Snykの2022年Fetch the Flagに参加したものの、Juggalo Centralのチャレンジをクリアできなかった方は、ぜひこの先をお読みください。解法を一緒に見ていきましょう!

解説

まず、一般的なログインフォームがあるWebサイトへのリンクがあり、usernameとpasswordの2つの入力欄があります。また、.php拡張子のファイルもあります。まずはWebサイトを見てみましょう。

Webサイト

前述のとおり、ページには2つの入力欄とピエロの画像があります。最初にページのソース、.cssファイル、さらには画像も確認しましたが、役立ちそうなものは見つかりませんでした。気づいたのは、エラーメッセージが2種類あることです。「admin」のような一般的なユーザー名を使った場合と、「qweasdzxc」のような適当な文字列を使った場合で異なっていました。

コード

index.phpの中には、getFlag、searchUserByName、validateCredentailsという3つの関数がありました。

最初の関数getFlagは、flagというデータベーステーブルを呼び出し、最初に見つかった行を返します。少し考えた結果、探しているキーはこのテーブルに保存されているとわかりました。

2つ目の関数searchUserByNameは、$usernameという入力変数をもとにユーザーを検索します。結果があれば、その結果を返します。ここで気づいたのは、次の行です。

     return $stmt->fetchAll(PDO::FETCH_ASSOC);

つまり、この関数は1行だけを返すとは限りません(ユーザー名は一意だと思っていたのですが)。同じユーザー名のユーザーが複数いる場合は、すべて返します。後になって、次の関数の処理のため、これは役に立たないとわかりました。CTFでは、こういうこともあります。

3つ目の関数validateCredentailsは、POSTリクエストの本文からユーザー名とパスワードを読み取り、searchUserByNameを呼び出して、そのユーザー名のユーザーがいるかを確認します。ランダムな文字列のユーザー名でエラーメッセージが異なる理由もわかりました。存在しないユーザー名を入力すると、関数は「Juggalo not found!」を返します。一方、存在するユーザー名でパスワードを間違えると、「Invalid credalo」というエラーメッセージが表示されます。

有効なユーザー名を見つける

次に、有効な認証情報を探します。「admin」のようなよくある値を試したところ、「Invalid credalo」というエラーメッセージが表示されました。前のステップでコードを分析したことで、ユーザー名の値が「admin」のユーザーがいるとわかります。

最初の推測

有効なユーザー名がわかったので、次はパスワードを突破する方法を考えます。このユーザーにアクセスできないというエラーメッセージが表示されるのは、次のコード行が原因です。

    if ($results[0]['password'] != substr(md5($username . $password), 0, 20)) {

条件式の左辺では、データベースからユーザーのパスワードを取得しています。右辺には、ユーザーが指定した2つの変数usernameとpasswordを含むmd5ハッシュの先頭20文字があります。

最初に思いついたのは、この条件から、有効なパスワードは20文字の文字列だということでした。結論から言うと、これは間違いでした。

2つ目の推測

しばらくチャレンジについて考えた後、別の考えが浮かびました。この行では!=比較演算子を使っています。そこでPHPの公式ドキュメントを開き、PHPでの比較演算子の動作を確認しました。ドキュメントの!=演算子のすぐ隣にある一文の意味がわかりませんでした。

true if $a is not equal to $b after type juggling.

待って、「型のジャグリング」って何だろう?説明を読んでから、もう一度チャレンジのWebページを開きました。あのピエロはまだそこにいて、こちらを見つめていました。

ハッキング

PHPには「型のジャグリング」という仕組みがあることがわかりました。このトピックについて調べ、オンラインでPHPのテストをいくつか実行した結果、次の条件を思いつきました。

    if ("0" == "0e12345")

驚いたことに、これは「true」を返しました。PHPが両方のオペランドを数値に変換するためです。「admin」ユーザーの正しいパスワードを見つけるには、「0e」で始まり、その後が数字だけのmd5ハッシュを生成すればよいのです(substrの使用箇所を考えると、その後に少なくとも18文字が必要です)。

スクリプト

正しいハッシュを見つけるPythonスクリプトをご紹介します。

import hashlib

target = '0e'
candidate = 0;
while True:
   plaintext = 'admin'+target+str(candidate)
   hash = hashlib.md5(plaintext.encode('ascii')).hexdigest()
   # Hash starts with “0e”
   if hash[:2] == target:
       # Hash contains only one letter (“e”) in first twenty characters
       # So it can be considered as a number by PHP
       if sum(c.isalpha() for c in hash[:20]) == 1:
           print('username and password:' + plaintext);
           break
   candidate = candidate + 1

1分ほどで、このスクリプトから得た認証情報を使ってJuggalo CentralのWebページにログインできました。

Fetch the Flagのその他の解法

ほかのフラグをどう見つけたか知りたいですか?Fetch the Flagの解法ページで、詳しい手順をご覧ください。

続きを読む

Blog

フロンティアモデルは脆弱性を発見した。攻撃者だけがエクスプロイトチェーンを見つけた。

静的解析で欠陥は見つかりましたが、ライブ攻撃テストで侵害につながる連鎖を実証できたのは唯一でした。Evo COS、Claude Security、Claude Code Securityを比較します。

feature insights context
Blog

自律型攻撃はすでに始まっている。防御もそのスピードに追いつかなければならない。

自律型攻撃者によって、防御に使える時間は短くなっています。継続的な検出、修復、検証、予防で、セキュリティチームが攻撃に歩調を合わせる方法をご紹介します。

Blog

AIコーディングエージェントが不適切なアクセス制御を繰り返し実装する理由

AIコーディングエージェントは、コンパイルが通りレビューも通過する一方で、あるテナントのデータを別のテナントに公開してしまう認可ロジックを生成することがあります。不適切なアクセス制御が検出しにくい理由と、その防止策をご紹介します。