Skip to content
Published on

Webセキュリティ OWASP Top 10 完全ガイド2025:開発者が必ず防ぐべき10大脆弱性

Authors

はじめに:なぜすべての開発者(かいはつしゃ)がセキュリティを知(し)るべきか

2024年(ねん)のIBM Cost of a Data Breach Reportによると、データ漏洩(ろうえい)事故(じこ)の平均(へいきん)コストは488万(まん)ドルに達(たっ)します。さらに衝撃的(しょうげきてき)なのは、**漏洩事故の約(やく)70%がアプリケーションレベルの脆弱性(ぜいじゃくせい)**から始(はじ)まるという事実(じじつ)です。

セキュリティはセキュリティチームだけの責任(せきにん)ではありません。 コードを書(か)くすべての開発者(かいはつしゃ)がセキュリティの最初(さいしょ)の防御線(ぼうぎょせん)です。OWASP(Open Web Application Security Project)は、Webアプリケーションで最(もっと)も深刻(しんこく)な10大(だい)セキュリティリスクを定期的(ていきてき)に発表(はっぴょう)しています。

この記事(きじ)では、OWASP Top 10 2021の各(かく)脆弱性(ぜいじゃくせい)を実際(じっさい)のコードと共(とも)に分析(ぶんせき)し、防御方法(ぼうぎょほうほう)と実践(じっせん)チェックリストを提供(ていきょう)します。


1. OWASP Top 10 概要(がいよう)

1.1 OWASP Top 10 2021 ランキング

順位(じゅんい)カテゴリ2017年(ねん)比(ひ)変化(へんか)
A01Broken Access Control5位(い)から1位(い)に上昇(じょうしょう)
A02Cryptographic Failures3位(い)から2位(い)(名称(めいしょう)変更(へんこう))
A03Injection1位(い)から3位(い)に下降(かこう)
A04Insecure Design新規(しんき)項目(こうもく)
A05Security Misconfiguration6位(い)から5位(い)に上昇(じょうしょう)
A06Vulnerable and Outdated Components9位(い)から6位(い)に上昇(じょうしょう)
A07Identification and Authentication Failures2位(い)から7位(い)に下降(かこう)
A08Software and Data Integrity Failures新規(しんき)(8位(い)から分離(ぶんり))
A09Security Logging and Monitoring Failures10位(い)から9位(い)に上昇(じょうしょう)
A10Server-Side Request Forgery (SSRF)新規(しんき)項目(こうもく)

1.2 2017年(ねん)からの主(おも)な変化(へんか)

**Injectionが1位(い)から3位(い)に下降(かこう)**したのは、フレームワークのデフォルト防御(ぼうぎょ)メカニズムが改善(かいぜん)されたためです。一方(いっぽう)、**Broken Access Controlが1位(い)に上昇(じょうしょう)**したのは、APIベースのアーキテクチャの普及(ふきゅう)により権限(けんげん)制御(せいぎょ)のミスが急増(きゅうぞう)したためです。

Insecure Design(A04)SSRF(A10) が新(あら)たに加(くわ)わったことは、クラウドネイティブ環境(かんきょう)とマイクロサービスアーキテクチャで新(あたら)しい攻撃(こうげき)ベクトルが登場(とうじょう)したことを意味(いみ)します。


2. A01: Broken Access Control(アクセス制御(せいぎょ)の失敗(しっぱい))

2.1 概要(がいよう)

アクセス制御(せいぎょ)の失敗(しっぱい)とは、ユーザーが自身(じしん)の権限(けんげん)を超(こ)えた行動(こうどう)ができる脆弱性(ぜいじゃくせい)です。94%のアプリケーションで何(なん)らかの形(かたち)でアクセス制御(せいぎょ)の失敗(しっぱい)が発見(はっけん)されました。

2.2 攻撃(こうげき)シナリオ

IDOR(Insecure Direct Object Reference):

# 攻撃者がURLIDを変更して他のユーザー情報にアクセス
GET /api/users/12345/profile  -> 自分のプロフィール
GET /api/users/12346/profile  -> 他のユーザーのプロフィール(権限なしでアクセス!

2.3 脆弱(ぜいじゃく)なコード

// 脆弱 - 権限確認なしで直接取得
app.get('/api/users/:id/profile', async (req, res) => {
  const user = await User.findById(req.params.id);
  res.json(user); // IDさえ分かれば誰でもアクセス可能!
});

// 脆弱 - ロールのみ確認、リソース所有権未確認
app.delete('/api/posts/:id', requireRole('user'), async (req, res) => {
  await Post.findByIdAndDelete(req.params.id); // 他ユーザーの投稿も削除可能!
  res.json({ message: 'Deleted' });
});

2.4 安全(あんぜん)なコード

// 安全 - リソース所有権の確認
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
  if (req.params.id !== req.user.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  const user = await User.findById(req.params.id);
  res.json(user);
});

