PHP Object Injection (POI) とは何か
以下のリンクが参考になります。
PHP の unserialize
関数に、外部からコントロールでき改竄されたことを検証できない値を渡している場合に発生する脆弱性です。
修正方法は、上記のリンクのいずれにも記載がある通り unserialize
/ serialize
関数を使う代わりに json_decode
/ json_encode
を使うようにすることです。
なお PHP7 から導入された allowed_classes を false
に設定するという方法もありますが、今回は PHP5 系のことを考えて以下では検討しないことにします。
(allowed_classes
を設定する方法については Practical PHP Object Injection (p. 82) のように本当にバイパスできないものなのか、という意見もあります)
POI 修正前後で失われる互換性について
上記の通り、修正のためにはデータのシリアライズ方式を変更する必要があります。
つまり、アプリケーションの修正前にシリアライズされたデータが、アプリケーション修正後にはデシリアライズできないことになります。
そういった事態が問題になるかどうかはアプリケーションの仕様によるとは思いますが、互換性は失われてしまいます。
もちろん POI により任意の PHP コードが実行できるようになってしまう可能性を考えると、修正により互換性が失われることを許容できる場合もあると思いますが(というよりもそれが大半の場合ではないかと思いますが)、許容できない場合もあると思います。
許容できない、となると前述の方法では修正できないということになってしまいますので、以下ではこの場合について修正方針を考えていきます。
考えられる対応
以下のような状況にあるアプリケーションを考えます。
- (0):
unserialize
で外部からコントロール可能な値を処理している (データのシリアライズはserialize
で実施)
これを以下のように変更していくとどうでしょうか?
- (1): データのシリアライズは
json_encode
で実施するように変更。デシリアライズを、最初にjson_decode
を試みて失敗したらunserialize
するようにする - (2):
unserialize
でデシリアライズする必要のあるデータの割合が、あらかじめ決めておいた閾値より小さくなったタイミングでデシリアライズをjson_decode
のみに変更する
このように (0) -> (1) -> (2) の順で修正を実施すると、(0) の段階でシリアライズされたデータを (1) の段階でも引き続き処理でき、最終的な (2) に至るまでに互換性が失われるデータを少なくすることができます。
しかし、この修正方法では (1) の修正途中の段階では依然として POI が残ったままであり、この点で問題があるといえます。
そこで、(1) の部分を以下のように変えてみます。
- (1'): データのシリアライズは
json_encode
で実施するように変更。デシリアライズを、最初にjson_decode
を試みて失敗したらunserialize'
するようにする。
unserialize'
というのは以下のものと考えてください。
- POI を引き起こす object のデシリアライズはしない
- それ以外の boolean、integer、string、arrayといったPOIを引き起こさない値のみをデシリアライズする
(0) -> (1') -> (2) の順で修正を実施することで、(0) の段階でシリアライズされたデータを (1') でも引き続き処理しつつ POI は発生させないようになっており、(0) -> (1) -> (2) の順で修正した場合に比べて状況が改善されているといえると思います。
作ったもの
前述した unserialize'
に相当するものとして、 restricted-unserialize という composer package を作成しました。
まとめ
- POI を修正するために
unserialize
をjson_encode
に置き換える過程で生じる互換性の問題について記述しました - 互換性の問題を回避しつつ POI を修正する方法として、
unserialize'
を利用する方法を提案しました -
unserialize'
の実装として、 restricted-unserialize というパッケージを作成しました