Webアプリケーションを開発する上で、セキュリティは避けて通れない重要なテーマです。OWASP(Open Web Application Security Project)は、Webセキュリティのリスクを体系的に整理した「Top 10」を定期的に公開しています。本記事では、最も危険な脅威とその具体的な対策を、実際のコード例とともに解説します。

⚠️ 重要

本記事のコード例は教育目的です。実際の攻撃に利用することは法律で禁止されています。セキュリティ知識は防御のために活用してください。

1. OWASPとTop 10とは

OWASPは、Webアプリケーションのセキュリティ向上を目的とした非営利団体です。OWASP Top 10は、最も深刻なWebセキュリティリスクを10項目にまとめたもので、世界中の開発者・セキュリティエンジニアが参照する事実上の標準です。

2025年版 OWASP Top 10 概要

  1. A01: アクセス制御の不備 — 権限チェックの漏れ
  2. A02: 暗号化の失敗 — データ暗号化の不適切な実装
  3. A03: インジェクション — SQL/NoSQL/OSコマンドインジェクション
  4. A04: 安全でない設計 — アーキテクチャレベルの脆弱性
  5. A05: セキュリティ設定のミス — デフォルト設定の放置
  6. A06: 脆弱なコンポーネント — 古いライブラリの使用
  7. A07: 認証の不備 — パスワード管理や認証フローの問題
  8. A08: ソフトウェアとデータの整合性 — CI/CDやアップデートの脆弱性
  9. A09: セキュリティログの不足 — 監視・ログ記録の欠如
  10. A10: SSRF — サーバーサイドリクエストフォージェリ

2. インジェクション攻撃

最も古典的かつ危険な攻撃手法の一つがSQLインジェクションです。ユーザー入力を直接SQLクエリに埋め込むことで、データベースを不正に操作されます。

❌ 脆弱なコード

// 危険!ユーザー入力を直接SQL文に結合
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  // 攻撃者が username に ' OR '1'='1' -- と入力すると…
  const query = `SELECT * FROM users
    WHERE username = '${username}'
    AND password = '${password}'`;
  db.query(query); // 全ユーザー情報が漏洩!
});

✅ 安全なコード(パラメータバインド)

// プリペアドステートメントを使用
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
  db.query(query, [username, password]); // 安全!
});
✅ 対策のポイント

ORMを使用するとSQLインジェクションのリスクを大幅に軽減できます。Prisma、Sequelize、TypeORMなどのORMを積極的に活用しましょう。

広告

3. 認証の不備

認証機能は多くのアプリケーションの要です。よくある脆弱性と対策を見てみましょう。

よくある認証の問題

  • 弱いパスワードポリシー:「password123」を許可してしまう
  • ブルートフォース対策なし:ログイン試行回数の制限がない
  • セッション管理の不備:セッションIDの予測可能性
  • JWTの不適切な使用:alg=noneの許可、秘密鍵の漏洩

安全な認証の実装例

import bcrypt from 'bcrypt';
import rateLimit from 'express-rate-limit';

// ログイン試行回数の制限
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分間
  max: 5, // 最大5回
  message: 'ログイン試行回数を超えました。15分後に再試行してください。'
});

app.post('/login', loginLimiter, async (req, res) => {
  const { email, password } = req.body;
  const user = await User.findByEmail(email);

  if (!user) {
    // タイミング攻撃対策:ユーザーが存在しなくても同じ時間で応答
    await bcrypt.compare(password, '$2b$10$invalidhashplaceholder');
    return res.status(401).json({ error: '認証に失敗しました' });
  }

  const isValid = await bcrypt.compare(password, user.passwordHash);
  if (!isValid) {
    return res.status(401).json({ error: '認証に失敗しました' });
  }

  // セッション作成(安全な設定)
  req.session.userId = user.id;
  req.session.cookie.httpOnly = true;
  req.session.cookie.secure = true;
  req.session.cookie.sameSite = 'strict';
});

4. クロスサイトスクリプティング(XSS)

XSS攻撃は、悪意のあるスクリプトをWebページに注入し、他のユーザーのブラウザで実行させる攻撃です。

XSSの3つのタイプ

  • Stored XSS:悪意のあるスクリプトがサーバーに保存される(掲示板、コメント欄)
  • Reflected XSS:URLパラメータが反映されるページで発生
  • DOM-based XSS:JavaScriptが動的にDOMを操作する際に発生

XSS対策

// ❌ 危険:innerHTMLでユーザー入力を挿入
element.innerHTML = userInput;

// ✅ 安全:textContentを使用
element.textContent = userInput;

// ✅ サーバーサイドでのエスケープ
function escapeHtml(str) {
  return str
    .replace(/&/g, '&')
    .replace(//g, '>')
    .replace(/"/g, '"')
    .replace(/'/g, ''');
}

// ✅ Content Security Policy(CSP)ヘッダーの設定
app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy',
    "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");
  next();
});

5. CSRF攻撃と対策

CSRF(Cross-Site Request Forgery)は、ユーザーが意図しないリクエストを、認証済みのセッションを利用して送信させる攻撃です。

import csrf from 'csurf';

// CSRFトークンの設定
const csrfProtection = csrf({ cookie: true });

app.get('/transfer', csrfProtection, (req, res) => {
  res.render('transfer', { csrfToken: req.csrfToken() });
});

app.post('/transfer', csrfProtection, (req, res) => {
  // CSRFトークンが有効な場合のみ処理が実行される
  processTransfer(req.body);
});
広告

6. セキュリティチェックリスト

Webアプリケーションをリリースする前に、以下のチェックリストを確認しましょう:

  • ☑️ すべてのユーザー入力をバリデーション・サニタイズしているか
  • ☑️ パラメータバインドを使用しているか(SQLインジェクション対策)
  • ☑️ パスワードをbcrypt/argon2でハッシュ化しているか
  • ☑️ HTTPSを強制しているか
  • ☑️ CSPヘッダーを設定しているか
  • ☑️ CSRFトークンを使用しているか
  • ☑️ セッションCookieにHttpOnly, Secure, SameSiteを設定しているか
  • ☑️ 依存パッケージの脆弱性を定期チェックしているか(npm audit
  • ☑️ エラーメッセージに機密情報を含めていないか
  • ☑️ ログに不正アクセスの試行を記録しているか

セキュリティは「一度対策すれば終わり」ではありません。新しい脆弱性は常に発見されるため、継続的な学習とアップデートが不可欠です。まずはこの記事で紹介した基本的な対策から始めて、徐々にセキュリティ知識を深めていきましょう。