// 安全 - 所有権 + ロール確認
app.delete('/api/posts/:id', authenticate, async (req, res) => {
  const post = await Post.findById(req.params.id);
  if (!post) return res.status(404).json({ error: 'Not found' });

  if (post.authorId !== req.user.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  await post.deleteOne();
  res.json({ message: 'Deleted' });
});

2.5 防御(ぼうぎょ)チェックリスト

  • デフォルトですべてのアクセスを拒否(きょひ)し、必要(ひつよう)な場合(ばあい)のみ許可(きょか)(Deny by default)
  • IDOR防止(ぼうし):リソースアクセス時(じ)に常(つね)に所有権(しょゆうけん)または権限(けんげん)を確認(かくにん)
  • CORS設定(せってい)を最小限(さいしょうげん)に制限(せいげん)
  • JWTトークンのロール情報(じょうほう)をサーバー側(がわ)で再検証(さいけんしょう)
  • APIエンドポイントごとにアクセス制御(せいぎょ)テストを作成(さくせい)

3. A02: Cryptographic Failures(暗号化(あんごうか)の失敗(しっぱい))

3.1 概要(がいよう)

機密(きみつ)データを保護(ほご)するための暗号化(あんごうか)が欠落(けつらく)しているか、誤(あやま)って実装(じっそう)されている場合(ばあい)です。以前(いぜん)は「Sensitive Data Exposure」と呼(よ)ばれていました。

3.2 脆弱(ぜいじゃく)なコード vs 安全(あんぜん)なコード

// 脆弱: MD5でパスワードハッシュ
const crypto = require('crypto');
const hashedPassword = crypto.createHash('md5').update(password).digest('hex');

// 脆弱: ハードコードされた暗号化キー
const SECRET_KEY = 'my-super-secret-key-123';
const encrypted = encrypt(data, SECRET_KEY);
const bcrypt = require('bcrypt');

// 安全: bcryptで自動ソルト生成
const SALT_ROUNDS = 12;
const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);
const isMatch = await bcrypt.compare(inputPassword, hashedPassword);

// 安全: 環境変数からキー読み込み + AES-256-GCM使用
const crypto = require('crypto');
const SECRET_KEY = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');

function encrypt(plaintext) {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv('aes-256-gcm', SECRET_KEY, iv);
  const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
  const tag = cipher.getAuthTag();
  return { iv: iv.toString('hex'), encrypted: encrypted.toString('hex'), tag: tag.toString('hex') };
}

// 安全: TLS 1.2以上のみ許可
const server = https.createServer({
  minVersion: 'TLSv1.2',
  ciphers: 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256',
});

3.3 防御(ぼうぎょ)チェックリスト

  • すべての通信(つうしん)にHTTPS(TLS 1.2+)を強制(きょうせい)
  • パスワードはbcrypt、scrypt、Argon2を使用(しよう)(MD5、SHA-1は禁止(きんし))
  • 暗号化(あんごうか)キーは環境変数(かんきょうへんすう)またはKMS(Key Management Service)を使用(しよう)
  • AES-256-GCMなどの認証(にんしょう)付(つ)き暗号化(あんごうか)アルゴリズムを使用(しよう)
  • 機密(きみつ)データを不必要(ふひつよう)に保存(ほぞん)しない

4. A03: Injection(インジェクション)

4.1 概要(がいよう)

ユーザー入力(にゅうりょく)がクエリ、コマンド、コードの一部(いちぶ)として解釈(かいしゃく)される脆弱性(ぜいじゃくせい)です。SQL Injection、NoSQL Injection、OS Command Injection、XSSが代表的(だいひょうてき)です。

4.2 SQL Injection攻撃(こうげき)シナリオ

-- 正常なクエリ
SELECT * FROM users WHERE username = 'admin' AND password = 'password123'

-- 攻撃入力: username = ' OR '1'='1' --
SELECT * FROM users WHERE username = '' OR '1'='1' --' AND password = ''
-- 結果: すべてのユーザー情報が返される!

4.3 脆弱(ぜいじゃく)なコード vs 安全(あんぜん)なコード

// ===== SQL Injection =====

// 脆弱: 文字列結合でクエリ生成
const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;
db.query(query);

// 安全: パラメータ化クエリ(Prepared Statement)
const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.query(query, [username, hashedPassword]);

// 安全: ORM使用(Sequelize例)
const user = await User.findOne({
  where: { username, password: hashedPassword },
});

// ===== NoSQL Injection =====

// 脆弱: ユーザー入力を直接クエリに使用
app.post('/login', async (req, res) => {
  const user = await User.findOne(req.body);
});

// 安全: 型検証 + mongo-sanitize
const sanitize = require('mongo-sanitize');
app.post('/login', async (req, res) => {
  const username = sanitize(req.body.username);
  if (typeof username !== 'string') return res.status(400).send('Invalid input');
  const user = await User.findOne({ username, password: hashedPassword });
});

// ===== OS Command Injection =====

// 脆弱: ユーザー入力をシェルコマンドに直接使用
const { exec } = require('child_process');
exec(`ping ${userInput}`, callback);
// 攻撃: userInput = "8.8.8.8; rm -rf /"

// 安全: execFile使用(シェル解釈なし)
const { execFile } = require('child_process');
execFile('ping', ['-c', '4', userInput], callback);

4.4 XSS(Cross-Site Scripting)

// ===== Stored XSS =====

// 脆弱: ユーザー入力をそのままHTMLに挿入
app.get('/comments', async (req, res) => {
  const comments = await Comment.find();
  let html = '';
  comments.forEach(c => {
    html += `<div>${c.text}</div>`; // XSS脆弱!
  });
  res.send(html);
});

