ストーリー
佐藤CTOがシステム構成図を広げました。
対象システム:ECプラットフォーム「ShopNow」
graph TD
Users["ユーザー(Web/Mobile)"]
Users -->|HTTPS| CDN["CDN + WAF<br/>(CloudFront)"]
CDN --> Kong["API Gateway (Kong)"]
Kong --> Auth["認証サービス<br/>(Auth0)"]
Kong --> Product["商品サービス<br/>(Node)"]
Kong --> Cart["カートサービス<br/>(Node)"]
Kong --> OrderSvc["注文サービス<br/>(Java)"]
Kong --> PaySvc["決済サービス<br/>(Node)"]
Auth --> UsersDB[("Users<br/>(RDS)")]
Product --> ProductsDB[("Products<br/>(RDS)")]
Cart --> Redis[("Redis Cache")]
OrderSvc --> OrdersDB[("Orders<br/>(RDS)")]
PaySvc --> Stripe["Stripe API<br/>(外部サービス)"]
classDef client fill:#6c757d,stroke:#495057,color:#fff
classDef infra fill:#0d6efd,stroke:#0a58ca,color:#fff
classDef service fill:#198754,stroke:#146c43,color:#fff
classDef datastore fill:#f5a623,stroke:#c47d10,color:#fff
classDef external fill:#e94560,stroke:#c23050,color:#fff
class Users client
class CDN,Kong infra
class Auth,Product,Cart,OrderSvc,PaySvc service
class UsersDB,ProductsDB,Redis,OrdersDB datastore
class Stripe external
システム概要
| コンポーネント | 技術スタック | データ |
|---|---|---|
| フロントエンド | Next.js, React | セッション情報 |
| API Gateway | Kong | ルーティング、レート制限 |
| 認証サービス | Auth0 + カスタムJWT | ユーザー認証情報、MFA設定 |
| 商品サービス | Node.js + TypeScript | 商品情報、在庫データ |
| カートサービス | Node.js + Redis | カートデータ(一時) |
| 注文サービス | Java + Spring Boot | 注文履歴、配送情報 |
| 決済サービス | Node.js + Stripe SDK | 決済トークン、取引記録 |
| データベース | Amazon RDS (PostgreSQL) | 各サービスの永続データ |
Mission 1:攻撃面(Attack Surface)のマッピング(15分)
上記のシステム構成をもとに、以下の観点で攻撃面を洗い出してください。
要件
- 外部公開エンドポイントを全て列挙する
- 信頼境界を少なくとも4つ特定する
- 各境界を通過するデータの種類と機密レベルを記述する
- 第三者連携ポイント(外部API)のリスクを評価する
出力フォーマット
interface AttackSurface {
externalEndpoints: {
endpoint: string;
method: string;
authRequired: boolean;
dataClassification: 'Public' | 'Internal' | 'Confidential' | 'Restricted';
description: string;
}[];
trustBoundaries: {
id: string;
from: string;
to: string;
protocol: string;
dataTypes: string[];
risks: string[];
}[];
thirdPartyIntegrations: {
service: string;
dataShared: string[];
trustLevel: 'High' | 'Medium' | 'Low';
failureImpact: string;
}[];
}
解答例
const shopNowAttackSurface: AttackSurface = {
externalEndpoints: [
{
endpoint: '/api/auth/login',
method: 'POST',
authRequired: false,
dataClassification: 'Restricted',
description: 'ユーザー認証エンドポイント。ブルートフォース攻撃の対象。',
},
{
endpoint: '/api/auth/register',
method: 'POST',
authRequired: false,
dataClassification: 'Confidential',
description: 'ユーザー登録。スパムアカウント作成のリスク。',
},
{
endpoint: '/api/products',
method: 'GET',
authRequired: false,
dataClassification: 'Public',
description: '商品一覧の取得。DoS攻撃の対象になりやすい。',
},
{
endpoint: '/api/products/:id',
method: 'GET',
authRequired: false,
dataClassification: 'Public',
description: '商品詳細の取得。IDORの可能性。',
},
{
endpoint: '/api/cart',
method: 'POST/PUT/DELETE',
authRequired: true,
dataClassification: 'Internal',
description: 'カート操作。価格改ざんのリスク。',
},
{
endpoint: '/api/orders',
method: 'POST',
authRequired: true,
dataClassification: 'Confidential',
description: '注文作成。不正注文、在庫操作のリスク。',
},
{
endpoint: '/api/orders/:id',
method: 'GET',
authRequired: true,
dataClassification: 'Confidential',
description: '注文詳細の取得。IDOR(他人の注文閲覧)のリスク。',
},
{
endpoint: '/api/payments',
method: 'POST',
authRequired: true,
dataClassification: 'Restricted',
description: '決済処理。最も高いセキュリティが必要。',
},
],
trustBoundaries: [
{
id: 'TB-1',
from: 'ユーザー(ブラウザ/モバイル)',
to: 'CDN + WAF',
protocol: 'HTTPS/TLS 1.3',
dataTypes: ['認証トークン', 'ユーザー入力', 'セッションCookie'],
risks: ['XSS', 'CSRF', 'セッションハイジャック', 'DDoS'],
},
{
id: 'TB-2',
from: 'API Gateway',
to: '各マイクロサービス',
protocol: 'HTTP(内部ネットワーク)',
dataTypes: ['認証済みリクエスト', 'ユーザーデータ'],
risks: ['サービス間通信の盗聴', '不正なサービス呼び出し', '権限昇格'],
},
{
id: 'TB-3',
from: 'マイクロサービス',
to: 'データベース(RDS)',
protocol: 'PostgreSQL over TLS',
dataTypes: ['PII', '注文データ', '在庫データ'],
risks: ['SQLインジェクション', '過剰なデータアクセス', '接続情報の漏洩'],
},
{
id: 'TB-4',
from: '決済サービス',
to: 'Stripe API(外部)',
protocol: 'HTTPS',
dataTypes: ['決済トークン', '取引ID', '金額情報'],
risks: ['APIキーの漏洩', '中間者攻撃', 'Stripe障害時の処理'],
},
],
thirdPartyIntegrations: [
{
service: 'Auth0',
dataShared: ['メールアドレス', 'パスワードハッシュ', 'MFA設定'],
trustLevel: 'High',
failureImpact: '全ユーザーがログイン不可。ビジネス停止。',
},
{
service: 'Stripe',
dataShared: ['決済トークン', '取引金額', '顧客参照ID'],
trustLevel: 'High',
failureImpact: '決済不可。売上直接影響。',
},
{
service: 'CloudFront (CDN)',
dataShared: ['静的コンテンツ', 'APIリクエストのプロキシ'],
trustLevel: 'High',
failureImpact: 'サイトアクセス不可。全面障害。',
},
],
};
ポイント: 攻撃面の洗い出しでは、認証不要のエンドポイントが最もリスクが高い。また、信頼境界を跨ぐデータフローには必ず検証と暗号化が必要。
Mission 2:STRIDE脅威モデリング(15分)
Mission 1で特定した攻撃面に対して、STRIDEの6カテゴリを適用し、脅威を洗い出してください。
要件
- 各STRIDEカテゴリについて、少なくとも2つの具体的な脅威を特定する
- 脅威ごとに攻撃シナリオを記述する
- 脅威が影響するコンポーネントを明記する
- 各脅威に対する初期の緩和策を提案する
出力フォーマット
interface StrideThreat {
id: string;
category: 'Spoofing' | 'Tampering' | 'Repudiation' | 'InformationDisclosure' | 'DoS' | 'ElevationOfPrivilege';
title: string;
attackScenario: string;
affectedComponents: string[];
impact: 'Critical' | 'High' | 'Medium' | 'Low';
mitigation: string;
}
解答例
const strideThreats: StrideThreat[] = [
// Spoofing(なりすまし)
{
id: 'S-001',
category: 'Spoofing',
title: 'JWTトークンの窃取によるなりすまし',
attackScenario: 'XSS脆弱性を悪用してlocalStorageからJWTを窃取し、被害者のアカウントでAPI呼び出しを行う',
affectedComponents: ['フロントエンド', 'API Gateway', '全サービス'],
impact: 'Critical',
mitigation: 'HttpOnly Cookie、SameSite属性、CSP、トークンローテーション、異常検知',
},
{
id: 'S-002',
category: 'Spoofing',
title: 'Credential Stuffing攻撃',
attackScenario: '漏洩したメール/パスワードリストを使い、ログインエンドポイントに大量のリクエストを送信',
affectedComponents: ['認証サービス', 'Auth0'],
impact: 'High',
mitigation: 'アカウントロックアウト、CAPTCHA、MFA必須化、漏洩パスワードDB照合',
},
// Tampering(改ざん)
{
id: 'T-001',
category: 'Tampering',
title: 'カート内商品の価格改ざん',
attackScenario: 'APIリクエストを改ざんし、カートに追加する商品の価格を0円に書き換える',
affectedComponents: ['カートサービス', '注文サービス'],
impact: 'Critical',
mitigation: 'サーバーサイドでの価格再取得、署名付きカートデータ、整合性チェック',
},
{
id: 'T-002',
category: 'Tampering',
title: 'レスポンスの改ざん(MITM)',
attackScenario: 'TLS未設定の内部通信を傍受し、商品情報や注文ステータスを改ざんする',
affectedComponents: ['API Gateway', '各マイクロサービス間'],
impact: 'High',
mitigation: 'サービス間mTLS、サービスメッシュ導入、レスポンス署名',
},
// Repudiation(否認)
{
id: 'R-001',
category: 'Repudiation',
title: '注文取消しの否認',
attackScenario: 'ユーザーが注文後にキャンセルし「注文していない」と主張。ログが不十分で証明できない',
affectedComponents: ['注文サービス', '決済サービス'],
impact: 'Medium',
mitigation: 'イベントソーシング、タイムスタンプ付き監査ログ、操作の電子署名',
},
{
id: 'R-002',
category: 'Repudiation',
title: '管理者操作の否認',
attackScenario: '管理者が商品価格を不正に変更し、操作履歴が残っていないため証跡なし',
affectedComponents: ['商品サービス', '管理画面'],
impact: 'High',
mitigation: '全管理操作のイミュータブルな監査ログ、変更理由の必須入力、承認フロー',
},
// Information Disclosure(情報漏洩)
{
id: 'I-001',
category: 'InformationDisclosure',
title: 'エラーレスポンスによる内部情報の漏洩',
attackScenario: 'APIが詳細なスタックトレースやDB情報をエラーレスポンスに含む',
affectedComponents: ['全マイクロサービス'],
impact: 'Medium',
mitigation: '本番環境でのスタックトレース非表示、統一エラーフォーマット',
},
{
id: 'I-002',
category: 'InformationDisclosure',
title: 'IDOR(Insecure Direct Object Reference)',
attackScenario: '/api/orders/123 のIDを変更して他人の注文情報(住所、連絡先)を閲覧',
affectedComponents: ['注文サービス'],
impact: 'Critical',
mitigation: '全エンドポイントでの所有者チェック、UUID使用、認可ミドルウェア',
},
// Denial of Service(サービス拒否)
{
id: 'D-001',
category: 'DoS',
title: 'API Gatewayへの大量リクエスト',
attackScenario: 'ボットネットからAPI Gatewayに大量リクエストを送信し、正規ユーザーのアクセスを妨害',
affectedComponents: ['API Gateway', 'CDN', '全サービス'],
impact: 'High',
mitigation: 'WAFルール、レート制限、オートスケーリング、CDNキャッシュ活用',
},
{
id: 'D-002',
category: 'DoS',
title: 'Redisキャッシュの枯渇',
attackScenario: '大量のカート操作でRedisメモリを枯渇させ、カートサービスを停止させる',
affectedComponents: ['カートサービス', 'Redis'],
impact: 'Medium',
mitigation: 'TTL設定、メモリ上限アラート、evictionポリシー、ユーザーごとのカート数制限',
},
// Elevation of Privilege(権限昇格)
{
id: 'E-001',
category: 'ElevationOfPrivilege',
title: 'JWT claimsの改ざんによる権限昇格',
attackScenario: 'JWTのペイロードを改ざんし、roleをadminに変更。署名検証が不十分な場合成功する',
affectedComponents: ['API Gateway', '認証サービス'],
impact: 'Critical',
mitigation: 'RS256署名検証の厳格化、role情報はDB参照、JWTの短い有効期限',
},
{
id: 'E-002',
category: 'ElevationOfPrivilege',
title: 'サービス間通信を悪用した権限昇格',
attackScenario: '内部APIを直接呼び出し、API Gatewayの認証チェックをバイパスする',
affectedComponents: ['各マイクロサービス'],
impact: 'High',
mitigation: 'ネットワークポリシーで内部API直接アクセスを禁止、サービス間認証',
},
];
Mission 3:リスクマトリクスの作成(15分)
Mission 2で特定した脅威に対して、DREADスコアリングを適用し、リスクマトリクスを作成してください。
要件
- Mission 2の脅威から上位6つを選定する
- 各脅威にDREADスコアを付ける(各項目1-10)
- 総合スコアを計算し、リスクレベルを分類する
- 結果をリスクマトリクスとして図示する
出力フォーマット
interface DreadAssessment {
threatId: string;
title: string;
damage: number;
reproducibility: number;
exploitability: number;
affectedUsers: number;
discoverability: number;
totalScore: number;
riskLevel: 'Critical' | 'High' | 'Medium' | 'Low';
priority: number; // 1が最優先
}
解答例
const dreadAssessments: DreadAssessment[] = [
{
threatId: 'I-002',
title: 'IDOR -- 他人の注文情報閲覧',
damage: 9,
reproducibility: 9,
exploitability: 8,
affectedUsers: 8,
discoverability: 8,
totalScore: 8.4,
riskLevel: 'Critical',
priority: 1,
},
{
threatId: 'E-001',
title: 'JWT claims改ざんによる権限昇格',
damage: 10,
reproducibility: 6,
exploitability: 5,
affectedUsers: 10,
discoverability: 6,
totalScore: 7.4,
riskLevel: 'High',
priority: 2,
},
{
threatId: 'T-001',
title: 'カート内商品の価格改ざん',
damage: 8,
reproducibility: 8,
exploitability: 7,
affectedUsers: 5,
discoverability: 7,
totalScore: 7.0,
riskLevel: 'High',
priority: 3,
},
{
threatId: 'S-001',
title: 'JWTトークン窃取によるなりすまし',
damage: 9,
reproducibility: 5,
exploitability: 6,
affectedUsers: 7,
discoverability: 6,
totalScore: 6.6,
riskLevel: 'High',
priority: 4,
},
{
threatId: 'D-001',
title: 'API GatewayへのDDoS攻撃',
damage: 7,
reproducibility: 9,
exploitability: 7,
affectedUsers: 10,
discoverability: 3,
totalScore: 7.2,
riskLevel: 'High',
priority: 5,
},
{
threatId: 'E-002',
title: 'サービス間通信のバイパス',
damage: 8,
reproducibility: 4,
exploitability: 4,
affectedUsers: 10,
discoverability: 4,
totalScore: 6.0,
riskLevel: 'High',
priority: 6,
},
];
リスクマトリクス(可視化)
影響度 ↑
極大 │ │ D-001 │ I-002 │
│ │ E-002 │ E-001 │
大 │ │ │ T-001 │
│ │ │ S-001 │
中 │ │ │ │
│ │ │ │
小 │ │ │ │
└─────────┴─────────┴─────────→ 発生可能性
低 中 高
Mission 4:対策設計書の作成(15分)
Mission 3で優先度が高いと判断した脅威に対して、具体的な対策を設計してください。
要件
- 上位3つの脅威に対する対策を詳細に設計する
- 各対策の実装方法をコードまたは設定で示す
- 対策の効果とコストを見積もる
- 残留リスクを評価する
解答例
対策1:IDOR対策(I-002)
// 認可ミドルウェア: リソースの所有者チェック
import { Request, Response, NextFunction } from 'express';
interface AuthorizationRule {
resource: string;
ownerField: string;
adminOverride: boolean;
}
function authorizeResourceOwner(rule: AuthorizationRule) {
return async (req: Request, res: Response, next: NextFunction) => {
const resourceId = req.params.id;
const currentUserId = req.user!.id;
const isAdmin = req.user!.roles.includes('admin');
if (rule.adminOverride && isAdmin) {
return next();
}
const resource = await getResource(rule.resource, resourceId);
if (!resource) {
return res.status(404).json({ error: 'Not Found' });
}
if (resource[rule.ownerField] !== currentUserId) {
// セキュリティログに記録(不正アクセスの試み)
securityLogger.warn('IDOR_ATTEMPT', {
userId: currentUserId,
resourceType: rule.resource,
resourceId,
timestamp: new Date().toISOString(),
});
return res.status(404).json({ error: 'Not Found' }); // 403ではなく404を返す
}
next();
};
}
// 適用例
app.get('/api/orders/:id',
authenticate,
authorizeResourceOwner({ resource: 'orders', ownerField: 'userId', adminOverride: true }),
orderController.getById
);
対策2:JWT改ざん防止(E-001)
// JWT検証の厳格化
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
const client = jwksClient({
jwksUri: 'https://auth.shopnow.com/.well-known/jwks.json',
cache: true,
rateLimit: true,
});
async function verifyToken(token: string): Promise<JwtPayload> {
const decoded = jwt.decode(token, { complete: true });
if (!decoded || decoded.header.alg !== 'RS256') {
throw new Error('Invalid token algorithm');
}
const key = await client.getSigningKey(decoded.header.kid);
const publicKey = key.getPublicKey();
const payload = jwt.verify(token, publicKey, {
algorithms: ['RS256'], // HS256を許可しない
issuer: 'https://auth.shopnow.com/',
audience: 'shopnow-api',
maxAge: '15m', // 最大有効期間15分
}) as JwtPayload;
// roleはDBから取得(JWTのclaimsを信頼しない)
const userRoles = await roleRepository.findByUserId(payload.sub);
payload.roles = userRoles;
return payload;
}
対策3:価格改ざん防止(T-001)
// サーバーサイドでの価格再計算
async function createOrder(cartId: string, userId: string): Promise<Order> {
const cart = await cartService.getCart(cartId, userId);
// クライアントから送られた価格を信頼しない
const verifiedItems = await Promise.all(
cart.items.map(async (item) => {
const product = await productService.getProduct(item.productId);
if (!product || !product.isAvailable) {
throw new BusinessError(`商品 ${item.productId} は購入できません`);
}
return {
productId: product.id,
name: product.name,
price: product.currentPrice, // DB上の最新価格を使用
quantity: item.quantity,
subtotal: product.currentPrice * item.quantity,
};
})
);
const totalAmount = verifiedItems.reduce((sum, item) => sum + item.subtotal, 0);
// 整合性チェック用のハッシュを付与
const integrityHash = createHmac('sha256', ORDER_SIGNING_KEY)
.update(JSON.stringify({ items: verifiedItems, totalAmount, userId }))
.digest('hex');
return orderRepository.create({
userId,
items: verifiedItems,
totalAmount,
integrityHash,
status: 'pending',
});
}
対策の効果とコスト
| 対策 | 対象脅威 | 工数(人日) | リスク低減率 | 残留リスク |
|---|---|---|---|---|
| IDOR対策(認可ミドルウェア) | I-002 | 3 | 95% | Low |
| JWT厳格化(RS256 + DB参照) | E-001 | 2 | 90% | Low |
| サーバーサイド価格再計算 | T-001 | 2 | 98% | Low |
| mTLS導入 | E-002 | 5 | 85% | Medium |
| WAF + レート制限強化 | D-001 | 2 | 75% | Medium |
| HttpOnly Cookie移行 | S-001 | 3 | 80% | Medium |
評価基準
| 評価項目 | 配点 | 合格ライン |
|---|---|---|
| 攻撃面の網羅性 | 25% | 主要なエンドポイントと信頼境界を70%以上特定 |
| STRIDE分析の精度 | 25% | 各カテゴリで2つ以上の現実的な脅威を特定 |
| リスク評価の妥当性 | 25% | DREADスコアの根拠が論理的 |
| 対策設計の実現性 | 25% | 具体的なコード/設定で実装方法を示せている |
推定読了時間: 60分