PHPにおけるオブジェクトインジェクション脆弱性について

SQLインジェクションはかなり有名になりましたが、オブジェクトインジェクションはまだあまり聞かないので、まとめておきます。

Dependency Injection(DI)とは関係ありません。

オブジェクトインジェクション脆弱性とは?

SQLインジェクションが外部からSQL文を注入する攻撃であるのと同じように、オブジェクトインジェクションとは外部からオブジェクトを注入する攻撃です。

外部からオブジェクトを注入できれば、そのオブジェクトの機能によりさまざまな攻撃ができる可能性があります。最悪の場合、任意のコードを実行できる脆弱性になります。

PHPの場合、この攻撃が可能なのは、unserialize()関数を悪用できる場合です。

攻撃の方法

unserialize()関数に外部から任意のデータを渡すコードがあった場合、攻撃者は自由にシリアライズされたデータを送信することで、生成されるオブジェクトをコントロールすることができます。

しかし、自由にオブジェクト(クラス)を作成できるわけではないので、それだけでは任意のコードを実行することはできません。

シリアライズされたオブジェクトをunserialize()するためには、そのオブジェクトのクラスが定義済みかオートロードできる必要があるためです。

また、オブジェクトが生成されても攻撃に使えるメソッドが実行されなければ、実際に攻撃を成功させることはできません。

ただし、一部のマジックメソッドは明示的にコールされていなくても実行されます。unserialize()によるオブジェクトの生成では、__construct()メソッドは実行されませんが、__wakeup()メソッドが実行されます。また、そのオブジェクトの消滅時(PHPの終了時)には__destruct()メソッドが実行されます。また、__toString()メソッドなども場合により実行される可能性があります。

つまり、攻撃者は攻撃に使えるクラスを探し、そのオブジェクトを注入する必要があります。

例えば、以下のような、Example1クラスがある場合、かつ、unserialize($_GET['data'])というコードがあれば、このクラスのオブジェクトを注入することで任意のファイルを削除するという攻撃が可能になります。

class Example1
{
   public $cache_file;

   function __construct()
   {
      // some PHP code...
   }

   function __destruct()
   {
      $file = "/var/www/cache/tmp/{$this->cache_file}";
      if (file_exists($file)) @unlink($file);
   }
}

https://www.owasp.org/index.php/PHP_Object_Injection

脆弱性の事例

JVN#69986880 OpenPNE において任意の PHP コードが実行される脆弱性

ログイン画面にて利用可能な「次回から自動ログイン」機能にこの脆弱性があった事例です。

Cookieの値をそのままunserialize()してしまっています。

 /**
  * get remember login cookie
  *
  * @return array
  */
  protected function getRememberLoginCookie()
  {
    $key = md5(sfContext::getInstance()->getRequest()->getHost());
    if ($value = sfContext::getInstance()->getRequest()->getCookie($key))
    {
      $value = unserialize(base64_decode($value));

      return $value;
    }
  }

https://redmine.openpne.jp/projects/op3/repository/revisions/8fbdf778cd6133103bbf08a261928893a8c2eb0d/entry/lib/user/opSecurityUser.class.php#L150

JVN#94791545 FuelPHP において任意のコードが実行される脆弱性

HTTPクライアント機能を提供するRequest_Curlクラスでauto_formatが有効な場合(FuelPHP 1.7.1までのデフォルト)、取得したページのMIMEタイプに従って自動的にデータ変換処理が実行されるため、シリアライズされたデータの場合、自動的にunserialize()されるため攻撃可能であるという脆弱性です。

    // do we have auto formatting enabled and can we format this mime type?
    if ($this->auto_format and array_key_exists($mime, static::$auto_detect_formats))
    {
        $body = \Format::forge($body, static::$auto_detect_formats[$mime])->to_array();
    }

https://github.com/fuel/core/blob/1.8/develop/classes/request/driver.php#L360

    private function _from_serialize($string)
    {
        return unserialize(trim($string));
    }

https://github.com/fuel/core/blob/1.8/develop/classes/format.php#L530

そもそもRequest_Curlクラスを使っているユーザがかなり少ない上に、FuelPHPから攻撃者のサイトにHTTPリクエストさせる必要があるため、この脆弱性の影響を受けるユーザは非常に少ないと考えられます。ただし、このクラスでWeb APIにアクセスしており、そのAPI提供元サイトがクラックされた場合は、この攻撃が可能になります。

また、auto_formatを有効にした場合は、FuelPHP 1.7.2以降もこの脆弱性が存在しますので相応の対策をする必要があります。

私の調べた範囲では、FuelPHP自体にはこの脆弱性により任意のコードを実行できるようなクラスはありませんでした(ただし、ファイルを削除することは可能でした。また、ユーザの作成したクラスに脆弱なものが存在する可能性もあります)。

次のCakePHPの事例のように複雑な組み合わせで攻撃できる可能性があるため、ちょっと見ただけで問題ないとは簡単には言えません。もし、FuelPHPのクラスで任意のコードを実行する方法を発見された方がいましたら、お教え願いたいです。

JVNDB-2011-003903 CakePHP の _validatePost 関数における内部 Cake キャッシュを変更される脆弱性

CakePHPのSecurityComponentにこの脆弱性があった事例です。実際に任意のコードが実行できたようで非常にうまく攻撃可能になった事例です。

かなり複雑な事例です。解説は以下のブログが詳しいです。

その他の事例

(16:01 追記)

調べると、去年から今年にかけていろいろと報告されています。

対策

攻撃手法は複雑になりがちですが、対策は割と簡単で、unserialize()関数に外部からの入力を渡さないことです。

serialize()がどうしても必要な場合以外はserialize()を使わないというのもいいと思います。serialize()json_encode()に変更できないか検討してみましょう。

関連

参考

Date: 2014/07/22

Tags: php, security, fuelphp, cakephp