// 安全: 出力エンコーディング
const he = require('he');
comments.forEach(c => {
  html += `<div>${he.encode(c.text)}</div>`;
});

// ReactはデフォルトでXSS防止(JSXが自動エスケープ)
function Comment({ text }) {
  return <div>{text}</div>; // 自動的にHTMLエスケープされる
}

4.5 防御(ぼうぎょ)チェックリスト

  • SQL:常(つね)にパラメータ化(か)クエリまたはORMを使用(しよう)
  • NoSQL:入力(にゅうりょく)型(かた)検証(けんしょう) + mongo-sanitize
  • OSコマンド:execFileを使用(しよう)、ユーザー入力(にゅうりょく)をシェルに直接渡(わた)さない
  • XSS:出力(しゅつりょく)エンコーディング、CSPヘッダー設定(せってい)、フレームワークの自動(じどう)エスケープを活用(かつよう)

5. A04: Insecure Design(安全(あんぜん)でない設計(せっけい))

5.1 概要(がいよう)

設計(せっけい)段階(だんかい)からセキュリティ制御(せいぎょ)が欠落(けつらく)している場合(ばあい)です。A04はコードレベルではなく、アーキテクチャレベルの脆弱性(ぜいじゃくせい)です。

5.2 安全(あんぜん)な設計(せっけい)原則(げんそく)

// 原則1: Rate Limiting(リクエスト制限)
const rateLimit = require('express-rate-limit');

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分
  max: 5, // 最大5回試行
  message: 'Too many login attempts. Please try again after 15 minutes.',
  standardHeaders: true,
});
app.post('/login', loginLimiter, loginHandler);

// 原則2: ビジネスロジック検証
async function applyCoupon(userId, couponCode) {
  const coupon = await Coupon.findOne({ code: couponCode });
  if (!coupon || coupon.usedBy.includes(userId)) {
    throw new Error('Invalid or already used coupon');
  }
  // アトミック更新でレースコンディション防止
  const result = await Coupon.findOneAndUpdate(
    { code: couponCode, usedBy: { $ne: userId } },
    { $push: { usedBy: userId } },
    { new: true }
  );
  if (!result) throw new Error('Coupon already used');
  return result;
}

5.3 防御(ぼうぎょ)チェックリスト

  • 設計(せっけい)段階(だんかい)で脅威(きょうい)モデリングを実施(じっし)(STRIDE、DREAD)
  • ビジネスロジックにRate Limitingを含(ふく)める
  • Abuse CaseをUse Caseと一緒(いっしょ)に作成(さくせい)
  • セキュリティ設計(せっけい)パターンライブラリを活用(かつよう)
  • セキュリティ要件(ようけん)をUser Storyに含(ふく)める

6. A05: Security Misconfiguration(セキュリティ設定(せってい)ミス)

6.1 概要(がいよう)

サーバー、フレームワーク、クラウドサービスのセキュリティ設定(せってい)がデフォルトのまま、または誤(あやま)って構成(こうせい)されている場合(ばあい)です。90%のアプリケーションで発見(はっけん)される最(もっと)も一般的(いっぱんてき)な脆弱性(ぜいじゃくせい)です。

6.2 安全(あんぜん)な設定(せってい)

// 安全: Helmetミドルウェア + セキュリティ強化
const helmet = require('helmet');
const app = express();
app.use(helmet());
app.disable('x-powered-by');

// 安全: エラーメッセージの情報漏洩防止
app.use((err, req, res, next) => {
  console.error(err.stack); // サーバーログにのみ記録
  res.status(500).json({
    error: 'Internal Server Error', // 一般的なメッセージのみ返却
  });
});
# Docker セキュリティ設定

# 脆弱: rootユーザーで実行
FROM node:20
COPY . .
CMD ["node", "server.js"]

# 安全: non-rootユーザー使用
FROM node:20-slim
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
COPY --chown=appuser:appuser . .
USER appuser
CMD ["node", "server.js"]

6.3 防御(ぼうぎょ)チェックリスト

  • プロダクションでデバッグモードを無効化(むこうか)
  • デフォルトアカウント/パスワードを変更(へんこう)
  • 不要(ふよう)な機能(きのう)、ポート、サービスを無効化(むこうか)
  • セキュリティヘッダーを設定(せってい)(Helmetなど)
  • クラウドリソースのパブリックアクセスを遮断(しゃだん)
  • Infrastructure as Code(IaC)で設定(せってい)の一貫性(いっかんせい)を維持(いじ)

7. A06: Vulnerable and Outdated Components(脆弱(ぜいじゃく)な構成(こうせい)コンポーネント)

7.1 概要(がいよう)

既知(きち)の脆弱性(ぜいじゃくせい)のあるライブラリ、フレームワーク、ソフトウェアコンポーネントを使用(しよう)している場合(ばあい)です。Log4Shell(CVE-2021-44228)が代表的(だいひょうてき)な事例(じれい)です。

7.2 防御(ぼうぎょ)ツールと自動化(じどうか)

# npm依存関係の脆弱性監査
npm audit
npm audit fix

# Snykで脆弱性スキャン
npx snyk test
npx snyk monitor
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
    reviewers:
      - "security-team"

