メタ文字(エスケープシーケンス)をawkでエスケープする
目的
- LATS(Linux Apache Textfile and Script)でWEBアプリを作っています。
- セキュリティ向上のためHTMLから悪意のある文字列入力がであってもサーバで実行できないようににしたいと思いました。
- 具体的にはダイレクトOSコマンドインジェクションやディレクトリトラバーサルとか呼ばれる攻撃を避けようと思いました。
- rubyにはescapeHTML()があり
与えられた文字列中の '、&、"、<、> を実体参照に置換した文字列を新しく作成し返します。
とあります。
-
phpにも HTML特殊文字をエスケープする htmlspecialchars()があります。
-
今回はサーバサイドつまりscriptでHTMLの制御文字だけでなく、メタ文字(エスケープシーケンス)をHTMLの文字実体に変換することを考えます。クライアントサイトでは比較的に簡単に検索できました。でもBrowserだけでなく、curlなどでも攻撃がありえるのでJavaScriptだけでなく、サーバサイドでの対策します。
クライアントサイド(JavaScript)の例
-
JavaScriptでは、
string.replace()
でできるそうです。
実験
- Shell Scriptでは
sed
とかawk
とかが思いつきます。それで実験してみます。
$ echo '!' | sed -e 's|!|\!|g'
!
とうまくいきそうです。でも2つ以上-e
の変換コマンドを続けるとうまくいきません。
$ echo '!' | sed -e 's|!|\!|g' -e 's|\&|\?|g'
?#33;
これはsedが一つ目の-e
で置換した結果に対して2つ目の-e
を適用するためです。一つ目の変換された&
が2つ目の-e
でさらに変換されたのです。
awkも次の構文ではうまくいきません。
$ echo '!' | awk '{gsub("!","\\!",$0); gsub("&","\\?",$0);print}'
?#33;
sedと同じく、一つ目のgsub()
は$0
を書き換えるため、二つ目のgsub()
でそれを変換するためです。それではどうしましょうか。お風呂に入っていて思いつきました。
考え方とテスト
- 文字毎に変換したら終わりのスクリプトにすればいいです。そのため、文を文字に分解して、分解された文字がメタ文字と一致したら一度だけ変換しおしまいにします。
- ツールは万能
awk
を使いましょう。文字毎に変換はfold -w 1
でもできますが、パイプで渡すくらいならawk
だけで処理しましょう。文字列分解の参考はこちら。gawk FS=''
で文字分解できました。MacOSのawk
ではNGでした。
$ cat escapeHTML.bash
#!/usr/bin/env bash
gawk -v FS='' '{
for (i = 1; i <= NF; i++) {
if( $i == "!" ) { printf "&!#34;"
}else if( $i == "?" ) { printf "?"
}else if( $i == "&" ) { printf "&"
}else if( $i == "#" ) { printf "#"
}else {printf "%s", $i}
}
}'
$ echo '!' | ./escapeHTML.awk
&!#34;
いい感じです。長い文字列も試してみましょう。
$ echo '12345!?&#あいうえお' | ./escapeHTML.bash
12345&!#34;?&#あいうえお
うまくいっています。テストコードですがご参考まで。もっといい方法があったら教えてください。