サイトのAPI図鑑B版
掲載情報が正確でない可能性があります。
セキュリティ・コンプライアンス

APIインプット検証とサニタイゼーションの完全ガイド【SQLインジェクション・XSS対策】

APIのリクエストデータのバリデーション・サニタイゼーションの実装方法(Zod・Joi・class-validator)とSQLインジェクション・XSS・コマンドインジェクション・パストラバーサルの対策を解説します。

#インプット検証#SQLインジェクション#XSS対策#セキュリティ

インプット検証の重要性

「信頼しないこと」はセキュリティの基本原則です。APIが受け取る全てのリクエストデータ(クエリパラメータ・リクエストボディ・ヘッダー・パスパラメータ)は悪意を持って操作される可能性があります。適切なバリデーション・サニタイゼーションが攻撃を防ぎます。

Zodを使ったスキーマバリデーション(TypeScript)

import { z } from 'zod';
import { fromZodError } from 'zod-validation-error';

// スキーマの定義
const CreateUserSchema = z.object({
  name: z.string()
    .min(1, '名前は必須です')
    .max(100, '名前は100文字以内で入力してください')
    .trim()
    .regex(/^[a-zA-Zぁ-ん一-龯ァ-ンs]+$/, '名前に使用できない文字が含まれています'),
  
  email: z.string()
    .email('有効なメールアドレスを入力してください')
    .toLowerCase()
    .max(254),
  
  age: z.number()
    .int('年齢は整数で入力してください')
    .min(18, '18歳以上が対象です')
    .max(120)
    .optional(),
  
  role: z.enum(['user', 'editor'], {
    errorMap: () => ({ message: 'roleはuser、editorのみ有効です' })
  }).default('user')
});

// バリデーションミドルウェア
const validate = (schema) => (req, res, next) => {
  const result = schema.safeParse(req.body);
  if (!result.success) {
    const error = fromZodError(result.error);
    return res.status(400).json({
      error: 'Validation Error',
      details: error.message
    });
  }
  req.body = result.data; // バリデーション済みデータで上書き
  next();
};

app.post('/api/users', validate(CreateUserSchema), async (req, res) => {
  const user = await db.users.create({ data: req.body });
  res.status(201).json(user);
});

SQLインジェクション対策(ORMとパラメータ化クエリ)

// 悪い例:文字列連結(SQLインジェクション脆弱)
const getUser = async (email) => {
  const result = await db.query(
    "SELECT * FROM users WHERE email = '" + email + "'"
  ); // email = "'; DROP TABLE users; --" で攻撃可能
};

// 良い例1:パラメータ化クエリ(Prisma)
const getUser = async (email) => {
  return db.users.findUnique({ where: { email } });
  // Prismaは内部でパラメータ化クエリを使用
};

// 良い例2:PostgreSQLのパラメータ化RAWクエリ
const getUser = async (email) => {
  const result = await db.query(
    'SELECT * FROM users WHERE email = $1',
    [email] // プレースホルダーでパラメータを分離
  );
  return result.rows[0];
};

XSS対策(HTMLエスケープ)

import DOMPurify from 'isomorphic-dompurify';

// APIで受け取ったHTMLコンテンツをサニタイズ
const sanitizeHtml = (dirtyHtml) => {
  return DOMPurify.sanitize(dirtyHtml, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
    ALLOWED_ATTR: ['href', 'target']
  });
};

// プレーンテキストはHTMLエスケープ
const escapeHtml = (text) => {
  return text
    .replace(/&/g, '&')
    .replace(//g, '>')
    .replace(/"/g, '"')
    .replace(/'/g, ''');
};

ファイルアップロードのバリデーション

import fileType from 'file-type';

const validateFile = async (buffer) => {
  // MIMEタイプの実際の内容で検証(拡張子だけの偽装を防ぐ)
  const detected = await fileType.fromBuffer(buffer);
  
  const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp'];
  if (!detected || !ALLOWED_TYPES.includes(detected.mime)) {
    throw new Error('許可されないファイル形式です');
  }
  
  const MAX_SIZE = 5 * 1024 * 1024; // 5MB
  if (buffer.length > MAX_SIZE) {
    throw new Error('ファイルサイズは5MB以下にしてください');
  }
};

まとめ

APIのインプット検証はSQLインジェクション・XSS・コマンドインジェクションなどの攻撃を防ぐための第一防衛線です。ZodやJoiなどのスキーマバリデーションライブラリを使ってリクエストの全パラメータを検証し、ORMのパラメータ化クエリでSQLインジェクションを防ぎ、HTMLコンテンツはDOMPurifyでサニタイズすることを習慣にしてください。

よくある質問

Q.バリデーションとサニタイゼーションの違いは何ですか?

バリデーションはデータが期待する形式・範囲・条件を満たしているかを検証することです。サニタイゼーションは危険な文字・コードを無害化(エスケープ・除去)することです。両方を組み合わせて適切な順序(まずバリデーション、次にサニタイゼーション)で実装します。

Q.ORMを使えばSQLインジェクションは防げますか?

一般的にORM(Prisma・TypeORM・Sequelize等)はパラメータ化クエリを使うため、SQLインジェクションを防ぎます。ただしRAWクエリを書く場合はパラメータ化(プレースホルダー)を必ず使い、文字列連結でクエリを組み立てないよう注意が必要です。

Q.Zodでのバリデーションはzod-validationErrorと組み合わせると便利ですか?

はい。ZodのデフォルトエラーメッセージはAPI利用者にとって分かりにくい場合があります。zod-validation-error パッケージを使うとZodのエラーを人間が読みやすいメッセージに変換できます。エラーレスポンスの品質が向上します。

関連記事