目次

この記事の内容

アプリケーション開発において、セキュリティは「後付け」ではなく「設計段階」で組み込むべきものです。本記事では、OWASP Top 10 を中心に、実践的なセキュリティ設計の原則を解説します。

セキュリティ設計の核心原則

1. 防御の多層化(Defense in Depth)

一つの防御手段が破られても、他の層で防ぐ考え方です。

ユーザー入力 → バリデーション → エスケープ → 認証 → 認可 → 監査ログ
     │            │            │        │       │        │
     └───── 第 1 層 ─────┴── 第 2 層 ──┴─ 第 3 層 ─┘

**「一つの層が破られても、全体は守られる」**設計を目指します。

2. 最小権限の原則

ユーザー・プロセス・システムに、必要な最小限の権限だけを与えます。

悪い例良い例
全ユーザーに admin 権限役割ごとに権限を分離
データベースユーザーに DROP 権限SELECT/INSERT だけ付与
サービスアカウントにルート権限必要リソースへのアクセスだけ許可

3. デフォルトで安全(Secure by Default)

設定をデフォルトで安全側にします。

# 悪い設定(デフォルトで危険)
debug: true
cors_enabled: false
session_timeout: 0  # 無期限

# 良い設定(デフォルトで安全)
debug: false
cors_enabled: true
cors_allowed_origins: ["https://example.com"]
session_timeout: 1800  # 30 分

4. 完全な仲介(Complete Mediation)

すべてのリクエストに対して認証・認可を検証します。

// 悪い例:キャッシュされた権限情報
if (user.isAdminCached) {
  return deleteResource(id);
}

// 良い例:毎回検証
if (await auth.checkPermission(user.id, 'delete', resource)) {
  return deleteResource(id);
}

OWASP Top 10 と対策

1. 破損したアクセス制御(Broken Access Control)

問題: 認可チェックの不備で、不正なアクセスを許可してしまう。

❌ 脆弱なコード
app.get('/user/:id/profile', (req, res) => {
  const profile = db.getUser(req.params.id);  // 他人の ID も取得可能
  res.json(profile);
});

✅ 安全なコード
app.get('/user/profile', (req, res) => {
  const profile = db.getUser(req.user.id);  // ログインユーザーのみ
  res.json(profile);
});

対策:

  • サーバー側で認可チェック(クライアント依存禁止)
  • デフォルトで拒否(ホワイトリスト方式)
  • 定期的なアクセス権限の見直し

2. 暗号化の失敗(Cryptographic Failures)

問題: 機密データが平文で保存・送信される。

データタイプ対策
パスワードbcrypt/Argon2 でハッシュ化
個人情報DB で暗号化(AES-256)
通信TLS 1.2+(HTTPS)
セッショントークン暗号論的ランダム生成
// パスワードハッシュ化(Node.js 例)
const bcrypt = require('bcrypt');
const saltRounds = 12;

const hash = await bcrypt.hash(password, saltRounds);
const valid = await bcrypt.compare(password, hash);

3. インジェクション(Injection)

問題: 入力値がコードとして解釈され、不正な命令が実行される。

-- ❌ SQL インジェクション脆弱
const query = `SELECT * FROM users WHERE id = ${userId}`;
-- userId = "1 OR 1=1" → 全ユーザーが漏洩

-- ✅ パラメータ化クエリ
const query = 'SELECT * FROM users WHERE id = ?';
db.execute(query, [userId]);

対策:

  • パラメータ化クエリ(プレプレアードステートメント)
  • ORM/クエリビルダの使用
  • 入力値のバリデーション

4. 安全でない設計(Insecure Design)

問題: 設計段階でセキュリティが考慮されていない。

対策フレームワーク:

  1. 脅威モデリング

    • STRIDE モデル:Spoofing, Tampering, Repudiation, Information Disclosure, DoS, Elevation of Privilege
    • 各コンポーネントの脅威を特定
  2. セキュリティ要件の定義

    • 認証要件(多要素認証の要否)
    • 認可要件(RBAC, ABAC)
    • 監査要件(ログ記録範囲)
  3. セキュリティテストの計画

    • 静的解析(SAST)
    • 動的解析(DAST)
    • 侵入テスト

