TLS 証明書検証の無効化

TLS 証明書検証の無効化

説明

rejectUnauthorized: false や NODE_TLS_REJECT_UNAUTHORIZED=0 などでサーバー証明書の検証を無効にすると、TLS クライアントがサーバーの身元を適切に確認できなくなります。通信経路上の攻撃者が信頼されていない証明書を提示し、正規のサーバーになりすます可能性があります。悪意のあるプロキシ、ゲートウェイ、公衆ネットワークなどを介した傍受により、機密情報の漏えい、応答の改ざん、セッションの窃取につながるおそれがあります。

想定される影響

  • 中間者が偽の証明書を使って接続を成立させる可能性があります。
  • 認証情報、トークン、個人情報などが、サーバーになりすました攻撃者に漏れるおそれがあります。TLS の暗号化自体がなくなるわけではありません。
  • 応答の書き換えや悪意のあるペイロードの挿入が行われる可能性があります。
  • 正規のサービスだと誤認したまま、攻撃者と通信するおそれがあります。

対処方法

  • rejectUnauthorized を削除するか、既定の true を維持してください。
  • process.env.NODE_TLS_REJECT_UNAUTHORIZED=0 で検証を全体的に無効にしないでください。
  • 社内または公開 CA の信頼する証明書を、https.Agent や tls.connect の ca で指定してください。
  • ホスト名検証を有効にしてください。tls.connect は SNI を自動で有効にしないため、サーバーに必要な場合は host と併せて servername を設定してください。
  • ピンニングが必要な場合は、信頼する証明書や公開キーとの一致を確認し、証明書の更新にも備えてください。以下の fingerprint256 は、公開キーではなく証明書全体の SHA-256 フィンガープリントを比較します。
  • テスト用に一時的な例外が必要な場合は、本番の設定やビルドから分離してください。

例

変更前

javascript
// 変更前の例 1: 証明書検証を全体で無効化
const https = require('https');
const tls = require('tls');

function fetchInsecure() {
  // TLS 検証の全体的な無効化は危険
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

  https.get({ hostname: 'api.example.org', path: '/v1/data' }, (res) => {
    res.on('data', () => {});
  });
}

// 変更前の例 2: エージェントで証明書検証を無効化
function postInsecure() {
  const insecureAgent = new https.Agent({ keepAlive: true, rejectUnauthorized: false });

  const req = https.request({
    hostname: 'secure.example.org',
    method: 'POST',
    path: '/login',
    agent: insecureAgent,
    headers: { 'Content-Type': 'application/json' }
  }, (res) => res.resume());

  req.write(JSON.stringify({ user: 'alice', pass: 'secret' }));
  req.end();
}

変更後

javascript
// 変更後: 既定の検証を維持し、信頼する CA バンドルを使用
const https = require('https');
const fs = require('fs');

// 社内または公開 CA バンドルのパスの例
const caBundle = fs.readFileSync('/etc/ssl/certs/company-ca-bundle.pem');

function fetchSecure() {
  // rejectUnauthorized の既定値は true
  const secureAgent = new https.Agent({
    keepAlive: true,
    ca: caBundle,           // 信頼する CA を指定
    // https は要求先ホストから servername を設定するため、無効にしない
  });

  https.get({
    hostname: 'api.example.org',
    path: '/v1/data',
    agent: secureAgent
  }, (res) => {
    let body = '';
    res.on('data', (c) => body += c);
    res.on('end', () => console.log('OK:', body.length));
  });
}

// 必要に応じた証明書ピンニング: 証明書の SHA-256 フィンガープリントを比較
const tls = require('tls');
function connectWithPinning(expectedFingerprint256) {
  const socket = tls.connect({ host: 'secure.example.org', port: 443, ca: caBundle }, () => {
    const cert = socket.getPeerCertificate(true);
    if (!cert || !cert.fingerprint256 || cert.fingerprint256 !== expectedFingerprint256) {
      socket.destroy(new Error('Certificate pinning mismatch'));
      return;
    }
    socket.write('GET / HTTP/1.1\r\nHost: secure.example.org\r\n\r\n');
  });
}

説明:

  • 変更前: rejectUnauthorized: false や NODE_TLS_REJECT_UNAUTHORIZED=0 により、証明書やホスト名の検証に失敗しても接続できるため、偽の証明書を使ったなりすましを許すおそれがあります。
  • 変更後: 既定の検証(rejectUnauthorized: true)と信頼する CA バンドルでサーバーを認証します。必要に応じたピンニングは追加の確認になります。本番環境では CA の信頼設定、ホスト名検証、証明書更新時の動作も確認してください。

参考資料