はじめに
WebアプリケーションでユーザーからのURL入力を受け付ける際、セキュリティとバリデーションは避けて通れません。
特に、javascript:やdata:などの危険なプロトコルを含むURLを受け入れてしまうと、XSS(クロスサイトスクリプティング)攻撃のリスクがあるため、対策をする必要があります。
今回は、ZodとReact Hook Formを組み合わせて、セキュアかつユーザーフレンドリーなURL入力バリデーションを実装する方法を解説します。
危険なURLを受け入れてしまうリスク
ユーザーからURL入力を受け付ける際、javascript:やdata:などの危険なプロトコルを許可すると、XSS攻撃のリスクが発生します。
// ❌ 攻撃可能な危険なURL例
javascript: alert('XSS')
data: (text / html, (<script>alert('XSS')</script>))
ホワイトリスト方式(http://とhttps://のみ許可)なら、未知の攻撃にも対応できます。
Zodスキーマ
import { z } from 'zod'
const urlSchema = z.union([
z.string().refine((url) => {
if (!url) return true
const lowerUrl = url.toLowerCase() // 大文字小文字バイパス防止
// プロトコルホワイトリスト
if (!lowerUrl.startsWith('http://') && !lowerUrl.startsWith('https://')) {
return false
}
try {
new URL(url) // URL形式チェック
return true
} catch {
return false
}
}, 'Only http:// and https:// URLs are allowed'),
z.string().length(0),
z.undefined(),
])
export const validateUrl = (url?: string) => {
const result = urlSchema.safeParse(url)
return result.success
? { isValid: true, error: undefined }
: { isValid: false, error: result.error.errors[0].message }
}
export const validateUrlForForm = (value?: string) => {
if (!value) return true
const result = validateUrl(value)
return result.isValid ? true : result.error
}
ポイント:new URL()の前にプロトコルチェックすることで、ブラウザがサポートする危険なプロトコルを確実にブロックできます。
フロントエンド実装
import { validateUrlForForm } from '@/utils'
export const UrlInputForm = ({ register, errors }) => (
<TextField
{...register('url', { validate: validateUrlForForm })}
error={!!errors.url}
helperText={errors.url?.message}
/>
)
バックエンド二重検証(必須)
フロントエンドは簡単にバイパスできるため、サーバーサイドでの検証も必須です。
import { validateUrl } from '@/utils'
export async function POST(request: NextRequest) {
const { url } = await request.json()
if (url && !validateUrl(url).isValid) {
return NextResponse.json(
{ error: 'Only http:// and https:// URLs are allowed' },
{ status: 400 }
)
}
}
まとめ
プロトコルホワイトリスト方式を採用することで、javascript:やdata:などの危険なプロトコルを確実にブロックし、XSS攻撃を防ぐことができます。また、大文字小文字の正規化により、バイパス攻撃にも対応しています。
フロントエンドとバックエンドで同じバリデーションロジックを使用することで、コードの一貫性を保ちながら、セキュリティを多層的に確保できる点も重要なポイントです。