1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ZodとReact Hook FormでXSS対策:プロトコルホワイトリストによるURL検証

Posted at

はじめに

WebアプリケーションでユーザーからのURL入力を受け付ける際、セキュリティとバリデーションは避けて通れません。
特に、javascript:data:などの危険なプロトコルを含むURLを受け入れてしまうと、XSS(クロスサイトスクリプティング)攻撃のリスクがあるため、対策をする必要があります。

今回は、ZodReact 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攻撃を防ぐことができます。また、大文字小文字の正規化により、バイパス攻撃にも対応しています。

フロントエンドとバックエンドで同じバリデーションロジックを使用することで、コードの一貫性を保ちながら、セキュリティを多層的に確保できる点も重要なポイントです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?