PluginProbe ʕ •ᴥ•ʔ
CloudSecure WP Security / 1.4.13
CloudSecure WP Security v1.4.13
1.4.14 1.4.13 1.4.12 1.4.11 1.4.10 1.4.9 trunk 0.9.0 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.3.1 1.3.10 1.3.11 1.3.12 1.3.13 1.3.14 1.3.15 1.3.16 1.3.17 1.3.18 1.3.19 1.3.2 1.3.20 1.3.21 1.3.22 1.3.23 1.3.24 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8
cloudsecure-wp-security / modules / waf.php
cloudsecure-wp-security / modules Last commit date
admin 2 weeks ago cli 2 weeks ago lib 1 month ago captcha.php 2 weeks ago cloudsecure-wp.php 2 weeks ago common.php 4 months ago config.php 2 years ago disable-access-system-file.php 1 month ago disable-author-query.php 2 weeks ago disable-login.php 1 month ago disable-restapi.php 2 weeks ago disable-xmlrpc.php 1 year ago htaccess.php 4 months ago login-log.php 1 month ago login-notification.php 3 months ago protect-rest-batch.php 1 month ago rename-login-page.php 4 months ago restrict-admin-page.php 3 months ago server-error-notification.php 3 months ago two-factor-authentication.php 3 months ago unify-messages.php 2 years ago update-notice.php 9 months ago waf-engine.php 1 month ago waf.php 1 month ago
waf.php
477 lines
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit;
5 }
6
7 class CloudSecureWP_Waf extends CloudSecureWP_Waf_Engine {
8 private const KEY_FEATURE = 'waf';
9 private const KEY_SEND_ADMIN_MAIL = self::KEY_FEATURE . '_send_admin_mail';
10 private const SEND_ADMIN_MAIL_VALUES = array( 1, 2 ); // 無効、有効 .
11 private const KEY_SEND_AT = self::KEY_FEATURE . '_send_at';
12 private const KEY_AVAILABLE_RULES = self::KEY_FEATURE . '_available_rules';
13 private const KEY_DENY_BACKTRACK_ERROR = self::KEY_FEATURE . '_backtrack_error';
14 private const RULES_CATEGORY = self::KEY_FEATURE . '_rules_category';
15 private const RULES_CATEGORY_VALUES = array( 1, 2, 4, 8, 16 );
16 private const RULES_CATEGORY_NAMES = array(
17 'SQLインジェクション',
18 'クロスサイトスクリプティング',
19 'OSコマンドインジェクション',
20 'コードインジェクション',
21 'メールヘッダインジェクション',
22 );
23 private const BACKTRACK_ATTACK_NAME = '検査上限エラー';
24 private const TABLE_NAME = 'cloudsecurewp_waf_log';
25 private const COLUMN_ID = 'id';
26 private const COLUMN_ACCESS_AT = 'access_at';
27 private const COLUMN_ATTACK = 'attack';
28 private const COLUMN_URL = 'url';
29 private const COLUMN_MATCHED = 'matched';
30 private const COLUMN_IP = 'ip';
31 private const COLUMNS = array(
32 self::COLUMN_ID => 'ID',
33 self::COLUMN_ACCESS_AT => '日時',
34 self::COLUMN_ATTACK => '検知�
35 ',
36 self::COLUMN_URL => '検知されたページのURL',
37 self::COLUMN_MATCHED => '検知されたデータ',
38 self::COLUMN_IP => 'アクセス�
39 �IPアドレス',
40 );
41 private const MAX_LOG = 10000;
42 private $config;
43 private $waf_rules;
44
45
46
47 function __construct( array $info, CloudSecureWP_Config $config ) {
48 parent::__construct( $info );
49 $this->config = $config;
50 $this->waf_rules = new CloudSecureWP_Waf_Rules();
51 }
52
53
54 /**
55 * 機能毎のKEY取得
56 *
57 * @return string
58 */
59 public function get_feature_key(): string {
60 return self::KEY_FEATURE;
61 }
62
63
64 /**
65 * 有効無効判定
66 *
67 * @return bool
68 */
69 public function is_enabled(): bool {
70 return $this->config->get( $this->get_feature_key() ) === 't' ? true : false;
71 }
72
73
74 /**
75 * 初期設定値取得
76 *
77 * @return array
78 */
79 public function get_default(): array {
80 $ret = array(
81 self::KEY_FEATURE => 'f',
82 self::KEY_SEND_ADMIN_MAIL => self::SEND_ADMIN_MAIL_VALUES[1],
83 self::KEY_SEND_AT => array(),
84 self::KEY_AVAILABLE_RULES => 31,
85 self::KEY_DENY_BACKTRACK_ERROR => '1',
86 );
87
88 return $ret;
89 }
90
91
92 /**
93 * 設定定義値取得
94 *
95 * @return array
96 */
97 public function get_constant_settings(): array {
98 $ret = array(
99 self::KEY_SEND_ADMIN_MAIL => self::SEND_ADMIN_MAIL_VALUES,
100 self::KEY_AVAILABLE_RULES => self::RULES_CATEGORY_VALUES,
101 self::RULES_CATEGORY => array(
102 self::RULES_CATEGORY_VALUES[0] => self::RULES_CATEGORY_NAMES[0],
103 self::RULES_CATEGORY_VALUES[1] => self::RULES_CATEGORY_NAMES[1],
104 self::RULES_CATEGORY_VALUES[2] => self::RULES_CATEGORY_NAMES[2],
105 self::RULES_CATEGORY_VALUES[3] => self::RULES_CATEGORY_NAMES[3],
106 self::RULES_CATEGORY_VALUES[4] => self::RULES_CATEGORY_NAMES[4],
107 ),
108 );
109 return $ret;
110 }
111
112
113 /**
114 * 設定値取得
115 *
116 * @return array
117 */
118 public function get_settings(): array {
119 $settings = array();
120 $default = $this->get_default();
121
122 foreach ( $default as $key => $val ) {
123 $settings[ $key ] = $this->config->get( $key );
124 }
125
126 return $settings;
127 }
128
129
130 /**
131 * 設定値保存
132 *
133 * @param array $settings
134 * @return void
135 */
136 public function save_settings( $settings ): void {
137 $default = $this->get_default();
138
139 foreach ( $default as $key => $val ) {
140 $this->config->set( $key, $settings[ $key ] ?? '' );
141 }
142
143 $this->config->save();
144 }
145
146
147 /**
148 * テーブル名取得
149 *
150 * @return string
151 */
152 public function get_table_name(): string {
153 global $wpdb;
154 return $wpdb->prefix . self::TABLE_NAME;
155 }
156
157
158 /**
159 * wafLogテーブルカラム�
160 報取得
161 *
162 * @return array
163 */
164 public function get_cloumns(): array {
165 return self::COLUMNS;
166 }
167
168
169 /**
170 * テーブル作成
171 *
172 * @return void
173 */
174 public function create_table(): void {
175 global $wpdb;
176 $table_name = $this->get_table_name();
177 $table = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table_name ) ) );
178 $charset_collate = $wpdb->get_charset_collate();
179
180 if ( ! is_null( $table ) ) {
181 $sql = "ALTER TABLE {$table_name} ALTER url DROP DEFAULT";
182 $sql2 = "ALTER TABLE {$table_name} ALTER matched DROP DEFAULT";
183 $wpdb->query( $sql );
184 $wpdb->query( $sql2 );
185
186 } else {
187 $sql = "CREATE TABLE {$table_name} (
188 id BIGINT( 20 ) UNSIGNED NOT NULL AUTO_INCREMENT,
189 access_at DATETIME,
190 attack VARCHAR( 255 ) NOT NULL DEFAULT '',
191 url VARCHAR( 32767 ) NOT NULL,
192 matched VARCHAR( 32767 ) NOT NULL,
193 ip VARCHAR( 39 ) NOT NULL DEFAULT '',
194 UNIQUE KEY id ( id )
195 ) {$charset_collate}";
196
197 $wpdb->query( $sql );
198 }
199 }
200
201
202 /**
203 * 攻撃種別の名前を取得
204 *
205 * @param string $attack
206 * @return string
207 */
208 public function attack2name( $attack ): string {
209 $attack_category = $this->get_constant_settings();
210 return $attack_category[ self::RULES_CATEGORY ][ $attack ];
211 }
212
213
214 /**
215 * ログ登録
216 *
217 * @param array $match_results
218 * @return void
219 */
220 public function write_log( $match_results ): void {
221 global $wpdb;
222 $table_name = $this->get_table_name();
223 $max_log = self::MAX_LOG;
224
225 if ( mb_strlen( $match_results['url'], 'UTF-8' ) > 32767 ) {
226 $match_results['url'] = mb_substr( $match_results['url'], 0, 32767, 'UTF-8' );
227 }
228
229 if ( mb_strlen( $match_results['matched'], 'UTF-8' ) > 32767 ) {
230 $match_results['matched'] = mb_substr( $match_results['matched'], 0, 32767, 'UTF-8' );
231 }
232
233 // attack_nameが指定されている場合はそのまま使用、それ以外はattack値から変換
234 $attack_name = isset( $match_results['attack_name'] )
235 ? $match_results['attack_name']
236 : $this->attack2name( $match_results['attack'] );
237
238 $data = array(
239 self::COLUMN_ACCESS_AT => $match_results['access_at'],
240 self::COLUMN_ATTACK => $attack_name,
241 self::COLUMN_URL => $match_results['url'],
242 self::COLUMN_MATCHED => $match_results['matched'],
243 self::COLUMN_IP => $match_results['ip'],
244 );
245
246 try {
247 $wpdb->query( 'START TRANSACTION' );
248
249 $result = $wpdb->insert( $table_name, $data );
250 if ( $result === false || ! empty( $wpdb->last_error ) ) {
251 throw new Exception( 'Failed to insert WAF log.' );
252 }
253
254 $row = $wpdb->get_row( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}cloudsecurewp_waf_log ORDER BY id DESC LIMIT 1 OFFSET %d", $max_log ), ARRAY_A );
255
256 if ( ! empty( $row ?? array() ) ) {
257 $result = $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}cloudsecurewp_waf_log WHERE id <= %d", $row['id'] ) );
258 if ( $result === false || ! empty( $wpdb->last_error ) ) {
259 throw new Exception( 'Failed to delete old WAF logs.' );
260 }
261 }
262
263 $wpdb->query( 'COMMIT' );
264 } catch ( Exception $e ) {
265 $wpdb->query( 'ROLLBACK' );
266 }
267 }
268
269
270 /**
271 * ログ取得
272 *
273 * @param string $orderby
274 * @param string $order
275 * @param int $per_page
276 * @param int $offset
277 * @return array
278 */
279 public function get_block_history( $orderby, $order, $per_page, $offset ): array {
280 global $wpdb;
281 $table_name = $this->get_table_name();
282 $allowed_orderby = array( 'access_at', 'attack', 'url', 'matched', 'ip' );
283 $orderby = in_array( $orderby, $allowed_orderby, true ) ? $orderby : 'access_at';
284 $order = ( 'asc' === $order ) ? 'ASC' : 'DESC';
285 $sql = $wpdb->prepare( "SELECT * FROM {$table_name} ORDER BY {$orderby} {$order} LIMIT %d OFFSET %d", $per_page, $offset );
286
287 return array(
288 $wpdb->get_results( $sql, ARRAY_A ),
289 $wpdb->get_var( "SELECT count(*) FROM {$table_name}" ),
290 );
291 }
292
293
294 /**
295 * バックトラック�
296 過エラーの通知処理
297 *
298 * @param array $results waf_engine から返された結果�
299 �列
300 * @return void
301 */
302 public function block_notice_backtrack( $results ): void {
303 $match_access_at = strtotime( $results['access_at'] );
304 $settings = $this->get_settings();
305 $send_at = $settings[ self::KEY_SEND_AT ] ?? array();
306 $tmp_send_at = 0;
307 $throttle_key = 'backtrack_error';
308
309 if ( ! empty( $send_at ) && is_array( $send_at ) ) {
310 foreach ( $send_at as $key => $val ) {
311 if ( $throttle_key === $key ) {
312 $tmp_send_at = strtotime( $val );
313 break;
314 }
315 }
316 }
317
318 if ( 60 <= $match_access_at - $tmp_send_at || $tmp_send_at === 0 ) {
319 $subject = 'アクセスをブロックしました [' . $results['access_at'] . ']';
320
321 $body = $results['access_at'] . ' にWAFの検査で「' . self::BACKTRACK_ATTACK_NAME . "」となったアクセスをブロックしました。\n\n";
322 $body .= "詳細はこちらからご確認ください。\n";
323 $body .= admin_url( 'admin.php?page=cloudsecurewp_waf&childpage=log' ) . "\n\n";
324 $body .= "--\nCloudSecure WP Security\n";
325
326 $admins = $this->get_admin_users();
327
328 foreach ( $admins as $admin ) {
329 $this->wp_send_mail( $admin->user_email, esc_html( $subject ), esc_html( $body ) );
330 }
331
332 $send_at[ $throttle_key ] = $results['access_at'];
333
334 $this->config->set( self::KEY_SEND_AT, $send_at );
335 $this->config->save();
336 }
337 }
338
339
340 /**
341 * ブロック通知処理
342 *
343 * @param array $match_results
344 * @return void
345 */
346 public function block_notice( $match_results ): void {
347 $match_access_at = strtotime( $match_results['access_at'] );
348 $settings = $this->get_settings();
349 $send_at = $settings[ self::KEY_SEND_AT ] ?? array();
350 $tmp_send_at = 0;
351
352 if ( ! empty( $send_at ) && is_array( $send_at ) ) {
353 foreach ( $send_at as $key => $val ) {
354 if ( $match_results['attack'] === $key ) {
355 $tmp_send_at = strtotime( $val );
356 break;
357 }
358 }
359 }
360
361 if ( 60 <= $match_access_at - $tmp_send_at || $tmp_send_at === 0 ) {
362 $subject = 'アクセスをブロックしました [' . $match_results['access_at'] . ']';
363
364 $body = $match_results['access_at'] . ' に「' . $this->attack2name( $match_results['attack'] ) . "」の攻撃をブロックしました。\n\n";
365 $body .= "詳細はこちらからご確認ください。\n";
366 $body .= admin_url( 'admin.php?page=cloudsecurewp_waf&childpage=log' ) . "\n\n";
367 $body .= "--\nCloudSecure WP Security\n";
368
369 $admins = $this->get_admin_users();
370
371 foreach ( $admins as $admin ) {
372 $this->wp_send_mail( $admin->user_email, esc_html( $subject ), esc_html( $body ) );
373 }
374
375 $send_at[ $match_results['attack'] ] = $match_results['access_at'];
376
377 $this->config->set( self::KEY_SEND_AT, $send_at );
378 $this->config->save();
379 }
380 }
381
382
383 public function waf() :void {
384 $settings = $this->get_settings();
385 $waf_rules = $this->waf_rules->get_waf_rules();
386 $locationmatch_rules = $this->waf_rules->get_locationmatch_rules();
387 $remove_rules = array(
388 'ajax_editor' => array('950001', '950901', '950004', '950904', '950006', '950906', '950007', '950907', '950908', '950013', '950019', 'm340095' ),
389 'ajax_customize' => array( '950904', '950906', '950004', '950001', '950007' ),
390 'rest_api' => array( '950004', '950001', '950007', '950006' ),
391 'rest_api_search' => array( '950007', '950904', '950001', '950901', '950906', '950908', '950004', '950006', '950907', '950013', '950019', 'm340095' ),
392 'comment' => array( '950004' ),
393 'cocoon' => array( '950004' ),
394 'emanon' => array( '950004', '950001', '950007' ),
395 'vkexunit' => array( '950004', '950001', '950007' ),
396 'nishiki' => array( '950004', '950001', '950007' ),
397 'swell' => array( '950004', '950001', '950007' ),
398 'woocommerce' => array( '959006' ),
399 'rename_login_page' => array(
400 'login_page_name' => $this->config->get( 'rename_login_page' ) === 't' ? $this->config->get( 'rename_login_page_name' ) : '',
401 'rule_ids' => array(
402 '950007', '959007', '950904', '959904', '950001', '959001',
403 '950906', '959906', '950908', '959908', '950004', '959004',
404 '950006', '959006', '950907', '959907', '950013', '959013',
405 ),
406 ),
407 );
408
409 // 未設定(移行前等で空文字)の場合は安�
410 �側の '1'(遮断)にフォールバックする
411 $deny_on_backtrack_error = $settings[ self::KEY_DENY_BACKTRACK_ERROR ];
412 if ( '' === $deny_on_backtrack_error ) {
413 $deny_on_backtrack_error = '1';
414 }
415
416 $results = $this->waf_engine( $waf_rules, $locationmatch_rules, $settings[ self::KEY_AVAILABLE_RULES ], $remove_rules, $deny_on_backtrack_error );
417
418 if ( $results['is_deny'] && $results['is_write_log'] ) {
419 if ( ! empty( $results['is_backtrack_error'] ) ) {
420 $results['attack_name'] = self::BACKTRACK_ATTACK_NAME;
421 }
422 $this->write_log( $results );
423
424 if ( self::SEND_ADMIN_MAIL_VALUES[1] === (int) $settings[ self::KEY_SEND_ADMIN_MAIL ] ) {
425 if ( ! empty( $results['is_backtrack_error'] ) ) {
426 $this->block_notice_backtrack( $results );
427 } else {
428 $this->block_notice( $results );
429 }
430 }
431
432 $this->page403();
433 exit;
434
435 } elseif ( $results['is_write_log'] ) {
436 if ( ! empty( $results['is_backtrack_error'] ) ) {
437 $results['attack_name'] = self::BACKTRACK_ATTACK_NAME;
438 }
439 $this->write_log( $results );
440 }
441 }
442
443 /**
444 * 有効化
445 *
446 * @return void
447 */
448 public function activate(): void {
449 $this->save_settings( $this->get_default() );
450 $this->create_table();
451 }
452
453
454 /**
455 * v1.4.12: 検査上限�
456 過時の扱いのデフォルト値を移行する
457 * WAFの有効・無効にかかわらず1(遮断)を設定する
458 *
459 * @return void
460 */
461 public function migrate_waf_backtrack_error_default(): void {
462 $this->config->set( self::KEY_DENY_BACKTRACK_ERROR, '1' );
463 $this->config->save();
464 }
465
466
467 /**
468 * 無効化
469 *
470 * @return void
471 */
472 public function deactivate(): void {
473 $this->config->set( self::KEY_FEATURE, 'f' );
474 $this->config->save();
475 }
476 }
477