- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに:なぜすべての開発者(かいはつしゃ)がセキュリティを知(し)るべきか
- 1. OWASP Top 10 概要(がいよう)
- 2. A01: Broken Access Control(アクセス制御(せいぎょ)の失敗(しっぱい))
- 3. A02: Cryptographic Failures(暗号化(あんごうか)の失敗(しっぱい))
- 4. A03: Injection(インジェクション)
- 5. A04: Insecure Design(安全(あんぜん)でない設計(せっけい))
- 6. A05: Security Misconfiguration(セキュリティ設定(せってい)ミス)
- 7. A06: Vulnerable and Outdated Components(脆弱(ぜいじゃく)な構成(こうせい)コンポーネント)
- 8. A07: Identification and Authentication Failures(認証(にんしょう)の失敗(しっぱい))
- 9. A08: Software and Data Integrity Failures(ソフトウェア整合性(せいごうせい)の失敗(しっぱい))
- 10. A09: Security Logging and Monitoring Failures(ロギングの失敗(しっぱい))
- 11. A10: Server-Side Request Forgery(SSRF)
- 12. OWASP以外(いがい)の追加(ついか)脆弱性(ぜいじゃくせい)
- 13. セキュリティヘッダー完全(かんぜん)ガイド
- 14. セキュリティテストツール
- 15. プロダクションセキュリティチェックリスト(20項目(こうもく))
- 16. 面接(めんせつ)予想(よそう)質問(しつもん)15選(せん)
- 17. クイズ
- 参考資料(さんこうしりょう)
はじめに:なぜすべての開発者(かいはつしゃ)がセキュリティを知(し)るべきか
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年(ねん)比(ひ)変化(へんか) |
|---|---|---|
| A01 | Broken Access Control | 5位(い)から1位(い)に上昇(じょうしょう) |
| A02 | Cryptographic Failures | 3位(い)から2位(い)(名称(めいしょう)変更(へんこう)) |
| A03 | Injection | 1位(い)から3位(い)に下降(かこう) |
| A04 | Insecure Design | 新規(しんき)項目(こうもく) |
| A05 | Security Misconfiguration | 6位(い)から5位(い)に上昇(じょうしょう) |
| A06 | Vulnerable and Outdated Components | 9位(い)から6位(い)に上昇(じょうしょう) |
| A07 | Identification and Authentication Failures | 2位(い)から7位(い)に下降(かこう) |
| A08 | Software and Data Integrity Failures | 新規(しんき)(8位(い)から分離(ぶんり)) |
| A09 | Security Logging and Monitoring Failures | 10位(い)から9位(い)に上昇(じょうしょう) |
| A10 | Server-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):
# 攻撃者がURLのIDを変更して他のユーザー情報にアクセス
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-Policy | XSS/インジェクション防御(ぼうぎょ) | default-src 'self' |
| Strict-Transport-Security | HTTPS強制(きょうせい) | max-age=31536000; includeSubDomains |
| X-Frame-Options | Clickjacking防御(ぼうぎょ) | DENY |
| X-Content-Type-Options | MIMEスニッフィング防御(ぼうぎょ) | nosniff |
| Referrer-Policy | Referrer情報(じょうほう)制限(せいげん) | 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 | 軽量(けいりょう)静的(せいてき)分析(ぶんせき)、カスタムルール対応(たいおう) |
| CodeQL | GitHub SAST | GitHub統合(とうごう)、セマンティック分析(ぶんせき) |
14.3 SCA(Software Composition Analysis)
| ツール | 種類(しゅるい) | 特徴(とくちょう) |
|---|---|---|
| Snyk | SCA + 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): 必要最低限(ひつようさいていげん)の権限(けんげん)のみ付与(ふよ)し、定期的(ていきてき)に見直(みなお)します。
参考資料(さんこうしりょう)
- OWASP Top 10 2021公式(こうしき)ドキュメント
- OWASP Cheat Sheet Series
- OWASP Testing Guide
- OWASP ASVS
- IBM Cost of a Data Breach Report 2024
- NIST Cybersecurity Framework
- CWE (Common Weakness Enumeration)
- CVE (Common Vulnerabilities and Exposures)
- MDN Web Security
- Google Web Security Guidelines
- Node.js Security Checklist
- Helmet.js Documentation
- OWASP ZAP
- Snyk Learn - Security Education
- PortSwigger Web Security Academy