PluginProbe ʕ •ᴥ•ʔ
CloudSecure WP Security / 1.3.20
CloudSecure WP Security v1.3.20
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 7 months ago cli 8 months ago lib 11 months ago captcha.php 2 years ago cloudsecure-wp.php 8 months ago common.php 1 year ago config.php 2 years ago disable-access-system-file.php 1 year ago disable-author-query.php 2 years ago disable-login.php 1 year ago disable-restapi.php 7 months ago disable-xmlrpc.php 1 year ago htaccess.php 1 year ago login-log.php 1 year ago login-notification.php 1 year ago rename-login-page.php 1 year ago restrict-admin-page.php 10 months ago server-error-notification.php 1 year ago two-factor-authentication.php 10 months ago unify-messages.php 2 years ago update-notice.php 7 months ago waf-engine.php 7 months ago waf.php 1 year ago
waf.php
370 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_VARUES = 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 => 63,
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_VARUES,
97 self::RULES_CATEGORY => array(
98 self::RULES_CATEGORY_VARUES[0] => self::RULES_CATEGORY_NAMES[0],
99 self::RULES_CATEGORY_VARUES[1] => self::RULES_CATEGORY_NAMES[1],
100 self::RULES_CATEGORY_VARUES[2] => self::RULES_CATEGORY_NAMES[2],
101 self::RULES_CATEGORY_VARUES[3] => self::RULES_CATEGORY_NAMES[3],
102 self::RULES_CATEGORY_VARUES[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( "SHOW TABLES 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 $wpdb->query( 'START TRANSACTION' );
238 $wpdb->insert( $table_name, $data );
239 $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 );
240
241 if ( ! empty( $row ?? array() ) ) {
242 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}cloudsecurewp_waf_log WHERE id <= %d", $row['id'] ) );
243 }
244
245 $wpdb->query( 'COMMIT' );
246 }
247
248
249 /**
250 * ログ取得
251 *
252 * @param string $orderby
253 * @param string $order
254 * @param int $per_page
255 * @param int $offset
256 * @return array
257 */
258 public function get_block_history( $orderby, $order, $per_page, $offset ): array {
259 global $wpdb;
260 $table_name = $this->get_table_name();
261 $sanitized_orderby = sanitize_sql_orderby( "$orderby $order" );
262 $sql = "SELECT * FROM {$table_name} ORDER BY {$sanitized_orderby} LIMIT %d OFFSET %d";
263
264 return array(
265 $wpdb->get_results( $wpdb->prepare( $sql, $per_page, $offset ), ARRAY_A ),
266 $wpdb->get_var( "SELECT count(*) FROM {$table_name}" ),
267 );
268 }
269
270
271 /**
272 * ブロック通知処理
273 *
274 * @param array $match_results
275 * @return void
276 */
277 public function brock_notice( $match_results ): void {
278 $match_access_at = strtotime( $match_results['access_at'] );
279 $settings = $this->get_settings();
280 $send_at = $settings[ self::KEY_SEND_AT ] ?? array();
281
282 if ( ! empty( $send_at ) && is_array( $send_at ) ) {
283 foreach ( $send_at as $key => $val ) {
284 if ( $match_results['attack'] === $key ) {
285 $tmp_send_at = strtotime( $val );
286 break;
287 }
288 }
289 } else {
290 $tmp_send_at = 0;
291 }
292
293 if ( 60 <= $match_access_at - $tmp_send_at || $tmp_send_at === 0 ) {
294 $subject = 'アクセスをブロックしました [' . $match_results['access_at'] . ']';
295
296 $body = $match_results['access_at'] . ' に「' . $this->attack2name( $match_results['attack'] ) . "」の攻撃をブロックしました。\n\n";
297 $body .= "詳細はこちらからご確認ください。\n";
298 $body .= admin_url( 'admin.php?page=cloudsecurewp_waf&childpage=log' ) . "\n\n";
299 $body .= "--\nCloudSecure WP Security\n";
300
301 $admins = $this->get_admin_users();
302
303 foreach ( $admins as $admin ) {
304 $this->wp_send_mail( $admin->user_email, esc_html( $subject ), esc_html( $body ) );
305 }
306
307 $send_at[ $match_results['attack'] ] = $match_results['access_at'];
308
309 $this->config->set( self::KEY_SEND_AT, $send_at );
310 $this->config->save();
311 }
312 }
313
314
315 public function waf() :void {
316 $settings = $this->get_settings();
317 $waf_rules = $this->waf_rules->get_waf_rules();
318 $locationmatch_rules = $this->waf_rules->get_locationmatch_rules();
319 $remove_rules = array(
320 'ajax_editor' => array('950001', '950901', '950004', '950904', '950006', '950906', '950007', '950907', '950908', '950013', '950019', 'm340095' ),
321 'ajax_customize' => array( '950904', '950906', '950004', '950001', '950007' ),
322 'rest_api' => array( '950004', '950001', '950007', '950006' ),
323 'comment' => array( '950004' ),
324 'coccon' => array( '950004' ),
325 'emanon' => array( '950004', '950001', '950007' ),
326 'vkexunit' => array( '950004', '950001', '950007' ),
327 'nishiki' => array( '950004', '950001', '950007' ),
328 'swell' => array( '950004', '950001', '950007' ),
329 'woocommerce' => array( '959006' ),
330 );
331
332 $results = $this->waf_engine( $waf_rules, $locationmatch_rules, $settings[ self::KEY_AVAILABLE_RULES ], $remove_rules );
333
334 if ( $results['is_deny'] && $results['is_write_log'] ) {
335 $this->write_log( $results );
336
337 if ( self::SEND_ADMIN_MAIL_VALUES[1] === (int) $settings[ self::KEY_SEND_ADMIN_MAIL ] ) {
338 $this->brock_notice( $results );
339 }
340
341 $this->page403();
342 exit;
343
344 } elseif ( $results['is_write_log'] ) {
345 $this->write_log( $results );
346 }
347 }
348
349 /**
350 * 有効化
351 *
352 * @return void
353 */
354 public function activate(): void {
355 $this->save_settings( $this->get_default() );
356 $this->create_table();
357 }
358
359
360 /**
361 * 無効化
362 *
363 * @return void
364 */
365 public function deactivate(): void {
366 $this->config->set( self::KEY_FEATURE, 'f' );
367 $this->config->save();
368 }
369 }
370