'Uploads (media files)', 'wp-includes/' => 'Core includes', 'wp-content/plugins/' => 'Plugins directory', ); $exposed = array(); $checked = array(); $details = array(); foreach ( $test_dirs as $dir => $label ) { $url = trailingslashit( $site_url ) . $dir; $response = wp_remote_get( $url, array( 'timeout' => 8, 'sslverify' => false, 'redirection' => 0, 'headers' => Scanner::get_loopback_headers(), ) ); if ( is_wp_error( $response ) ) { $details[ $dir ] = 'request_failed'; continue; } $code = wp_remote_retrieve_response_code( $response ); $body = wp_remote_retrieve_body( $response ); $checked[] = $dir; // A 200 response with common directory listing indicators. if ( 200 === $code && self::looks_like_directory_listing( $body ) ) { $exposed[] = $label; $details[ $dir ] = 'exposed'; } else { $details[ $dir ] = $code; } } $details['directories_checked'] = $checked; $details['directories_exposed'] = $exposed; if ( empty( $exposed ) ) { return static::pass( __( 'Directory listing is properly disabled. Visitors cannot browse the contents of your site\'s directories.', '0-day-analytics' ), $details ); } return static::fail( count( $exposed ) > 1 ? 'high' : 'medium', sprintf( /* translators: %s: comma-separated list of directory names */ __( 'Directory listing is enabled for: %s. This allows anyone to see all files in these directories, potentially exposing sensitive information, backup files, or other data that should be private.', '0-day-analytics' ), implode( ', ', $exposed ) ), __( 'Add "Options -Indexes" to your .htaccess file (Apache), or configure "autoindex off;" in Nginx. Most hosting providers also have a setting to disable directory browsing in the control panel.', '0-day-analytics' ), $details ); } /** * Heuristic: does the HTML body look like an auto-generated directory listing? * * @param string $body Response body. * * @return bool * * @since 5.1.0 */ private static function looks_like_directory_listing( string $body ): bool { // Apache "Index of /" pattern. if ( preg_match( '/\s*Index of\s/i', $body ) ) { return true; } // Nginx autoindex pattern. if ( preg_match( '/<title>.*listing.*<\/title>/i', $body ) ) { return true; } // LiteSpeed / generic indicators. if ( preg_match( '/Directory Listing For/i', $body ) ) { return true; } // Common directory listing HTML has <pre> with file links and Parent Directory link. if ( stripos( $body, 'Parent Directory' ) !== false && preg_match_all( '/<a\s[^>]*href=["\'][^"\']*["\'][^>]*>/i', $body ) > 5 ) { return true; } return false; } } }