7.3 防御(ぼうぎょ)チェックリスト

  • npm audit / Snyk / OWASP Dependency-Checkを定期的(ていきてき)に実行(じっこう)
  • CI/CDパイプラインに依存関係(いぞんかんけい)スキャンを統合(とうごう)
  • DependabotまたはRenovate Botで自動(じどう)更新(こうしん)PRを生成(せいせい)
  • 使用(しよう)していない依存関係(いぞんかんけい)を削除(さくじょ)
  • SBOM(Software Bill of Materials)を維持(いじ)

8. A07: Identification and Authentication Failures(認証(にんしょう)の失敗(しっぱい))

8.1 概要(がいよう)

認証(にんしょう)メカニズムが誤(あやま)って実装(じっそう)され、攻撃者(こうげきしゃ)がユーザーアカウントに不正(ふせい)アクセスできる脆弱性(ぜいじゃくせい)です。

8.2 安全(あんぜん)なコード

// 安全: 暗号学的に安全なランダムセッションID
const crypto = require('crypto');
function createSession() {
  return crypto.randomBytes(32).toString('hex'); // 256ビットランダム
}

// 安全: 明示的アルゴリズム指定
const payload = jwt.verify(token, secret, {
  algorithms: ['HS256'],
  issuer: 'my-app',
  audience: 'my-api',
});

// MFA実装
const speakeasy = require('speakeasy');

async function setupMFA(userId) {
  const secret = speakeasy.generateSecret({
    name: `MyApp:user-${userId}`,
    issuer: 'MyApp',
  });
  const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);
  await User.updateOne({ _id: userId }, { mfaSecret: encrypt(secret.base32) });
  return { qrCodeUrl, backupCodes: generateBackupCodes() };
}

function verifyMFA(token, userSecret) {
  return speakeasy.totp.verify({
    secret: userSecret,
    encoding: 'base32',
    token: token,
    window: 1,
  });
}

8.3 防御(ぼうぎょ)チェックリスト

  • MFA(多要素認証(たようそにんしょう))の実装(じっそう)と推奨(すいしょう)
  • パスワード最低(さいてい)12文字(もじ) + 複雑性(ふくざつせい)要求(ようきゅう)
  • 失敗(しっぱい)したログイン試行(しこう)の制限(せいげん)(Account Lockout)
  • セッションタイムアウトの設定(せってい)
  • パスワードハッシュにbcrypt/Argon2を使用(しよう)
  • JWT:アルゴリズム明示(めいじ)、有効期限(ゆうこうきげん)設定(せってい)、Refresh Token Rotation

9. A08: Software and Data Integrity Failures(ソフトウェア整合性(せいごうせい)の失敗(しっぱい))

9.1 概要(がいよう)

ソフトウェアの更新(こうしん)、CI/CDパイプライン、データのシリアライズで整合性(せいごうせい)が検証(けんしょう)されない場合(ばあい)です。SolarWindsのサプライチェーン攻撃(こうげき)が代表的(だいひょうてき)な事例(じれい)です。

9.2 安全(あんぜん)なコード

// 安全: デシリアライズ前にスキーマ検証
const Joi = require('joi');
const schema = Joi.object({
  name: Joi.string().max(100).required(),
  age: Joi.number().integer().min(0).max(150),
  email: Joi.string().email(),
});

app.post('/api/data', (req, res) => {
  const { error, value } = schema.validate(req.body);
  if (error) return res.status(400).json({ error: error.details });
  processData(value);
});

9.3 防御(ぼうぎょ)チェックリスト

  • CDNリソースにSRI(Subresource Integrity)ハッシュを追加(ついか)
  • CI/CDパイプラインでアーティファクトの署名(しょめい)と検証(けんしょう)
  • npmのlockfile(package-lock.json)の整合性(せいごうせい)を確認(かくにん)
  • デシリアライズ前(まえ)にスキーマ検証(けんしょう)(Joi、Zod)
  • ソフトウェア更新(こうしん)のデジタル署名(しょめい)を確認(かくにん)

10. A09: Security Logging and Monitoring Failures(ロギングの失敗(しっぱい))

10.1 概要(がいよう)

セキュリティ関連(かんれん)のイベントが記録(きろく)されないか、監視(かんし)されず、攻撃(こうげき)を検知(けんち)できない場合(ばあい)です。平均的(へいきんてき)に**漏洩事故(ろうえいじこ)の検知(けんち)に194日(にち)**かかります。

10.2 安全(あんぜん)なセキュリティロギング

const winston = require('winston');

const securityLogger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  defaultMeta: { service: 'security' },
  transports: [
    new winston.transports.File({ filename: 'security.log' }),
  ],
});

function logAuthEvent(event) {
  securityLogger.info('Authentication event', {
    type: event.type,
    userId: event.userId,
    ip: event.ip,
    userAgent: event.userAgent,
    timestamp: new Date().toISOString(),
    // パスワードは絶対にログに記録しない!
  });
}

function logAuditEvent(action, userId, resource, details) {
  securityLogger.info('Audit event', {
    action,
    userId,
    resource,
    details,
    timestamp: new Date().toISOString(),
  });
}

