実装は三段階。

  • ヌルバイト攻撃対策
  • 制御文字対策
  • 「../」対策

ヌルバイト攻撃対策

参考:https://blog.activetk.jp/attack/2021/56/

ヌルバイト(0バイト文字、あるいは(NUL(L)文字))とは

ヌルバイトは、ほとんどの言語で「\0」が割り当てられている文字列のことで、URLエンコードすると「%00」になる。
C言語では文字列の終端を示すために使われ、3文字を入れるchar配列を作る際は、以下のように示される。

char test[] = {'a', 'b', 'c', '\0'};

ほかにも printf という文字列を表示する関数は、最初のヌルバイトまでを表示し、strlen という文字列の長さを取得する関数は、最初のヌルバイトまでの長さを表示するなどC言語では意識的にヌルバイトを利用する必要がある。

ヌルバイト攻撃とは

PHP はC言語でプログラムされているため、PHP はC言語(またはOS)のファイルシステム関数に依存し、ファイルシステム機能において同様に動作する。
ちなみに、

プログラミング言語としてのPHPは、CやPerl, Javaなどのプログラミング言語に強く影響を受けており、これらの言語に近く学習しやすい文法を有する。 組み込み関数についてもこれらの言語から直接輸入されたものも多く、関数名を変えずにそのまま取り込んだことで標準関数の命名規則が一貫していないといった問題も有している。

https://ja.wikipedia.org/wiki/PHP_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E8%A8%80%E8%AA%9E)

とあるように、この一貫性のなさがPHPが一部の層に嫌われている原因でもある。

前述のようにC言語は「\0」を文字列ターミネーターとして扱うため、ファイルシステム関数の命令にヌルバイトを挿入すると、その後続にあるすべての文字を無視する。

この特性を生かして、たとえば「./{ファイル名}.txt」ファイル名の最後に0バイト文字を付ける事で、「.txt」の部分を無視させる事ができてしまい、自由な拡張子のファイルをアップロードが可能となるなど悪用されるケースがある。

PHP 5.3.4以降では文字列にヌルバイトが入っていると、requireやfile_get_contentsで警告が出るようにはなったが、念のためチェックする必要がある。

制御文字対策

制御文字 (#00 – #20、改行、改行、タブ、文字復帰、タブなど) を削除する。
改行などを意図的に使用することで、想定外のシステム関数が実行される可能性があるため、制御文字についてもチェックする必要がある。

https://www.rapidtables.com/code/text/ascii-table.html

もちろんここでは触れないが、そもそも空白文字についてもチェックをしなければいけない。

「../」対策

単純にディレクトリをさかのぼる文字列があるかどうかをチェックする必要がある。

実装例

function checkDirectoryTraversalPath( $string ) {
  if ( strpos( $string, chr( 0 ) ) !== false ) {
    return false;
  }
  if ( preg_match( '#[\x{0}-\x{1f}]#', $string ) ) {
    return false;
  }
  if ( preg_match( '#\.\.\/#', $string ) ) {
    return false;
  }
  return true;
}