a-blog cmsのセキュリティを高める方法


この記事は、a-blog cms Advent Calendar 2021 の9日目の記事です。Advent Calendarに取り上げる内容を迷っていて、皆さんに投票していただきました。投票ありがとうございました!

先日、a-blog cms の公式ドキュメントにてフォームのセキュリティ対応について紹介されました。

これまで a-blog cms を導入したサイトで何度か専門機関によるセキュリティ診断を受けたり、導入するシステムのセキュリティについていくつもの企業やセキュリティ団体にヒアリングを受けたりしてきました。その経験を踏まえa-blog cmsのセキュリティ対策について、こちらに補足する形で考えたいと思います。(このブログは、公式が書きにくいことを積極的に書くというスタンスです。)

攻撃者はどのように攻撃するか

セキュリティを高めるにはどのように攻撃されるのかを知っておかないと、セキュリティという言葉にいたずらに振り回されることになります。システムの脆弱性を狙った攻撃は多数ありますが、分かりやすく説明するため、大きく分けると下記の目的になります。

  1. 攻撃の手がかりを取得する
  2. サイトに攻撃を行うことで、個人情報や機密情報の取得を行う
  3. サイトの改ざんをすることで悪意のあるサイトに誘導する(フィッシング詐欺サイトや、マルウェア配布サイトに誘導する)
  4. メール送信機能を悪用し、メール送信の踏み台にする(公式サイトからのメールを装い、フィッシング詐欺サイトなどに誘導する)

ablog cmsのセキュリティ対策

a-blog cmsではメンテナンスポリシーに基づいて、5年間のセキュリティ修正が行われています。直近もセキュリティ修正のアップデートが行われていますので、必ず最新のバージョンにアップデートするようにしましょう。

今回は、それぞれの攻撃に対して、a-blog cmsではどのような対策が取ることができるか紹介します。

1. 攻撃の手がかりの取得

OSのバージョン、Apache、PHP、MySQLなどミドルウェアのバージョン、またサイトがどのようなフォルダに置かれているかが分かると、それを攻撃の手がかりにすることができます。例えば最近Apache 2.4.49、2.4.50の脆弱性が発表されましたが、phpinfo などによりサーバが該当のバージョンであることが分かると標的になりかねません。このように手がかりを残さないことが重要です。

a-blog cmsではスムーズに開発ができるように、エラーメッセージが表示されるデバッグモードがあります。エラーメッセージが有効だと攻撃の手がかりとなる情報が表示されることがありますので、DEBUG_MODE1 にしたままの状態で公開しないようにしましょう。

また、ログイン画面についても /login/ などわかりやすいURLのままにしておくと、ブルートフォースアタック(総当り攻撃)により突破される可能性があります。ログイン画面を残しておくにしても、IPアドレス制限を行うなど第三者がログインできないように設定しましょう。

管理画面のIPアドレス制限
パスワードポリシーの設定

2. 個人情報や機密情報の取得

a-blog cmsでは、SQLインジェクションなど一通りの脆弱性対策が対応されていますが、ゼロデイ攻撃(新たな脆弱性が発見されてから対策されるまでの間に攻撃されること)や、サーバそのものや、サーバに搭載しているPHPやMySQLなどのミドルウェアに脆弱性があったときに、個人情報や機密情報が取得される可能性があります。

フォームに入力があった場合にデータベースに蓄積しないようにするか、フォームの入力情報はCSVでダウンロードすることができますので、1ヶ月を過ぎたら削除するなど運用ポリシーを決めることが望ましいです。また、今では当たり前のことですが、SSLサーバ証明書による暗号化通信も有効です。

フォームに送信される情報を蓄積しない

3. サイトを改ざんすることで悪意のあるサイトに誘導する

サーバ上のファイルを直接書き換えるだけでなく、クロスサイトスクリプティング(XSS)や、クロスサイトリクエストフォージェリ(CSRF)といった、悪意のあるスクリプトをサイトに埋め込み、ログイン情報を収集したり、クレジットカードなどの情報を外部サイトに送信したりする攻撃手法があります。

a-blog cmsではXSS対策として、すべてのタグはスクリプトが実行されないようにエスケープされて出力されています。また、CSRF対策もされており、外部からフォームの送信や、正規の手順を踏まないフォームの送信ができないようになっています。

ただし、{field}[raw] のように校正オプションを使用している場合は、HTMLタグがそのまま出力されてしまいます。ユーザからの入力を受け付ける場合(サイト内検索やフォーム)や、現在閲覧しているページのURLをHTMLタグ中に出力している場合(カノニカルタグなど)は、エスケープされているか確かめておきましょう。

Ver. 2.11.5 以降のa-blog cmsではインストールしたときの .htaccess にXSSやCSRF対策のセキュリティ関連のヘッダーが記載されていますので、有効にしておきましょう。

# セキュリティ周りのヘッダー
Header set X-Frame-Options SAMEORIGIN
Header set X-Content-Type-Options 'nosniff'
Header set X-XSS-Protection '1; mode=block'
Header set Strict-Transport-Security 'max-age=315360000'
サイト内検索などでクロススクリプティングが対策されているか確認

4. メール送信機能の悪用

a-blog cmsではお問い合わせフォームの管理者宛、ユーザ宛に自動返信メールを送信することができますが、フォームをHTMLテンプレートで構築することができる分、このメール送信機能が悪用されるリスクがあります。

攻撃の手法としては次のとおりです。

  1. メールの送信先の改ざん( <input type="hidden" name="To[]" value=""> を書き換える)
  2. 氏名の入力欄などにフィッシング詐欺サイトのURLを入力し、メールの文面を改ざん
  3. 問い合わせを完了することで、改ざんしたメール送信先に、改ざんしたメールの文面を送付

