危険なスクリプト
View の基礎
問題
以下のようなページを作成してください:
POST /not-dangerにcontentとして文字列を送信すると、その文字列がそのまま表示される- たとえば、
Hello Laravel!を送信すると、Hello Laravel!が表示される
- たとえば、
- 上記の
POST /not-dangerへ、contentとして<script>alert('danger')</script>を送信したとき、以下の2つを同時に満たす- ブラウザ画面上に、
<script>alert('danger')</script>が 文字列として 表示される - JavaScript の alert 関数は、実行されない
- ブラウザ画面上に、
問題は、以下の手順で解いてください。
- Red:小さいテストを作成し、失敗を確認してください
- Green:テストを成功させてください
- Refactor:整理・整頓してください
- 必要に応じて、1から3を繰り返してください
ヒント
背景知識
- エスケープ方法: htmlspecialchars
- HTTP POST メソッド
便利なアサーションの例
アサーションの調べ方 も合わせてご覧ください。
今回は、以下を使うのではないかと思います。
-
Error: assertDontSee Not Found.
-
assertSeeText($value)(HTTP テスト : テキストコンテンツに $value が含まれるか)
解答例
続きを読む
実行環境:
- Laravel v13.12.0
- PHP 8.4
- PHPUnit
Red1: POST で受け取ったコンテンツをそのまま表示する
テストを作成し、失敗を確認します。
bash:
php artisan make:test NotDangerTest
tests/Feature/NotDangerTest.php:
<?php
namespace Tests\Feature;
use Tests\TestCase;
class NotDangerTest extends TestCase
{
public function test_postで受け取った文字列がそのまま表示される(): void
{
$hello = 'Hello Laravel!';
$response = $this->post('/not-danger', ['content' => $hello]);
$response->assertStatus(200)
->assertSeeText($hello);
}
}
Green1
このテストだけなら、以下のコントローラーで通ります。
bash:
php artisan make:controller NotDangerController
app/Http/Controllers/NotDangerController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class NotDangerController extends Controller
{
public function show(Request $request) {
$content = $request->input('content');
return $content;
}
}
routes/web.php:
use App\Http\Controllers\NotDangerController;
Route::post('/not-danger', [NotDangerController::class, 'show']);
Refactor1
テストとしては通るものの、このままでは XSS 脆弱性があるため、実際にはこのような実装を書いてはいけません。
次のテストで修正します。
Red2: エスケープ処理する
テストを追加します。
tests/Feature/NotDangerTest.php:
public function test_エスケープ処理されているか(): void
{
$hello = '<script></script>';
$response = $this->post('/not-danger', ['content' => $hello]);
$response->assertStatus(200)
->assertDontSee($hello, false);
}
失敗を確認します。
Green2
コントローラーを修正します。
app/Http/Controllers/NotDangerController.php:
public function show(Request $request) {
$content = $request->input('content');
return htmlspecialchars($content);
}
Refactor2
Blade を導入し、見やすくします。(いきなり Inertia.js を使っても OK です)
bash:
php artisan make:view not-danger
resources/views/not-danger.blade.php:
<div>{{ $content }}</div>
コントローラーから view を呼びます。
app/Http/Controllers/NotDangerController.php:
public function show(Request $request) {
$content = $request->input('content');
return view('not-danger', ['content' => $content]);
}
テストが成功していることを確認します。
解説
続きを読む
XSS 脆弱性について
XSS(クロスサイトスクリプティング)脆弱性とは、信頼できない外部入力が HTML 出力に挿入されることで、意図しない(悪意ある)処理が実行されてしまうことです。
たとえば、今回の問題のように、 <script>alert('danger')</script> という文字列をエスケープせずに表示してしまうと、 JavaScript が実行されます。
web アプリケーションを作成する際の脆弱性対策については、「体系的に学ぶ 安全なWebアプリケーションの作り方」(通称「徳丸本」)を参照してください。
View について
あらゆる出力をエスケープ処理することは、基本的なことなので、何も考えずにできる仕組みが必要です。
(何かを考えて実装しなければならない場合、自分や他人の「うっかり」を防げません)
さまざまな仕組みがありますが、Laravel では、Blade や、フロントエンドフレームワーク(React など)で自動的にエスケープしてくれます。
これらは View 層と呼ばれ、コントローラーから呼ばれ、適切な出力を作成します。
最終的にその出力は、 Response オブジェクトとなり、クライアントにレスポンスされます。