wordpressで自作メールフォームを作成して送信してみる

wordpressは基本的にphpで作られているため、mailやmb_send_mailといったphpのビルトイン関数を使うことができます。ところが、実はwordpressもユーザ定義のwp_mail関数を用意していて、wordpressを利用していればこちらの独自関数も利用することができます。全てmailという単語が入るため、似たような印象を持ちますが、それぞれ少しずつ違います。

mailとmb_send_mailの違い

phpのmail関数はそのまま使うと、日本語では文字化けします。そのため、以下のようなエンコーディングが必要となります。

mb_language("japanese");
mb_internal_encoding("UTF-8");

一方、mb_send_mailは、エンコーディングが不要です。そのため、日本語圏で上記関数を使っても余計なコードが増えるだけで実際にはあまり使いません。従って、phpでは通常、mb_send_mailを用います。

wordpressのwp_mail

こちらは、wordpressで用意されているもので、wordpressユーザーのみが利用できます。何が違うのかといえば、こちらは第一と第四の引数に配列指定ができるという点です。

phpの標準関数だと、第一引数に割り当てるメールアドレスをカンマ区切りにし、第四引数に割り当てるFromなどを「\r\n」で改行しなければならないといったルールがあります。wordpressの関数を使えば、配列操作に慣れた人にとって非常に分かりやすいです。

メールフォームからのメールが届かない

ただ最近は、スパム対策から独自ドメインからのメールまでも拒否するプロバイダーや回線業者もあります。つまり、メールをきちんと送信しているはずなのに届かないため、wordpressのシステムを含め、上記のいずれかの関数の使い方に間違いがあると勘違いしてしまう場合もあり得るという訳です。しかもReturn-Pathに他のドメインのメールを指定してもエラーをキャッチできないことがほとんどであるため、いつまで経っても原因が分からないという悪循環に陥ります。

送信元を偽装した不正なメールとして、迷惑メールフォルダなどに入れてくれるプロバイダーや回線業者ならまだいいのですが、疑わしいメールaddressを全て破棄して無視するような方法をとっていれば、こちら側からは送信に成功しているのか失敗しているのかが分かりにくくなります。

メールを拒否する側はスパムと判定しているため、どうしてメールを受け取らなかったのかという情報さえも与えないようにしているのかも知れません。いずれにしてもここにハマるとwordpressが原因ではないため、時間の無駄になります。

メールサーバ側での設定

「php.ini」の設定には、以下のような項目があります。SMTPのlocalhostをサーバのホスト名、smtp_portを587などに設定すると自分用にはメールが届いてユーザー用には届かないといった不具合が解消されるという情報もありましたが、筆者の環境では以下のコードでも問題なく動作します。

smtp_portの25は、通常のポート番号ですが、この番号は迷惑メール対策で使われないことがあります。サーバによって異なることの方が多いかも知れませんので、ご自身のサーバ環境に合わせて下さい。

[mail function]
SMTP = localhost
smtp_port = 25
sendmail_path = /usr/sbin/sendmail -t -i

ヘッダインジェクションへの対策

ヘッダインジェクションは、mb_send_mail()関数の第四引数に改行コード(「\r\n」)を挿入されることで発生しまので、この部分をpreg_mach関数の正規表現で以下のようにします。改行が問題となるのは、「From」「Cc」「Bcc」を追加する際にこれらを改行で区切らなければならない構文になっているからで、改行を継ぎ足せば悪意あるコードを注入できるためです。wordpressの独自関数は対策済みの模様。

filter_varはメールアドレスの形式を確認していますが、正規表現も含めて100%確実にメールアドレスの有効性を判定できるものではありません。従って、時代に合わせたものを使うべきです。

攻撃される場所は、inputやtextareaなどのname属性を利用して外部からリクエストを取得している部分です。inputは原則改行できませんが、textareaは改行できます。そのため、対象サイトのHTMLファイルを作ってinputのフォームをtextareaのフォームに変えて送信するとヘッダインジェクションが成立してしまいます。

if(!empty($_POST["headers"]) &&
!preg_match('/[\r\n]+$/um', $_POST["headers"]) &&
!preg_match('/[\r\n]+$/um', $_POST["subject"]) &&
filter_var($_POST["headers"], FILTER_VALIDATE_EMAIL)
){
// ~処理~
}

wordpressの自作メールフォーム

