SJ blog
security
S

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

OWASP Top 10 2025:Webアプリの脆弱性を正しく理解する

OWASP Top 10 2025版の各カテゴリをコード例付きで解説。Broken Access Control・Injection・Cryptographic Failures など、現代のWebアプリが直面するセキュリティリスクと対策を紹介します。

一言結論

OWASP Top 10でA01のBroken Access Controlが長年1位を占める事実が示すように、最も危険な脆弱性は複雑な攻撃ではなく「認可チェックの実装忘れ」という単純なミスであり、全エンドポイントでの認可検証が最優先の対策だ。

OWASP Top 10 2025 一覧

順位カテゴリ
A01Broken Access Control(アクセス制御の破綻)
A02Cryptographic Failures(暗号化の失敗)
A03Injection(インジェクション)
A04Insecure Design(安全でない設計)
A05Security Misconfiguration(セキュリティの設定ミス)
A06Vulnerable Components(脆弱なコンポーネント)
A07Identification & Authentication Failures(認証の失敗)
A08Software & Data Integrity Failures(整合性の失敗)
A09Security Logging & Monitoring Failures(監視の失敗)
A10Server-Side Request Forgery(SSRF)

A01: Broken Access Control

最も多く報告される脆弱性。「自分に見えてはいけないデータが見える」問題。

// ❌ ユーザーIDをクライアントから信頼する
app.get("/orders/:orderId", async (req, res) => {
  const order = await db.orders.findById(req.params.orderId);
  res.json(order); // 他人の注文も取れてしまう
});

// ✅ セッションのユーザーIDと照合する
app.get("/orders/:orderId", authenticate, async (req, res) => {
  const order = await db.orders.findOne({
    id: req.params.orderId,
    userId: req.user.id, // ← 認証済みユーザーの注文のみ返す
  });
  if (!order) return res.status(404).json({ error: "Not found" });
  res.json(order);
});

A02: Cryptographic Failures

暗号化の不備、平文での機密情報保存。

// ❌ パスワードを MD5/SHA1 でハッシュ(破られる)
const hash = crypto.createHash("md5").update(password).digest("hex");

// ✅ bcrypt / Argon2 を使う
import bcrypt from "bcrypt";
const hash = await bcrypt.hash(password, 12); // cost factor 12以上
const isValid = await bcrypt.compare(inputPassword, hash);
❌ HTTP で個人情報を送信
✅ HTTPS(TLS 1.2+)を強制
❌ 接続文字列をコードに直書き
✅ 環境変数 / Secrets Manager を使用

A03: Injection

SQLインジェクション・コマンドインジェクション・LDAP インジェクションなど。

// ❌ SQL インジェクション
const query = `SELECT * FROM users WHERE email = '${email}'`;
// email = "' OR '1'='1" でスキップできる

// ✅ プリペアドステートメント
const user = await db.query(
  "SELECT * FROM users WHERE email = $1",
  [email]
);

A05: Security Misconfiguration

デフォルト設定のままの使用、不要なポートの開放など。

// Express のセキュリティヘッダー(helmet を使う)
import helmet from "helmet";
app.use(helmet());
// X-Frame-Options, X-XSS-Protection, HSTS などを自動設定

// ❌ 詳細なエラーメッセージを本番で返す
app.use((err, req, res, next) => {
  res.status(500).json({ error: err.stack }); // スタックトレースが漏洩

// ✅ 本番では汎用エラーメッセージを返す
app.use((err, req, res, next) => {
  logger.error(err);
  res.status(500).json({ error: "Internal Server Error" });
});

A06: Vulnerable Components

脆弱性のある npm/pip パッケージの使用。

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

# 自動修正
npm audit fix

# CI での自動チェック(GitHub Dependabot を有効化)
# .github/dependabot.yml
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10

A07: Identification & Authentication Failures

// ✅ アカウントロックアウト
const MAX_ATTEMPTS = 5;
const LOCKOUT_DURATION = 15 * 60; // 15分

app.post("/login", async (req, res) => {
  const attempts = await redis.incr(`login_attempts:${req.body.email}`);
  if (attempts === 1) {
    await redis.expire(`login_attempts:${req.body.email}`, LOCKOUT_DURATION);
  }
  if (attempts > MAX_ATTEMPTS) {
    return res.status(429).json({ error: "Too many attempts. Try again later." });
  }
  // ... ログイン処理
});

// ✅ パスワードリセットトークンに有効期限を設ける
const token = crypto.randomBytes(32).toString("hex");
await db.resetTokens.create({
  token: hash(token),
  userId,
  expiresAt: new Date(Date.now() + 60 * 60 * 1000), // 1時間
});

A10: SSRF(Server-Side Request Forgery)

サーバーが内部リソースにアクセスさせられる攻撃。

// ❌ ユーザー入力の URL にリクエストを送る
app.post("/fetch", async (req, res) => {
  const data = await fetch(req.body.url); // http://169.254.169.254/metadata などが叩ける
  res.json(await data.json());
});

// ✅ 許可された URL のみを受け付ける
const ALLOWED_HOSTS = ["api.example.com", "cdn.example.com"];

app.post("/fetch", async (req, res) => {
  const url = new URL(req.body.url);
  if (!ALLOWED_HOSTS.includes(url.hostname)) {
    return res.status(400).json({ error: "Disallowed host" });
  }
  const data = await fetch(url);
  res.json(await data.json());
});

まとめ

最低限押さえるべき対策:

  1. すべての API で認可チェックを実装する(A01)
  2. パスワードは bcrypt/Argon2 でハッシュする(A02)
  3. プリペアドステートメントを使う(A03)
  4. helmet でセキュリティヘッダーを設定する(A05)
  5. npm audit を CI に組み込む(A06)

参考: OWASP Top 10 2025 / OWASP チートシートシリーズ