PHP と OpenSSL 3 の話

2022-06-23
PHP
OpenSSL 3

PHP はバージョン 8.1 から OpenSSL 3 に対応しています。

https://www.php.net/manual/ja/openssl.requirements.php

この OpenSSL 3 ですが、2021/9/7 にリリース されています。Quic などの新しいプロトコルへの対応をしていくということなので、大変喜ばしいことですが、知っておかないと困ることがあります。

OpenSSL 3: Support of SSL_OP_IGNORE_UNEXPECTED_EOF context option · Issue #8369 · php/php-src

OpenSSL 3 は unexpected EOF に対して厳密に処理をするようになったということで、close を送ってこないサーバーなどに対して送信エラーになってしまいます。

実際に PHP のイシューに上がっていたサンプルコードを、OpenSSL 3 を使ってコンパイルした PHP 8.1.5 で実行するとこうなります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ php -v
PHP 8.1.5 (cli) (built: Apr 24 2022 13:14:36) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.5, Copyright (c) Zend Technologies
with Zend OPcache v8.1.5, Copyright (c), by Zend Technologies
with Xdebug v3.1.3, Copyright (c) 2002-2022, by Derick Rethans
$ php -r "echo file_get_contents('https://chromedriver.storage.googleapis.com/LATEST_RELEASE', false, stream_context_create());"
PHP Warning: file_get_contents(): SSL operation failed with code 1. OpenSSL Error messages:
error:0A000126:SSL routines::unexpected eof while reading in Command line code on line 1
PHP Stack trace:
PHP 1. {main}() Command line code:0
PHP 2. file_get_contents($filename = 'https://chromedriver.storage.googleapis.com/LATEST_RELEASE', $use_include_path = FALSE, $context = resource(4) of type (stream-context)) Command line code:1
PHP Warning: file_get_contents(): SSL: Success in Command line code on line 1
PHP Stack trace:
PHP 1. {main}() Command line code:0
PHP 2. file_get_contents($filename = 'https://chromedriver.storage.googleapis.com/LATEST_RELEASE', $use_include_path = FALSE, $context = resource(4) of type (stream-context)) Command line code:1

今まで普通に使えていた REST API が突然接続エラーになるということも起こりそうです。

回避策

ウェブサーバー側が、正しく close を送るように対応するのが良いでしょうが、現状では未対応のサーバーが大量にあるため、現実的ではありません。

PHP 8.1.7 で後方互換性維持のための修正がはいりました。

https://github.com/php/php-src/commit/74f75db0c3665677ec006cd379fd561feacffdc6

SSL_OP_IGNORE_UNEXPECTED_EOF が無条件でコンテキストに追加されるので、8.1.7以降であれば今までと同じ挙動に戻ります。

実際に実行してみると…

1
2
3
4
5
6
7
8
$ php -v
PHP 8.1.7RC1 (cli) (built: Jun 1 2022 08:43:27) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.7RC1, Copyright (c) Zend Technologies
with Zend OPcache v8.1.7RC1, Copyright (c), by Zend Technologies
with Xdebug v3.1.5-dev, Copyright (c) 2002-2022, by Derick Rethans
$ php -r "echo file_get_contents('https://chromedriver.storage.googleapis.com/LATEST_RELEASE', false, stream_context_create());"
102.0.5005.61

PHP 8.0 系以前は、OpenSSL の 1.1.1 系未満になるので、特に問題ないと思います。8.1.7 リリース後は速やかにアップグレードしたほうが良さそうです。

OpenSSL の上記の挙動は、 1.1.1e でも試験的に導入されたようなので、タイミングが悪い人は 8.0系のPHP を OpenSSL 1.1.1e で使うというニッチなセットになってしまい。上記不具合が再現されそうです。

おまけ

Truncation Attack とはなにか

そもそも、OpenSSL 3 が unexpected EOF に対して厳密になった理由は、Truncation Attack を回避するためということなのですが、そもそもこれが分からない。

プロフェッショナルSSL/TLS – 技術書出版と販売のラムダノート

とりあえず、上記書籍には 6.7 強制切断攻撃ということで載っていました。close_notify を監視することで、強制切断による接続断なのか、正しい接続断なのかを判断するということらしいです。

OpenSSL 3 では、この close_notify の監視を厳密化したということのようです。この攻撃を成功させるのは難しいようですが、セキュリティリスクに敏感なサービスにおいては、PHP での TLS 接続においても SSL_OP_IGNORE_UNEXPECTED_EOF をあえて外して厳密化すると良さそうです。