LESSON 40分

ストーリー

佐藤CTO
現代のアプリケーションはAPIが核心だ。マイクロサービスの通信もSPAのバックエンドも全てAPI

佐藤CTOがAPI一覧を見せました。

佐藤CTO
OWASPはWebアプリケーションとは別に、API Security Top 10を公開している。APIにはAPIならではの脆弱性がある
あなた
レート制限、入力検証、認可の問題…
佐藤CTO
そうだ。APIセキュリティは今最もホットな分野だ。攻撃面が広いだけに、体系的な対策が必要になる

OWASP API Security Top 10 (2023)

順位リスク説明
API1壊れたオブジェクトレベル認可(BOLA)オブジェクトIDを操作して他ユーザーのデータにアクセス
API2壊れた認証認証メカニズムの欠陥
API3壊れたオブジェクトプロパティレベル認可過剰なデータ公開、マスアサインメント
API4無制限のリソース消費レート制限の欠如
API5壊れた関数レベル認可管理者APIへの不正アクセス
API6サーバーサイドリクエストフォージェリ内部リソースへのSSRF
API7セキュリティの設定ミスCORSの設定不備等
API8自動化された脅威の管理不足ボット攻撃への対策不足
API9不適切なインベントリ管理古いAPIバージョンの放置
API10安全でないAPIの消費外部APIの応答を信頼しすぎる

API1: BOLA(Broken Object Level Authorization)

// 脆弱なコード: オブジェクトレベルの認可チェックなし
app.get('/api/orders/:orderId', authenticate, async (req, res) => {
  // 危険: 認証済みなら誰でもアクセス可能
  const order = await orderService.findById(req.params.orderId);
  res.json(order);
});

// 安全なコード: オブジェクト所有者の検証
app.get('/api/orders/:orderId', authenticate, async (req, res) => {
  const order = await orderService.findById(req.params.orderId);

  if (!order) {
    return res.status(404).json({ error: 'Not found' });
  }

  // オブジェクトの所有者を検証
  if (order.userId !== req.user.id && !req.user.roles.includes('admin')) {
    // 403ではなく404を返す(情報漏洩防止)
    return res.status(404).json({ error: 'Not found' });
  }

  res.json(order);
});

// 汎用的なBOLA防御ミドルウェア
function checkObjectOwnership(resourceType: string, ownerField: string) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const resourceId = req.params.id || req.params[`${resourceType}Id`];
    const resource = await getResource(resourceType, resourceId);

    if (!resource || resource[ownerField] !== req.user.id) {
      return res.status(404).json({ error: 'Not found' });
    }

    req.resource = resource;
    next();
  };
}

API3: 過剰なデータ公開

// 脆弱なコード: 全フィールドをレスポンスに含める
app.get('/api/users/:id', authenticate, async (req, res) => {
  const user = await prisma.user.findUnique({ where: { id: req.params.id } });
  res.json(user);  // passwordHash, internalNotes 等も含まれる
});

// 安全なコード: レスポンスのシリアライゼーション
interface UserResponse {
  id: string;
  name: string;
  email: string;
  role: string;
  createdAt: string;
}

function toUserResponse(user: User): UserResponse {
  return {
    id: user.id,
    name: user.name,
    email: user.email,
    role: user.role,
    createdAt: user.createdAt.toISOString(),
    // passwordHash, internalNotes は含めない
  };
}

app.get('/api/users/:id', authenticate, async (req, res) => {
  const user = await prisma.user.findUnique({
    where: { id: req.params.id },
    select: { id: true, name: true, email: true, role: true, createdAt: true },
  });

  if (!user) return res.status(404).json({ error: 'Not found' });
  res.json(toUserResponse(user));
});

API4: レート制限

// 多層レート制限の実装
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);

// グローバルレート制限(全エンドポイント)
const globalLimiter = rateLimit({
  store: new RedisStore({ sendCommand: (...args: string[]) => redis.call(...args) }),
  windowMs: 15 * 60 * 1000,  // 15分
  max: 1000,
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: (req) => req.user?.id || req.ip,
  handler: (req, res) => {
    res.status(429).json({
      error: 'Too many requests',
      retryAfter: res.getHeader('Retry-After'),
    });
  },
});

