PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.2-a.3 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-blocklog-manager.php
jetpack / jetpack_vendor / automattic / jetpack-waf / src Last commit date
brute-force-protection 9 months ago exceptions 9 months ago abstract-blocked-login-page.php 2 months ago class-brute-force-protection.php 2 months ago class-compatibility.php 2 months ago class-rest-controller.php 9 months ago class-waf-blocked-login-page.php 9 months ago class-waf-blocklog-manager.php 8 months ago class-waf-cli.php 9 months ago class-waf-constants.php 1 year ago class-waf-initializer.php 2 months 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 2 months ago class-waf-runtime.php 3 months ago class-waf-standalone-bootstrap.php 2 months ago class-waf-stats.php 1 year ago class-waf-transforms.php 2 years ago functions.php 1 year ago
class-waf-blocklog-manager.php
442 lines
1 <?php
2 /**
3 * Blocklog manager for the WAF
4 *
5 * @package automattic/jetpack-waf
6 */
7
8 namespace Automattic\Jetpack\Waf;
9
10 /**
11 * Class used to manage blocklog operations
12 */
13 class Waf_Blocklog_Manager {
14
15 const BLOCKLOG_OPTION_NAME_DAILY_SUMMARY = 'jetpack_waf_blocklog_daily_summary';
16 const BLOCKLOG_OPTION_NAME_ALL_TIME_BLOCK_COUNT = 'jetpack_waf_all_time_block_count';
17
18 /**
19 * Database connection.
20 *
21 * @var \mysqli|null
22 */
23 private static $db_connection = null;
24
25 /**
26 * Gets the path to the waf-blocklog file.
27 *
28 * @return string The waf-blocklog file path.
29 */
30 public static function get_blocklog_file_path() {
31 return trailingslashit( JETPACK_WAF_DIR ) . 'waf-blocklog';
32 }
33
34 /**
35 * Connect to WordPress database.
36 *
37 * @return \mysqli|null
38 */
39 private static function connect_to_wordpress_db() {
40 if ( self::$db_connection !== null ) {
41 return self::$db_connection;
42 }
43
44 if ( ! file_exists( JETPACK_WAF_WPCONFIG ) ) {
45 return null;
46 }
47
48 require_once JETPACK_WAF_WPCONFIG;
49 // @phan-suppress-next-line PhanUndeclaredConstant - These constants are defined in the wp-config file.
50 $conn = new \mysqli( DB_HOST, DB_USER, DB_PASSWORD, DB_NAME ); // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__mysqli
51
52 if ( $conn->connect_error ) {
53 error_log( 'Could not connect to the database:' . $conn->connect_error );
54 return null;
55 }
56
57 self::$db_connection = $conn;
58 return self::$db_connection;
59 }
60
61 /**
62 * Close the database connection.
63 *
64 * @return void
65 */
66 private static function close_db_connection() {
67 if ( self::$db_connection ) {
68 self::$db_connection->close();
69 self::$db_connection = null;
70 }
71 }
72
73 /**
74 * Serialize a value for storage in a WordPress option.
75 *
76 * @param mixed $value The value to serialize.
77 * @return string The serialized value.
78 */
79 private static function serialize_option_value( $value ) {
80 return serialize( $value );
81 }
82
83 /**
84 * Unserialize a value from a WordPress option.
85 *
86 * @param string $value The serialized value.
87 * @return mixed The unserialized value.
88 */
89 private static function unserialize_option_value( string $value ) {
90 return unserialize( $value );
91 }
92
93 /**
94 * Create the log table when plugin is activated.
95 *
96 * @return void
97 */
98 public static function create_blocklog_table() {
99 global $wpdb;
100
101 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
102
103 $sql = "
104 CREATE TABLE {$wpdb->prefix}jetpack_waf_blocklog (
105 log_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
106 timestamp datetime NOT NULL,
107 rule_id BIGINT NOT NULL,
108 reason longtext NOT NULL,
109 PRIMARY KEY (log_id),
110 KEY timestamp (timestamp)
111 )
112 ";
113
114 dbDelta( $sql );
115 }
116
117 /**
118 * Write block logs to database.
119 *
120 * @param array $log_data Log data.
121 *
122 * @return void
123 */
124 private static function write_blocklog_row( $log_data ) {
125 $conn = self::connect_to_wordpress_db();
126
127 if ( ! $conn ) {
128 return;
129 }
130
131 global $table_prefix;
132
133 $statement = $conn->prepare( "INSERT INTO {$table_prefix}jetpack_waf_blocklog(reason,rule_id, timestamp) VALUES (?, ?, ?)" );
134 if ( false !== $statement ) {
135 $statement->bind_param( 'sis', $log_data['reason'], $log_data['rule_id'], $log_data['timestamp'] );
136 $statement->execute();
137
138 if ( $conn->insert_id > 100 ) {
139 $conn->query( "DELETE FROM {$table_prefix}jetpack_waf_blocklog ORDER BY log_id LIMIT 1" );
140 }
141 }
142 }
143
144 /**
145 * Get the daily summary stats from the database.
146 *
147 * @return array The daily summary stats.
148 */
149 private static function get_daily_summary() {
150 global $table_prefix;
151 $db_connection = self::connect_to_wordpress_db();
152 if ( ! $db_connection ) {
153 return array();
154 }
155
156 $result = $db_connection->query( "SELECT option_value FROM {$table_prefix}options WHERE option_name = '" . self::BLOCKLOG_OPTION_NAME_DAILY_SUMMARY . "'" );
157 if ( ! $result ) {
158 return array();
159 }
160
161 $row = $result->fetch_assoc();
162 if ( ! $row ) {
163 return array();
164 }
165
166 $daily_summary = self::unserialize_option_value( $row['option_value'] );
167 $result->free();
168
169 return is_array( $daily_summary ) ? $daily_summary : array();
170 }
171
172 /**
173 * Increments the current date's daily summary stat.
174 *
175 * @param array $current_value The current value of the daily summary.
176 *
177 * @return array The updated daily summary.
178 */
179 public static function increment_daily_summary( array $current_value ) {
180 $date = gmdate( 'Y-m-d' );
181 $value = intval( $current_value[ $date ] ?? 0 );
182 $current_value[ $date ] = $value + 1;
183
184 return $current_value;
185 }
186
187 /**
188 * Update the daily summary option in the database.
189 *
190 * @param array $value The value to update.
191 *
192 * @return void
193 */
194 private static function write_daily_summary_row( array $value ) {
195 global $table_prefix;
196 $option_name = self::BLOCKLOG_OPTION_NAME_DAILY_SUMMARY;
197
198 $db_connection = self::connect_to_wordpress_db();
199 if ( ! $db_connection ) {
200 return;
201 }
202
203 $updated_value = self::serialize_option_value( $value );
204
205 $statement = $db_connection->prepare( "INSERT INTO {$table_prefix}options (option_name, option_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE option_value = ?" );
206 if ( false !== $statement ) {
207 $statement->bind_param( 'sss', $option_name, $updated_value, $updated_value );
208 $statement->execute();
209 }
210 }
211
212 /**
213 * Update the daily summary stats for the current date.
214 *
215 * @return void
216 */
217 private static function write_daily_summary() {
218 $stats = self::get_daily_summary();
219 $stats = self::increment_daily_summary( $stats );
220 $stats = self::filter_last_30_days( $stats );
221
222 self::write_daily_summary_row( $stats );
223 }
224
225 /**
226 * Get the all-time block count value from the database.
227 *
228 * @return int The all-time block count.
229 */
230 private static function get_all_time_block_count_value() {
231 global $table_prefix;
232 $db_connection = self::connect_to_wordpress_db();
233 if ( ! $db_connection ) {
234 return 0;
235 }
236
237 $result = $db_connection->query( "SELECT option_value FROM {$table_prefix}options WHERE option_name = '" . self::BLOCKLOG_OPTION_NAME_ALL_TIME_BLOCK_COUNT . "'" );
238 if ( ! $result ) {
239 return 0;
240 }
241
242 $row = $result->fetch_assoc();
243 if ( ! $row ) {
244 return 0;
245 }
246
247 $all_time_block_count = intval( $row['option_value'] );
248 $result->free();
249
250 return $all_time_block_count;
251 }
252
253 /**
254 * Update the all-time block count value in the database.
255 *
256 * @param int $value The value to update.
257 * @return void
258 */
259 private static function write_all_time_block_count_row( int $value ) {
260 global $table_prefix;
261 $option_name = self::BLOCKLOG_OPTION_NAME_ALL_TIME_BLOCK_COUNT;
262
263 $db_connection = self::connect_to_wordpress_db();
264 if ( ! $db_connection ) {
265 return;
266 }
267
268 $statement = $db_connection->prepare( "INSERT INTO {$table_prefix}options (option_name, option_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE option_value = ?" );
269 if ( false !== $statement ) {
270 $statement->bind_param( 'sii', $option_name, $value, $value );
271 $statement->execute();
272 }
273 }
274
275 /**
276 * Increment the all-time stats.
277 *
278 * @return void
279 */
280 private static function write_all_time_block_count() {
281 $block_count = self::get_all_time_block_count_value();
282 if ( ! $block_count ) {
283 $block_count = self::get_default_all_time_stat_value();
284 }
285
286 self::write_all_time_block_count_row( $block_count + 1 );
287 }
288
289 /**
290 * Filters the stats to retain only data for the last 30 days.
291 *
292 * @param array $stats The array of stats to prune.
293 *
294 * @return array Pruned stats array.
295 */
296 public static function filter_last_30_days( array $stats ) {
297 $today = gmdate( 'Y-m-d' );
298 $one_month_ago = gmdate( 'Y-m-d', strtotime( '-30 days' ) );
299
300 return array_filter(
301 $stats,
302 function ( $date ) use ( $one_month_ago, $today ) {
303 return $date >= $one_month_ago && $date <= $today;
304 },
305 ARRAY_FILTER_USE_KEY
306 );
307 }
308
309 /**
310 * Get the total number of blocked requests for today.
311 *
312 * @return int
313 */
314 public static function get_current_day_block_count() {
315 $stats = get_option( self::BLOCKLOG_OPTION_NAME_DAILY_SUMMARY, array() );
316 $today = gmdate( 'Y-m-d' );
317
318 return $stats[ $today ] ?? 0;
319 }
320
321 /**
322 * Get the total number of blocked requests for last thirty days.
323 *
324 * @return int
325 */
326 public static function get_thirty_days_block_counts() {
327 $stats = get_option( self::BLOCKLOG_OPTION_NAME_DAILY_SUMMARY, array() );
328 $total_blocks = 0;
329
330 foreach ( $stats as $count ) {
331 $total_blocks += intval( $count );
332 }
333
334 return $total_blocks;
335 }
336
337 /**
338 * Get the total number of blocked requests for all time.
339 *
340 * @return int
341 */
342 public static function get_all_time_block_count() {
343 $all_time_block_count = get_option( self::BLOCKLOG_OPTION_NAME_ALL_TIME_BLOCK_COUNT, false );
344
345 if ( false !== $all_time_block_count ) {
346 return intval( $all_time_block_count );
347 }
348
349 return self::get_default_all_time_stat_value();
350 }
351
352 /**
353 * Compute the initial all-time stats value.
354 *
355 * @return int The initial all-time stats value.
356 */
357 private static function get_default_all_time_stat_value() {
358 $conn = self::connect_to_wordpress_db();
359 if ( ! $conn ) {
360 return 0;
361 }
362
363 global $table_prefix;
364
365 $last_log_id_result = $conn->query( "SELECT log_id FROM {$table_prefix}jetpack_waf_blocklog ORDER BY log_id DESC LIMIT 1" );
366
367 $all_time_block_count = 0;
368
369 if ( $last_log_id_result && $last_log_id_result->num_rows > 0 ) {
370 $row = $last_log_id_result->fetch_assoc();
371 if ( $row !== null && isset( $row['log_id'] ) ) {
372 $all_time_block_count = $row['log_id'];
373 }
374 }
375
376 return intval( $all_time_block_count );
377 }
378
379 /**
380 * Get the headers for logging purposes.
381 *
382 * @return array The headers.
383 */
384 public static function get_request_headers() {
385 $all_headers = getallheaders();
386 $exclude_headers = array( 'Authorization', 'Cookie', 'Proxy-Authorization', 'Set-Cookie' );
387
388 foreach ( $exclude_headers as $header ) {
389 unset( $all_headers[ $header ] );
390 }
391
392 return $all_headers;
393 }
394
395 /**
396 * Write block logs. We won't write to the file if it exceeds 100 mb.
397 *
398 * @param string $rule_id The rule ID that triggered the block.
399 * @param string $reason The reason for the block.
400 *
401 * @return void
402 */
403 public static function write_blocklog( $rule_id, $reason ) {
404 $log_data = array();
405 $log_data['rule_id'] = $rule_id;
406 $log_data['reason'] = $reason;
407 $log_data['timestamp'] = gmdate( 'Y-m-d H:i:s' );
408 $log_data['request_uri'] = isset( $_SERVER['REQUEST_URI'] ) ? \stripslashes( $_SERVER['REQUEST_URI'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
409 $log_data['user_agent'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? \stripslashes( $_SERVER['HTTP_USER_AGENT'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
410 $log_data['referer'] = isset( $_SERVER['HTTP_REFERER'] ) ? \stripslashes( $_SERVER['HTTP_REFERER'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
411 $log_data['content_type'] = isset( $_SERVER['CONTENT_TYPE'] ) ? \stripslashes( $_SERVER['CONTENT_TYPE'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
412 $log_data['get_params'] = json_encode( $_GET, JSON_UNESCAPED_SLASHES );
413
414 if ( defined( 'JETPACK_WAF_SHARE_DEBUG_DATA' ) && JETPACK_WAF_SHARE_DEBUG_DATA ) {
415 $log_data['post_params'] = json_encode( $_POST, JSON_UNESCAPED_SLASHES );
416 $log_data['headers'] = self::get_request_headers();
417 }
418
419 if ( defined( 'JETPACK_WAF_SHARE_DATA' ) && JETPACK_WAF_SHARE_DATA ) {
420 $file_path = JETPACK_WAF_DIR . '/waf-blocklog';
421 $file_exists = file_exists( $file_path );
422
423 if ( ! $file_exists || filesize( $file_path ) < ( 100 * 1024 * 1024 ) ) {
424 $fp = fopen( $file_path, 'a+' );
425
426 if ( $fp ) {
427 try {
428 fwrite( $fp, json_encode( $log_data, JSON_UNESCAPED_SLASHES ) . "\n" );
429 } finally {
430 fclose( $fp );
431 }
432 }
433 }
434 }
435
436 self::write_daily_summary();
437 self::write_all_time_block_count();
438 self::write_blocklog_row( $log_data );
439 self::close_db_connection();
440 }
441 }
442