Changeset 3365740
- Timestamp:
- 09/22/2025 11:26:26 AM (6 months ago)
- Location:
- ghostgate/trunk
- Files:
-
- 10 edited
-
ghostgate.php (modified) (2 diffs)
-
inc/admin-actions.php (modified) (1 diff)
-
inc/admin-ui.php (modified) (5 diffs)
-
inc/core.php (modified) (2 diffs)
-
inc/feature-login-slug.php (modified) (1 diff)
-
inc/session-manager.php (modified) (1 diff)
-
inc/template-functions.php (modified) (1 diff)
-
inc/two-factor-auth.php (modified) (1 diff)
-
languages/ghostgate.pot (modified) (1 diff)
-
readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
ghostgate/trunk/ghostgate.php
r3363903 r3365740 4 4 * Plugin URI: https://arce-experience.com/product/ 5 5 * Description: ログインURLを隠して、2FA認証やDoS遮断も可能なWordPress専用セキュリティ強化ツールです。WordPress Login Hardening Plugin. 6 * Version: 1. 2.16 * Version: 1.3.0 7 7 * Author: ジー(Code GEE) 8 8 * Author URI: https://arce-experience.com/developer/ … … 18 18 19 19 // 定数定義 20 define('GHOSTGATE_VERSION', '1. 2.1');20 define('GHOSTGATE_VERSION', '1.3.0'); 21 21 define('GHOSTGATE_PATH', plugin_dir_path(__FILE__)); 22 22 define('GHOSTGATE_URL', plugin_dir_url(__FILE__)); -
ghostgate/trunk/inc/admin-actions.php
r3351716 r3365740 5 5 6 6 function ghostgate_handle_unblock_ip() { 7 // メニューと同じ権限に揃える(必要ならフィルタで変更)8 $cap = apply_filters( 'ghostgate_required_capability', 'manage_options' );9 if ( ! current_user_can( $cap ) ) {10 wp_die( esc_html__( 'You do not have permission.', 'ghostgate' ), '', array( 'response' => 403 ) );11 }7 // メニューと同じ権限(必要ならフィルタで変更) 8 $cap = apply_filters( 'ghostgate_required_capability', 'manage_options' ); 9 if ( ! current_user_can( $cap ) ) { 10 wp_die( esc_html__( 'You do not have permission.', 'ghostgate' ), '', array( 'response' => 403 ) ); 11 } 12 12 13 check_admin_referer( 'ghostgate_unblock_ip_action' );13 check_admin_referer( 'ghostgate_unblock_ip_action' ); 14 14 15 // 単体ボタン(name="ip") と一括チェックボックス(name="ip[]") の両対応($_POST 直参照をやめる) 16 $ips_raw = filter_input( INPUT_POST, 'ip', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY ); 17 if ( null === $ips_raw ) { 18 $one = filter_input( INPUT_POST, 'ip', FILTER_UNSAFE_RAW ); // 単体のとき 19 $ips_raw = is_string( $one ) && $one !== '' ? array( $one ) : array(); 20 } 15 // --- 入力の安全な取り出し($_POSTを必ず配列化) --- 16 $ips_raw = array(); 17 if ( isset( $_POST['ip'] ) ) { 18 $val = $_POST['ip']; 19 // ボタン(name="ip") or チェックボックス(name="ip[]") の両対応 20 $ips_raw = is_array( $val ) ? $val : array( $val ); 21 } 21 22 22 // Unslash → サニタイズ(文字列のみ処理) 23 $ips_raw = array_map( 24 static function ( $v ) { 25 if ( ! is_string( $v ) ) { 26 return ''; 27 } 28 $v = wp_unslash( $v ); 29 return sanitize_text_field( $v ); 30 }, 31 $ips_raw 32 ); 23 // --- 正規化(unslash → sanitize) --- 24 $ips_raw = array_map( 25 static function ( $v ) { 26 $v = is_string( $v ) ? $v : ''; 27 $v = wp_unslash( $v ); 28 return sanitize_text_field( $v ); 29 }, 30 $ips_raw 31 ); 33 32 34 // 正規化 & 検証(IPv4/IPv6のみ) 35 $ips = array_values(36 array_filter(37 array_unique(38 array_map(39 static function ( $v ) {40 // 念のため許可文字しぼり(sanitization 後だが二重に守る) 41 $v = preg_replace( '/[^0-9a-fA-F:\.\-]/', '', (string) $v );42 return filter_var( $v, FILTER_VALIDATE_IP ) ? $v : '';43 },44 $ips_raw45 )46 )47 )48 );33 // --- 検証(IPv4/IPv6 のみ許可) --- 34 $ips = array_values( 35 array_filter( 36 array_unique( 37 array_map( 38 static function ( $v ) { 39 // 念のため許可文字だけ残す 40 $v = preg_replace( '/[^0-9a-fA-F:\.\-]/', '', (string) $v ); 41 return filter_var( $v, FILTER_VALIDATE_IP ) ? $v : ''; 42 }, 43 $ips_raw 44 ) 45 ) 46 ) 47 ); 49 48 50 foreach ( $ips as $ip ) { 51 if ( function_exists( 'ghostgate_unblock_ip' ) ) { 52 ghostgate_unblock_ip( $ip ); 53 } 54 } 49 // --- 未選択ならそのまま戻る --- 50 $dst = wp_get_referer(); 51 if ( ! $dst ) { 52 $dst = is_network_admin() 53 ? network_admin_url( 'admin.php?page=ghostgate' ) 54 : admin_url( 'admin.php?page=ghostgate' ); 55 } 55 56 56 // ★ 元のページ(Referer)に戻るだけ 57 $dst = wp_get_referer(); 58 if ( ! $dst ) { 59 // 参照元が取れない場合の保険(トップレベルメニュー想定) 60 $dst = is_network_admin() 61 ? network_admin_url( 'admin.php?page=ghostgate' ) 62 : admin_url( 'admin.php?page=ghostgate' ); 63 } 64 // 同一ホストに限定 65 $dst = wp_validate_redirect( $dst, admin_url( 'admin.php?page=ghostgate' ) ); 57 if ( empty( $ips ) ) { 58 $dst = add_query_arg( array( 'ghostgate_msg' => 'no_targets' ), $dst ); 59 wp_safe_redirect( wp_validate_redirect( $dst, admin_url( 'admin.php?page=ghostgate' ) ) ); 60 exit; 61 } 66 62 67 wp_safe_redirect( $dst ); 68 exit; 63 // --- 解除実行 --- 64 foreach ( $ips as $ip ) { 65 if ( function_exists( 'ghostgate_unblock_ip' ) ) { 66 ghostgate_unblock_ip( $ip ); 67 } 68 } 69 70 // --- 戻り先へ(件数付きメッセージ) --- 71 $dst = add_query_arg( 72 array( 73 'ghostgate_msg' => 'unblocked', 74 'count' => count( $ips ), 75 ), 76 $dst 77 ); 78 wp_safe_redirect( wp_validate_redirect( $dst, admin_url( 'admin.php?page=ghostgate' ) ) ); 79 exit; 69 80 } 70 -
ghostgate/trunk/inc/admin-ui.php
r3363903 r3365740 17 17 ); 18 18 }); 19 20 21 if ( ! function_exists('ghostgate_sanitize_allowed_routes') ) { 22 function ghostgate_sanitize_allowed_routes( $value ) { 23 if ( empty( $value ) || ! is_array( $value ) ) return array(); 24 25 global $ghostgate_bypass_json_filter; 26 $old_bypass = $ghostgate_bypass_json_filter ?? false; 27 $ghostgate_bypass_json_filter = true; 28 29 if ( ! did_action( 'rest_api_init' ) ) { 30 do_action( 'rest_api_init' ); 31 } 32 $server = function_exists('rest_get_server') ? rest_get_server() : null; 33 $all_routes = $server ? array_keys( $server->get_routes() ) : array(); 34 35 $ghostgate_bypass_json_filter = $old_bypass; 36 37 $value = array_map( 'strval', $value ); 38 $value = array_map( 'wp_check_invalid_utf8', $value ); 39 40 // 実在ルートとの突き合わせ + 重複除去 41 $whitelist = array_values( array_unique( array_intersect( $all_routes, $value ) ) ); 42 43 // `/` は保険で常に含める 44 if ( ! in_array( '/', $whitelist, true ) ) { 45 $whitelist[] = '/'; 46 } 47 return $whitelist; 48 } 49 } 50 51 52 if ( ! function_exists('ghostgate_sanitize_allowed_prefixes') ) { 53 function ghostgate_sanitize_allowed_prefixes( $value ) { 54 $value = is_string( $value ) ? wp_unslash( $value ) : ''; 55 $parts = array_map( 'trim', explode( ',', $value ) ); 56 57 $norms = array(); 58 foreach ( $parts as $p ) { 59 if ( $p === '' ) continue; 60 61 // 許可する文字は「英数/_- とスラッシュ・%・.」くらいまで緩める(REST名前空間の実情に合わせる) 62 // 余計な空白などは削除 63 $p = preg_replace( '#[^A-Za-z0-9/_\.\-%]#', '', $p ); 64 if ( $p === '' ) continue; 65 66 // 先頭スラッシュ付与、連続スラッシュを1本に 67 if ( strpos( $p, '/' ) !== 0 ) $p = '/' . $p; 68 $p = preg_replace( '#/{2,}#', '/', $p ); 69 70 $norms[] = $p; 71 } 72 73 // 重複除去して、保存/表示はカンマ区切り 74 $norms = array_values( array_unique( $norms ) ); 75 return implode( ', ', $norms ); 76 } 77 } 78 79 19 80 20 81 function ghostgate_render_settings_page() { … … 605 666 'ghostgate_rest_limit_cooldown' => 'absint', 606 667 'ghostgate_hide_json_endpoints' => 'absint', 607 'ghostgate_json_allowed_routes' => 'ghostgate_sanitize_array',608 'ghostgate_json_allowed_prefixes' => 'sanitize_text_field',668 //'ghostgate_json_allowed_routes' => 'ghostgate_sanitize_array', 669 //'ghostgate_json_allowed_prefixes' => 'sanitize_text_field', 609 670 'ghostgate_enable_session_control' => 'absint', 610 671 'ghostgate_session_timeout' => 'absint', … … 620 681 ]); 621 682 } 683 684 // 1) 許可ルート(配列)— 正規表現ルートを壊さず、存在ルートとの積集合のみ通す 685 register_setting( 686 'ghostgate_options', // ← 既存の settings group に合わせる 687 'ghostgate_json_allowed_routes', 688 array( 689 'type' => 'array', 690 'sanitize_callback' => 'ghostgate_sanitize_allowed_routes', 691 'default' => array(), 692 'show_in_rest' => false, 693 ) 694 ); 695 696 // 2) 接頭辞(文字列だが正規化は独自)— "/gbrl, /line/v1" のように整える 697 register_setting( 698 'ghostgate_options', 699 'ghostgate_json_allowed_prefixes', 700 array( 701 'type' => 'string', 702 'sanitize_callback' => 'ghostgate_sanitize_allowed_prefixes', 703 'default' => '', 704 'show_in_rest' => false, 705 ) 706 ); 707 708 // 3) トグル(ON/OFF) 709 register_setting( 710 'ghostgate_options', 711 'ghostgate_hide_json_endpoints', 712 array( 713 'type' => 'string', 714 'sanitize_callback' => static function( $v ) { return ( $v === '1' ) ? '1' : '0'; }, 715 'default' => '0', 716 'show_in_rest' => false, 717 ) 718 ); 622 719 623 720 // セクション(1つでOK) … … 3452 3549 $base_url . 'js/ghost-script-adminui.js', 3453 3550 [], 3454 defined('GHOSTGATE_VERSION') ? GHOSTGATE_VERSION : '1. 2.1',3551 defined('GHOSTGATE_VERSION') ? GHOSTGATE_VERSION : '1.3.0', 3455 3552 true 3456 3553 ); … … 3461 3558 $base_url . 'css/ghost-style-adminui.css', 3462 3559 [], 3463 defined('GHOSTGATE_VERSION') ? GHOSTGATE_VERSION : '1. 2.1'3560 defined('GHOSTGATE_VERSION') ? GHOSTGATE_VERSION : '1.3.0' 3464 3561 ); 3465 3562 } -
ghostgate/trunk/inc/core.php
r3351716 r3365740 473 473 474 474 //json秘匿化処理 475 add_filter('rest_endpoints', 'ghostgate_hide_json_endpoints', 102); 476 function ghostgate_hide_json_endpoints($endpoints) { 475 add_filter( 'rest_endpoints', 'ghostgate_hide_json_endpoints', 102 ); 476 function ghostgate_hide_json_endpoints( $endpoints ) { 477 // REST サーバ不在なら何もしない(早期リターン) 478 if ( ! function_exists( 'rest_get_server' ) ) return $endpoints; 479 477 480 global $ghostgate_bypass_json_filter; 478 481 479 // 秘匿OFF または UI構築中はスキップ 480 if (!get_option('ghostgate_hide_json_endpoints') || !empty($ghostgate_bypass_json_filter)) { 482 // ✅ 秘匿 OFF or UI 構築中(バイパス中)は何もしない 483 $enabled = get_option( 'ghostgate_hide_json_endpoints', '0' ); 484 if ( $enabled !== '1' || ! empty( $ghostgate_bypass_json_filter ) ) { 481 485 return $endpoints; 482 486 } 483 487 484 // 許可ルート・接頭辞の取得と整形485 $allowed_routes = get_option('ghostgate_json_allowed_routes', []);486 $prefixes_string = get_option('ghostgate_json_allowed_prefixes', '');487 488 if (!is_array($allowed_routes)) $allowed_routes = [];489 $allowed_routes = array_map( 'strval', $allowed_routes);490 491 // `/` は必ず許可492 if ( !in_array('/', $allowed_routes, true)) {488 // ✅ 許可ルートの取得(配列のみ許可)— 正規表現を壊さない 489 $allowed_routes = get_option( 'ghostgate_json_allowed_routes', array() ); 490 if ( ! is_array( $allowed_routes ) ) $allowed_routes = array(); 491 // 文字列化 & UTF-8 妥当性のみ(sanitize_key などは絶対に使わない) 492 $allowed_routes = array_map( 'strval', $allowed_routes ); 493 $allowed_routes = array_map( 'wp_check_invalid_utf8', $allowed_routes ); 494 495 // ✅ `/` は常に許可(ルート一覧のルート) 496 if ( ! in_array( '/', $allowed_routes, true ) ) { 493 497 $allowed_routes[] = '/'; 494 498 } 495 499 496 $prefixes = array_filter(array_map(function($p) { 497 $p = trim($p); 498 return ($p === '') ? '' : ((strpos($p, '/') === 0) ? $p : '/' . $p); 499 }, explode(',', $prefixes_string))); 500 501 // ルートをフィルタ 502 foreach ($endpoints as $route => $handler) { 503 if (in_array($route, $allowed_routes, true)) { 500 // ✅ 自作プレフィックス(カンマ区切り → 正規化) 501 $prefixes_raw = (string) get_option( 'ghostgate_json_allowed_prefixes', '' ); 502 $prefixes = array_filter( array_map( static function( $p ) { 503 $p = trim( (string) $p ); 504 if ( $p === '' ) return ''; 505 // 先頭に `/` を付与、連続スラッシュも畳む 506 $p = ( strpos( $p, '/' ) === 0 ) ? $p : "/{$p}"; 507 $p = preg_replace( '#/{2,}#', '/', $p ); 508 return $p; 509 }, explode( ',', $prefixes_raw ) ) ); 510 511 // ✅ 高速化のため、許可ルートを set 化 512 $allow_set = array_fill_keys( $allowed_routes, true ); 513 514 // ✅ フィルタリング 515 foreach ( $endpoints as $route => $handler ) { 516 // 完全一致で許可 517 if ( isset( $allow_set[ $route ] ) ) { 504 518 continue; 505 519 } 506 foreach ($prefixes as $prefix) { 507 if ($prefix !== '' && strpos($route, $prefix) === 0) { 508 continue 2; 509 } 510 } 511 unset($endpoints[$route]); 520 // 接頭辞で許可 521 $allowed_by_prefix = false; 522 foreach ( $prefixes as $px ) { 523 if ( $px !== '' && strpos( $route, $px ) === 0 ) { 524 $allowed_by_prefix = true; 525 break; 526 } 527 } 528 if ( $allowed_by_prefix ) { 529 continue; 530 } 531 532 // ここまで来たら非表示 533 unset( $endpoints[ $route ] ); 512 534 } 513 535 514 536 return $endpoints; 515 537 } 538 516 539 517 540 … … 786 809 } 787 810 } 811 812 -
ghostgate/trunk/inc/feature-login-slug.php
r3363903 r3365740 321 321 $handle = 'ghostgate-disable-remember'; 322 322 $src = GHOSTGATE_URL . 'assets/js/ghost-disable-remember.js'; 323 $ver = defined( 'GHOSTGATE_VERSION' ) ? GHOSTGATE_VERSION : '1. 2.1';323 $ver = defined( 'GHOSTGATE_VERSION' ) ? GHOSTGATE_VERSION : '1.3.0'; 324 324 325 325 // セッションが残っていた場合のクリア処理(ログアウト後の安全対策) -
ghostgate/trunk/inc/session-manager.php
r3363903 r3365740 94 94 $handle = 'ghostgate-session-tracker'; 95 95 $src = GHOSTGATE_URL . 'assets/js/ghost-session-tracker.js'; 96 $ver = defined( 'GHOSTGATE_VERSION' ) ? GHOSTGATE_VERSION : '1. 2.1';96 $ver = defined( 'GHOSTGATE_VERSION' ) ? GHOSTGATE_VERSION : '1.3.0'; 97 97 98 98 // UIは秒、JSにはmsで渡す。未設定時は5秒。 -
ghostgate/trunk/inc/template-functions.php
r3363903 r3365740 44 44 $style_handle = 'ghostgate-code-style'; 45 45 $style_url = GHOSTGATE_URL . 'assets/css/ghost-style.css'; 46 $version = defined('GHOSTGATE_VERSION') ? GHOSTGATE_VERSION : '1. 2.1';46 $version = defined('GHOSTGATE_VERSION') ? GHOSTGATE_VERSION : '1.3.0'; 47 47 48 48 // JS注入 -
ghostgate/trunk/inc/two-factor-auth.php
r3363903 r3365740 143 143 plugins_url( '../assets/css/ghost-style-2fa.css', __FILE__ ), 144 144 array(), 145 defined( 'GHOSTGATE_VERSION' ) ? GHOSTGATE_VERSION : '1. 2.1'145 defined( 'GHOSTGATE_VERSION' ) ? GHOSTGATE_VERSION : '1.3.0' 146 146 ); 147 147 } -
ghostgate/trunk/languages/ghostgate.pot
r3363903 r3365740 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: GhostGate 1. 2.1\n"5 "Project-Id-Version: GhostGate 1.3.0\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/ghostgate\n" 7 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" -
ghostgate/trunk/readme.txt
r3363903 r3365740 5 5 Tested up to: 6.8 6 6 Requires PHP: 7.4 7 Stable tag: 1. 2.17 Stable tag: 1.3.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 70 70 71 71 == Changelog == 72 = 1.3.0 - 2025-09-22 = 73 * Security: Strengthened “Hide wp-json structure” — allowlist now stores **only actually registered routes** (including regex routes) and never breaks parameterized patterns. 74 * Fix: Route allowlist UI now correctly preserves selections for regex endpoints such as `/gbrl/v1/notify/(?P<slug>[^/]+)` and nested variants. 75 * Fix: Resolved rare fatal error on “Unblock IP” admin action by hardening input handling (supports single `ip` and `ip[]`, sanitizes/validates IPv4/IPv6, safe redirect). 76 * Dev: Added `ghostgate_sanitize_allowed_routes()` and `ghostgate_sanitize_allowed_prefixes()`; introduced a temporary bypass flag so the settings UI can enumerate all routes without being filtered by itself. 77 * Dev: Always whitelists `/` root in `rest_endpoints` filter; normalized custom prefixes (auto-leading slash, condensed duplicate slashes). 78 * Perf: Reduced overhead when building the REST route list on the settings page. 79 * Tweak: Copy and help text polish in settings; minor CSS/UI adjustments. 80 * Tested: Confirmed compatibility with WordPress 6.8. 81 72 82 = 1.2.1 = 73 83 * Tweak: Added brand header (logo + subtitle) to the code entry screen with Retina and dark mode support, plus minor a11y improvements.
Note: See TracChangeset
for help on using the changeset viewer.