PHP文字列のエスケープ

(更新日: 2014/12/04)

PHP文字列をテキストとして出力したい場合もあります。PHPの文字列型はバイナリセーフなのでどのようなデータでも保存可能ですが、テキストとして出力するにはエスケープ処理が必要です。

 

エスケープ処理をしないでPHP文字列を出力すると不正なコードを実行される可能性があります。例えば、ユーザー入力を利用して設定ファイルを作成する場合にエスケープ処理をしないと脆弱になります。

$_GET[‘admin_user’]に

が設定されていた場合、config.phpは以下のようになります。

これをPHPスクリプトとして読み込むと、/etc/passwdファイルの中身が送信されてしまいます。

PHP文字列をテキストとして出力する場合、addslashes関数を利用します。addslashes関数はPHP文字列として出力する為にエスケープが必要な文字を \ (バックスラッシュ)でエスケープします。

addslashes関数がエスケープする文字

  • ‘(シングルクォート)
  • “(ダブルクォート)
  • \(バックスラッシュ)
  • NUL (ヌル文字)

PHPの任意コードが実行できる脆弱性を修正したスクリプトは以下の様になります。

先ほどの攻撃用文字列をaddslashes関数を利用した場合の出力は

となり、ユーザー入力の$_GET[‘admin_user’]がプログラムの一部として解釈されず文字列として保存されます。

addslashes関数はPHP文字列が問題なく処理される為に必要な最低限の文字をエスケープします。PHPの文字列は以下のエスケープもサポートしています。

\nlinefeed (LF or 0x0A (10) in ASCII)
\rcarriage return (CR or 0x0D (13) in ASCII)
\thorizontal tab (HT or 0x09 (9) in ASCII)
\vvertical tab (VT or 0x0B (11) in ASCII) (since PHP 5.2.5)
\eescape (ESC or 0x1B (27) in ASCII) (since PHP 5.4.0)
\fform feed (FF or 0x0C (12) in ASCII) (since PHP 5.2.5)
\\backslash
\$dollar sign
\”double-quote
\[0-7]{1,3}the sequence of characters matching the regular expression is a character in octal notation
\x[0-9A-Fa-f]{1,2}the sequence of characters matching the regular expression is a character in hexadecimal notation

“(ダブルクォート)で囲まれた文字列とHeredoc、では$varが現れた場合、$varは変数の内容に置換されます。PHPは ‘ (シングルクォート)で囲まれた文字列とNowdocは文字列中に$が現れても変数の開始として処理しません。 $が現れてもエスケープする必要はありません。

エスケープ文字でない文字をエスケープした場合、エスケープ文字も出力されます。この仕様はPythonと同じです。参考:Ruby, Perlはエスケープ文字が削除されて出力されます。

を出力します。警告エラーなどは発生しません。

他のエスケープ方法

addslashes関数でエスケープする以外に、addcslashes関数でエスケープしたり、バイナリをテキストに変換するbase64_encode関数、rawurlencode関数などを利用する事もできます。

PHP文字列となる変数をaddslashes関数でエスケープした場合、デコード処理は必要ありません。他の方法でエスケープした場合はデコード処理が必要になります。

var_export関数

PHP変数をテキストとして出力するvar_export関数を利用すれば、エスケープ処理されて出力されます。addslashes関数の代わりにvar_export関数を使用したコードも安全です。

$_GET[‘admin_user’]には配列も設定できます。

例: http://example.com/update_config.php?admin_user[]=abc&admin_user[]=xyz

var_export関数はPHP変数であれば、配列・オブジェクトをPHPスクリプトとして読み込んでも安全な形でエクスポートします。

 

まとめ

変数をPHP文字列として保存する場合、エスケープ処理は必須です。エスケープ処理を行わないと、任意のPHPコードを実行される可能性があります。PHP文字列を保存する際には、エスケープ処理の有無に関わらず常にaddslashes関数などでエスケープしてから保存します。

エスケープ処理を自動的に行うvar_exportは便利ですが、配列やオブジェクトも問題なく出力します。この動作は意図しない場合もあるので、データ型をチェックするなどの注意が必要です。

 

追記

@kenji_s さんよりSJISを使った場合は

http://localhost/test.php?admin_user=%95′;%20$a=%3C%3C%3CEOL%0D%0A/etc/passwd%0D%0AEOL;%0D%0Aecho%20file_get_contents($a);%20//

な形で攻撃可能ですね、と指摘を頂きました。

addslashes関数は非マルチバイト文字対応なので文字エンコーディングに関係なく、エスケープバイトが現れるとエスケープします。つまり、ISO-8859-1非互換のSJISなどを使っている場合にはaddslashes/var_exportは使えません。使うと脆弱になります。文字エンコーディングをバリデーションした後、SJIS対応のaddslashes関数を自作してエスケープする必要があります。(mbstring関数を使って、エスケープ対象バイトを\でエスケープ)

どの文字エンコーディングを使っていても入力時にバリデーション(文字エンコーディングが正しいかを確認)しなければなりませんが、SJISなどの場合は特に注意が必要です。PHPの文字列型はバイナリセーフなので壊れた文字エンコーディングでも何でも受け入れます。SJISなどの文字エンコーディングの場合、zend_multibyteも正しく設定(有効に設定)、スクリプトエンコーディングとmbstringの内部エンコーディングをSJISに設定してください。(入力文字エンコーディングをSJISからUTF-8に変換している場合、スクリプトエンコーディング・内部エンコーディングはUTF-8になります)

@kinji_sさん、鋭い指摘をありがとうございました。

 現在のPHPでSJISを使う場合、ロケールでSJISを使うように設定する事も重要です。Windowsでは通常SJISになっていると思いますが、UNIX系OSではUTF-8になっている事が多いと思います。少なくとも5.5までのPHPはescapeshellarg/filegetcsv関数などでロケールを利用してマルチバイト文字処理を行っています。文字エンコーディングを正確に利用・設定することはセキュリティ対策には必須ですが、設定のミスマッチがあると脆弱になる場合があります。特にSJISなどの文字エンコーディングで注意が必要です。

 

Comments

comments

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です