なぜWebセキュリティを学ぶ必要があるのか
2026年も依然としてサイバー攻撃は増加し続けており、Webエンジニアにとってセキュリティの知識は欠かせません。IPAの報告によると、不正アクセスによる被害の多くは、既知の脆弱性への対策不足が原因です。この記事では、Webアプリケーションの主要な脆弱性と対策を実践的に解説します。
OWASP Top 10(2021年版)を理解する
OWASP(Open Web Application Security Project)が定期的に発表する「OWASP Top 10」は、Webアプリケーションにおける最重要セキュリティリスクのリストです。
A01: アクセス制御の不備
最も深刻なリスク。認証済みユーザーが許可されていないリソースにアクセスできてしまう問題です。
// 悪い例:IDをそのままURLパラメータに使用
app.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
res.json(user); // 他のユーザーのデータも取れてしまう
});
// 良い例:ログインユーザーのIDと一致するかチェック
app.get('/api/users/:id', authenticate, async (req, res) => {
if (req.params.id !== req.user.id) {
return res.status(403).json({ error: 'Forbidden' });
}
const user = await User.findById(req.params.id);
res.json(user);
});
A02: 暗号化の失敗
機密データの平文保存や不適切な暗号化が原因で情報漏洩が起きます。
// 悪い例:パスワードを平文で保存
const user = { password: req.body.password }; // 絶対NG
// 良い例:bcryptでパスワードをハッシュ化
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12;
const hashedPassword = await bcrypt.hash(req.body.password, SALT_ROUNDS);
// 検証時
const isValid = await bcrypt.compare(inputPassword, hashedPassword);
// HTTPSの強制(Express例)
app.use((req, res, next) => {
if (!req.secure) {
return res.redirect(301, 'https://' + req.hostname + req.url);
}
next();
});
A03: インジェクション(SQLインジェクション・XSS)
最も有名な脆弱性。ユーザー入力をサニタイズせずにSQLや HTMLに埋め込むことで攻撃されます。
// 悪い例:SQLインジェクション
const query = `SELECT * FROM users WHERE email = '${email}'`;
// email に ' OR '1'='1 が入ると全件取得される
// 良い例:プリペアドステートメント使用
const result = await db.query(
'SELECT * FROM users WHERE email = $1',
[email]
);
// XSS対策:出力時のエスケープ
import DOMPurify from 'dompurify';
const safeHTML = DOMPurify.sanitize(userInput);
// Content Security Policy(CSP)ヘッダーの設定
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'nonce-" + nonce + "'"
);
next();
});
A07: 認証・セッション管理の不備
// JWT(JSON Web Token)の安全な実装
import jwt from 'jsonwebtoken';
// トークンの生成
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '1h', algorithm: 'HS256' }
);
// トークンの検証
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
} catch (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expired' });
}
return res.status(401).json({ error: 'Invalid token' });
}
// レート制限の実装
import rateLimit from 'express-rate-limit';
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分
max: 5, // 最大5回
message: 'ログイン試行回数が多すぎます。しばらくしてから再試行してください。'
});
app.post('/login', loginLimiter, loginHandler);
セキュリティヘッダーの設定(Helmet.js)
import helmet from 'helmet';
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'nonce-${nonce}'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000, // 1年
includeSubDomains: true,
preload: true,
},
noSniff: true,
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
}));
依存関係の脆弱性チェック
# npm auditで依存関係の脆弱性をチェック
npm audit
npm audit fix
npm audit fix --force # 注意:破壊的変更の可能性あり
# Snykによる継続的セキュリティ監視
npm install -g snyk
snyk auth
snyk test
snyk monitor
# GitHub Dependabotの設定(.github/dependabot.yml)
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 10
セキュリティチェックリスト
| カテゴリ | チェック項目 | 優先度 |
|---|---|---|
| 認証 | パスワードのハッシュ化(bcrypt) | ★★★ |
| 認証 | 多要素認証(MFA)の実装 | ★★★ |
| 通信 | HTTPS通信の強制 | ★★★ |
| 入力 | 全ての入力値のバリデーション | ★★★ |
| ヘッダー | セキュリティヘッダーの設定 | ★★☆ |
| 依存関係 | 定期的なnpm audit実行 | ★★☆ |
| ログ | アクセスログ・エラーログの保存 | ★★☆ |
まとめ
Webセキュリティは一度対策すれば終わりではなく、継続的な改善が必要です。OWASP Top 10を参考に、まずは自分のアプリケーションの脆弱性を把握し、優先度の高いものから対策を始めましょう。
- ✅ SQLインジェクション・XSS対策は必須
- ✅ パスワードは必ずbcryptでハッシュ化
- ✅ セキュリティヘッダーを適切に設定
- ✅ 依存関係の脆弱性を定期的にチェック