Changeset 3316692
- Timestamp:
- 06/24/2025 06:01:25 AM (9 months ago)
- Location:
- prevent-file-access
- Files:
-
- 58 added
- 3 edited
-
tags/2.6.1 (added)
-
tags/2.6.1/README.txt (added)
-
tags/2.6.1/admin (added)
-
tags/2.6.1/admin/class-media-restriction-admin.php (added)
-
tags/2.6.1/admin/class-miniorange-media-restriction-customer.php (added)
-
tags/2.6.1/admin/css (added)
-
tags/2.6.1/admin/css/bootstrap.min.css (added)
-
tags/2.6.1/admin/css/font-awesome.min.css (added)
-
tags/2.6.1/admin/css/jquery.dataTables.min.css (added)
-
tags/2.6.1/admin/css/media-restriction-admin.css (added)
-
tags/2.6.1/admin/css/phone.min.css (added)
-
tags/2.6.1/admin/css/style.min.css (added)
-
tags/2.6.1/admin/fonts (added)
-
tags/2.6.1/admin/fonts/FontAwesome.otf (added)
-
tags/2.6.1/admin/fonts/fontawesome-webfont.eot (added)
-
tags/2.6.1/admin/fonts/fontawesome-webfont.svg (added)
-
tags/2.6.1/admin/fonts/fontawesome-webfont.ttf (added)
-
tags/2.6.1/admin/fonts/fontawesome-webfont.woff (added)
-
tags/2.6.1/admin/fonts/fontawesome-webfont.woff2 (added)
-
tags/2.6.1/admin/images (added)
-
tags/2.6.1/admin/images/addon-icon.png (added)
-
tags/2.6.1/admin/images/etgh.png (added)
-
tags/2.6.1/admin/images/flags16.png (added)
-
tags/2.6.1/admin/images/logo.png (added)
-
tags/2.6.1/admin/images/mail.png (added)
-
tags/2.6.1/admin/images/memberpress.png (added)
-
tags/2.6.1/admin/images/miniorange.png (added)
-
tags/2.6.1/admin/images/miniorange_logo.png (added)
-
tags/2.6.1/admin/images/nft.png (added)
-
tags/2.6.1/admin/images/paidmember.png (added)
-
tags/2.6.1/admin/images/pricing-icon.png (added)
-
tags/2.6.1/admin/images/remove.png (added)
-
tags/2.6.1/admin/images/setup-icon.png (added)
-
tags/2.6.1/admin/images/tick.png (added)
-
tags/2.6.1/admin/images/trials-icon.png (added)
-
tags/2.6.1/admin/index.php (added)
-
tags/2.6.1/admin/js (added)
-
tags/2.6.1/admin/js/custom.min.js (added)
-
tags/2.6.1/admin/js/fontawesome.js (added)
-
tags/2.6.1/admin/js/jquery.dataTables.min.js (added)
-
tags/2.6.1/admin/js/media-restriction-admin.min.js (added)
-
tags/2.6.1/admin/js/phone.js (added)
-
tags/2.6.1/admin/partials (added)
-
tags/2.6.1/admin/partials/class-mo-media-restriction-admin-feedback.php (added)
-
tags/2.6.1/admin/partials/media-restriction-addon .php (added)
-
tags/2.6.1/admin/partials/media-restriction-admin-display.php (added)
-
tags/2.6.1/includes (added)
-
tags/2.6.1/includes/class-media-restriction-activator.php (added)
-
tags/2.6.1/includes/class-media-restriction-deactivator.php (added)
-
tags/2.6.1/includes/class-media-restriction-i18n.php (added)
-
tags/2.6.1/includes/class-media-restriction-loader.php (added)
-
tags/2.6.1/includes/class-media-restriction.php (added)
-
tags/2.6.1/includes/index.php (added)
-
tags/2.6.1/index.php (added)
-
tags/2.6.1/languages (added)
-
tags/2.6.1/languages/media-restriction.pot (added)
-
tags/2.6.1/media-restriction.php (added)
-
tags/2.6.1/uninstall.php (added)
-
trunk/README.txt (modified) (3 diffs)
-
trunk/admin/class-media-restriction-admin.php (modified) (6 diffs)
-
trunk/media-restriction.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
prevent-file-access/trunk/README.txt
r3297575 r3316692 6 6 Tested up to: 6.8 7 7 Requires PHP: 5.6 8 Stable tag: 2.6. 08 Stable tag: 2.6.1 9 9 License: Expat 10 10 License URI: https://plugins.miniorange.com/mit-license … … 143 143 144 144 == Changelog == 145 = 2.6.1 = 146 * Security fixes 147 145 148 = 2.6.0 = 146 149 * Added Compatibility with WordPress 6.8 … … 226 229 227 230 == Upgrade Notice == 231 = 2.6.1 = 232 * Security fixes 233 228 234 = 2.6.0 = 229 235 * Added Compatibility with WordPress 6.8 -
prevent-file-access/trunk/admin/class-media-restriction-admin.php
r2957943 r3316692 64 64 65 65 /** 66 * Validate and sanitize file path to prevent path traversal attacks 67 * 68 * @param string $path The path to validate. 69 * @return string|false The validated path or false if invalid 70 */ 71 private function mo_media_restriction_validate_path( $path ) { 72 $path = str_replace( array( "\0", "\x00" ), '', $path ); 73 $path = str_replace( '\\', '/', $path ); 74 75 $previous_path = ''; 76 while ( $path !== $previous_path ) { 77 $previous_path = $path; 78 $path = urldecode( $path ); 79 } 80 81 $path = str_replace( '\\', '/', $path ); 82 83 $dangerous_patterns = array( 84 '../', 85 '..\\', 86 '%2e%2e%2f', 87 '%2e%2e%5c', 88 '%252e%252e%252f', 89 '%252e%252e%255c', 90 '0x2e0x2e0x2f', 91 '0x2e0x2e0x5c', 92 '%c0%ae%c0%ae%c0%af', 93 '%c1%9c', 94 '..%2f', 95 '..%5c', 96 '%2e.', 97 '.%2e', 98 ); 99 100 foreach ( $dangerous_patterns as $pattern ) { 101 if ( stripos( $path, $pattern ) !== false ) { 102 $this->mo_media_restriction_log_security_event( 'Dangerous path pattern detected', $path ); 103 return false; 104 } 105 } 106 107 if ( substr( $path, 0, 1 ) === '/' || ( strlen( $path ) > 1 && substr( $path, 1, 1 ) === ':' ) ) { 108 return false; 109 } 110 111 $full_path = ABSPATH . DIRECTORY_SEPARATOR . ltrim( $path, '/' ); 112 113 $real_path = realpath( $full_path ); 114 115 if ( false === $real_path ) { 116 return false; 117 } 118 119 $wp_root = realpath( ABSPATH ); 120 if ( strpos( $real_path, $wp_root ) !== 0 ) { 121 return false; 122 } 123 124 $allowed_dirs = $this->mo_media_restriction_get_allowed_directories(); 125 $is_allowed = false; 126 127 foreach ( $allowed_dirs as $allowed_dir ) { 128 $allowed_real_path = realpath( $allowed_dir ); 129 if ( false !== $allowed_real_path && strpos( $real_path, $allowed_real_path ) === 0 ) { 130 $is_allowed = true; 131 break; 132 } 133 } 134 135 if ( ! $is_allowed ) { 136 $this->mo_media_restriction_log_security_event( 'Access to disallowed directory attempted', $path ); 137 return false; 138 } 139 140 return $real_path; 141 } 142 143 /** 144 * Get list of allowed directories for file access 145 * 146 * @return array Array of allowed directory paths 147 */ 148 private function mo_media_restriction_get_allowed_directories() { 149 $uploads_dir = wp_upload_dir(); 150 151 $allowed_dirs = array( 152 $uploads_dir['basedir'], 153 ); 154 155 $protected_dir = $uploads_dir['basedir'] . DIRECTORY_SEPARATOR . 'protectedfiles'; 156 if ( file_exists( $protected_dir ) ) { 157 $allowed_dirs[] = $protected_dir; 158 } 159 160 $custom_dirs = get_option( 'mo_media_restriction_allowed_dirs', array() ); 161 if ( is_array( $custom_dirs ) ) { 162 $allowed_dirs = array_merge( $allowed_dirs, $custom_dirs ); 163 } 164 165 $allowed_dirs = apply_filters( 'mo_media_restriction_allowed_directories', $allowed_dirs ); 166 167 return array_unique( $allowed_dirs ); 168 } 169 170 /** 66 171 * Show restricted folder path 67 172 * … … 70 175 */ 71 176 public function mo_media_show_file_or_folder( $redirect_url ) { 72 $file_path = ABSPATH . DIRECTORY_SEPARATOR . $redirect_url; 73 $file_url = site_url() . '/' . $redirect_url; 177 $validated_path = $this->mo_media_restriction_validate_path( $redirect_url ); 178 179 if ( false === $validated_path ) { 180 wp_die( 'WPMR001: Invalid file path', 'Access Denied', array( 'response' => 403 ) ); 181 } 182 183 $file_path = $validated_path; 184 $relative_path = str_replace( ABSPATH, '', $validated_path ); 185 $file_url = site_url() . '/' . ltrim( $relative_path, '/' ); 186 74 187 if ( file_exists( $file_path ) ) { 75 188 if ( is_dir( $file_path ) ) { 76 189 $dh = opendir( $file_path ); 77 190 if ( $dh ) { 78 // reading the contents of the directory. 191 79 192 $file = readdir( $dh ); 80 193 while ( false !== $file ) { 81 194 if ( '..' !== $file && '.' !== $file ) { 82 echo "<a href='" . esc_attr( $file_url ) . '/' . esc_attr( $file ) . "'>" . esc_attr( $file ) . '</a><br>'; 195 $file_name = sanitize_file_name( $file ); 196 if ( $file_name === $file ) { 197 $child_relative_path = ltrim( str_replace( ABSPATH, '', $validated_path ), '/' ); 198 $child_url = site_url() . '/' . $child_relative_path . '/' . $file_name; 199 echo "<a href='" . esc_url( $child_url ) . "'>" . esc_html( $file ) . '</a><br>'; 200 } 83 201 } 84 202 } … … 87 205 exit; 88 206 } else { 207 $allowed_extensions = array( 'jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx', 'txt' ); 208 $file_extension = strtolower( pathinfo( $file_path, PATHINFO_EXTENSION ) ); 209 210 if ( ! in_array( $file_extension, $allowed_extensions, true ) ) { 211 wp_die( 'WPMR002: File type not allowed', 'Access Denied', array( 'response' => 403 ) ); 212 } 213 89 214 header( 'content-type: ' . mime_content_type( $file_path ) ); 90 echo esc_attr( wp_remote_get( $file_path ));215 readfile( $file_path ); 91 216 exit; 92 217 } 93 218 } else { 94 wp_die( ' No such file exist' );219 wp_die( 'WPMR003: No such file exist' ); 95 220 } 96 221 } … … 102 227 */ 103 228 public function mo_media_restriction_validate() { 104 if ( isset( $_GET['mo_media_restrict_request'] ) && '1' === sanitize_text_field( wp_unslash( $_GET['mo_media_restrict_request'] ) ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Ignoring nonce verification because we are not fetching data on form submission.229 if ( isset( $_GET['mo_media_restrict_request'] ) && '1' === sanitize_text_field( wp_unslash( $_GET['mo_media_restrict_request'] ) ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- We are implementing custom nonce verification below. 105 230 if ( is_user_logged_in() === false ) { 106 231 $restrict_option = get_option( 'mo_mr_redirect_to' ); … … 114 239 exit; 115 240 } else { 116 $redirect_url = isset( $_GET['redirect_to'] ) ? sanitize_text_field( wp_unslash( $_GET['redirect_to'] ) ) : site_url(); //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Ignoring nonce verification because we are fetching data from URL and not on form submission. 241 $redirect_url = isset( $_GET['redirect_to'] ) ? sanitize_text_field( wp_unslash( $_GET['redirect_to'] ) ) : site_url(); 242 243 if ( empty( $redirect_url ) || strlen( $redirect_url ) > 255 ) { 244 wp_die( 'WPMR005: Invalid file path', 'Access Denied', array( 'response' => 403 ) ); 245 } 246 247 $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : ''; 248 if ( ! wp_verify_nonce( $nonce, 'mo_media_restriction_access' ) ) { 249 $this->mo_media_restriction_log_security_event( 'Invalid nonce for file access', $redirect_url ); 250 wp_die( 'WPMR006: Invalid file path', 'Access Denied', array( 'response' => 403 ) ); 251 } 252 253 if ( strpos( $redirect_url, '..' ) !== false || strpos( $redirect_url, '/' ) === 0 ) { 254 $this->mo_media_restriction_log_security_event( 'Path traversal attempt detected', $redirect_url ); 255 wp_die( 'WPMR007: Invalid file path', 'Access Denied', array( 'response' => 403 ) ); 256 } 257 258 if ( ! $this->mo_media_restriction_check_rate_limit() ) { 259 wp_die( 'WPMR008: Too many requests. Please try again later.', 'Rate Limit Exceeded', array( 'response' => 429 ) ); 260 } 261 117 262 $this->mo_media_show_file_or_folder( $redirect_url ); 118 263 } 119 264 } 265 } 266 267 /** 268 * Log security events for monitoring 269 * 270 * @param string $event_type Type of security event. 271 * @param string $details Additional details. 272 * @return void 273 */ 274 private function mo_media_restriction_log_security_event( $event_type, $details ) { 275 $user = wp_get_current_user(); 276 $log_entry = array( 277 'timestamp' => current_time( 'mysql' ), 278 'user_id' => $user->ID, 279 'user_login' => $user->user_login, 280 'ip_address' => isset( $_SERVER['REMOTE_ADDR'] ) 281 ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) 282 : 'unknown', 283 284 'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) 285 ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) 286 : 'unknown', 287 'event_type' => $event_type, 288 'details' => $details, 289 ); 290 291 $existing_logs = get_option( 'mo_media_restriction_security_logs', array() ); 292 $existing_logs[] = $log_entry; 293 294 if ( count( $existing_logs ) > 100 ) { 295 $existing_logs = array_slice( $existing_logs, -100 ); 296 } 297 298 update_option( 'mo_media_restriction_security_logs', $existing_logs ); 299 300 } 301 302 /** 303 * Simple rate limiting to prevent abuse 304 * 305 * @return bool True if request is allowed, false if rate limited 306 */ 307 private function mo_media_restriction_check_rate_limit() { 308 $user_id = get_current_user_id(); 309 $rate_limit_key = "mo_media_restriction_rate_limit_{$user_id}"; 310 $current_time = time(); 311 $window_size = 60; 312 $max_requests = 30; 313 314 $user_requests = get_transient( $rate_limit_key ); 315 316 if ( false === $user_requests ) { 317 set_transient( $rate_limit_key, array( $current_time ), $window_size ); 318 return true; 319 } 320 321 $user_requests = array_filter( 322 $user_requests, 323 function( $timestamp ) use ( $current_time, $window_size ) { 324 return ( $current_time - $timestamp ) < $window_size; 325 } 326 ); 327 328 if ( count( $user_requests ) >= $max_requests ) { 329 return false; 330 } 331 332 $user_requests[] = $current_time; 333 set_transient( $rate_limit_key, $user_requests, $window_size ); 334 335 return true; 120 336 } 121 337 … … 671 887 } 672 888 889 /** 890 * Generate secure URL for file access with proper nonce 891 * 892 * @param string $file_path The file path to generate URL for. 893 * @return string The secure URL with nonce 894 */ 895 public function mo_media_restriction_generate_secure_url( $file_path ) { 896 $file_path = sanitize_text_field( $file_path ); 897 898 $nonce = wp_create_nonce( 'mo_media_restriction_access' ); 899 900 $secure_url = add_query_arg( 901 array( 902 'mo_media_restrict_request' => '1', 903 'redirect_to' => $file_path, 904 '_wpnonce' => $nonce, 905 ), 906 home_url() 907 ); 908 909 return $secure_url; 910 } 911 673 912 } -
prevent-file-access/trunk/media-restriction.php
r3297575 r3316692 4 4 * Plugin URI: http://miniorange.com 5 5 * Description: Allows to protect your files and folders (wp-content, uploads, images, pdf, documents) from public access, Role base folder access, User base folder access, giving access to only logged in users. 6 * Version: 2.6. 06 * Version: 2.6.1 7 7 * Author: miniOrange 8 8 * Author URI: http://miniorange.com … … 23 23 * Rename this for your plugin and update it as you release new versions. 24 24 */ 25 define( 'MO_MEDIA_RESTRICTION_PLUGIN_NAME_VERSION', '2.6. 0' );25 define( 'MO_MEDIA_RESTRICTION_PLUGIN_NAME_VERSION', '2.6.1' ); 26 26 /** 27 27 * The code that runs during plugin activation.
Note: See TracChangeset
for help on using the changeset viewer.