Welcome to Tech Athletes | テック・アスリート   Click to listen highlighted text! Welcome to Tech Athletes | テック・アスリート

【2026年版】OWASP Top 10完全解説|Webセキュリティ脆弱性と対策コードサンプル集

Webアプリケーションのセキュリティは、エンジニアとして必ず習得すべき必須スキルです。本記事では、OWASP Top 10(2021年版)を中心に、各脆弱性の仕組みと実践的な対策方法をコードサンプル付きで解説します。

OWASP Top 10とは?

OWASP(Open Web Application Security Project)は、Webアプリケーションのセキュリティを改善するための非営利団体です。OWASP Top 10は、最も深刻なWebアプリケーションセキュリティリスクのリストで、4〜5年ごとに更新されます。

A01 – アクセス制御の不備(Broken Access Control)

最も多く報告される脆弱性です。認証済みユーザーが本来アクセスできないリソースにアクセスできてしまう問題です。

❌ 脆弱なコード例

// ユーザーIDをパラメータで受け取るだけで認可チェックなし
app.get("/api/users/:id", async (req, res) => {
  const user = await db.user.findById(req.params.id);
  return res.json(user);  // 誰でも他人の情報を見られる!
});

✅ 安全なコード例

app.get("/api/users/:id", requireAuth, async (req, res) => {
  // 自分自身かAdminのみアクセス可能
  if (req.user.id !== req.params.id && req.user.role !== "admin") {
    return res.status(403).json({ error: "Forbidden" });
  }
  const user = await db.user.findById(req.params.id);
  return res.json(user);
});

A02 – 暗号化の失敗(Cryptographic Failures)

❌ 脆弱なパスワード保存

// ❌ 絶対にやってはいけない
const hashedPassword = md5(password);  // MD5は破られている
const hashedPassword2 = sha1(password); // SHA1も同様
const plainPassword = password; // 平文保存論外

✅ 安全なパスワード保存(bcrypt使用)

import bcrypt from "bcrypt";

const SALT_ROUNDS = 12;

// パスワードのハッシュ化
async function hashPassword(plainPassword: string): Promise {
  return bcrypt.hash(plainPassword, SALT_ROUNDS);
}

// パスワードの検証
async function verifyPassword(plainPassword: string, hashedPassword: string): Promise {
  return bcrypt.compare(plainPassword, hashedPassword);
}

// 使用例
const hash = await hashPassword("user_password_123");
const isValid = await verifyPassword("user_password_123", hash); // true

A03 – インジェクション(Injection)

SQLインジェクション対策

// ❌ 脆弱なコード(SQLインジェクション可能)
const query = `SELECT * FROM users WHERE email = "${email}"`;
// email = "admin@example.com" OR "1"="1" で全ユーザー取得可能!

// ✅ 安全なコード(プリペアドステートメント)
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();

const user = await prisma.user.findFirst({
  where: { email: email }  // Prismaは自動的にエスケープ
});

// または生SQLを使う場合はパラメータ化クエリ
const result = await prisma.$queryRaw`
  SELECT * FROM users WHERE email = ${email}
`;

A05 – セキュリティの設定ミス(Security Misconfiguration)

// HTTPセキュリティヘッダーの設定(Expressの例)
import helmet from "helmet";

app.use(helmet({  // Helmetで主要なセキュリティヘッダーを自動設定
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["self"],
      scriptSrc: ["self", "trusted-cdn.com"],
      styleSrc: ["self", "unsafe-inline"],
      imgSrc: ["self", "data:", "cdn.example.com"],
    }
  },
  hsts: {
    maxAge: 31536000,  // 1年間HTTPSを強制
    includeSubDomains: true,
    preload: true
  },
  noSniff: true,          // MIME sniffing防止
  frameguard: { action: "deny" }  // クリックジャッキング防止
}));

A07 – 認証の失敗(Identification and Authentication Failures)

// JWT認証の安全な実装
import jwt from "jsonwebtoken";
import { randomBytes } from "crypto";

const JWT_SECRET = process.env.JWT_SECRET!; // 最低256ビットのランダム値
const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET!;

function generateTokens(userId: string) {
  const accessToken = jwt.sign(
    { sub: userId, type: "access" },
    JWT_SECRET,
    { expiresIn: "15m" }  // アクセストークンは短命に
  );
  
  const refreshToken = jwt.sign(
    { sub: userId, type: "refresh", jti: randomBytes(16).toString("hex") },
    JWT_REFRESH_SECRET,
    { expiresIn: "7d" }   // リフレッシュトークンは長め
  );
  
  return { accessToken, refreshToken };
}

// レートリミットの実装
import rateLimit from "express-rate-limit";

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分
  max: 5,                   // 5回まで
  message: "ログイン試行回数が多すぎます。15分後に再試行してください。",
  standardHeaders: true
});

app.post("/api/login", loginLimiter, loginHandler);

A09 – セキュリティログとモニタリングの失敗

import winston from "winston";

const securityLogger = winston.createLogger({
  level: "info",
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: "security.log" }),
    // 本番環境ではSIEMやCloudWatchへ送信
  ]
});

// セキュリティイベントのログ記録
function logSecurityEvent(event: string, details: object) {
  securityLogger.warn({
    timestamp: new Date().toISOString(),
    event,
    ...details
  });
}

// 使用例
logSecurityEvent("FAILED_LOGIN", {
  ip: req.ip,
  email: req.body.email,
  userAgent: req.headers["user-agent"]
});

セキュリティツールの活用

  • SAST(静的解析):ESLint-security、SonarQube、Snyk CodeをCIに組み込む
  • DAST(動的解析):OWASP ZAP、Burp Suiteで実際のリクエストをテスト
  • 依存関係スキャン:npm audit、Dependabot、Snykで脆弱なライブラリを検出
  • コンテナスキャン:Trivy、Clair、Snyk ContainerでDockerイメージの脆弱性確認

セキュリティ資格:キャリアアップに役立つ認定

  • CISSP:最も権威あるセキュリティ資格(難易度高)
  • CEH(Certified Ethical Hacker):ペネトレーションテスト専門家
  • CompTIA Security+:入門レベルの国際資格
  • 情報処理安全確保支援士:国内最高峰のセキュリティ国家資格

Webセキュリティの知識は、エンジニアとしての価値を大きく高めます。OWASP Top 10を理解し、セキュアコーディングを実践することで、信頼性の高いシステムを構築できるエンジニアを目指しましょう。

投稿者 kasata

IT企業でエンジニアとして勤務後、テクノロジー情報メディア「Tech Athletes(テック・アスリート)」を運営。プログラミング、クラウドインフラ(AWS/GCP/Azure)、AI活用、Webサービス開発を専門とする。エンジニア・ビジネスパーソン向けに、実際に使ってみた経験をもとに信頼できる技術情報を発信中。資格:AWS認定ソリューションアーキテクト、Python 3 エンジニア認定試験合格。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

Click to listen highlighted text!