Skip to content

セキュリティベストプラクティス

このガイドでは、TCPDF-Nextを本番環境にデプロイするための実践的なセキュリティ推奨事項を提供します。これらのプラクティスに従うことで、PDF生成パイプラインがエンタープライズセキュリティ基準を満たすことを保証します。

入力検証(writeHtml前のHTMLサニタイゼーション)

ユーザー提供のHTMLからPDFを生成する場合、HTMLレンダラーに渡す前に必ず入力をサニタイズしてください。TCPDF-NextのHtmlRendererはHTMLを忠実に解析してレンダリングするため、サニタイズしないと悪意のあるマークアップが悪用される可能性があります。

php
use YeeeFang\TcpdfNext\Html\HtmlRenderer;

// 危険:生のユーザー入力を直接渡さないでください
// $renderer->writeHtml($userInput);

// 安全:最初に専用ライブラリでサニタイズ
$clean = \HTMLPurifier::getInstance()->purify($userInput);
$renderer->writeHtml($clean);

主要なルール:

  • レンダリング前に<script><iframe><object><embed><link>タグを除去してください。
  • hrefおよびsrc属性からjavascript:およびdata:URIスキームを除去してください。
  • 許可するCSSプロパティをレイアウトに必要なもののみに制限してください(position: fixedなし、CSS値でのurl()なし)。
  • 文字エンコーディングを検証してください -- レンダラーに渡す前に入力が有効なUTF-8であることを確認してください。

証明書管理(安全な保管とローテーション)

保管の階層

方法セキュリティレベルユースケース
ハードウェアセキュリティモジュール(HSM)最高エンタープライズ、規制産業
クラウドKMS(AWS KMS、Azure Key Vault、GCP KMS)クラウドネイティブデプロイ
強力なパスフレーズ付きPKCS#12ファイル小規模デプロイ
PEMファイル(暗号化済み)中低開発、テスト
PEMファイル(非暗号化)最低本番では絶対使用不可

ローテーションポリシー

  • 期限切れの少なくとも30日前に証明書を更新してください。
  • 30日、14日、7日、1日のしきい値で自動アラートによる期限切れを監視してください。
  • 漏洩した証明書は発行CAを通じて直ちに失効させてください。
  • すべての証明書ライフサイクル操作の署名付き監査ログを維持してください。
php
use YeeeFang\TcpdfNext\Certificate\CertificateStore;

$store = new CertificateStore();
$store->loadFromDirectory('/etc/tcpdf-next/certs/', '*.pem');

$activeCert = $store->getActiveCertificate('document-signing');

if ($activeCert->getExpirationDate() < new \DateTimeImmutable('+30 days')) {
    $logger->warning('Signing certificate expires soon', [
        'subject' => $activeCert->getSubject(),
        'expires' => $activeCert->getExpirationDate()->format('Y-m-d'),
    ]);
}

DANGER

秘密鍵をソースコードリポジトリ、共有ファイルシステム上の非暗号化ファイル、保存時暗号化なしのデータベースカラム、またはログファイルに保存しないでください。

パスワード処理(SASLprepと強力なパスワード)

PDF暗号化パスワードを設定する際、プラットフォーム間で一貫したパスワード処理を保証するためにSASLprep(RFC 4013)経由でUnicode正規化を適用してください:

php
// TCPDF-Nextはパスワードに自動的にSASLprepを適用します
$pdf->setEncryption()
    ->setAlgorithm(EncryptionAlgorithm::AES256)
    ->setUserPassword('pässwörd-with-ünïcöde')  // 内部でSASLprep正規化
    ->setOwnerPassword($strongOwnerPassword)
    ->apply();

パスワードポリシーの推奨事項:

  • ユーザーパスワードは最低12文字、オーナーパスワードは20文字。
  • オーナーパスワードには暗号的にセキュアな乱数生成器を使用(random_bytes())。
  • ソースコードにパスワードをハードコードしないでください -- 環境変数またはシークレットマネージャーから読み込んでください。
  • 使用後はsodium_memzero()でパスワードをメモリからクリアしてください。

SSRF防止(画像、TSA、OCSP用のURL検証)

TCPDF-NextはデフォルトでSSRFをブロックしますが、正当な外部リソース用に許可リストを設定する必要があります:

php
use YeeeFang\TcpdfNext\Security\NetworkPolicy;

$networkPolicy = NetworkPolicy::create()
    ->denyPrivateNetworks()     // 10.x、172.16.x、192.168.xをブロック
    ->denyLoopback()            // 127.0.0.1をブロック
    ->denyLinkLocal()           // 169.254.xをブロック
    ->allowDomain('cdn.yourcompany.com')       // 画像
    ->allowDomain('timestamp.digicert.com')    // TSA
    ->allowDomain('ocsp.digicert.com')         // OCSP
    ->setMaxRedirects(3)
    ->setRequestTimeout(10);

$pdf = PdfDocument::create()
    ->setNetworkPolicy($networkPolicy)
    ->build();

チェックリスト:

  • 取得前にすべてのURLを検証してください(スキーム、ホスト、ポート)。
  • TSAおよびOCSPレスポンダドメインを明示的に許可リストに登録してください。
  • file://gopher://ftp://、その他の非HTTP(S)スキームをブロックしてください。
  • セキュリティ監視のためにすべてのブロックされたリクエストをログに記録してください。

ファイルパス検証(パストラバーサル防止)

