Remove \GuzzleHttp\Cookie\FileCookieJar from the autoloader for security

Created on 16 May 2025, 2 months ago

Problem/Motivation

There's a viable Gadget Chain in \GuzzleHttp\Cookie\FileCookieJar which can be abused to achieve an arbitrary File Write if there's a PHP Object Injection vulnerability in a Drupal application. It cannot be exploited by itself.

(https://portswigger.net/web-security/deserialization#what-is-insecure-de... for general context).

Details of this Gadget Chain have been public for a long time - it looks like it was originally added to PHPGGC around 8 years ago:

https://github.com/ambionics/phpggc/tree/master/gadgetchains/Guzzle/FW/1

I filed an issue to make sure that the Guzzle maintainers are aware but there has been no activity:

https://github.com/guzzle/guzzle/issues/3271

Steps to reproduce

To emphasise again, in order for this to be exploitable within Drupal there first needs to be a PHP Object Injection vulnerability.

https://www.drupal.org/project/gadget_chain_poc/ is a development / security research tool to simulate such a vulnerability.

With that module installed, the guzzle/fw1 gadget chain can be used to write a file to the server (somewhere the PHP user has write permission) which could be used as part of an attack.

I'll leave the full details as an exercise for the reader initially.

Proposed resolution

It doesn't look like the FileCookieJar class is ever used in Drupal core, and a quick search suggests it only appears in contrib as part of the vendored library where that's been committed to projects.

We could remove this class from Drupal's autoloader, and that would render it inert in terms of being exploitable in a PHP Object Injection scenario.

I asked about approaches to doing this in drupal slack and received several interesting suggestions. I'll try to summarise them briefly:

joachim: Have a look at the patch for the composer cache ID thingy - @alexpott worked out how to do it in that with a composer plugin: https://www.drupal.org/project/drupal/issues/3509069 📌 Use a better container cache key Active

catch: you could add class_alias() very early in a middleware or similar, to point to either your own version, or an empty class.

longwave: look at https://www.drupal.org/node/3509577 as well which messes with the autoloader a bit.. but to block something you might need ... [to throw an exception. Or] you could maybe use reflection to alter Composer's ClassLoader::$missingClasses property to fake that the class doesn't exist, but iirc getting hold of the object to do that is tricky.. or maybe i'm thinking of something else actually

berdir: instead of preventing loading, why don't you ensure the file doesn't exist? similar to core had (has?) workarounds in place to delete certain dev files if they are in the docroot folder with a composer plugin during composer install? ... hooking directly into composer install, you can set that up in composer.json or with a registered plugin ... which is also used to add the scaffold files like robots.txt

chx: a very simple way to do this.. [referring to \Composer\Autoload\ClassLoader::findFile] create an empty file and set apcu to it. it will load it instead of the real class and unlike with class_alias it will not exist after loading it

catch: If we wanted this in core, we could add the file to core-vendor-hardening specifically.

Remaining tasks

* MR
* Tests
* Review / commit etc..
* Release note snippet

User interface changes

n/a

Introduced terminology

n/a

API changes

The class would no longer be available via the autoloader.

Any contrib / custom code that needs to use it would need to load the class explicitly.

Data model changes

n/a

Release notes snippet

This would warrant a CR - that's a todo.

📌 Task
Status

Active

Version

11.0 🔥

Component

composer

Created by

🇬🇧United Kingdom mcdruid 🇬🇧🇪🇺

Live updates comments and jobs are added and updated live.
  • Security improvements

    It makes Drupal less vulnerable to abuse or misuse. Note, this is the preferred tag, though the Security tag has a large body of issues tagged to it. Do NOT publicly disclose security vulnerabilities; contact the security team instead. Anyone (whether security team or not) can apply this tag to security improvements that do not directly present a vulnerability e.g. hardening an API to add filtering to reduce a common mistake in contributed modules.

Sign in to follow issues

Comments & Activities

  • Issue created by @mcdruid
  • 🇬🇧United Kingdom mcdruid 🇬🇧🇪🇺

    In the meantime I've submitted a PR to guzzle with a fairly crude way of minimising the impact of the Gadget Chain:

    https://github.com/guzzle/guzzle/pull/3286

    We'll see how that goes, but I don't think it's a reason to stop looking at dropping the class from Drupal.

  • 🇬🇧United Kingdom catch

    If the PR solves the issue but is won't fixed, an option would be to fully replace the class in core via adding it to core and then specifying that in the autoloader. I think we have already done that to backport a guzzle security fix in the past. Don't remember which one at the moment.

  • 🇬🇧United Kingdom mcdruid 🇬🇧🇪🇺

    The issue and linked PR are going nowhere fast.

    I think we should consider replacing the class in Drupal core (per #3), removing it via composer, or removing it from the autoloader if that's feasible.

  • 🇬🇧United Kingdom catch

    I think we could use 📌 Add a classloader that can handle class moves Active now to alias it to an empty class somewhere, or hard-code similar behaviour somewhere earlier. Maybe a valid class that throws exceptions in every method or something?

Production build 0.71.5 2024