以下のメールフォームはphp標準関数で作成したものをコメントアウトして、ワードプレスの独自関数で動かしています。ヒアドキュメントを代入している$toにはワードプレス管理者のメールaddressを指定していますが、他にも送りたいあて先を配列で指定できます。管理者は、$master_ワードプレス変数ですね。ワードプレスの自作メールフォームからアドレスを入力したユーザは$_POST["headers"]で受け取っています。

また、$headers変数内の「From:」と「Return-Path:」には管理者を指定しますが、「Return-Path:」には別のメールを指定することもできます。通常、この二つは同じメールアドレスになるのですが、これを一致させないとスパム判定されてしまう可能性も否定できません。

Return-Pathはメールの配送途中でエラーが発生した場合にその情報を返すメールアドレスを指定する部分です。第四引数の$headersには、他にもBccやCcを設定できますが、こちらは一般にワードプレスのメールフォームから入力したユーザーに対して確認メールを送るという形で利用します。

ちなみにワードプレスのwp_mailは、ヘッダインジェクション対策が施されているようで、改行を入れたら管理者以外にメールが送信されない仕様になっているみたいです。下記コードは$_POST["headers"]内に改行があると弾いてますが、preg_matchの部分をまとめてコメントアウトするとワープレ用のコードで確認できると思います。テストする場合は、閉じカッコも忘れずにコメントアウトして下さい。

あと、メールフォームで出力しているvalue値は、テスト環境で使っていましたので、本番環境では取り除いて下さい。htmlspecialcharsを代入している$escを用いたヒアドキュメント内の関数のことですね。テストでメールアドレスを何度も入力するのが面倒だったために施した処置ですので。

$esc="htmlspecialchars";
$result="";
if(empty($_POST["message"])){$_POST["message"]="";}
if(empty($_POST["attachments"])){$_POST["attachments"]="";}
if(!empty($_POST["headers"]) &&
!preg_match('/[\r\n]+$/um', $_POST["headers"]) &&
!preg_match('/[\r\n]+$/um', $_POST["subject"]) &&
filter_var($_POST["headers"], FILTER_VALIDATE_EMAIL)
){
// php標準メール送信
/********************************
$master_wordpress="master_wordpress@example .com";
$to = <<< EOM
{$master_wordpress}
EOM;
//$headers = "From: ".$master_wordpress."\r\nReturn-Path:".$master_wordpress."\r\nBcc: ".$_POST["headers"];
$headers = <<< EOM
From: {$master_wordpress}
Return-Path: {$master_wordpress}
Bcc: {$_POST["headers"]}
EOM;
$result = mb_send_mail($to, $_POST["subject"], $_POST["message"], $headers );
*******************************/
// wordpressで作ったメールフォームから送信
//********************************
$master_wordpress="master_wordpress@example .com";
$to[]=$master_wordpress;
$headers[] = 'From: '.$master_wordpress;
$headers[] = 'Return-Path: '.$master_wordpress;
$headers[] = 'Bcc: '.$_POST["headers"];
$result = wp_mail($to, $_POST["subject"], $_POST["message"], $headers );
//*******************************
}
$nt2=$nt=preg_replace('/\/$/', '', network_home_url('','https'));
$nt=$nt.$_SERVER['REQUEST_URI'];
if($result){
echo "送信成功";
}else{
echo "送信失敗";
}
echo <<< EOM
<form action="{$nt}" method="post">
<div style="margin-top:100px;">
<table style="margin:0 auto;width:500px;">
<tr><td colspan="2">メール送信</td></tr>
<tr><td>mail:</td><td><input type="text" style="width:95%;" name="headers" value="{$esc($_POST["headers"])}" /></td></tr>
<tr><td>件名:</td><td><input type="text" style="width:95%;" name="subject" value="{$esc($_POST["subject"])}" /></td></tr>
<tr><td>本文</td><td><textarea name="message" style="width:95%;height:300px;display:block;overflow:auto;font-size:15px;padding:10px;line-height:1.2em;">{$esc($_POST["message"])}</textarea></td></tr>
<tr><td></td><td style="text-align:right;"><input type="submit" value="送信" /></td></tr>
</table>
</div>
</form>
EOM;

まとめ

ワープレで作るメールフォームは、書式さえ理解できればそれほど難しいことはありません。ただ、php標準の関数だとセキュリティ対策が必要です。また、メールアドレスの形式判定もややこしいかも知れませんが、こちらは直接ワープレのセキュリティを脅かすものではないため、優先順位は低いかも知れません。また、このメールフォームはワープレ上から簡易的に動作させるために作ったコードであるため、実用するにあたってはワープレやphpのセキュリティも十分考慮して下さい。