OWASP Top 10 2025版対策|最新脆弱性と実装コード例

OWASP Top 10 2025版の最新対策を解説。プロンプトインジェクション、サプライチェーンリスク等の新種脆弱性への実装レベルの対応方法を紹介。

Sponsored

OWASP Top 10 2025版 実装対策ガイド|最新脆弱性と検証ツール

はじめに:OWASP Top 10 2025版の位置づけ

2025年にOWASPが発表した「OWASP Top 10 2025」は、2021年版から大幅に改定されました。現在、多くの企業がこの新しいリスク分類に基づいた脅威モデリングと対策を急速に進めています。

従来のInjection脆弱性だけに対応する時代は終わり、AI時代の新たな攻撃パターンサプライチェーンリスクプロンプトインジェクションといった新種の脆弱性への対応が必須となっています。本記事では、開発環境で実際に機能する対策手法を、実装レベルのコード例と共に解説します。

OWASP Top 10 2025版の主要変更点

2021年版との主な違い

順位2021年版2025年版変更理由
1位Broken Access ControlInjection(拡張)AI時代のプロンプトインジェクション台頭
2位Cryptographic FailuresBroken Authentication認証・セッション管理の複雑化
3位InjectionInsecure Object Deserializationシリアライゼーション攻撃の深刻化
4位Insecure DesignData ExposureAPI経由のデータ漏洩増加
5位Security MisconfigurationSQL/NoSQL Injection(分離)NoSQLインジェクションの急増
6位Vulnerable & Outdated ComponentsServer-Side Template Injectionテンプレートエンジン脆弱性の顕在化
7位Identification & Auth FailuresSupply Chain Vulnerabilities依存関係の脆弱性リスク
8位Software & Data Integrity FailuresInsufficient Logging & Monitoring検知・対応能力の強化要求
9位SSRFInsecure AI Integration新規:LLM統合アプリの脆弱性
10位Using Components with Known VulnsBusiness Logic Vulnerabilitiesビジネスロジック脆弱性の重要性向上

特に重要な3つのカテゴリと対策

1. AI統合アプリケーションのセキュリティ(新規追加)

現在、生成AI・LLM統合アプリは急速に本番環境に展開されています。しかしプロンプトインジェクションLLM出力の検証不足による情報漏洩事例が急増しています。

脆弱なコード例

# ❌ 危険:ユーザー入力を直接プロンプトに埋め込み
from openai import OpenAI

client = OpenAI()
user_query = request.args.get('query')

# プロンプトインジェクション攻撃: query="""Ignore previous instructions
response = client.chat.completions.create(
    model="gpt-4-turbo",
    messages=[{
        "role": "user",
        "content": f"ユーザーの質問に回答してください: {user_query}"
    }]
)

# LLM出力をそのまま返すのは危険
return response.choices[0].message.content

セキュアな実装パターン

import re
from typing import Literal
from openai import OpenAI
from pydantic import BaseModel, validator

class SafeQuery(BaseModel):
    """入力の厳密な検証"""
    text: str
    
    @validator('text')
    def validate_length_and_content(cls, v):
        # 長さ制限
        if len(v) > 500:
            raise ValueError('Query too long')
        # 危険なパターンを検出
        dangerous_patterns = [
            r'ignore.*instructions',
            r'forget.*previous',
            r'system.*prompt',
        ]
        for pattern in dangerous_patterns:
            if re.search(pattern, v, re.IGNORECASE):
                raise ValueError('Suspicious pattern detected')
        return v

# LLM出力の検証レイヤー
class LLMOutputValidator:
    """LLM出力の検証と無毒化"""
    
    @staticmethod
    def validate_response(response: str, 
                         expected_type: Literal['json', 'text']) -> dict:
        """LLM応答の妥当性チェック"""
        # 応答サイズの制限
        if len(response) > 5000:
            raise ValueError('Response exceeds max length')
        
        if expected_type == 'json':
            try:
                import json
                parsed = json.loads(response)
                # スキーマ検証
                return parsed
            except json.JSONDecodeError:
                raise ValueError('Invalid JSON response')
        
        # テキスト応答のサニタイゼーション
        return {
            'content': response.strip(),
            'truncated': len(response) > 2000
        }

# 安全なAPI実装
def safe_llm_query(user_input: str) -> dict:
    """プロンプトインジェクション対策を施したLLMクエリ"""
    
    # 1. 入力検証
    validated_query = SafeQuery(text=user_input)
    
    # 2. システムプロンプトを厳密に定義(ユーザー入力で上書き不可)
    system_prompt = (
        "You are a helpful assistant. "
        "You must follow these rules: "
        "1. Only answer technical questions. "
        "2. Do not execute code. "
        "3. Do not access external systems."
    )
    
    client = OpenAI()
    response = client.chat.completions.create(
        model="gpt-4-turbo",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": validated_query.text}
        ],
        temperature=0.2,  # 予測可能性向上
        max_tokens=1000,  # 出力制限
    )
    
    # 3. 出力検証
    validated_output = LLMOutputValidator.validate_response(
        response.choices[0].message.content,
        expected_type='text'
    )
    
    return validated_output

