PluginProbe
Patchstack – WordPress & Plugins Security / 2.2.11
Patchstack – WordPress & Plugins Security v2.2.11
2.3.7 trunk 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.17 2.1.18 2.1.19 2.1.2 2.1.20 2.1.21 2.1.22 2.1.23 2.1.24 2.1.25 2.1.3 2.1.4 2.1.5 2.1.6 All 49 releases
← All changes | includes/hide-login.php +81 -254 2.1.32.2.11 View file →
@@ -8,17 +8,9 @@
8 8 /**
9 9 * This class is used to hide the login page, if it's enabled.
10 10 */
11 11 class P_Hide_Login extends P_Core {
12 -
13 12 /**
14 - * Whether we should use PHP to redirect the login.
15 - *
16 - * @var boolean
17 - */
18 - protected $wp_login_php = false;
19 -
20 - /**
21 13 * Add the actions required to hide the login page.
22 14 *
23 15 * @param Patchstack $core
24 16 * @return void
@@ -29,14 +21,11 @@
29 21 if ( $this->get_option( 'patchstack_license_free', 0 ) == 1 ) {
30 22 return;
31 23 }
32 24
33 - // Determine if the proper WordPress version is installed.
34 - global $wp_version;
35 - if ( version_compare( $wp_version, '4.0-RC1-src', '<' ) ) {
36 - add_action( 'admin_notices', array( $this, 'admin_notices_incompatible' ) );
37 - add_action( 'network_admin_notices', array( $this, 'admin_notices_incompatible' ) );
38 - return;
25 + // Update the renamed login page if it's set to our hardcoded one.
26 + if ( get_site_option( 'patchstack_mv_wp_login' ) == 0 && get_site_option( 'patchstack_rename_wp_login' ) == 'swlogin' ) {
27 + update_site_option( 'patchstack_rename_wp_login', md5( wp_generate_password( 32, true, true ) ) );
39 28 }
40 29
41 30 // No need to continue if it is not enabled.
42 31 if ( ! get_site_option( 'patchstack_mv_wp_login' ) || ! get_site_option( 'patchstack_rename_wp_login' ) ) {
@@ -42,287 +31,125 @@
42 31 if ( ! get_site_option( 'patchstack_mv_wp_login' ) || ! get_site_option( 'patchstack_rename_wp_login' ) ) {
43 32 return;
44 33 }
45 34
46 - // We need to load the plugin library for multisite.
47 - if ( is_multisite() && ( ! function_exists( 'is_plugin_active_for_network' ) || ! function_exists( 'is_plugin_active' ) ) ) {
48 - require_once ABSPATH . '/wp-admin/includes/plugin.php';
49 - }
50 -
51 35 // Register the filters and actions for the functionality.
52 - add_filter( 'site_url', array( $this, 'site_url' ), 10, 4 );
53 - add_filter( 'network_site_url', array( $this, 'network_site_url' ), 10, 3 );
54 - add_filter( 'wp_redirect', array( $this, 'wp_redirect' ), 10, 2 );
55 - add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ), 9999 );
56 - add_action( 'wp_loaded', array( $this, 'wp_loaded' ) );
57 - add_action( 'init', array( $this, 'deny_default_login_page' ) );
36 + add_action( 'init', [ $this, 'init' ], ~PHP_INT_MAX + 1 );
37 + add_action( 'wp_logout', [ $this, 'wp_logout' ] );
58 38 }
59 39
60 40 /**
61 - * Deny access to wp-login if rewrite wp-admin option is enabled.
41 + * Deny access to wp-login.php if the login page rename feature is enabled.
62 42 *
63 43 * @return void
64 44 */
65 - public function deny_default_login_page() {
66 - if ( get_site_option( 'patchstack_mv_wp_login' ) && strpos( strtolower( $_SERVER['REQUEST_URI'] ), 'wp-login.php' ) !== false ) {
67 - die( 'Forbidden!' );
45 + public function init() {
46 + // Do not block the user if they are already logged in as that would block a logout.
47 + if ( is_user_logged_in() ) {
48 + return;
68 49 }
69 - }
70 50
71 - /**
72 - * Send the email that contains the new login page URL.
73 - *
74 - * @return boolean If the email was sent or not.
75 - */
76 - public function send_email() {
77 - global $current_user;
78 - $subject = __( 'New Login URL', 'patchstack' );
79 - $message = '<br /><br />Your login page is now here: <strong> <a href="' . get_site_url() . '/' . get_site_option( 'patchstack_rename_wp_login' ) . '">' . get_site_url() . '/' . get_site_option( 'patchstack_rename_wp_login' ) . '</strong></a>';
80 - return wp_mail( $current_user->user_email, $subject, $message );
81 - }
51 + // Determine if the user is whitelisted.
52 + if ( ( stripos( $_SERVER['REQUEST_URI'], 'wp-login.php' ) !== false || $GLOBALS['pagenow'] === 'wp-login.php' || $_SERVER['PHP_SELF'] === '/wp-login.php' ) && ! $this->is_whitelisted() ) {
53 + if ( isset( $_REQUEST['action'] ) && in_array( $_REQUEST['action'], ['confirm_admin_email', 'postpass', 'lostpassword', 'retrievepassword', 'resetpass', 'rp', 'register', 'checkemail', 'confirmaction'] ) ) {
54 + return;
55 + }
82 56
83 - /**
84 - * Get the current site's URL.
85 - *
86 - * @param string $url
87 - * @param string $path
88 - * @param string $scheme
89 - * @param integer $blog_id
90 - * @return string
91 - */
92 - public function site_url( $url, $path, $scheme, $blog_id ) {
93 - return $this->filter_wp_login_php( $url, $scheme );
94 - }
95 -
96 - /**
97 - * Determine if the current page is a login/registration page.
98 - *
99 - * @return void
100 - */
101 - public function plugins_loaded() {
102 - global $pagenow;
103 - $stop = false;
104 - $request = parse_url( $_SERVER['REQUEST_URI'] );
105 -
106 - // If the current page is wp-login.php
107 - if ( ( strpos( rawurldecode( $_SERVER['REQUEST_URI'] ), 'wp-login.php' ) !== false || untrailingslashit( $request['path'] ) === site_url( 'wp-login', 'relative' ) ) && ! is_admin() ) {
108 - $this->wp_login_php = true;
109 - $_SERVER['REQUEST_URI'] = $this->user_trailingslashit( '/' . str_repeat( '-/', 10 ) );
110 - $pagenow = 'index.php';
111 - $stop = true;
57 + $this->plugin->firewall_base->display_error_page( 'login' );
112 58 }
113 59
114 - // If the current page is the renamed login page.
115 - if ( ! $stop && ( untrailingslashit( $request['path'] ) === home_url( $this->new_login_slug(), 'relative' ) || ( ! get_site_option( 'permalink_structure' ) && isset( $_GET[ $this->new_login_slug() ] ) && empty( $_GET[ $this->new_login_slug() ] ) ) ) ) {
116 - $pagenow = 'wp-login.php';
117 - $stop = true;
118 - }
60 + // If the current page is the renamed login page we give the user access for 10 minutes to the login page.
61 + if ( strpos( $_SERVER['REQUEST_URI'], get_site_option( 'patchstack_rename_wp_login' ) ) !== false ) {
62 + // Whitelist the current IP address.
63 + $this->whitelist_ip();
119 64
120 - // If the current page is registration page.
121 - if ( ! $stop && ( ( strpos( rawurldecode( $_SERVER['REQUEST_URI'] ), 'wp-register.php' ) !== false || untrailingslashit( $request['path'] ) === site_url( 'wp-register', 'relative' ) ) && ! is_admin() ) ) {
122 - $this->wp_login_php = true;
123 - $_SERVER['REQUEST_URI'] = $this->user_trailingslashit( '/' . str_repeat( '-/', 10 ) );
124 - $pagenow = 'index.php';
125 - }
126 - }
127 -
128 - /**
129 - * Determine if we should redirect the user upon visiting new/old login page.
130 - *
131 - * @return void
132 - */
133 - public function wp_loaded() {
134 - global $pagenow;
135 - $request = parse_url( $_SERVER['REQUEST_URI'] );
136 -
137 - // Redirect when admin page is requested but no admin access.
138 - if ( is_admin() && ! is_user_logged_in() && ! defined( 'DOING_AJAX' ) && $pagenow !== 'admin-post.php' && ( isset( $_GET ) && empty( $_GET['adminhash'] ) && $request['path'] !== '/wp-admin/options.php' ) ) {
139 - wp_safe_redirect( home_url( '/404' ) );
140 - exit;
141 - }
142 -
143 - // If the current page is the login page, redirect to 404.
144 - if ( $pagenow === 'wp-login.php' && $request['path'] !== $this->user_trailingslashit( $request['path'] ) && get_site_option( 'permalink_structure' ) ) {
145 - wp_safe_redirect( home_url( '/404' ) );
146 - exit;
147 - }
148 -
149 - // Other possible login pages that we need to block by default.
150 - $login_pages = array(
151 - home_url( 'wp-login.php', 'relative' ),
152 - home_url( 'login', 'relative' ),
153 - site_url( 'login', 'relative' ),
154 - );
155 - if ( in_array( untrailingslashit( $_SERVER['REQUEST_URI'] ), $login_pages, true ) ) {
156 - wp_safe_redirect( home_url( '/404' ) );
157 - exit;
158 - }
159 -
160 - // Determine if we should redirect the user to the new login page.
161 - if ( $this->wp_login_php ) {
162 - if ( ( $referer = wp_get_referer() ) && strpos( $referer, 'wp-activate.php' ) !== false && ( $referer = parse_url( $referer ) ) && ! empty( $referer['query'] ) ) {
163 - parse_str( $referer['query'], $referer );
164 -
165 - // When a user is self-created.
166 - if ( ! empty( $referer['key'] ) && ( $result = wpmu_activate_signup( $referer['key'] ) ) && is_wp_error( $result ) && ( $result->get_error_code() === 'already_active' || $result->get_error_code() === 'blog_taken' ) ) {
167 - wp_safe_redirect( $this->new_login_url() . ( ! empty( $_SERVER['QUERY_STRING'] ) ? '?' . $_SERVER['QUERY_STRING'] : '' ) );
168 - exit;
169 - }
65 + // Supported by a number of popular caching plugins.
66 + if ( ! defined( 'DONOTCACHEPAGE' ) ) {
67 + define( 'DONOTCACHEPAGE', true );
170 68 }
171 - $this->wp_template_loader();
172 - }
173 69
174 - @require_once ABSPATH . 'wp-includes/post.php';
70 + // No caching.
71 + send_nosniff_header();
72 + nocache_headers();
175 73
176 - // There must be a better way
177 - $one = get_home_url() . '/' . $request['path'] . '/';
178 - $two = get_home_url() . '/' . get_site_option( 'patchstack_rename_wp_login' ) . '/';
179 - $one = str_replace( get_home_url(), '', $one );
180 - $two_url = parse_url( get_home_url() );
181 - $two = str_replace( $two_url['scheme'] . '://' . $two_url['host'], '', $two );
182 - $one = str_replace( '//', '/', $one );
183 - $two = str_replace( '//', '/', $two );
184 -
185 - // Show the login page if not already logged in.
186 - if ( $one == $two ) {
187 - global $error, $interim_login, $action, $user_login;
188 -
189 - if ( is_user_logged_in() && ! isset( $_REQUEST['action'] ) ) {
190 - wp_safe_redirect( admin_url() );
191 - exit;
192 - }
193 - @require_once ABSPATH . 'wp-login.php';
74 + // User should be whitelisted now, redirect to the login page.
75 + wp_safe_redirect( 'wp-login.php', 307 );
194 76 exit;
195 77 }
196 78 }
197 79
198 80 /**
199 - * Load the template loader.
200 - *
81 + * If the user is logging out, whitelist them again so they don't see a blocked page.
82 + *
201 83 * @return void
202 84 */
203 - private function wp_template_loader() {
204 - global $pagenow;
205 - $pagenow = 'index.php';
206 -
207 - if ( ! defined( 'WP_USE_THEMES' ) ) {
208 - define( 'WP_USE_THEMES', true );
209 - }
210 -
211 - wp();
212 - if ( $_SERVER['REQUEST_URI'] === $this->user_trailingslashit( str_repeat( '-/', 10 ) ) ) {
213 - $_SERVER['REQUEST_URI'] = $this->user_trailingslashit( '/wp-login-php/' );
214 - }
215 -
216 - @require_once ABSPATH . WPINC . '/template-loader.php';
217 - exit;
85 + public function wp_logout() {
86 + $this->whitelist_ip();
218 87 }
219 88
220 89 /**
221 - * Filter the wp-login.php URL.
222 - *
223 - * @param string $url
224 - * @param string $scheme
225 - * @return string
90 + * Determine if the IP address is whitelisted.
91 + *
92 + * @return boolean
226 93 */
227 - public function filter_wp_login_php( $url, $scheme = null ) {
228 - // Attempt to retrieve the URL.
229 - if ( strpos( $url, 'wp-login.php' ) !== false ) {
230 - if ( is_ssl() ) {
231 - $scheme = 'https';
232 - }
94 + private function is_whitelisted() {
95 + // Process the whitelist, and remove old ones.
96 + $whitelist = get_site_option( 'patchstack_rename_wp_login_whitelist', [] );
97 + $new_whitelist = [];
98 + $allow = false;
233 99
234 - $args = explode( '?', $url );
235 - if ( isset( $args[1] ) ) {
236 - parse_str( $args[1], $args );
237 - if ( isset( $args['login'] ) ) {
238 - $args['login'] = rawurlencode( $args['login'] );
100 + // Only continue if there are actually any whitelist entries.
101 + if ( is_array( $whitelist ) && count( $whitelist ) != 0 ){
102 + $ip = $this->get_ip();
103 + foreach ( $whitelist as $entry ) {
104 +
105 + // Determine if the whitelist entry is still valid.
106 + if ( ( time() - $entry[1] ) <= 600 ) {
107 + array_push( $new_whitelist, $entry );
108 +
109 + // Determine if the IP address matches.
110 + if ( $ip === $entry[0] ) {
111 + $allow = true;
112 + }
239 113 }
240 - $url = add_query_arg( $args, $this->new_login_url( $scheme ) );
241 - } else {
242 - $url = $this->new_login_url( $scheme );
243 114 }
115 +
116 + update_site_option( 'patchstack_rename_wp_login_whitelist', $new_whitelist );
244 117 }
245 118
246 - return $url;
119 + return $allow;
247 120 }
248 121
249 122 /**
250 - * Get the network site URL.
251 - *
252 - * @param string $url
253 - * @param string $path
254 - * @param string $scheme
255 - * @return string
123 + * Whitelist the current IP address, or extend the time.
124 + *
125 + * @return void
256 126 */
257 - public function network_site_url( $url, $path, $scheme ) {
258 - return $this->filter_wp_login_php( $url, $scheme );
259 - }
127 + private function whitelist_ip() {
128 + $whitelist = get_site_option( 'patchstack_rename_wp_login_whitelist', [] );
129 + $new_whitelist = [];
260 130
261 - /**
262 - * Redirect the user to given location.
263 - *
264 - * @param string $location
265 - * @param integer $status
266 - * @return string
267 - */
268 - public function wp_redirect( $location, $status ) {
269 - return $this->filter_wp_login_php( $location );
270 - }
131 + // If the IP address is already whitelisted, reset the timestamp.
132 + if ( is_array( $whitelist ) && count( $whitelist ) != 0 ) {
133 + $ip = $this->get_ip();
134 + $whitelisted = false;
135 + foreach ( $whitelist as $entry ) {
136 + // Determine if we should extend the whitelist time or ignore if already whitelisted.
137 + if ( $ip === $entry[0] ) {
138 + $new_whitelist[] = [ $ip, time() ];
139 + $whitelisted = true;
140 + } else {
141 + $new_whitelist[] = $entry;
142 + }
143 + }
271 144
272 - /**
273 - * Show a notice that WordPress needs to be upgraded.
274 - *
275 - * @return void
276 - */
277 - public function admin_notices_incompatible() {
278 - echo wp_kses( '<div class="error notice is-dismissible"><p>' . __( 'Patchstack: Please upgrade to the latest version of WordPress to activate', 'patchstack' ) . '</p></div>', $this->allowed_html );
279 - }
145 + // Whitelist the IP address.
146 + if ( ! $whitelisted ) {
147 + $new_whitelist[] = [ $ip, time() ];
148 + }
280 149
281 - /**
282 - * Get the new login slug.
283 - *
284 - * @return string
285 - */
286 - private function new_login_slug() {
287 - if ( $slug = get_site_option( 'patchstack_rename_wp_login' ) ) {
288 - return $slug;
289 - } elseif ( ( is_multisite() && is_plugin_active_for_network( plugin_basename( __FILE__ ) ) && ( $slug = get_site_option( 'patchstack_rename_wp_login', 'login' ) ) ) ) {
290 - return $slug;
291 - } elseif ( $slug = 'login' ) {
292 - return $slug;
293 - }
294 - }
295 -
296 - /**
297 - * Get the new login URL.
298 - *
299 - * @param string $scheme
300 - * @return void
301 - */
302 - public function new_login_url( $scheme = null ) {
303 - if ( get_site_option( 'permalink_structure' ) ) {
304 - return $this->user_trailingslashit( home_url( '/', $scheme ) . $this->new_login_slug() );
150 + update_site_option( 'patchstack_rename_wp_login_whitelist', $new_whitelist );
305 151 } else {
306 - return home_url( '/', $scheme ) . $this->new_login_slug();
152 + update_site_option( 'patchstack_rename_wp_login_whitelist', [ [ $this->get_ip(), time() ] ] );
307 153 }
308 - }
309 -
310 - /**
311 - * Determine if we have to use trailing slashes.
312 - *
313 - * @return boolean
314 - */
315 - private function use_trailing_slashes() {
316 - return substr( get_site_option( 'permalink_structure' ), - 1, 1 ) === '/';
317 - }
318 -
319 - /**
320 - * Use trailing slashes, if needed.
321 - *
322 - * @param string $string
323 - * @return void
324 - */
325 - private function user_trailingslashit( $string ) {
326 - return $this->use_trailing_slashes() ? trailingslashit( $string ) : untrailingslashit( $string );
327 154 }
328 155 }