php / php/php-src

CURLOPT_SSLENGINE loads arbitrary shared objects

オープン
#22,035 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

Bug Extension: curl Status: Needs Triage
主要言語
C
スター
40.4k
フォーク
8.2k
平均マージ
2日 13時間
マージ済み PR(30日)
96

説明

Description

ext/curl extension passes CURLOPT_SSLENGINE directly to libcurl. When the OpenSSL backend is used, libcurl forwards the value to OpenSSL's dynamic engine loader, ultimately calling dlopen() on the path. This allows executing native code from PHP code subject to open_basedir / `disable_functions (which are not security boundaries anyway).

This libcurl option is documented in https://curl.se/libcurl/c/CURLOPT_SSLENGINE.html. When libcurl is built with OpenSSL, this calls ENGINE_by_id:

static CURLcode ossl_set_engine(struct Curl_easy *data, const char *name)
{
#ifdef USE_OPENSSL_ENGINE
  CURLcode result = CURLE_SSL_ENGINE_NOTFOUND;
  ENGINE *e = ENGINE_by_id(name);
  // (...)
}

https://github.com/curl/curl/blob/6f1dfab6a29242525cdc7b48f5ee49269b9cc316/lib/vtls/openssl.c#L1657-L1693

Internally, if OpenSSL can't find the engine in the default path, it loads the dynamic engine support and uses control commands to load id:

if (strcmp(id, "dynamic")) {
    if ((load_dir = ossl_safe_getenv("OPENSSL_ENGINES")) == NULL)
        load_dir = ossl_get_enginesdir();
    iterator = ENGINE_by_id("dynamic");
    if (!iterator || !ENGINE_ctrl_cmd_string(iterator, "ID", id, 0) || !ENGINE_ctrl_cmd_string(iterator, "DIR_LOAD", "2", 0) || !ENGINE_ctrl_cmd_string(iterator, "DIR_ADD", load_dir, 0) || !ENGINE_ctrl_cmd_string(iterator, "LIST_ADD", "1", 0) || !ENGINE_ctrl_cmd_string(iterator, "LOAD", NULL, 0))
        goto notfound;
    return iterator;
}

https://github.com/openssl/openssl/blob/fe686e15d84334b284f883118ed92f64b409b3aa/crypto/engine/eng_list.c#L460-L466

Then the dynamic loader uses DSO_load with the absolute path as the second parameter merge:

char *merge = DSO_merge(ctx->dynamic_dso, ctx->DYNAMIC_LIBNAME, s);
if (!merge)
    return 0;
if (DSO_load(ctx->dynamic_dso, merge, NULL, 0)) {

https://github.com/openssl/openssl/blob/fe686e15d84334b284f883118ed92f64b409b3aa/crypto/engine/eng_dyn.c#L379-L382

Internally, DSO_load calls dlfcn_load, which in turn calls dlopen and loads the library:

static int dlfcn_load(DSO *dso)
{
    // (...)
    char *filename = DSO_convert_filename(dso, NULL);
    // (...)
    ptr = dlopen(filename, flags);

https://github.com/openssl/openssl/blob/fe686e15d84334b284f883118ed92f64b409b3aa/crypto/dso/dso_dlfcn.c#L93-L113

I have a patch that prevents any path separator in this option, but that may be too restrictive? I'll open a PR later this week if not.

PHP Version
PHP 8.6.0-dev (cli) (built: May 10 2026 23:58:07) (NTS)
Copyright © The PHP Group and Contributors
Zend Engine v4.6.0-dev, Copyright © Zend by Perforce
    with Zend OPcache v8.6.0-dev, Copyright ©, by Zend by Perforce
Operating System

No response

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

ext/curl での CURLOPT_SSLENGINE の処理から始め、その値が libcurl と OpenSSL のエンジンローダーにどのように渡されるかを追跡します。ドキュメントに記載されたオプションの動作を確認し、パスベースの読み込みに対する安全な制限を決定します。回帰がカバーされ、サポートされているエンジン識別子を壊すことなく任意の共有オブジェクトの読み込みが防止されれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
c
領域
security
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
静か
明瞭さ
おおむね明確
初心者へのやさしさ
45/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。