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を理解し、セキュアコーディングを実践することで、信頼性の高いシステムを構築できるエンジニアを目指しましょう。