10.3 防御(ぼうぎょ)チェックリスト

  • ログイン成功(せいこう)/失敗(しっぱい)、権限(けんげん)変更(へんこう)、重要(じゅうよう)な操作(そうさ)を必(かなら)ずロギング
  • パスワード、トークンなどの機密情報(きみつじょうほう)をログに含(ふく)めない
  • 中央集中型(ちゅうおうしゅうちゅうがた)ログ管理(かんり)(ELK Stack、CloudWatch)
  • 異常検知(いじょうけんち)アラートの設定(せってい)
  • ログ改竄(かいざん)防止(ぼうし)(別(べつ)サーバーに保存(ほぞん)、監査(かんさ)ログのimmutability)

11. A10: Server-Side Request Forgery(SSRF)

11.1 概要(がいよう)

サーバーに攻撃者(こうげきしゃ)が指定(してい)したURLへリクエストを送(おく)らせる攻撃(こうげき)です。2019年(ねん)のCapital Oneハッキング事件(じけん)の核心的(かくしんてき)な脆弱性(ぜいじゃくせい)でした。

11.2 脆弱(ぜいじゃく)なコード vs 安全(あんぜん)なコード

const axios = require('axios');
const { URL } = require('url');
const dns = require('dns').promises;
const ipaddr = require('ipaddr.js');

// 脆弱: ユーザー入力URLに直接リクエスト
app.post('/api/fetch-url', async (req, res) => {
  const response = await axios.get(req.body.url);
  res.json(response.data);
});

// 安全: URL検証 + IP遮断
async function isUrlSafe(urlString) {
  const parsed = new URL(urlString);

  if (parsed.protocol !== 'https:') return false;

  const blockedHosts = ['localhost', '127.0.0.1', '0.0.0.0', 'metadata.google.internal'];
  if (blockedHosts.includes(parsed.hostname)) return false;

  const addresses = await dns.resolve4(parsed.hostname);
  for (const addr of addresses) {
    const ip = ipaddr.parse(addr);
    if (ip.range() !== 'unicast') return false;
  }
  return true;
}

app.post('/api/fetch-url', async (req, res) => {
  if (!await isUrlSafe(req.body.url)) {
    return res.status(403).json({ error: 'URL not allowed' });
  }
  const response = await axios.get(req.body.url, {
    timeout: 5000,
    maxRedirects: 0,
  });
  res.json(response.data);
});

11.3 防御(ぼうぎょ)チェックリスト

  • 許可(きょか)リスト(allowlist)ベースのURL検証(けんしょう)
  • 内部(ないぶ)IPレンジ(10.x、172.16-31.x、192.168.x、169.254.x)を遮断(しゃだん)
  • IMDSv2を使用(しよう)(AWSメタデータサービス保護(ほご))
  • サーバーからの外部(がいぶ)リクエストに専用(せんよう)ネットワークインターフェースを使用(しよう)
  • DNS Rebinding防止(ぼうし):DNS解決後(かいけつご)にIPを再確認(さいかくにん)

12. OWASP以外(いがい)の追加(ついか)脆弱性(ぜいじゃくせい)

12.1 CSRF(Cross-Site Request Forgery)

const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

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

app.post('/transfer', csrfProtection, (req, res) => {
  processTransfer(req.body);
});

// SameSiteクッキーでCSRF防御
res.cookie('session', sessionId, {
  httpOnly: true,
  secure: true,
  sameSite: 'Strict',
  maxAge: 3600000,
});

12.2 CORS(Cross-Origin Resource Sharing)

const cors = require('cors');

// 脆弱: すべてのオリジンを許可
app.use(cors());

// 安全: 特定のオリジンのみ許可
app.use(cors({
  origin: ['https://myapp.com', 'https://admin.myapp.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true,
  maxAge: 86400,
}));

12.3 CSP(Content Security Policy)

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'nonce-abc123'"],
    styleSrc: ["'self'", "https://fonts.googleapis.com"],
    imgSrc: ["'self'", "data:", "https:"],
    connectSrc: ["'self'", "https://api.myapp.com"],
    fontSrc: ["'self'", "https://fonts.gstatic.com"],
    objectSrc: ["'none'"],
    frameSrc: ["'none'"],
  },
}));

12.4 Rate Limiting

const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');

const limiter = rateLimit({
  store: new RedisStore({
    sendCommand: (...args) => redisClient.call(...args),
  }),
  windowMs: 15 * 60 * 1000,
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: (req) => req.ip,
});

const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5 });
const apiLimiter = rateLimit({ windowMs: 60 * 1000, max: 30 });

app.use('/api/auth', authLimiter);
app.use('/api/', apiLimiter);

13. セキュリティヘッダー完全(かんぜん)ガイド

app.use((req, res, next) => {
  // XSS防御
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-XSS-Protection', '0');

  // Clickjacking防御
  res.setHeader('X-Frame-Options', 'DENY');

  // HTTPS強制
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');

  // Referrer情報制限
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');

  // ブラウザ機能制限
  res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');

  // CSP
  res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self'");

  next();
});
ヘッダー目的(もくてき)推奨値(すいしょうち)
Content-Security-PolicyXSS/インジェクション防御(ぼうぎょ)default-src 'self'
Strict-Transport-SecurityHTTPS強制(きょうせい)max-age=31536000; includeSubDomains
X-Frame-OptionsClickjacking防御(ぼうぎょ)DENY
X-Content-Type-OptionsMIMEスニッフィング防御(ぼうぎょ)nosniff
Referrer-PolicyReferrer情報(じょうほう)制限(せいげん)strict-origin-when-cross-origin
Permissions-Policyブラウザ機能(きのう)制限(せいげん)camera=(), microphone=()

