PluginProbe ʕ •ᴥ•ʔ
CloudSecure WP Security / 1.4.8
CloudSecure WP Security v1.4.8
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 months ago cli 2 months ago lib 3 months ago captcha.php 3 months ago cloudsecure-wp.php 2 months ago common.php 2 months ago config.php 2 years ago disable-access-system-file.php 4 months ago disable-author-query.php 2 years ago disable-login.php 2 months ago disable-restapi.php 2 months ago disable-xmlrpc.php 1 year ago htaccess.php 3 months ago login-log.php 2 months ago login-notification.php 1 year ago rename-login-page.php 3 months ago restrict-admin-page.php 3 months ago server-error-notification.php 2 months ago two-factor-authentication.php 2 months ago unify-messages.php 2 years ago update-notice.php 7 months ago waf-engine.php 3 months ago waf.php 2 months ago
waf.php
383 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 RULES_CATEGORY = self::KEY_FEATURE . '_rules_category';
14 private const RULES_CATEGORY_VALUES = array( 1, 2, 4, 8, 16 );
15 private const RULES_CATEGORY_NAMES = array(
16 'SQLインジェクション',
17 'クロスサイトスクリプティング',
18 'OSコマンドインジェクション',
19 'コードインジェクション',
20 'メールヘッダインジェクション',
21 );
22 private const TABLE_NAME = 'cloudsecurewp_waf_log';
23 private const COLUMN_ID = 'id';
24 private const COLUMN_ACCESS_AT = 'access_at';
25 private const COLUMN_ATTACK = 'attack';
26 private const COLUMN_URL = 'url';
27 private const COLUMN_MATCHED = 'matched';
28 private const COLUMN_IP = 'ip';
29 private const COLUMNS = array(
30 self::COLUMN_ID => 'ID',
31 self::COLUMN_ACCESS_AT => '日時',
32 self::COLUMN_ATTACK => '攻撃種別',
33 self::COLUMN_URL => '検知したページのURL',
34 self::COLUMN_MATCHED => 'マッチした文字列',
35 self::COLUMN_IP => '攻撃�
36 �IPアドレス',
37 );
38 private const MAX_LOG = 10000;
39 private $config;
40 private $waf_rules;
41
42
43
44 function __construct( array $info, CloudSecureWP_Config $config ) {
45 parent::__construct( $info );
46 $this->config = $config;
47 $this->waf_rules = new CloudSecureWP_Waf_Rules();
48 }
49
50
51 /**
52 * 機能毎のKEY取得
53 *
54 * @return string
55 */
56 public function get_feature_key(): string {
57 return self::KEY_FEATURE;
58 }
59
60
61 /**
62 * 有効無効判定
63 *
64 * @return bool
65 */
66 public function is_enabled(): bool {
67 return $this->config->get( $this->get_feature_key() ) === 't' ? true : false;
68 }
69
70
71 /**
72 * 初期設定値取得
73 *
74 * @return array
75 */
76 public function get_default(): array {
77 $ret = array(
78 self::KEY_FEATURE => 'f',
79 self::KEY_SEND_ADMIN_MAIL => self::SEND_ADMIN_MAIL_VALUES[1],
80 self::KEY_SEND_AT => array(),
81 self::KEY_AVAILABLE_RULES => 31,
82 );
83
84 return $ret;
85 }
86
87
88 /**
89 * 設定定義値取得
90 *
91 * @return array
92 */
93 public function get_constant_settings(): array {
94 $ret = array(
95 self::KEY_SEND_ADMIN_MAIL => self::SEND_ADMIN_MAIL_VALUES,
96 self::KEY_AVAILABLE_RULES => self::RULES_CATEGORY_VALUES,
97 self::RULES_CATEGORY => array(
98 self::RULES_CATEGORY_VALUES[0] => self::RULES_CATEGORY_NAMES[0],
99 self::RULES_CATEGORY_VALUES[1] => self::RULES_CATEGORY_NAMES[1],
100 self::RULES_CATEGORY_VALUES[2] => self::RULES_CATEGORY_NAMES[2],
101 self::RULES_CATEGORY_VALUES[3] => self::RULES_CATEGORY_NAMES[3],
102 self::RULES_CATEGORY_VALUES[4] => self::RULES_CATEGORY_NAMES[4],
103 ),
104 );
105 return $ret;
106 }
107
108
109 /**
110 * 設定値取得
111 *
112 * @return array
113 */
114 public function get_settings(): array {
115 $settings = array();
116 $default = $this->get_default();
117
118 foreach ( $default as $key => $val ) {
119 $settings[ $key ] = $this->config->get( $key );
120 }
121
122 return $settings;
123 }
124
125
126 /**
127 * 設定値保存
128 *
129 * @param array $settings
130 * @return void
131 */
132 public function save_settings( $settings ): void {
133 $default = $this->get_default();
134
135 foreach ( $default as $key => $val ) {
136 $this->config->set( $key, $settings[ $key ] ?? '' );
137 }
138
139 $this->config->save();
140 }
141
142
143 /**
144 * テーブル名取得
145 *
146 * @return string
147 */
148 public function get_table_name(): string {
149 global $wpdb;
150 return $wpdb->prefix . self::TABLE_NAME;
151 }
152
153
154 /**
155 * wafLogテーブルカラム�
156 報取得
157 *
158 * @return array
159 */
160 public function get_cloumns(): array {
161 return self::COLUMNS;
162 }
163
164
165 /**
166 * テーブル作成
167 *
168 * @return void
169 */
170 public function create_table(): void {
171 global $wpdb;
172 $table_name = $this->get_table_name();
173 $table = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table_name ) ) );
174 $charset_collate = $wpdb->get_charset_collate();
175
176 if ( ! is_null( $table ) ) {
177 $sql = "ALTER TABLE {$table_name} ALTER url DROP DEFAULT";
178 $sql2 = "ALTER TABLE {$table_name} ALTER matched DROP DEFAULT";
179 $wpdb->query( $sql );
180 $wpdb->query( $sql2 );
181
182 } else {
183 $sql = "CREATE TABLE {$table_name} (
184 id BIGINT( 20 ) UNSIGNED NOT NULL AUTO_INCREMENT,
185 access_at DATETIME,
186 attack VARCHAR( 255 ) NOT NULL DEFAULT '',
187 url VARCHAR( 32767 ) NOT NULL,
188 matched VARCHAR( 32767 ) NOT NULL,
189 ip VARCHAR( 39 ) NOT NULL DEFAULT '',
190 UNIQUE KEY id ( id )
191 ) {$charset_collate}";
192
193 $wpdb->query( $sql );
194 }
195 }
196
197
198 /**
199 * 攻撃種別の名前を取得
200 *
201 * @param string $attack
202 * @return string
203 */
204 public function attack2name( $attack ): string {
205 $attack_category = $this->get_constant_settings();
206 return $attack_category[ self::RULES_CATEGORY ][ $attack ];
207 }
208
209
210 /**
211 * ログ登録
212 *
213 * @param array $match_results
214 * @return void
215 */
216 public function write_log( $match_results ): void {
217 global $wpdb;
218 $table_name = $this->get_table_name();
219 $max_log = self::MAX_LOG;
220
221 if ( mb_strlen( $match_results['url'], 'UTF-8' ) > 32767 ) {
222 $match_results['url'] = mb_substr( $match_results['url'], 0, 32767, 'UTF-8' );
223 }
224
225 if ( mb_strlen( $match_results['matched'], 'UTF-8' ) > 32767 ) {
226 $match_results['matched'] = mb_substr( $match_results['matched'], 0, 32767, 'UTF-8' );
227 }
228
229 $data = array(
230 self::COLUMN_ACCESS_AT => $match_results['access_at'],
231 self::COLUMN_ATTACK => $this->attack2name( $match_results['attack'] ),
232 self::COLUMN_URL => $match_results['url'],
233 self::COLUMN_MATCHED => $match_results['matched'],
234 self::COLUMN_IP => $match_results['ip'],
235 );
236
237 try {
238 $wpdb->query( 'START TRANSACTION' );
239
240 $result = $wpdb->insert( $table_name, $data );
241 if ( $result === false || ! empty( $wpdb->last_error ) ) {
242 throw new Exception( 'Failed to insert WAF log.' );
243 }
244
245 $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 );
246
247 if ( ! empty( $row ?? array() ) ) {
248 $result = $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}cloudsecurewp_waf_log WHERE id <= %d", $row['id'] ) );
249 if ( $result === false || ! empty( $wpdb->last_error ) ) {
250 throw new Exception( 'Failed to delete old WAF logs.' );
251 }
252 }
253
254 $wpdb->query( 'COMMIT' );
255 } catch ( Exception $e ) {
256 $wpdb->query( 'ROLLBACK' );
257 }
258 }
259
260
261 /**
262 * ログ取得
263 *
264 * @param string $orderby
265 * @param string $order
266 * @param int $per_page
267 * @param int $offset
268 * @return array
269 */
270 public function get_block_history( $orderby, $order, $per_page, $offset ): array {
271 global $wpdb;
272 $table_name = $this->get_table_name();
273 $allowed_orderby = array( 'access_at', 'attack', 'url', 'matched', 'ip' );
274 $orderby = in_array( $orderby, $allowed_orderby, true ) ? $orderby : 'access_at';
275 $order = ( 'asc' === $order ) ? 'ASC' : 'DESC';
276 $sql = "SELECT * FROM {$table_name} ORDER BY {$orderby} {$order} LIMIT %d OFFSET %d";
277
278 return array(
279 $wpdb->get_results( $wpdb->prepare( $sql, $per_page, $offset ), ARRAY_A ),
280 $wpdb->get_var( "SELECT count(*) FROM {$table_name}" ),
281 );
282 }
283
284
285 /**
286 * ブロック通知処理
287 *
288 * @param array $match_results
289 * @return void
290 */
291 public function brock_notice( $match_results ): void {
292 $match_access_at = strtotime( $match_results['access_at'] );
293 $settings = $this->get_settings();
294 $send_at = $settings[ self::KEY_SEND_AT ] ?? array();
295 $tmp_send_at = 0;
296
297 if ( ! empty( $send_at ) && is_array( $send_at ) ) {
298 foreach ( $send_at as $key => $val ) {
299 if ( $match_results['attack'] === $key ) {
300 $tmp_send_at = strtotime( $val );
301 break;
302 }
303 }
304 }
305
306 if ( 60 <= $match_access_at - $tmp_send_at || $tmp_send_at === 0 ) {
307 $subject = 'アクセスをブロックしました [' . $match_results['access_at'] . ']';
308
309 $body = $match_results['access_at'] . ' に「' . $this->attack2name( $match_results['attack'] ) . "」の攻撃をブロックしました。\n\n";
310 $body .= "詳細はこちらからご確認ください。\n";
311 $body .= admin_url( 'admin.php?page=cloudsecurewp_waf&childpage=log' ) . "\n\n";
312 $body .= "--\nCloudSecure WP Security\n";
313
314 $admins = $this->get_admin_users();
315
316 foreach ( $admins as $admin ) {
317 $this->wp_send_mail( $admin->user_email, esc_html( $subject ), esc_html( $body ) );
318 }
319
320 $send_at[ $match_results['attack'] ] = $match_results['access_at'];
321
322 $this->config->set( self::KEY_SEND_AT, $send_at );
323 $this->config->save();
324 }
325 }
326
327
328 public function waf() :void {
329 $settings = $this->get_settings();
330 $waf_rules = $this->waf_rules->get_waf_rules();
331 $locationmatch_rules = $this->waf_rules->get_locationmatch_rules();
332 $remove_rules = array(
333 'ajax_editor' => array('950001', '950901', '950004', '950904', '950006', '950906', '950007', '950907', '950908', '950013', '950019', 'm340095' ),
334 'ajax_customize' => array( '950904', '950906', '950004', '950001', '950007' ),
335 'rest_api' => array( '950004', '950001', '950007', '950006' ),
336 'comment' => array( '950004' ),
337 'cocoon' => array( '950004' ),
338 'emanon' => array( '950004', '950001', '950007' ),
339 'vkexunit' => array( '950004', '950001', '950007' ),
340 'nishiki' => array( '950004', '950001', '950007' ),
341 'swell' => array( '950004', '950001', '950007' ),
342 'woocommerce' => array( '959006' ),
343 );
344
345 $results = $this->waf_engine( $waf_rules, $locationmatch_rules, $settings[ self::KEY_AVAILABLE_RULES ], $remove_rules );
346
347 if ( $results['is_deny'] && $results['is_write_log'] ) {
348 $this->write_log( $results );
349
350 if ( self::SEND_ADMIN_MAIL_VALUES[1] === (int) $settings[ self::KEY_SEND_ADMIN_MAIL ] ) {
351 $this->brock_notice( $results );
352 }
353
354 $this->page403();
355 exit;
356
357 } elseif ( $results['is_write_log'] ) {
358 $this->write_log( $results );
359 }
360 }
361
362 /**
363 * 有効化
364 *
365 * @return void
366 */
367 public function activate(): void {
368 $this->save_settings( $this->get_default() );
369 $this->create_table();
370 }
371
372
373 /**
374 * 無効化
375 *
376 * @return void
377 */
378 public function deactivate(): void {
379 $this->config->set( self::KEY_FEATURE, 'f' );
380 $this->config->save();
381 }
382 }
383