インプット検証の重要性
「信頼しないこと」はセキュリティの基本原則です。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でサニタイズすることを習慣にしてください。