14. セキュリティテストツール

14.1 DAST(Dynamic Application Security Testing)

ツール種類(しゅるい)特徴(とくちょう)
OWASP ZAPオープンソースDAST自動(じどう)スキャン + 手動(しゅどう)テスト、CI/CD統合(とうごう)
Burp Suite商用(しょうよう)DAST業界(ぎょうかい)標準(ひょうじゅん)Webセキュリティテストツール
Nucleiオープンソーススキャナーテンプレートベースの脆弱性(ぜいじゃくせい)スキャン

14.2 SAST(Static Application Security Testing)

ツール種類(しゅるい)特徴(とくちょう)
SonarQubeオープンソースSASTコード品質(ひんしつ) + セキュリティ脆弱性(ぜいじゃくせい)分析(ぶんせき)
SemgrepオープンソースSAST軽量(けいりょう)静的(せいてき)分析(ぶんせき)、カスタムルール対応(たいおう)
CodeQLGitHub SASTGitHub統合(とうごう)、セマンティック分析(ぶんせき)

14.3 SCA(Software Composition Analysis)

ツール種類(しゅるい)特徴(とくちょう)
SnykSCA + SAST依存関係(いぞんかんけい) + コード脆弱性(ぜいじゃくせい)
npm audit内蔵(ないぞう)npm依存関係(いぞんかんけい)脆弱性(ぜいじゃくせい)検査(けんさ)
Trivyオープンソースコンテナイメージ + ファイルシステムスキャン
# GitHub Actionsセキュリティスキャン統合
name: Security Scan
on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Snyk
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

      - name: Run Semgrep
        uses: semgrep/semgrep-action@v1
        with:
          config: p/owasp-top-ten

      - name: Run Trivy
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          severity: 'CRITICAL,HIGH'

15. プロダクションセキュリティチェックリスト(20項目(こうもく))

認証(にんしょう)とアクセス制御(せいぎょ)

  • MFA(多要素認証(たようそにんしょう))の実装(じっそう)
  • パスワード最低(さいてい)12文字(もじ) + 複雑性(ふくざつせい)要求(ようきゅう)
  • セッションタイムアウト設定(せってい)(例(れい):30分(ぷん))
  • JWT Refresh Token Rotation
  • RBACまたはABACベースの権限管理(けんげんかんり)

データ保護(ほご)

  • すべての通信(つうしん)にHTTPS(TLS 1.2+)を強制(きょうせい)
  • パスワードはbcrypt/Argon2でハッシュ化(か)
  • 暗号化(あんごうか)キーはKMSで管理(かんり)
  • PII(個人識別情報(こじんしきべつじょうほう))は最小限(さいしょうげん)の収集(しゅうしゅう) + 暗号化(あんごうか)保存(ほぞん)

入力検証(にゅうりょくけんしょう)

  • パラメータ化(か)クエリ使用(しよう)(SQL Injection防止(ぼうし))
  • 出力(しゅつりょく)エンコーディング(XSS防止(ぼうし))
  • CSRFトークンまたはSameSiteクッキー
  • ファイルアップロードの種類(しゅるい)/サイズ制限(せいげん)

インフラストラクチャと設定(せってい)

  • セキュリティヘッダー設定(せってい)(CSP、HSTS、X-Frame-Options)
  • プロダクションでデバッグモード無効化(むこうか)
  • Dockerコンテナをnon-rootで実行(じっこう)
  • クラウドリソースのパブリックアクセスを遮断(しゃだん)

監視(かんし)と対応(たいおう)

  • セキュリティイベントのロギング(認証(にんしょう)、権限変更(けんげんへんこう)、重要(じゅうよう)な操作(そうさ))
  • 依存関係(いぞんかんけい)の脆弱性(ぜいじゃくせい)自動(じどう)スキャン(CI/CD統合(とうごう))
  • インシデント対応(たいおう)プレイブック準備(じゅんび)

16. 面接(めんせつ)予想(よそう)質問(しつもん)15選(せん)

基礎(きそ)質問(しつもん)

Q1: OWASP Top 10とは何(なに)ですか?なぜ重要(じゅうよう)ですか?

OWASP Top 10は、Webアプリケーションで最(もっと)も深刻(しんこく)な10大(だい)セキュリティリスクをまとめたリストです。3〜4年(ねん)ごとに更新(こうしん)され、セキュリティ意識(いしき)の向上(こうじょう)と開発(かいはつ)ガイドラインとして業界(ぎょうかい)で広(ひろ)く使用(しよう)されています。PCI DSSなどの規制(きせい)遵守(じゅんしゅ)でも参照(さんしょう)されます。

Q2: SQL Injectionを防止(ぼうし)する方法(ほうほう)を3つ説明(せつめい)してください。

1)パラメータ化(か)クエリ(Prepared Statement):クエリとデータを分離(ぶんり)します。2)ORM使用(しよう):Sequelize、Prismaなどが自動的(じどうてき)にクエリをパラメータ化(か)します。3)入力検証(にゅうりょくけんしょう):ホワイトリストベースで許可(きょか)された文字(もじ)のみ通過(つうか)させます。

Q3: XSSの3つの種類(しゅるい)を説明(せつめい)してください。