// 認証エンドポイント用(厳格)
const authLimiter = rateLimit({
  store: new RedisStore({ sendCommand: (...args: string[]) => redis.call(...args) }),
  windowMs: 15 * 60 * 1000,
  max: 10,  // 15分間に10回まで
  keyGenerator: (req) => `auth:${req.ip}:${req.body?.email || 'unknown'}`,
  skipSuccessfulRequests: true,  // 成功したリクエストはカウントしない
});

// API検索エンドポイント用(コスト考慮)
const searchLimiter = rateLimit({
  store: new RedisStore({ sendCommand: (...args: string[]) => redis.call(...args) }),
  windowMs: 60 * 1000,  // 1分
  max: 30,
  keyGenerator: (req) => `search:${req.user?.id || req.ip}`,
});

app.use(globalLimiter);
app.use('/api/auth', authLimiter);
app.use('/api/search', searchLimiter);

API7: セキュリティの設定ミス

CORS設定

// 脆弱なCORS設定
app.use(cors({ origin: '*' }));  // 危険: 全オリジン許可

// 安全なCORS設定
const allowedOrigins = [
  'https://app.example.com',
  'https://admin.example.com',
];

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('CORS policy violation'));
    }
  },
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
  credentials: true,
  maxAge: 3600,  // プリフライトキャッシュ
}));

セキュリティヘッダー

// 包括的なセキュリティヘッダー
app.use((req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-XSS-Protection', '0');  // CSPを使用するため無効化
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  res.setHeader('Cache-Control', 'no-store');  // APIレスポンスはキャッシュしない
  res.setHeader('Pragma', 'no-cache');
  res.removeHeader('X-Powered-By');  // 技術スタック情報の隠蔽
  next();
});

API9: 不適切なインベントリ管理

// APIバージョン管理とレガシーAPI検出
interface ApiInventory {
  version: string;
  status: 'active' | 'deprecated' | 'retired';
  deprecationDate?: string;
  retirementDate?: string;
  endpoints: string[];
  documentation: string;
}

const apiInventory: ApiInventory[] = [
  {
    version: 'v3',
    status: 'active',
    endpoints: ['/api/v3/*'],
    documentation: 'https://docs.example.com/api/v3',
  },
  {
    version: 'v2',
    status: 'deprecated',
    deprecationDate: '2024-01-01',
    retirementDate: '2024-07-01',
    endpoints: ['/api/v2/*'],
    documentation: 'https://docs.example.com/api/v2',
  },
  {
    version: 'v1',
    status: 'retired',
    retirementDate: '2023-06-01',
    endpoints: [],  // アクセス不可
    documentation: 'Archived',
  },
];

// 非推奨APIへのアクセス時に警告ヘッダーを返す
app.use('/api/v2', (req, res, next) => {
  res.setHeader('Deprecation', 'true');
  res.setHeader('Sunset', 'Sat, 01 Jul 2024 00:00:00 GMT');
  res.setHeader('Link', '</api/v3>; rel="successor-version"');
  next();
});

まとめ

ポイント内容
BOLA(API1)オブジェクトレベルの認可チェックを全エンドポイントに実装
データ公開(API3)レスポンスシリアライゼーションで必要最小限のフィールドのみ返す
レート制限(API4)エンドポイントの重要度に応じた多層レート制限
設定ミス(API7)CORS、セキュリティヘッダー、エラーメッセージの適切な設定
インベントリ管理(API9)全APIバージョンの管理とレガシーAPIの計画的廃止

チェックリスト

  • OWASP API Security Top 10の主要リスクを理解した
  • BOLA防御の汎用ミドルウェアを実装できる
  • レスポンスシリアライゼーションで過剰なデータ公開を防止できる
  • 多層レート制限を設計・実装できる
  • APIのバージョン管理とインベントリ管理を設計できる

次のステップへ

次は「脆弱性の修正と報告」を学びます。CVSSスコアリング、修正の優先順位付け、パッチ管理の方法を身につけましょう。


推定読了時間: 40分