通常の自動返信メールは次の通りですが、

攻撃に成功するとこのようなメールを送信することができます。氏名のところがフィッシング詐欺サイトに誘導する文言になっているのが分かるでしょうか。ものすごく改行を入れられたら、自動返信メールではなく普通のメールとして開いてしまいそうです。

氏名などに不正な文字を入力することでメールを悪用することに成功しているので、攻撃手法から逆算して、下記の対策が取れるとよいでしょう。

  • reCAPTCHAの導入でボットからのアクセスを防ぐ
  • フォームのセキュリティ対応の通りテンプレート指定は避ける
  • フォーム設定のバリデーション設定で最大文字数を設定する
  • メールの文面には校正オプションである {field}[delnl] を設定し余分な改行が入らないようにする
フォームのバリデーションはさぼらず設定する

それでも自動返信メールを送りたい

「4. メール送信機能の悪用」で紹介したとおり、テンプレートを書き換えることでメールの送信先を変えることができるので、自動返信メールを送信しないことが一番ですが、ユーザ側としては問い合わせが受理されたかどうかが分からないので、自動返信メールが来たほうが良いと思います。

ただし、自動返信メールの宛先についてはユーザの入力を元にしているため、テンプレートの指定が避けられません。また、フォームの選択状況によって、管理者宛のメールの送信先を変更することもあると思います。そこで、Hook.php を変更することで、テンプレートの指定をすることなく、ユーザ宛に自動返信メールを送信する方法もあります。

※内容を理解できる方のみ、自己責任でご利用ください。

<?php

namespace Acms\Custom;
use DB, SQL, Field, Field_Validation;

/**
 * 
 */
 class HookUtil
 {
    /**
     * @param string $code
     * @return bool|Field
     */
     public static function loadFrom($code)
     {
        $DB = DB::singleton(dsn());
        $SQL = SQL::newSelect('form');
        $SQL->addWhereOpr('form_code', $code);
        $row = $DB->query($SQL->get(dsn()), 'row');

        if (!$row) {
            return false;
        }
        $Form = new Field();
        $Form->set('code', $row['form_code']);
        $Form->set('name', $row['form_name']);
        $Form->set('scope', $row['form_scope']);
        $Form->set('log', $row['form_log']);
        $Form->overload(unserialize($row['form_data']), true);

        return $Form;
     }
  }

/**
 * ユーザー定義のHookを設定します。
 */
    class Hook
    {
    (中略)

    /**
     * POSTモジュール処理前
     * $thisModuleのプロパティを参照・操作するなど
     *
     * @param \ACMS_POST $thisModule
     */
     public function beforePostFire($thisModule)
     {

        /**
         * お問い合わせフォームの送信先を設定
         */
         $module = get_class($thisModule);
         // ACMS_POST_Form_Submitのみ処理
         if( $module === 'ACMS_POST_Form_Submit' ){
            // Stepが不正の場合は処理しない
            $step = $thisModule->Post->get('step');
            if ($step === 'repeated' || $step === 'forbidden') {
                return;
            }

            // フォームIDを取得しcontactが含まれるIDのみ処理
            $formId = $thisModule->Post->get('id');
            if (strpos($formId, 'contact') === false) {
                return;
            }

            // フォームIDよりAdminToを取得
            $formField   = \ACMS\Custom\HookUtil::loadFrom($formId);
            $formConfig  = $formField->getChild('mail');
            $formTo      = $formConfig->get('templateTo');
            $formAdminTo = $formConfig->get('templateAdminTo');

            // ToがなければToを指定
            if( !$formTo ){
                $tpl = '<!-- BEGIN_MODULE Form --><!-- BEGIN step#result -->{email}<!-- END step#result --><!-- END_MODULE Form -->';
                $to = build(setGlobalVars($tpl), Field_Validation::singleton('post'));
                $to = trim($to);
                if( $to ){
                    $thisModule->Post->set('To', $to);
                } else {
                    $thisModule->Post->set('To', '');
                }
            }

            // AdminToがなければAdminToを設定
            if( !$formAdminTo ){
                $tpl = '<!-- BEGIN_MODULE Form --><!-- BEGIN step#result --><!-- BEGIN_MOUDLE Category_Field --><!-- BEGIN contact_category:loop --><!-- BEGIN_IF [\{category\}/eq/{contact_category_id}] -->{contact_category_email}<!-- END_IF --><!-- END contact_category:loop --><!-- END_MOUDLE Category_Field --><!-- END step#result --><!-- END_MODULE Form -->';
                $adminTo = build(setGlobalVars($tpl), Field_Validation::singleton('post'));
                $adminTo = trim($adminTo);
                if( $adminTo ){
                    $thisModule->Post->set('AdminTo', $adminTo);
                } else {
                    $thisModule->Post->set('AdminTo', '');
                }
            }
         }
      }

作って終わりにしない

以上、a-blog cmsにおけるセキュリティ対策についてご紹介しました。セキュリティ対策は挙げればキリがなく、対策することで利便性が下がることもありますのでバランスが重要です。例えば、関係者の多いWebサイトの場合、管理画面にIPアドレス制限をかけない代わりに二段階認証を採用するなど、が考えられます。

また、セキュリティパッチの適用などは、Webサイトを受託して納品して終わりの関係だと対応しにくいです。できるだけお客様とは保守契約を結び、スムーズにアップデートできるようにしましょう。


関連記事

この記事のハッシュタグに関連する記事が見つかりませんでした。