PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-waf / src / class-waf-runner.php
jetpack / jetpack_vendor / automattic / jetpack-waf / src Last commit date
brute-force-protection 8 months ago exceptions 8 months ago abstract-blocked-login-page.php 2 months ago class-brute-force-protection.php 1 month ago class-compatibility.php 1 month ago class-rest-controller.php 8 months ago class-waf-blocked-login-page.php 8 months ago class-waf-blocklog-manager.php 8 months ago class-waf-cli.php 8 months ago class-waf-constants.php 1 year ago class-waf-initializer.php 1 month ago class-waf-operators.php 2 years ago class-waf-request.php 6 months ago class-waf-rules-manager.php 1 year ago class-waf-runner.php 1 month ago class-waf-runtime.php 2 months ago class-waf-standalone-bootstrap.php 1 month ago class-waf-stats.php 1 year ago class-waf-transforms.php 2 years ago functions.php 1 year ago
class-waf-runner.php
441 lines
1 <?php
2 /**
3 * Entrypoint for actually executing the WAF.
4 *
5 * @package automattic/jetpack-waf
6 */
7
8 namespace Automattic\Jetpack\Waf;
9
10 use Automattic\Jetpack\Modules;
11 use Automattic\Jetpack\Status\Host;
12 use Automattic\Jetpack\Waf\Brute_Force_Protection\Brute_Force_Protection;
13
14 /**
15 * Executes the WAF.
16 */
17 class Waf_Runner {
18
19 const WAF_MODULE_NAME = 'waf';
20 const MODE_OPTION_NAME = 'jetpack_waf_mode';
21 const SHARE_DATA_OPTION_NAME = 'jetpack_waf_share_data';
22 const SHARE_DEBUG_DATA_OPTION_NAME = 'jetpack_waf_share_debug_data';
23
24 /**
25 * Run the WAF
26 *
27 * @return void
28 */
29 public static function initialize() {
30 if ( ! self::is_enabled() ) {
31 return;
32 }
33 Waf_Constants::define_mode();
34 Waf_Constants::define_entrypoint();
35 Waf_Constants::define_share_data();
36
37 if ( ! self::is_allowed_mode( JETPACK_WAF_MODE ) ) {
38 return;
39 }
40 // Don't run if in standalone mode
41 if ( function_exists( 'add_action' ) ) {
42 self::add_hooks();
43 Waf_Rules_Manager::add_hooks();
44 Waf_Rules_Manager::schedule_rules_cron();
45 }
46 if ( ! self::did_run() ) {
47 self::run();
48 }
49 }
50
51 /**
52 * Set action hooks
53 *
54 * @return void
55 */
56 public static function add_hooks() {
57 // Register REST routes. Use a static callable so the controller class is not
58 // loaded into memory/opcache on requests that never reach `rest_api_init`.
59 add_action( 'rest_api_init', array( REST_Controller::class, 'register_rest_routes' ) );
60 }
61
62 /**
63 * Did the WAF run yet or not?
64 *
65 * @return bool
66 */
67 public static function did_run() {
68 return defined( 'JETPACK_WAF_RUN' );
69 }
70
71 /**
72 * Determines if the passed $option is one of the allowed WAF operation modes.
73 *
74 * @param string $option The mode option.
75 * @return bool
76 */
77 public static function is_allowed_mode( $option ) {
78 // Normal constants are defined prior to WP_CLI running causing problems for activation
79 if ( defined( 'WAF_CLI_MODE' ) ) {
80 $option = WAF_CLI_MODE;
81 }
82
83 $allowed_modes = array(
84 'normal',
85 'silent',
86 );
87
88 return in_array( $option, $allowed_modes, true );
89 }
90
91 /**
92 * Determines if the WAF is supported in the current environment.
93 *
94 * @since 0.8.0
95 * @return bool
96 */
97 public static function is_supported_environment() {
98 // Do not run when killswitch is enabled
99 if ( defined( 'DISABLE_JETPACK_WAF' ) && DISABLE_JETPACK_WAF ) {
100 return false;
101 }
102
103 if ( defined( 'IS_ATOMIC_JN' ) && IS_ATOMIC_JN ) {
104 return true;
105 }
106
107 // Do not run in the WPCOM context
108 if ( ( new Host() )->is_wpcom_simple() ) {
109 return false;
110 }
111
112 // Do not run on the Atomic platform
113 if ( ( new Host() )->is_atomic_platform() ) {
114 return false;
115 }
116
117 // Do not run on the VIP platform
118 if ( ( new Host() )->is_vip_site() ) {
119 return false;
120 }
121
122 return true;
123 }
124
125 /**
126 * Determines if the WAF module is enabled on the site.
127 *
128 * @return bool
129 */
130 public static function is_enabled() {
131 // if ABSPATH is defined, then WordPress has already been instantiated,
132 // so we can check to see if the waf module is activated.
133 if ( defined( 'ABSPATH' ) ) {
134 return ( new Modules() )->is_active( self::WAF_MODULE_NAME );
135 }
136
137 return true;
138 }
139
140 /**
141 * Enables the WAF module on the site.
142 *
143 * @return bool
144 */
145 public static function enable() {
146 return ( new Modules() )->activate( self::WAF_MODULE_NAME, false, false );
147 }
148
149 /**
150 * Disabled the WAF module on the site.
151 *
152 * @return bool
153 */
154 public static function disable() {
155 return ( new Modules() )->deactivate( self::WAF_MODULE_NAME );
156 }
157
158 /**
159 * Get Config
160 *
161 * @return array The WAF settings and current configuration data.
162 */
163 public static function get_config() {
164 return array(
165 Waf_Rules_Manager::AUTOMATIC_RULES_ENABLED_OPTION_NAME => Waf_Rules_Manager::automatic_rules_enabled(),
166 Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME => get_option( Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME ),
167 Waf_Rules_Manager::IP_ALLOW_LIST_ENABLED_OPTION_NAME => Waf_Rules_Manager::ip_allow_list_enabled(),
168 Waf_Rules_Manager::IP_BLOCK_LIST_OPTION_NAME => get_option( Waf_Rules_Manager::IP_BLOCK_LIST_OPTION_NAME ),
169 Waf_Rules_Manager::IP_BLOCK_LIST_ENABLED_OPTION_NAME => Waf_Rules_Manager::ip_block_list_enabled(),
170 self::SHARE_DATA_OPTION_NAME => get_option( self::SHARE_DATA_OPTION_NAME ),
171 self::SHARE_DEBUG_DATA_OPTION_NAME => get_option( self::SHARE_DEBUG_DATA_OPTION_NAME ),
172 'bootstrap_path' => self::get_bootstrap_file_path(),
173 'standalone_mode' => self::get_standalone_mode_status(),
174 'automatic_rules_available' => (bool) self::automatic_rules_available(),
175 'brute_force_protection' => (bool) Brute_Force_Protection::is_enabled(),
176
177 /**
178 * Provide the deprecated IP lists options for backwards compatibility with older versions of the Jetpack and Protect plugins.
179 * i.e. If one plugin is updated and the other is not, the latest version of this package will be used by both plugins.
180 *
181 * @deprecated 0.17.0
182 */
183 // @phan-suppress-next-line PhanDeprecatedClassConstant -- Needed for backwards compatibility.
184 Waf_Rules_Manager::IP_LISTS_ENABLED_OPTION_NAME => Waf_Rules_Manager::ip_allow_list_enabled() || Waf_Rules_Manager::ip_block_list_enabled(),
185 );
186 }
187
188 /**
189 * Get Bootstrap File Path
190 *
191 * @return string The path to the Jetpack Firewall's bootstrap.php file.
192 */
193 private static function get_bootstrap_file_path() {
194 $bootstrap = new Waf_Standalone_Bootstrap();
195 return $bootstrap->get_bootstrap_file_path();
196 }
197
198 /**
199 * Get WAF standalone mode status
200 *
201 * @return bool|array True if WAF standalone mode is enabled, false otherwise.
202 */
203 public static function get_standalone_mode_status() {
204 return defined( 'JETPACK_WAF_RUN' ) && JETPACK_WAF_RUN === 'preload';
205 }
206
207 /**
208 * Get WAF File Path
209 *
210 * @param string $file The file path starting in the WAF directory.
211 * @return string The full file path to the provided file in the WAF directory.
212 */
213 public static function get_waf_file_path( $file ) {
214 Waf_Constants::define_waf_directory();
215
216 // Ensure the file path starts with a slash.
217 if ( '/' !== substr( $file, 0, 1 ) ) {
218 $file = "/$file";
219 }
220
221 return JETPACK_WAF_DIR . $file;
222 }
223
224 /**
225 * Runs the WAF and potentially stops the request if a problem is found.
226 *
227 * @return void
228 */
229 public static function run() {
230 // Make double-sure we are only running once.
231 if ( self::did_run() ) {
232 return;
233 }
234
235 Waf_Constants::initialize_constants();
236
237 // if ABSPATH is defined, then WordPress has already been instantiated,
238 // and we're running as a plugin (meh). Otherwise, we're running via something
239 // like PHP's prepend_file setting (yay!).
240 define( 'JETPACK_WAF_RUN', defined( 'ABSPATH' ) ? 'plugin' : 'preload' );
241
242 // If the WAF is being run before a command line script, or in any other non-HTTP
243 // context (e.g. server-side cron executed via a PHP wrapper that does not report
244 // PHP_SAPI as 'cli'), there is no HTTP request to evaluate. Skip rule execution so
245 // HTTP-specific rules (e.g. rule 911100, which checks the request method) don't
246 // produce a false-positive 403 block.
247 if ( PHP_SAPI === 'cli' || ! isset( $_SERVER['REQUEST_METHOD'] ) ) {
248 return;
249 }
250
251 // if something terrible happens during the WAF running, we don't want to interfere with the rest of the site,
252 // so we intercept errors ONLY while the WAF is running, then we remove our handler after the WAF finishes.
253 $display_errors = ini_get( 'display_errors' );
254
255 ini_set( 'display_errors', 'Off' ); // phpcs:ignore WordPress.PHP.IniSet.display_errors_Disallowed -- We only customize error reporting while the WAF is running, and remove our handler afterwards.
256
257 set_error_handler( array( self::class, 'errorHandler' ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler -- We only customize error reporting while the WAF is running, and remove our handler afterwards.
258
259 try {
260
261 // phpcs:ignore
262 $waf = new Waf_Runtime( new Waf_Transforms(), new Waf_Operators() );
263
264 // execute waf rules.
265 $rules_file_path = self::get_waf_file_path( JETPACK_WAF_ENTRYPOINT );
266 if ( file_exists( $rules_file_path ) ) {
267 include $rules_file_path;
268 }
269 } catch ( \Exception $err ) { // phpcs:ignore
270 // Intentionally doing nothing.
271 }
272
273 // remove the custom error handler, so we don't interfere with the site.
274 restore_error_handler();
275
276 // Restore the original value.
277 ini_set( 'display_errors', $display_errors ); // phpcs:ignore WordPress.PHP.IniSet.display_errors_Disallowed -- We only customize error reporting while the WAF is running, and remove our handler afterwards.
278 }
279
280 /**
281 * Error handler to be used while the WAF is being executed.
282 *
283 * @param int $code The error code.
284 * @param string $message The error message.
285 * @param string $file File with the error.
286 * @param string $line Line of the error.
287 * @return void
288 */
289 public static function errorHandler( $code, $message, $file, $line ) { // phpcs:ignore
290 // Intentionally doing nothing for now.
291 }
292
293 /**
294 * Initializes the WP filesystem and WAF directory structure.
295 *
296 * @throws File_System_Exception If filesystem is unavailable.
297 *
298 * @return void
299 */
300 public static function initialize_filesystem() {
301 if ( ! function_exists( '\\WP_Filesystem' ) ) {
302 require_once ABSPATH . 'wp-admin/includes/file.php';
303 }
304
305 if ( ! \WP_Filesystem() ) {
306 throw new File_System_Exception( 'No filesystem available.' );
307 }
308
309 self::initialize_waf_directory();
310 }
311
312 /**
313 * Activates the WAF by generating the rules script and setting the version
314 *
315 * @throws Waf_Exception If the firewall mode is invalid.
316 * @throws Waf_Exception If the activation fails.
317 *
318 * @return void
319 */
320 public static function activate() {
321 $version = get_option( Waf_Rules_Manager::VERSION_OPTION_NAME );
322 if ( ! $version ) {
323 add_option( Waf_Rules_Manager::VERSION_OPTION_NAME, Waf_Rules_Manager::RULES_VERSION );
324 }
325
326 add_option( self::SHARE_DATA_OPTION_NAME, true );
327
328 self::initialize_filesystem();
329
330 Waf_Rules_Manager::generate_automatic_rules();
331 Waf_Rules_Manager::generate_ip_rules();
332 Waf_Rules_Manager::generate_rules();
333
334 Waf_Blocklog_Manager::create_blocklog_table();
335 }
336
337 /**
338 * Ensures that the waf directory is created.
339 *
340 * @throws File_System_Exception If filesystem is unavailable.
341 * @throws File_System_Exception If creating the directory fails.
342 *
343 * @return void
344 */
345 public static function initialize_waf_directory() {
346 WP_Filesystem();
347 Waf_Constants::define_waf_directory();
348
349 global $wp_filesystem;
350 if ( ! $wp_filesystem ) {
351 throw new File_System_Exception( 'Cannot work without the file system being initialized.' );
352 }
353
354 if ( ! $wp_filesystem->is_dir( JETPACK_WAF_DIR ) ) {
355 if ( ! $wp_filesystem->mkdir( JETPACK_WAF_DIR ) ) {
356 throw new File_System_Exception( 'Failed creating WAF file directory: ' . JETPACK_WAF_DIR );
357 }
358 }
359 }
360
361 /**
362 * Deactivates the WAF by deleting the relevant options and emptying rules file.
363 *
364 * @throws File_System_Exception If file writing fails.
365 *
366 * @return void
367 */
368 public static function deactivate() {
369 delete_option( self::MODE_OPTION_NAME );
370 delete_option( Waf_Rules_Manager::VERSION_OPTION_NAME );
371
372 global $wp_filesystem;
373 self::initialize_filesystem();
374 Waf_Constants::define_entrypoint();
375
376 // If the rules file doesn't exist, there's nothing else to do.
377 if ( ! $wp_filesystem->exists( self::get_waf_file_path( JETPACK_WAF_ENTRYPOINT ) ) ) {
378 return;
379 }
380
381 // Empty the rules entrypoint file.
382 if ( ! $wp_filesystem->put_contents( self::get_waf_file_path( JETPACK_WAF_ENTRYPOINT ), "<?php\n" ) ) {
383 throw new File_System_Exception( 'Failed to empty rules.php file.' );
384 }
385 }
386
387 /**
388 * Handle updates to the WAF
389 *
390 * @return void
391 */
392 public static function update_waf() {
393 Waf_Rules_Manager::update_rules_if_changed();
394
395 // Re-generate the standalone bootstrap file on every update
396 // TODO: We may consider only doing this when the WAF version changes
397 ( new Waf_Standalone_Bootstrap() )->generate();
398 }
399
400 /**
401 * Check if an automatic rules file is available
402 *
403 * @return bool False if an automatic rules file is not available, true otherwise
404 */
405 public static function automatic_rules_available() {
406 $automatic_rules_last_updated = get_option( Waf_Rules_Manager::AUTOMATIC_RULES_LAST_UPDATED_OPTION_NAME );
407
408 // If we do not have a automatic rules last updated timestamp cached, return false.
409 if ( ! $automatic_rules_last_updated ) {
410 return false;
411 }
412
413 // Validate that the automatic rules file exists and is not empty.
414 global $wp_filesystem;
415
416 try {
417 self::initialize_filesystem();
418 } catch ( Waf_Exception $e ) {
419 return false;
420 }
421
422 $automatic_rules_file_contents = $wp_filesystem->get_contents( self::get_waf_file_path( Waf_Rules_Manager::AUTOMATIC_RULES_FILE ) );
423
424 // If the automatic rules file was removed or is now empty, return false.
425 if ( ! $automatic_rules_file_contents || "<?php\n" === $automatic_rules_file_contents ) {
426
427 // Delete the automatic rules last updated option.
428 delete_option( Waf_Rules_Manager::AUTOMATIC_RULES_LAST_UPDATED_OPTION_NAME );
429
430 // If automatic rules setting is enabled, disable it.
431 if ( Waf_Rules_Manager::automatic_rules_enabled() ) {
432 update_option( Waf_Rules_Manager::AUTOMATIC_RULES_ENABLED_OPTION_NAME, false );
433 }
434
435 return false;
436 }
437
438 return true;
439 }
440 }
441