1)Stored XSS:悪意(あくい)のあるスクリプトがDBに保存(ほぞん)され、他(ほか)のユーザーに配信(はいしん)。2)Reflected XSS:URLパラメータのスクリプトがレスポンスに含(ふく)まれる。3)DOM-based XSS:クライアント側(がわ)のJavaScriptがDOMを操作(そうさ)して発生(はっせい)。防御(ぼうぎょ)は出力(しゅつりょく)エンコーディングとCSPヘッダーの設定(せってい)です。

Q4: CSRFとは何(なに)ですか?どのように防御(ぼうぎょ)しますか?

CSRFは認証(にんしょう)されたユーザーのブラウザを利用(りよう)して悪意(あくい)のあるリクエストを送(おく)る攻撃(こうげき)です。防御(ぼうぎょ):1)Anti-CSRFトークンの使用(しよう)、2)SameSiteクッキー属性(ぞくせい)、3)Referer/Originヘッダーの検証(けんしょう)、4)重要(じゅうよう)な操作(そうさ)での再認証(さいにんしょう)要求(ようきゅう)。

Q5: HTTPSがHTTPより安全(あんぜん)な理由(りゆう)を説明(せつめい)してください。

HTTPSはTLSを使用(しよう)して3つを保証(ほしょう)します:1)機密性(きみつせい):暗号化(あんごうか)で盗聴(とうちょう)を防止(ぼうし)、2)整合性(せいごうせい):データ改竄(かいざん)を検知(けんち)、3)認証(にんしょう):証明書(しょうめいしょ)でサーバーの身元(みもと)を確認(かくにん)。HTTPは平文(へいぶん)で送信(そうしん)するため、中間者攻撃(ちゅうかんしゃこうげき)(MITM)に脆弱(ぜいじゃく)です。

中級(ちゅうきゅう)質問(しつもん)

Q6: Broken Access ControlとAuthentication Failureの違(ちが)いは?

Authentication Failureは「あなたは誰(だれ)ですか?」を誤(あやま)って判断(はんだん)することであり、Broken Access Controlは「あなたは何(なに)ができますか?」を誤(あやま)って制御(せいぎょ)することです。認証(にんしょう)は身元確認(みもとかくにん)、アクセス制御(せいぎょ)は権限制御(けんげんせいぎょ)です。

Q7: SSRF攻撃(こうげき)がクラウド環境(かんきょう)で特(とく)に危険(きけん)な理由(りゆう)は?

クラウド環境(かんきょう)にはメタデータサービス(169.254.169.254)があり、SSRFでIAMクレデンシャル、環境変数(かんきょうへんすう)などを窃取(せっしゅ)できます。Capital Oneのハッキングが代表的(だいひょうてき)な事例(じれい)であり、AWS IMDSv2の使用(しよう)で防御(ぼうぎょ)します。

Q8: JWTのセキュリティ脆弱性(ぜいじゃくせい)と対策(たいさく)を説明(せつめい)してください。

脆弱性(ぜいじゃくせい):1)Algorithm None攻撃(こうげき)、2)シークレットキーのブルートフォース、3)トークン窃取(せっしゅ)後(ご)の期限切(きげんぎ)れまでの悪用(あくよう)。対策(たいさく):アルゴリズムの明示的(めいじてき)検証(けんしょう)、強力(きょうりょく)なシークレットキー(256ビット以上(いじょう))、短(みじか)い有効期限(ゆうこうきげん) + Refresh Token Rotation。

Q9: Content Security Policy(CSP)の動作原理(どうさげんり)を説明(せつめい)してください。

CSPはブラウザに許可(きょか)されたリソースの出所(しゅっしょ)を指定(してい)するHTTPヘッダーです。script-src、style-srcなどのディレクティブで各(かく)リソースタイプごとの許可(きょか)出所(しゅっしょ)を指定(してい)します。インラインスクリプトはnonceまたはhashで許可(きょか)し、違反(いはん)はreport-uriで報告(ほうこく)できます。

Q10: Rate Limitingの実装戦略(じっそうせんりゃく)と分散環境(ぶんさんかんきょう)での考慮事項(こうりょじこう)は?

戦略(せんりゃく):1)Fixed Window、2)Sliding Window、3)Token Bucket、4)Leaky Bucket。分散環境(ぶんさんかんきょう)ではRedisのような中央(ちゅうおう)ストレージを使用(しよう)して、すべてのサーバーで一貫(いっかん)したカウンティングが必要(ひつよう)です。IPベース + ユーザーIDベースの複合(ふくごう)制限(せいげん)が効果的(こうかてき)です。

上級(じょうきゅう)質問(しつもん)

Q11: Zero Trustアーキテクチャとは何(なに)ですか?従来(じゅうらい)の境界(きょうかい)セキュリティとどう異(こと)なりますか?

従来(じゅうらい)のセキュリティはネットワーク境界(きょうかい)(ファイアウォール)を基(もと)に内部(ないぶ)を信頼(しんらい)します。Zero Trustは「絶対(ぜったい)に信頼(しんらい)せず、常(つね)に検証(けんしょう)する」という原則(げんそく)で、内部(ないぶ)トラフィックも検証(けんしょう)します。核心要素(かくしんようそ):最小権限(さいしょうけんげん)、マイクロセグメンテーション、継続的(けいぞくてき)検証(けんしょう)、暗号化(あんごうか)通信(つうしん)。