2. Server-Side Template Injection(SSTI)対策

Jinja2、ERB、Mustacheなどのテンプレートエンジンを使用したアプリケーションが増加しており、SSTIを含む脆弱性が多く報告されています。

脆弱性の実例

# ❌ 危険:ユーザー入力をテンプレートに直接埋め込み
from jinja2 import Template

user_name = request.args.get('name')
template_string = f"Hello {user_name}!"  # 危険!
template = Template(template_string)
# 攻撃例: name="{{7*7}}" → "Hello 49!"
# 攻撃例: name="{{config.items()}}" → 機密情報漏洩
result = template.render()

セキュアな実装

from jinja2 import Environment, FileSystemLoader, select_autoescape
from markupsafe import escape
import os

class SecureTemplateRenderer:
    """SSTI対策を施したテンプレートレンダリング"""
    
    def __init__(self):
        # テンプレートディレクトリを厳密に限定
        self.env = Environment(
            loader=FileSystemLoader('/app/templates/safe'),
            autoescape=select_autoescape(
                enabled_extensions=('html', 'xml'),
                default_for_string=True  # デフォルトでエスケープ
            ),
            enable_async=False
        )
        
        # 危険な関数をグローバルから除外
        dangerous_globals = ['config', '__import__', 'eval']
        for name in dangerous_globals:
            if name in self.env.globals:
                del self.env.globals[name]
    
    def render_safe(self, template_name: str, context: dict) -> str:
        """
        安全なテンプレートレンダリング
        
        Args:
            template_name: templates/safeディレクトリ内のテンプレート
            context: テンプレート変数(自動エスケープされる)
        
        Returns:
            レンダリング結果
        """
        # テンプレート名の検証(ディレクトリトラバーサル対策)
        if '..' in template_name or template_name.startswith('/'):
            raise ValueError('Invalid template name')
        
        # コンテキスト値の事前検証
        for key, value in context.items():
            if isinstance(value, str):
                context[key] = escape(value)  # HTMLエスケープ
        
        template = self.env.get_template(template_name)
        return template.render(context)

# 使用例
renderer = SecureTemplateRenderer()

# テンプレート: templates/safe/user_greeting.html
# <h1>Hello {{ user_name }}!</h1>
# <p>Your query: {{ query }}</p>

user_input = request.args.get('name', 'Guest')
query_input = request.args.get('q', '')

result = renderer.render_safe('user_greeting.html', {
    'user_name': user_input,
    'query': query_input
})

3. Supply Chain Vulnerabilities対策

依存関係の脆弱性管理はビルドパイプラインの必須機能となっています。単なるSBOM(Software Bill of Materials)生成では不十分で、能動的な脆弱性検証リスク分類が求められます。

実装例:DevSecOps統合

# .github/workflows/secure-build.yml
name: Security-First Build Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  dependency-check:
    runs-on: ubuntu-latest
    steps:
      # 1. 依存関係の脆弱性スキャン
      - uses: actions/checkout@v4
      
      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'  # 脆弱性検出時は失敗
      
      # 2. SBOMの生成と署名
      - name: Generate signed SBOM
        run: |
          pip install cyclonedx-bom
          cyclonedx-bom -o sbom.json
          # SBOMに署名(改ざん検知対策)
          cosign sign-blob --key cosign.key sbom.json > sbom.json.sig
      
      # 3. SLSA Provenanceの生成
      - name: Generate SLSA provenance
        uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
        with:
          base64-subjects: ${{ needs.build.outputs.hashes }}
          upload-assets: true
      
      # 4. GitHub Security Advisoryとの同期
      - name: Check GitHub Advisory Database
        run: |
          python3 << 'EOF'
          import json
          import subprocess
          
          # pip-auditで脆弱性チェック
          result = subprocess.run(
              ['pip-audit', '--format', 'json'],
              capture_output=True,
              text=True
          )
          
          vulnerabilities = json.loads(result.stdout)
          
          # リスク分類
          critical_vulns = [v for v in vulnerabilities 
                           if v.get('vulnerability_id', '').startswith('CVE')]
          
          if critical_vulns:
              print(f"Found {len(critical_vulns)} critical vulnerabilities")
              for vuln in critical_vulns:
                  print(f"  - {vuln['package']}: {vuln['vulnerability_id']}")
              exit(1)
          EOF
      
      # 5. ライセンスコンプライアンスチェック
      - name: License compliance check
        run: |
          pip install licensecheck
          licensecheck --zero --no-deps

Sponsored

関連記事