こんにちは!元々は社内SEとしてセキュリティ業務に奮闘し、現在はAI・データサイエンスの世界に飛び込んで学習中のエンジニアです。
自分で言うのもなんですが…Qiita初投稿記事となります。
ぜひとも、お手柔らかに温かい目で見て頂けると幸いですw
現在、Qiitaでは「セキュリティ Qiita Tech Festa 2025」と題して、「セキュリティの知見を共有しよう」という素晴らしいイベントが開催されています。
社内SE時代に感じていたペインと、今学んでいるAI技術を掛け合わせることで、何かコミュニティに貢献できるのでは?と考え、学習のアウトプットとして一つのプロジェクトに挑戦しました。
本記事では、「日本語で使えるインシデント対応支援AI」の開発を目指した私の挑戦の全貌を、公開しているGitHubリポジトリのソースコードと共に、失敗も含め、包み隠さず生々しくお伝えします。
GitHub初心者なので、汚く乱暴なリポジトリですがご容赦ください💦
▼今回作成したリポジトリ
この記事が、AI×セキュリティというフロンティアで奮闘する方、特に日本語という言語の壁に悩む(私のような)エンジニアにとって、何かしらのヒントや勇気になれば幸いです。
私がこのツールを作ろうと思った原点は、社内SEとしてセキュリティを担当していた頃の経験にあります。以前の職場では、セキュリティ担当者はいたものの、専門知識を持つのはごく一部のメンバーに限られていました。他のメンバーは他業務と兼務しており、多忙の中で高度なセキュリティ知識をキャッチアップし続けるのは困難な状況でした。
結果として何が起こるか。インシデントは、こちらの都合などお構いなしに、深夜でも、休日でも発生します。そのたびに鳴り響くアラート。しかし、そのアラートが本当に危険なものなのか、的確な初動判断を下せるのは、結局限られたメンバーだけになってしまうのです。
- 「この海外からの不審なアクセス、ただの調査ポートスキャン? それとも本格的な侵入の予兆?」
- 「このマルウェア検知アラート、既知のパターン? それとも未知の脅威?」
こうした判断には、脅威インテリジェンスや過去の事例に関する深い知識が求められます。対応は特定の「スーパーマン」的なメンバーに集中し、チーム全体が疲弊していく…。この「専門知識の属人化」は、多くの組織が抱える根深い課題ではないでしょうか。
もし、そのスーパーマンの知識や判断プロセスをAIに移植できたら? 専門家でないメンバーでも、AIに自然言語で相談することで、次の一手を導き出せるのではないか?
そんな思いから、「サイバー攻撃の状況を自然言語で入力すると、LLMが危険度やとるべきアクションを日本語で教えてくれる支援ツール」の開発はスタートしました。
「セキュリティAIなんて、MicrosoftやGoogleがすごいものを作っているじゃないか」
その通りです。開発に着手するにあたり、まずは既存の巨人たちが提供するサービスを徹底的に調査しました。Microsoft Security Copilot、Google Cloud Security AI Workbenchなど、素晴らしいサービスが既に存在し、進化を続けています。
しかし、調査を進める中で、「私たちの現場で本当に欲しい機能」との間に、いくつかのギャップがあることにも気づきました。
この調査から見えてきたのは、以下の点です。
- 1. 「予測」へのフォーカス:
- 多くのサービスが「現状分析」に強い一方、「次に何が起こるか」という攻撃予測まで踏み込んでいるものは少ない。
- 2. 日本語での対話能力:
- 日本語での自然言語入力や、非専門家向けの平易な解説は、まだ発展途上の領域。
- 3. 柔軟性:
- 特定の製品エコシステムに依存せず、任意のログファイルやテキスト情報を気軽に放り込めるような柔軟性は、意外と少ない。
つまり、「日本語で、対話的に、攻撃の”次の一手”を予測してくれる」という、私たちが現場で本当に欲しかった機能は、まだ誰も完成させていないブルーオーシャンだったのです。これが、巨人の肩を借りるだけでなく、自ら手を動かしてでも開発に挑戦する大きな動機となりました。
システム全体のアーキテクチャ構想
目指したのは、単一の技術に頼るのではなく、SFT(Supervised Fine-Tuning)、RAG(Retrieval-Augmented Generation)、そしてAgent技術を組み合わせたハイブリッドなアーキテクチャです。
- 1. UI (Streamlit):
- ユーザーが自然言語で状況を入力し、結果を確認するためのインターフェース。
- 2. API (FastAPI):
- UIからのリクエストを受け付け、後段のLLMやRAGと連携するバックエンド。
- 3. LLM Agent (gemmaベース):
- SFTによってセキュリティ知識を強化したLLM。RAGから受け取った情報を元に、状況を分析し、対処法を思考する頭脳役。
- 4. 知識ベース:
-
-
構造化データ:
今回のプロジェクトの核となる「ATT&CK × Kill Chain 日本語データベース」。 -
非構造化データ:
社内ドキュメントや過去のインシデント報告書など(将来的展望)。 -
リアルタイム情報:
ログデータ(SIEMなどから連携)。
-
なぜこのような複雑な構成を目指したかというと、セキュリティ対応には「静的な知識」と「動的な状況判断」の両方が必要だからです。
- ・SFT(Supervised Fine-Tuning):
- LLMにセキュリティの「思考パターン」や「言語表現」を叩き込む。
- ・RAG(Retrieval-Augmented Generation):
- 最新の脅威情報や社内固有の情報を、LLMの知識として動的に注入する。
この両輪があって初めて、本当に役立つ支援ツールが作れると考えました。
最大の挑戦:日本語セキュリティ知識ベースの構築
構想は壮大ですが、現実は甘くありません。最初の、そして最大の壁は「日本語の学習データが存在しない」ことでした。
「ないなら、作るしかない」。この一心で、地獄のデータ整備が始まりました。
Step 1: データ収集と翻訳 (ATT&CK -> 日本語)
まず、世界の攻撃者の戦術・技術を体系化したナレッジベースであるMITRE ATT&CK®の情報を、STIX2という標準形式のJSONでごっそりダウンロードします。
そして、この英語の塊を日本語化するために書いたのが、BaseData_ATTACK_and_CKC/Source/Get_Mitre_Attack_Ent_data_jp.py
です。
Get_Mitre_Attack_Ent_data_jp.py
# (参考)DeepL APIで翻訳するコードの主要部分
import deepl
import json
import os
from dotenv import load_dotenv
load_dotenv()
auth_key = os.getenv("DEEPL_API_KEY") # .envファイルからAPIキーを読み込み
translator = deepl.Translator(auth_key)
def translate_text(text, target_lang="JA"):
"""DeepL APIを使ってテキストを翻訳する関数"""
try:
result = translator.translate_text(text, target_lang=target_lang)
return result.text
except Exception as e:
print(f"Error during translation: {e}")
return text # エラー時は原文を返す
def translate_stix_bundle(bundle):
"""STIXバンドル内の各オブジェクトを再帰的に翻訳"""
for obj in bundle.get("objects", []):
if "description" in obj:
description_en = obj["description"]
description_jp = translate_text(description_en)
# STIXのカスタムプロパティとして翻訳結果を格納
obj["x_mitre_description_jp"] = description_jp
print(f"Translated: {obj.get('name', obj.get('id'))}")
return bundle
# ...(ファイルの読み書き処理)...
ポイントは、翻訳結果をx_mitre_description_jpというキーに格納している点です。STIXでは、x_
から始まるキーはカスタムプロパティとして扱われるため、元の構造を壊さずに情報を付与できます。
Step 2: フレームワークの結合 (ATT&CK + Kill Chain)
次に、ATT&CKの各技術が、攻撃全体のどのフェーズで使われるかを示す「Cyber Kill Chain」の情報を紐付けます。これにより、点の情報(個々の技術)が、線の情報(攻撃シナリオ)へと進化します。
ATT&CKの戦術 (Tactics) と Cyber Kill Chain の関係 (イメージ)
Reconnaissance (偵察) -> [ATT&CK: TA0043, TA0042]
Weaponization (武器化) -> ...
Delivery (配送) -> [ATT&CK: TA0001]
...
Actions on Objectives (目的の実行) -> [ATT&CK: TA0002, TA0040, etc.]
このマッピング処理を自動化したのがBaseData_ATTACK_and_CKC/Source/Marge_ATTACK_ENT_with_subtec_CKC_mapping.py
です。ATT&CKデータと自作のKill Chain定義データを読み込み、力技で結合していきます。
Step 3: SFT用データセットへの変換 (知識 -> 対話)
LLMに知識を教えるには、単なる情報の羅列ではなく、「Instruction(指示)」と「Response(応答)」の対話形式データが必要です。
この変換作業には、GPT-4やGeminiといった強力な生成AIの力を借りました。例えば、以下のようなプロンプトを使い、ATT&CKの技術情報からリアルなQ&Aを生成させました。
# 指示プロンプトの例
あなたは経験豊富なSOCアナリストです。
以下のサイバー攻撃技術に関する情報を用いて、
セキュリティ初心者がするであろう質問(Instruction)と、
あなたが専門家として返すであろう詳細な回答(Response)の
ペアをJSON形式で生成してください。
# 入力情報
- 技術名: External Remote Services (T1133)
- 説明: 攻撃者は、VPNやCitrixなどの外部向けリモートサービスを悪用して、
ネットワークへの初期アクセスや永続化を試みることがあります...
- キルチェーンフェーズ: Initial Access (初期アクセス)
# 出力形式
{
"instruction": "(ここに初心者の質問を生成)",
"response": "(ここに専門家の回答を生成)"
}
この地道な作業を繰り返すことで、数千件の日本語対話データセットを作成しました。
Step 4: ログデータとの連携準備
実際のインシデント対応では、ログの解析が不可欠です。そこで、SIEM_LEAF_LOG/Source/SIEM_Log_Perther.py
のようなパーサーも準備しました。これは、SIEMから出力されるLEEF形式のログをパースし、LLMが理解しやすいJSON形式に変換するためのものです。将来的には、ユーザーがログを貼り付けるだけで、関連情報を自動で抽出・分析する機能を目指していました。
思わぬ副産物:日本語化された「ATT&CK × Kill Chain」データセットという宝物
この苦しいデータ整備の過程で、私は大きな発見をしました。
「このATT&CKとKill Chainを紐付けた日本語データセット、めちゃくちゃ価値があるのでは?」
当初はLLMの学習材料としか見ていませんでしたが、冷静に考えると、この構造化された日本語の脅威インテリジェンスデータ自体が、様々な用途に活用できる「宝の山」だったのです。
このデータセットattack_ent_with_subtec_ckc_mapping_jp.json
は、リポジトリのBaseData_ATTACK_and_CKC/Data/
に格納しています。
もし、皆さんの業務や研究で何かしら利用価値がありそうでしたら、ご活用いただけると嬉しいです。
さて、いよいよファインチューニングです。当初は高性能なモデルも検討しましたが、個人開発の現実的なリソース(時間・コスト)を鑑み、より軽量で高速なunsloth/gemma-3-1b-it
を選定しました。このモデルは、パラメータ数が10億と比較的小さいため、個人レベルのGPU環境でもファインチューニングの試行錯誤がしやすく、コストを抑えながら開発サイクルを回すのに適していると判断しました。UnslothライブラリとQLoRAを使い、少ない計算資源でも学習できるよう工夫しました。
学習の実行・管理を簡単にするため、streamlit_llm_train.py
で簡単なUIも作りました。しかし、現実は厳しく、私の挑戦はここで巨大な壁にぶつかります。
壁1:時間とコスト
高品質なデータセットの準備、ハイパーパラメータの調整、学習、評価…このサイクルを回すには、個人が趣味で捻出できる時間と予算を遥かに超えるリソースが必要でした。特にA100などの高性能GPUのクラウド料金は、試行錯誤を許してくれないレベルです。
壁2:英語力
DeepLは神ですが、万能ではありません。特にセキュリティの専門用語や微妙なニュアンスは、誤訳のリスクが常に伴います。結局、信頼性を担保するには原文の英語ドキュメントにあたる必要があり、自身の英語力のなさを痛感しました。
壁3:評価の難しさ
ファインチューニングしたモデルは、本当に「賢くなった」のでしょうか?特定の質問には流暢に答える一方で、少し聞き方を変えると支離滅裂なことを言い出す。このモデルの性能を客観的かつ定量的に評価する指標を立て、テストケースを整備する難しさに直面しました。
壁4:環境構築の沼
これは経験者なら誰もが頷いてくれると思いますが、CUDAのバージョン、PyTorch、Transformers、その他無数のライブラリ間の依存関係地獄…。動く環境を作るだけで、何日も溶けていきました。
壮大なファインチューニングの夢は一旦諦めることにしました。しかし、このプロジェクトは終わりではありません。むしろ、ここからが現実的なスタートです。
今後は、ファインチューニングという重厚長大なアプローチから、RAGを主軸とした軽量で柔軟なアプローチに切り替え、当初の目的だった「日本語対応インシデント対応支援ツール」の実現を目指します。
幸い、プロジェクトの過程で生まれた「日本語知識ベース」という強力な武器があります。これをRAGの知識ソースとして活用するのです。
例えば、以下のようなコードでFastAPIサーバーを実装すれば、RAGとAgentを組み合わせた応答も可能になるかもしれません。(※このコードは未実装・未検証の構想段階のものです)
(こんな感じかな?) sample.py
# (参考)FastAPIでRAG+AgentのストリーミングAPIを実装する構想
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
# ... LangChainやLLMモデルのインポート ...
app = FastAPI()
# 事前に日本語知識ベースを読み込んでおく
# retriever = ... (knowledge_base.jsonからVectorStoreRetrieverを作成)
# llm = ... (SFT済み、またはベースのLLMをロード)
class Query(BaseModel):
text: str
async def agent_stream(query: str):
# LangChainのAgent Executorを使ってストリーミング応答を生成
# agent_executor = create_agent(llm, retriever)
async for chunk in agent_executor.astream({"input": query}):
# ... chunkを処理してyield ...
yield f"data: {json.dumps(chunk)}\n\n"
@app.post("/chat")
async def chat_endpoint(query: Query):
return StreamingResponse(agent_stream(query.text), media_type="text/event-stream")
このようなアプローチであれば、LLMの再学習なしに知識の更新が可能になり、はるかに低コストで運用できるのでは、と考えています。
セキュリティとAI、どちらも日進月歩で進化する分野です。両者を繋ぐ試みは、多くの困難を伴いますが、それ以上に大きな可能性を秘めていると信じています。
今回の挑戦は、結果だけ見れば「失敗」かもしれません。しかし、その過程で得られた「日本語化された脅威インテリジェンスデータ」という宝物、そして「個人のLLM開発のリアルな知見」は、私にとって何物にも代えがたい財産となりました。
この泥臭い挑戦の記録が、同じようにAIとセキュリティの融合を目指す誰かの、そして未来の自分の背中を押すことになれば、これほど嬉しいことはありません。
最後までお読みいただき、本当にありがとうございました。
Views: 0