フォームに入力されたHTMLにscriptタグなどが含まれていないか検証する

最近仕事でもプライベートでもPHPしか書いてないので、久々にPHPネタを。

HTMLを入力できるフォームを作りたくって、ただscriptタグやonloadなど、スクリプトを実行する要素が入ってほしくなかったので、そのバリデーションを書いた。

ただ正規表現として処理するだけだと不十分だと思ったので、DOM的に解析して、scriptタグやonから始まる属性があれば不正なHTMLとして処理することにした。

続きにサンプルコードを書く。

<?php

/*
// 正常パターン
$html = <<<DOC
<p style="color:red">test1<a href="#">hoge</a></p>
<p>test2<a href="#">hoge</a></p>
DOC;
 */

/*
// HTMLが不正パターン
$html = <<<DOC
p style="color:red">test1<a href="#">hoge</a></p>
<p>test2<a href="#">hoge</a></p>
DOC;
 */

/*
// empty
$html = '';
 */

/*
// onclick 埋められたパターン
$html = <<<DOC
<p style="color:red">test1<a href="#" onclick="alert(1);">hoge</a></p>
<p>test2<a href="#">hoge</a></p>
DOC;
 */

// <script> 埋められたパターン
$html = <<<DOC
<p style="color:red">test1<a href="#">hoge</a></p>
<p>test2<a href="#">hoge</a></p>
<script>alert(1);</script>
DOC;

function validateNode($node) 
{
    foreach ($node->attributes() as $name => $attr) {
        if ((strlen($name)) > 1 && (strtolower(substr($name, 0, 2)) == 'on')) {
            return false;
        }
    }
    foreach ($node->children() as $child) {
        $name = $child->getName();
        if (strtolower($name) == 'script') {
            return false;
        }
        if (!validateNode($child)) {
            return false;
        }
    }
    return true;
}

function validateHtml($html) {

    if (empty($html)) {
        return true;
    }

    $domDocument = new DOMDocument();
    $domDocument->loadHTML($html);
    $xmlString = $domDocument->saveXML();
    $xmlObject = simplexml_load_string($xmlString);

    return validateNode($xmlObject->body);
}

if (validateHtml($html)) {
    echo "HTML is secure.";
}
else {
    echo "HTML is insecure!";
}

CakePHP 3 のバリデーターとしたものを gist に貼っておいた。

[PHP] 入力されたHTMLにscriptタグが入っていないか検証する · GitHub

こちらの記事が参考になった。

PHPでHTML文書を解析する | 果報は寝て待ってられない!