5. セキュリティ設定の不備(Security Misconfiguration)

問題: デフォルト設定、不要な機能、エラーメッセージからの情報漏洩。

// ❌ 本番環境でデバッグ有効
app.use(express.debug());

// ❌ 詳細なエラーメッセージ
app.use((err, req, res, next) => {
  res.status(500).send({
    error: err.message,
    stack: err.stack,  // スタックトレース漏洩
    query: req.query   // クエリパラメータ漏洩
  });
});

// ✅ 安全なエラーハンドリング
app.use((err, req, res, next) => {
  logger.error(err);  // ログには詳細
  res.status(500).send({
    error: 'Internal Server Error'  // ユーザーには最小限
  });
});

チェックリスト:

  • デフォルトパスワードを変更
  • 不要な機能を無効化
  • エラーメッセージを一般化
  • セキュリティヘッダーを設定
  • 定期的な設定監査

6 脆弱なライブラリ(Vulnerable and Outdated Components)

問題: 既知の脆弱性を持つライブラリを使用。

対策:

# 依存関係の脆弱性チェック
npm audit
pip-audit
bundle-audit

# 自動化(CI に組み込み)
npm audit --audit-level=high

運用ルール:

  • 依存ライブラリの Inventories を維持
  • セキュリティアドバイザリの購読
  • 自動化されたアップデート(Dependabot, Renovate)

7. 認証・認可の失敗(Identification and Authentication Failures)

問題: 認証プロセスの弱点を突かれる。

脆弱性対策
パスワード総当たりアカウントロックアウト、レート制限
セッションハイジャックSecure, HttpOnly フラグ、定期的な再生成
ブルートフォースCAPTCHA、多要素認証
パスワードリセット一時的トークン、有効期限付き
// セッション管理のベストプラクティス
app.use(session({
  secret: process.env.SESSION_SECRET,
  cookie: {
    secure: true,        // HTTPS のみ
    httpOnly: true,      // JavaScript からアクセス不可
    sameSite: 'strict',  // CSRF 対策
    maxAge: 1800000      // 30 分
  },
  resave: false,
  saveUninitialized: false
}));

8. ソフトウェア・データ整合性の失敗(Software and Data Integrity Failures)

問題: 改ざんされたソフトウェア・データの信頼。

対策:

  • デジタル署名の検証
  • サプライチェーン攻撃対策(SLSA フレームワーク)
  • CI/CD パイプラインの保護
# GitHub Actions の例
permissions:
  contents: read
  id-token: write  # OIDC 用

# デプロイ前に署名検証
- name: Verify artifact signature
  run: cosign verify --key cosign.pub ${{ github.sha }}

9. 監査ログの失敗(Security Logging and Monitoring Failures)

問題: 侵害の検出・対応が遅れる。

記録すべきイベント:

  • ログイン成功/失敗
  • 権限変更
  • データアクセス(特に機密情報)
  • 入力値バリデーション失敗
  • システムエラー
// 構造化ログの例
logger.warn({
  event: 'LOGIN_FAILED',
  userId: user.id,
  ip: req.ip,
  userAgent: req.get('user-agent'),
  attempts: failedAttempts,
  timestamp: new Date().toISOString()
});

10. サーバーサイドリクエストフォージェリ(SSRF)

問題: サーバーから内部ネットワークへ不正リクエスト。

// ❌ 脆弱なコード(ユーザー入力 URL にリクエスト)
app.get('/fetch', async (req, res) => {
  const url = req.query.url;
  const response = await fetch(url);  // 内部 IP にもアクセス可能
  res.send(response.body);
});

// ✅ 安全なコード
const ALLOWED_PROTOCOLS = ['https:'];
const BLOCKED_IPS = ['127.0.0.1', '10.', '192.168.', '172.16.'];

