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 / disable-restapi.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
disable-restapi.php
297 lines
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit;
5 }
6
7 class CloudSecureWP_Disable_RESTAPI extends CloudSecureWP_Common {
8 private const KEY_FEATURE = 'disable_rest_api';
9 private const KEY_EXCLUDE = self::KEY_FEATURE . '_exclude';
10 private const DEFAULT_EXCLUDE = array( 'oembed', 'contact-form-7', 'akismet' );
11
12 /**
13 * 除外名から追加で許可するルートプレフィックスへの読み替え表
14 *
15 * テーマ名・プラグイン名とREST APIの名前空間が一致しないものを登録する。
16 * 素の「/除外名/」前方一致に加えて、ここで定義したプレフィックスも許可される。
17 * ※テーマ側のアップデートで名前空間が変わると除外が壊れるため、対象テーマの
18 * register_rest_route() を確認してから変更すること。
19 */
20 private const EXCLUDE_ALIASES = array(
21 // プラグイン
22 'snow-monkey-forms' => array( '/snow-monkey-form/' ),
23 'woocommerce' => array( '/wc/' ),
24 // テーマ
25 'swell' => array( '/wp/v2/swell-' ),
26 'snow-monkey' => array( '/wp-oembed-blog-card/' ),
27 'emanon' => array( '/emanon-ai/' ),
28 'nishiki_pro' => array( '/nishiki-pro/' ),
29 );
30 private $config;
31
32 function __construct( array $info, CloudSecureWP_Config $config ) {
33 parent::__construct( $info );
34 $this->config = $config;
35 }
36
37 /**
38 * 機能毎のKEY取得
39 *
40 * @return string
41 */
42 public function get_feature_key(): string {
43 return self::KEY_FEATURE;
44 }
45
46 /**
47 * 有効無効判定
48 *
49 * @return bool
50 */
51 public function is_enabled(): bool {
52 return $this->config->get( $this->get_feature_key() ) === 't' ? true : false;
53 }
54
55 /**
56 * 初期設定値取得
57 *
58 * @return array
59 */
60 public function get_default(): array {
61 $ret = array(
62 self::KEY_FEATURE => 'f',
63 self::KEY_EXCLUDE => self::DEFAULT_EXCLUDE,
64 );
65 return $ret;
66 }
67
68 /**
69 * 設定値取得
70 */
71 public function get_settings(): array {
72 $settings = array();
73 $default = $this->get_default();
74
75 foreach ( $default as $key => $val ) {
76 $settings[ $key ] = $this->config->get( $key );
77 }
78
79 return $settings;
80 }
81
82 /**
83 * 設定値保存
84 *
85 * @param array $settings
86 * @return void
87 */
88 public function save_settings( $settings ): void {
89 $default = $this->get_default();
90
91 foreach ( $default as $key => $val ) {
92 $this->config->set( $key, $settings[ $key ] ?? '' );
93 }
94
95 $this->config->save();
96 }
97
98 /**
99 * 除外指定していない有効なプラグイン名リスト取得
100 *
101 * @return array
102 */
103 public function get_active_plugin_names(): array {
104 $plugin_names = array();
105 $plugins = get_plugins();
106 $exclude_plugins = $this->config->get( self::KEY_EXCLUDE );
107
108 if ( ! is_array( $exclude_plugins ) ) {
109 $exclude_plugins = array();
110 }
111 $exclude_plugins = $this->normalize_exclude_names( $exclude_plugins );
112
113 if ( ! empty( $plugins ) ) {
114 foreach ( $plugins as $plugin_path => $plugin ) {
115 // プラグイン名取得
116 $plugin_name = $plugin['TextDomain'];
117 if ( $plugin['Name'] === 'Hello Dolly' ) {
118 // Hello Dolly対応
119 $plugin_name = 'hello-dolly';
120 }
121
122 if ( is_plugin_active( $plugin_path ) ) {
123 if ( false === in_array( strtolower( $plugin_name ), $exclude_plugins, true ) && $plugin_name !== $this->info['text_domain'] ) {
124 $plugin_names[] = $plugin_name;
125 }
126 }
127 }
128 }
129
130 return $plugin_names;
131 }
132
133 /**
134 * 除外指定していない有効なテーマ名リスト取得
135 *
136 * 子テーマが有効な場合はREST APIを提供する親テーマを対象にする。
137 * テーマ名はText Domainを使用し、未定義の場合はテーマディレクトリ名を使用する。
138 *
139 * @return array
140 */
141 public function get_active_theme_names(): array {
142 $theme = wp_get_theme();
143
144 // 子テーマの場合は親テーマを対象にする
145 $parent = $theme->parent();
146 if ( false !== $parent ) {
147 $theme = $parent;
148 }
149
150 $theme_name = (string) $theme->get( 'TextDomain' );
151 if ( '' === $theme_name ) {
152 $theme_name = (string) $theme->get_stylesheet();
153 }
154
155 if ( '' === $theme_name ) {
156 return array();
157 }
158
159 $exclude_names = $this->config->get( self::KEY_EXCLUDE );
160 if ( ! is_array( $exclude_names ) ) {
161 $exclude_names = array();
162 }
163
164 if ( in_array( strtolower( $theme_name ), $this->normalize_exclude_names( $exclude_names ), true ) ) {
165 return array();
166 }
167
168 return array( $theme_name );
169 }
170
171 /**
172 * テキストから�
173 �列に変換
174 *
175 * @param string $text
176 * @return array
177 */
178 public function text2Array( string $text ): array {
179 $searchs = array( "\r\n", "\r" );
180 $text = str_replace( $searchs, "\n", $text );
181 $text_array = explode( "\n", $text );
182
183 foreach ( $text_array as &$name ) {
184 $name = trim( $name );
185 }
186 unset( $name );
187
188 $text_array = array_filter( $text_array, 'strlen' );
189 $text_array = array_unique( $text_array );
190
191 return $text_array;
192 }
193
194 /**
195 * 除外名リストの正規化
196 *
197 * 候補一覧の除外済み判定を、実際の除外判定(rest_pre_dispatch)と同じ
198 * 規則(strtolower + trim)で行うために使用する。規則が食い違うと、
199 * 大文字・空白付きで保存された除外名が「除外は効くのに候補一覧へ出続ける」状�
200 �になる。
201 *
202 * @param array $names
203 * @return array
204 */
205 private function normalize_exclude_names( array $names ): array {
206 $normalized = array();
207
208 foreach ( $names as $name ) {
209 if ( ! is_string( $name ) ) {
210 continue;
211 }
212
213 $name = strtolower( trim( $name ) );
214 if ( '' !== $name ) {
215 $normalized[] = $name;
216 }
217 }
218
219 return $normalized;
220 }
221
222 /**
223 * ルート文字列の正規化
224 *
225 * コアのルートマッチングは preg_match( '@^' . $route . '$@i', $path ) で行われるため、
226 * それより厳格な一致で判定すると除外指定がすり抜けて過剰ブロックになる。判定前に正規化する。
227 * - strtolower: コアの正規表現は大文字小文字を区別しない(`i`)ため
228 * - trim: コアの正規表現は D 修飾子が無く、末尾の `$` が末尾改行の直前にもマッチするため
229 * - untrailingslashit: 末尾スラッシュを正規化する
230 *
231 * @param string $route
232 * @return string
233 */
234 private function normalize_route( string $route ): string {
235 return strtolower( untrailingslashit( trim( $route ) ) );
236 }
237
238 /**
239 * rest_pre_dispatch
240 */
241 function rest_pre_dispatch( $result, $server, $request ) {
242
243 if ( current_user_can( 'edit_pages' ) || current_user_can( 'edit_posts' ) ) {
244 return $result;
245 }
246
247 $setting = $this->get_settings();
248 $exclude_names = $setting[ self::KEY_EXCLUDE ];
249 $route = $this->normalize_route( (string) $request->get_route() );
250
251 if ( ! is_array( $exclude_names ) ) {
252 $exclude_names = array();
253 }
254
255 foreach ( $exclude_names as $name ) {
256 if ( ! is_string( $name ) ) {
257 continue;
258 }
259
260 $name = strtolower( trim( $name ) );
261 if ( '' === $name ) {
262 continue;
263 }
264
265 // 素の名前空間前方一致に加え、読み替え表のプレフィックスも許可する
266 $prefixes = array_merge( array( "/{$name}/" ), self::EXCLUDE_ALIASES[ $name ] ?? array() );
267
268 foreach ( $prefixes as $prefix ) {
269 if ( strpos( $route, $prefix ) === 0 ) {
270 return $result;
271 }
272 }
273 }
274
275 return new WP_Error( $this->get_feature_key(), 'REST API が無効化されています', array( 'status' => rest_authorization_required_code() ) );
276 }
277
278 /**
279 * 有効化
280 *
281 * @return void
282 */
283 public function activate(): void {
284 $this->save_settings( $this->get_default() );
285 }
286
287 /**
288 * 無効化
289 *
290 * @return void
291 */
292 public function deactivate(): void {
293 $this->config->set( $this->get_feature_key(), 'f' );
294 $this->config->save();
295 }
296 }
297