いつもお世話になっております。
前提とやりたいこと
ユーザーからhtml構造のデータをformで受け取る予定なのですが、html構造として出力させたいため、htmlspecialchars
を単純に利用できないという状況にございます。そこで、
0. src
やhref
といった不正なJSを埋め込まれる可能性がある属性については、Domを利用してチェック
0. タグについては、ホワイトリストを作成して限定して無害なものに置換→出力する際に一旦htmlspecialchars
して、ホワイトリスト対象の物だけ、戻すというアプローチ
で以下のコードを作成いたしました。
php
1$get_html = '<p>this is a test <a href="dammy">link</a><img src="dammy"></p>'; 2 3$dom = new DOMDocument(); 4$dom->loadHtml($get_html); 5$a_tag = $dom->getElementsByTagName('a'); 6$img_tag = $dom->getElementsByTagName('img'); 7 8foreach($a_tag as $attr) { 9 $item = $attr->getAttribute('href'); 10 $java = substr($item,0,4) == "java";//著しい不正処理 11 $http = substr($item,0,4) == "http"; 12 $https = substr($item,0,5) == "https"; 13 if ($java) {//著しい不正処理と認定して識別 14 } 15 if(!$http||!$https){ 16 $attr->setAttribute('href', '#'); 17 } 18} 19foreach($img_tag as $attr) { 20 $item = $attr->getAttribute('src'); 21 $java = substr($item,0,4) == "java";//著しい不正処理 22 $data = substr($item,0,4) == "data";//base 64形式で取得 23 24 if ($java) {//著しい不正処理と認定して識別 25 } 26 if(!$data){ 27 $attr->setAttribute('src', '#'); 28 } 29} 30$pre_html = $dom->saveHTML(); 31 32 $whiteList = [ 33 ["<p","*^[p"], 34 ["<span","*^[span"], 35 ["<strong","*^[strong"], 36 ["<em","*^[em"], 37 ["<u","*^[u"], 38 ["<s","*^[s"], 39 ["<blockquote","*^[blockquote"], 40 ["<ol","*^[ol"], 41 ["<ul","*^[ul"], 42 ["<li","*^[li"], 43 ["<sub","*^[sub"], 44 ["<sup","*^[sup"], 45 ["<a","*^[a"], 46 ["<iframe","*^[iframe"], 47 ["<img","*^[img"], 48 ]; 49 50 $whiteList_endTag = [ 51 ["<br>","[-/br-]"], 52 ["</p>","[/p]_*"], 53 ["</span>","[/span]_*"], 54 ["</strong>","[/strong]_*"], 55 ["</em>","[/em]_*"], 56 ["</u>","[/u]_*"], 57 ["</s>","[/s]_*"], 58 ["</blockquote>","[/blockquote]_*"], 59 ["</ol>","[/ol]_*"], 60 ["</ul>","[/ul]_*"], 61 ["</li>","[/li]_*"], 62 ["</sub>","[/sub]_*"], 63 ["</a>","[/a]_*"], 64 ["</iframe>","[/iframe]_*"], 65 ]; 66 67 $whiteList_search=[];$whiteList_replace=[]; 68 foreach ($whiteList as $value) { 69 $whiteList_search[]= $value[0]; 70 $whiteList_replace[]= $value[1]; 71 } 72 $whiteList_endTag_search=[];$whiteList_endTag_replace=[]; 73 foreach ($whiteList_endTag as $value) { 74 $whiteList_endTag_search[]= $value[0]; 75 $whiteList_endTag_replace[]= $value[1]; 76 } 77 78$pre_html = str_replace($whiteList_search,$whiteList_replace,$pre_html); 79$result_html = str_replace($whiteList_endTag_search,$whiteList_endTag_replace,$pre_html); 80 81echo $result_html; 82echo "<br>"; 83 84$rec_html = str_replace(["<html><body>","</body></html>"],["",""],$result_html ); 85$rec_html = preg_replace('@<!DOCTYPE.*>@',"",$rec_html); 86$rec_html = htmlspecialchars($rec_html, ENT_NOQUOTES, 'UTF-8'); 87$rec_html = str_replace($whiteList_replace,$whiteList_search,$rec_html); 88$rec_html = str_replace($whiteList_endTag_replace,$whiteList_endTag_search,$rec_html ); 89 90echo $rec_html;// ここです
問題点
ここで、特に$whiteList
についてですが、"<p"
という<
+p
にしており、"<p>"
や"<p [regEx]>"
のようにしていないかというと、属性値の種類や数はパターンがあり、"<p
"と>
の間の情報は残しつつタグを変換することはpreg_replace
等ではできないという認識による物でした。
その結果として、htmlspecialchars
はタグ全体ではなく>
も区別して識別することが判明し、$rec_html;// ここです
において、以下のような意図しない出力をもたらすことがわかりました。
html
1<p>this is="" a="" test="" <a="" href="#" >link<=""><img src="#" ><="" p="">
ご質問
最終的には元の$get_html = '<p>this is a test <a href="dammy">link</a><img src="dammy"></p>';
の構造に復帰させて、特定の属性値やホワイトリスト以外のタグを除去して出力させたいのですが、ここで行き詰まってしまいました。
例えば、strip_tags()
やDomdocument / SimplXML
なども検討したのですが、前者は基本的に使用すべきではないという記事を読んだり、後者は、調べても例えば、存在する要素ノード<p>
を存在しない*^[]p^*
などに置換する術を見つけることができませんでした。
この状況で、元のhtml構造に復帰させる術について、アドバイスを頂ければ幸いでございます。
よろしくお願い申し上げます。
加筆
yambejp様からのコメントも参考にdomを使用して、ホワイトリスト外のものをreplaceしてみるというアプローチを考えました。
例えば以下のようなreplaceChild()
を使用したとしても、ノードはツリー構造を持っている(と推察)ため単純に含めたくないnodeNameやtagNameの要素をreplaceすることができません。。
やはり手詰まりです。
php
1$str = '<p>aaaa<span class="">bbb<table><tbody><td>a</td><tbody></table></span>a</p><img><script>alert(danger)</script>'; 2$dom = new DOMDocument(); 3$dom->loadHtml($str); 4$all_tag = $dom->getElementsByTagName('*'); 5$white_tag_list = ['p','span','strong','em','u','s','blockquote','ol','ul','li','sub','sup','a','iframe','img']; 6 7$replace_node =$dom->createElement('span'); 8 9 10foreach ($all_tag as $key) { 11 if (!in_array($key->nodeName,$white_tag_list,true)) { 12 // $key->tagName = "span"; 13 // $key->nodeName = "span"; 14 $key->parentNode->replaceChild($replace_node, $key); 15 var_dump($key); 16 } 17} 18 19echo $dom->saveHTML();
新たに試したこととして追記いたします。
回答2件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/04/23 10:33
2021/04/23 10:55
2021/04/23 11:05
2021/04/23 11:29 編集
2021/04/23 11:28
2021/04/23 11:43
2021/04/23 11:45
2021/04/23 12:27
2021/04/23 14:13 編集
2021/04/23 14:56 編集
2021/04/23 15:52