app.get('/fetch', async (req, res) => {
  const url = new URL(req.query.url);

  if (!ALLOWED_PROTOCOLS.includes(url.protocol)) {
    return res.status(400).send('Invalid protocol');
  }

  const ip = await dns.resolve(url.hostname);
  if (BLOCKED_IPS.some(blocked => ip.startsWith(blocked))) {
    return res.status(400).send('Access denied');
  }

  const response = await fetch(url);
  res.send(response.body);
});

セキュリティヘッダー

HTTP レスポンスに追加すべきセキュリティヘッダー:

// Express.js 例(helmet ミドルウェア)
const helmet = require('helmet');
app.use(helmet());

// 個別設定
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'"],
    styleSrc: ["'self'", "'unsafe-inline'"]
  }
}));
ヘッダー目的
Content-Security-PolicyXSS 対策、リソース読み込み制限
X-Content-Type-OptionsMIME タイプスニッフィング防止
X-Frame-Optionsクリックジャキング防止
Strict-Transport-SecurityHTTPS 強制
X-XSS-Protectionブラウザ XSS フィルタ有効化

認証設計のパターン

1. セッション認証(传统方式)

ログイン → サーバーがセッション作成 → セッション ID を Cookie で保存

メリット: サーバー側で無効化可能、実装がシンプル デメリット: サーバーに状態が必要、スケーラビリティ課題

2. トークン認証(JWT)

ログイン → サーバーが JWT 発行 → クライアントが保存 → 各リクエストに付与

メリット: ステートレス、スケーラブル デメリット: 失効が困難、トークンサイズ大

// JWT 発行・検証例
const jwt = require('jsonwebtoken');

// 発行
const token = jwt.sign(
  { userId: user.id, role: user.role },
  process.env.JWT_SECRET,
  { expiresIn: '1h', issuer: 'example.com' }
);

// 検証
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
  issuer: 'example.com'
});

3. OAuth 2.0 / OIDC

サードパーティ認証やシングルサインオン(SSO)に使用。

フローの種類:

  • 認証コードフロー(ウェブアプリ)
  • PKCE(ネイティブアプリ、SPA)
  • クライアントクレデンシャル(サーバー間)

入力値バリデーション

原則: 「入力値はすべて悪意があるとみなす」

const { z } = require('zod');

// スキーマ定義
const CreateUserSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8).regex(/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/),
  name: z.string().min(1).max(50),
  age: z.number().int().min(0).max(150)
});

// 検証
try {
  const userData = CreateUserSchema.parse(req.body);
  // 安全に処理
} catch (error) {
  res.status(400).json({ error: 'Invalid input' });
}

バリデーション項目:

  • 型チェック(文字列、数値、配列)
  • 長さ制限
  • 形式チェック(メール、URL)
  • 範囲チェック(数値、日付)
  • 許容値リスト(enum)

セキュリティ設計チェックリスト

設計レビューで使用できるチェックリスト:

認証・認可

  • パスワードはハッシュ化(bcrypt/Argon2)
  • 多要素認証を実装
  • セッションタイムアウトを設定
  • 権限チェックはサーバー側で
  • 最小権限の原則を適用

データ保護

  • 通信は TLS(HTTPS)
  • 機密データは暗号化
  • パスワードフィールドは logging 除外
  • 個人情報の保存期間を定義

入力・出力

  • 入力値バリデーション
  • 出力エスケープ(XSS 対策)
  • パラメータ化クエリ(SQL インジェクション対策)
  • ファイルアップロードは種類・サイズ制限

監視・運用

  • セキュリティログを記録
  • アラート閾値を設定
  • インシデント対応手順を策定
  • 定期的な脆弱性診断

まとめ

セキュリティ設計の核心は:

  1. 多層防御——一つの防御が破られても全体は守る
  2. デフォルトで安全——設定は安全側に倒す
  3. 最小権限——必要な権限だけを与える
  4. 完全な仲介——すべてのリクエストを検証
  5. 継続的改善——新たな脅威に対応

「セキュリティはプロセスであって、プロダクトではない」

OWASP Top 10 を設計段階でチェックし、継続的に改善していくことが、安全なシステム構築への道です。


参考資料

免責事項 — 掲載情報は執筆時点のものです。料金・機能は変更される場合があります。最新情報は各公式サイトをご確認ください。