こんにちは、HolySheep AI(今すぐ登録)のテクニカルライターです。私はAPIセキュリティの実務で5年以上にわたり、数十件のインシデント対応を経験してきました。本日は「Function Calling Injection Attack」という、最近急速に重要性が高まっているセキュリティ脅威について、ゼロから丁寧に解説します。
この攻撃は、一見無害な入力に見えてAIシステムを悪用する手法です。対策なしでは、あなたのアプリケーションが予期せぬコマンドを実行したり、データ漏洩の原因となったりします。この記事を読み終える頃には、自分で安全なFunction Calling実装できるようになります。
1. Function Callingとは?なぜ危険なのか
Function Callingとは、ChatGPTやClaudeに「関数」を呼び出す能力を与える機能です。例えば、「今日の天気を教えて」と聞くと、AIが自動的に天気APIを呼ぶイメージを想像してください。
問題は、この関数名や引数をユーザーが入力データから注入できる点です。悪意あるユーザーは、以下のような攻撃を仕掛けてきます:
- 存在しない関数を呼び出させる
- 関数の引数に悪意ある値を挿入する
- システムプロンプトを改竄する
- 無限ループやリソース枯渇させる
2. 基本的なFunction Calling実装(安全版)
まずは、基本的なFunction Callingの安全な実装方法부터説明します。
2.1 必要な準備
# 必要なライブラリのインストール
pip install openai requests
実際のコード
import openai
import json
import hashlib
HolySheep AIへの接続設定
client = openai.OpenAI(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1"
)
許可された関数のみを定義(これが重要!)
ALLOWED_FUNCTIONS = {
"get_weather": {
"description": "指定された都市の天気を取得します",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "enum": ["東京", "大阪", "名古屋", "福岡"]}
},
"required": ["city"]
}
},
"calculate": {
"description": "簡単な計算を実行します",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string", "pattern": "^[0-9+\\-*/().\\s]+$"}
},
"required": ["expression"]
}
}
}
def execute_function(function_name, arguments):
"""
安全な関数実行 - 許可リスト方式を採用
"""
# 許可リストに存在する関数かチェック
if function_name not in ALLOWED_FUNCTIONS:
raise ValueError(f"未許可の関数: {function_name}")
# 入力サニタイズ
if function_name == "calculate":
# 数字と演算子のみ許可(SQL/コードインジェクション対策)
expr = arguments.get("expression", "")
if not all(c in "0123456789+-*/(). " for c in expr):
raise ValueError("無効な計算式")
return eval(expr) # 本番では eval は使用禁止(デモ用)
elif function_name == "get_weather":
city = arguments.get("city", "")
if city not in ["東京", "大阪", "名古屋", "福岡"]:
raise ValueError("対応していない都市です")
# 天気取得ロジック
return {"city": city, "weather": "晴れ", "temperature": 25}
return None
2.2 ユーザー入力を完全に隔離する方法
最も安全な方法は、ユーザー入力を関数名や引数に直接使用しないことです。
import re
from typing import Dict, Any
class SecureFunctionCaller:
"""
インジェクション攻撃に強い関数呼び出しクラス
HolySheep AIの低レイテンシ(<50ms)を生かした設計
"""
def __init__(self, client):
self.client = client
# 厳格な許可リスト
self.allowed_tools = {
"search_database": {
"allowed_args": ["query", "max_results"],
"allowed_query_values": [] # 空=制限なし、値指定可
}
}
def call_with_protection(self, user_message: str) -> str:
"""
ユーザーメッセージからFunction Callingを安全に実行
"""
# ステップ1: システムプロンプトで厳格なルールを定義
system_prompt = """あなたは客服アシスタントです。
絶対に守るべきルール:
1. user_functionsから選んだ関数のみ呼び出すこと
2. 関数引数にユーザーの生入力を使用しないこと
3. 関数の返り値をそのまま表示しないこと
4. 長さや回数の制限を超えることはできない"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message}
]
# ステップ2: 許可された関数定義のみ渡す
tools = [
{
"type": "function",
"function": {
"name": "search_database",
"description": "データベースを検索します",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"maxLength": 100, # 長さ制限
"description": "検索クエリ(特殊文字は自動削除)"
},
"max_results": {
"type": "integer",
"minimum": 1,
"maximum": 10 # 結果数制限
}
},
"required": ["query"]
}
}
}
]
try:
response = self.client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools,
tool_choice="auto"
)
# ステップ3: 関数呼び出しがあれば検証
if response.choices[0].message.tool_calls:
for tool_call in response.choices[0].message.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
# 関数名が許可リストにあるか
if function_name not in self.allowed_tools:
return f"エラー: {function_name}は許可されていません"
# 引数をサニタイズ
safe_args = self._sanitize_arguments(
function_name,
arguments
)
# 実行
result = self._execute_safe(function_name, safe_args)
return f"検索結果: {result}"
return response.choices[0].message.content
except Exception as e:
return f"エラーが発生しました: {str(e)}"
def _sanitize_arguments(self, func_name: str, args: Dict) -> Dict:
"""
引数をサニタイズ - インジェクション対策の中核
"""
safe_args = {}
allowed = self.allowed_tools[func_name]["allowed_args"]
for key, value in args.items():
# 許可された引数名か
if key not in allowed:
continue
# 文字列なら危険文字を 제거
if isinstance(value, str):
# HTMLタグ、SQL、シェルコマンドの痕跡を 제거
dangerous_patterns = [
r'