Q12: サプライチェーン攻撃(こうげき)をどのように防御(ぼうぎょ)しますか?

1)依存関係(いぞんかんけい)のロックファイル使用(しよう)と整合性検証(せいごうせいけんしょう)、2)CDNリソースへのSRIハッシュ適用(てきよう)、3)SBOMの維持(いじ)、4)CI/CDパイプラインセキュリティ(コード署名(しょめい)、アーティファクト検証(けんしょう))、5)最小依存関係(さいしょういぞんかんけい)の原則(げんそく)。

Q13: CORSとCSPの違(ちが)いとそれぞれの役割(やくわり)を説明(せつめい)してください。

CORSはブラウザの同一(どういつ)オリジンポリシーを緩和(かんわ)して、他(た)オリジンのリソースアクセスを制御(せいぎょ)します(サーバー間(かん)API呼(よ)び出(だ)し)。CSPはページ内(ない)でロードできるリソースの出所(しゅっしょ)を制限(せいげん)します(XSS防御(ぼうぎょ))。CORSは「誰(だれ)が私(わたし)のAPIを呼(よ)べるか」、CSPは「私(わたし)のページで何(なに)をロードできるか」です。

Q14: DevSecOpsにおけるShift Leftの意味(いみ)と実装方法(じっそうほうほう)は?

Shift Leftはセキュリティ活動(かつどう)を開発(かいはつ)の初期段階(しょきだんかい)に移動(いどう)させることです。実装(じっそう):1)IDEでのSASTプラグイン使用(しよう)、2)Pre-commitフックでのシークレットスキャン、3)PRレビューでのセキュリティチェック自動化(じどうか)、4)スプリントレベルの脅威(きょうい)モデリング、5)開発者(かいはつしゃ)セキュリティ教育(きょういく)。

Q15: セッション管理(かんり)のベストプラクティス5つを説明(せつめい)してください。

1)暗号学的(あんごうがくてき)に安全(あんぜん)なランダムセッションID生成(せいせい)(最低(さいてい)128ビット)、2)HTTPS専用(せんよう) + HttpOnly + Secure + SameSiteクッキー属性(ぞくせい)、3)ログイン/権限変更時(けんげんへんこうじ)にセッションIDを再生成(さいせいせい)、4)絶対/アイドルタイムアウトの設定(せってい)、5)ログアウト時(じ)のサーバー側(がわ)セッション完全(かんぜん)無効化(むこうか)。


17. クイズ

Q1: OWASP Top 10 2021で1位(い)に上昇(じょうしょう)した脆弱性(ぜいじゃくせい)は?

A01: Broken Access Controlです。2017年(ねん)は5位(い)でしたが、APIベースのアーキテクチャの普及(ふきゅう)により権限制御(けんげんせいぎょ)のミスが増加(ぞうか)し、1位(い)に上昇(じょうしょう)しました。

Q2: SQL Injectionに対(たい)する最(もっと)も効果的(こうかてき)な防御方法(ぼうぎょほうほう)は?

パラメータ化(か)クエリ(Prepared Statement) です。クエリ構造(こうぞう)とデータを分離(ぶんり)することで、ユーザー入力(にゅうりょく)がSQLコマンドとして解釈(かいしゃく)されることを根本的(こんぽんてき)に防止(ぼうし)します。ORM使用(しよう)も同(おな)じ原理(げんり)で防御(ぼうぎょ)されます。

Q3: SSRF攻撃(こうげき)で169.254.169.254アドレスが危険(きけん)な理由(りゆう)は?

169.254.169.254はクラウドインスタンスメタデータサービスのアドレスです。SSRFでアクセスするとIAMクレデンシャル、環境変数(かんきょうへんすう)、ネットワーク構成(こうせい)などの機密情報(きみつじょうほう)を窃取(せっしゅ)できます。AWS IMDSv2はPUTリクエストでトークンを先(さき)に発行(はっこう)する必要(ひつよう)があるため、SSRF攻撃(こうげき)が困難(こんなん)になります。

Q4: CSPで'unsafe-inline'を使用(しよう)してはいけない理由(りゆう)は?

'unsafe-inline'を許可(きょか)すると**インラインスクリプトの実行(じっこう)が可能(かのう)**になり、XSS攻撃(こうげき)に脆弱(ぜいじゃく)になります。代(か)わりにnonceベースのCSPを使用(しよう)し、各(かく)インラインスクリプトに固有(こゆう)のnonce値(ち)を付与(ふよ)して、一致(いっち)するnonceを持(も)つスクリプトのみ実行(じっこう)を許可(きょか)すべきです。

Q5: Zero Trustセキュリティモデルの核心原則(かくしんげんそく)3つは?

1)絶対(ぜったい)に信頼(しんらい)しない(Never Trust): 内部(ないぶ)ネットワークでもすべてのリクエストを検証(けんしょう)します。 2)常(つね)に検証(けんしょう)する(Always Verify): すべてのアクセスに認証(にんしょう)と権限検査(けんげんけんさ)を実施(じっし)します。 3)最小権限(さいしょうけんげん)(Least Privilege): 必要最低限(ひつようさいていげん)の権限(けんげん)のみ付与(ふよ)し、定期的(ていきてき)に見直(みなお)します。


参考資料(さんこうしりょう)