ユーザー提供のファイルパス(フォントファイル、画像、出力先など)を受け取る場合:

php
use YeeeFang\TcpdfNext\Security\ResourcePolicy;

$resourcePolicy = ResourcePolicy::strict()
    ->allowLocalDirectory('/app/public/assets/')
    ->allowLocalDirectory('/app/storage/fonts/')
    ->denyAllRemote();

$pdf = PdfDocument::create()
    ->setResourcePolicy($resourcePolicy)
    ->build();

ルール:

  • ユーザー入力をファイルパスに直接連結しないでください。
  • パスを絶対正規形式に解決し、許可されたディレクトリに対して検証してください。
  • ..、ヌルバイト、印刷不可文字を含むパスを拒否してください。
  • 本番ではResourcePolicy::strict()を使用してください -- デフォルトですべてのアクセスを拒否します。

デプロイメントセキュリティ(Dockerとファイル権限)

Docker設定

dockerfile
FROM php:8.5-fpm-alpine

# 非rootユーザーで実行
RUN addgroup -S tcpdf && adduser -S tcpdf -G tcpdf
USER tcpdf

# 危険なPHP関数を無効化
RUN echo "disable_functions = exec,passthru,shell_exec,system,proc_open,popen" \
    >> /usr/local/etc/php/conf.d/security.ini

# 読み取り専用ファイルシステム(書き込み可能なボリュームを明示的にマウント)
# docker run --read-only --tmpfs /tmp ...

ファイル権限

bash
# 証明書ディレクトリ:Webサーバーユーザーのみ読み取り可能
chown -R www-data:www-data /etc/tcpdf-next/certs/
chmod 700 /etc/tcpdf-next/certs/
chmod 600 /etc/tcpdf-next/certs/*.p12
chmod 600 /etc/tcpdf-next/certs/*.pem

# 出力ディレクトリ:Webサーバーユーザーのみ書き込み可能
chown -R www-data:www-data /var/lib/tcpdf-next/output/
chmod 700 /var/lib/tcpdf-next/output/

# 一時ディレクトリ:書き込み可能、ワールドリーダブルではない
chown -R www-data:www-data /tmp/tcpdf-next/
chmod 700 /tmp/tcpdf-next/

ブラウザ表示PDFのコンテンツセキュリティポリシー

PDFをブラウザ内でインライン表示する場合、埋め込み攻撃を防ぐために適切なHTTPヘッダーを設定してください:

php
return response($pdf->toString(), 200, [
    'Content-Type' => 'application/pdf',
    'Content-Disposition' => 'inline; filename="document.pdf"',
    'Content-Security-Policy' => "default-src 'none'; plugin-types application/pdf",
    'X-Content-Type-Options' => 'nosniff',
    'X-Frame-Options' => 'DENY',
    'Cache-Control' => 'no-store, no-cache, must-revalidate',
]);

機密データを含むPDFには、ブラウザ内レンダリングの代わりにダウンロードを強制するContent-Disposition: attachmentを推奨します。

監査ログの推奨事項

セキュリティ上重要なすべてのPDF操作について包括的な監査ログを設定してください:

php
use YeeeFang\TcpdfNext\Security\AuditLogger;

AuditLogger::configure([
    'channel' => 'tcpdf-security',
    'log_signing' => true,
    'log_encryption' => true,
    'log_validation' => true,
    'log_key_access' => true,
    'log_tsa_requests' => true,
    'log_resource_access' => true,   // 画像/フォント読み込みをログ
    'log_blocked_requests' => true,  // SSRFブロックをログ
    'redact_sensitive' => true,      // ログからパスワード/鍵を墨消し
]);

監視対象:

  • 署名検証の失敗(ドキュメント改ざんの可能性)。
  • 証明書の期限切れ警告。
  • TSA通信の失敗。
  • 異常な署名量(鍵漏洩の可能性)。
  • ブロックされたSSRF試行(攻撃探知の可能性)。
  • 予期しないパスからのリソース読み込み。

PDF権限の最小権限の原則

PDFドキュメントの権限を設定する際は、必要最小限のアクセスのみを付与してください:

php
use YeeeFang\TcpdfNext\Encryption\Permissions;

// 制限的:読み取り専用ドキュメント
$pdf->setEncryption()
    ->setPermissions(Permissions::ACCESSIBILITY)  // スクリーンリーダーアクセスのみ
    ->setUserPassword('reader')
    ->setOwnerPassword($strongOwnerPassword)
    ->apply();

// 中程度:印刷可能なドキュメント
$pdf->setEncryption()
    ->setPermissions(
        Permissions::PRINT_HIGH_QUALITY
        | Permissions::ACCESSIBILITY
    )
    ->apply();

// 避けるべき:すべての権限を付与すると暗号化の意味がなくなります
// Permissions::ALLは利用可能ですが、使用は稀にしてください

権限ガイドライン:

  • 受信者がドキュメントを編集する必要がない限り、MODIFY_CONTENTSを付与しないでください。
  • スクリーンリーダーの互換性のために常にACCESSIBILITYを付与してください(多くの法域で法的要件)。
  • 特定の理由がない限り、PRINT_LOW_QUALITYではなくPRINT_HIGH_QUALITYを使用してください。
  • コード内で各権限付与の根拠を文書化してください。

さらに詳しく

LGPL-3.0-or-later ライセンスの下で公開されています。