@@ -419,7 +419,7 @@ function get_page_template() {
419419 $ templates [] = $ template ;
420420 if ( $ pagename ) {
421421 $ pagename_decoded = urldecode ( $ pagename );
422- if ( $ pagename_decoded !== $ pagename ) {
422+ if ( $ pagename_decoded !== $ pagename && 0 === validate_file ( $ pagename_decoded ) ) {
423423 $ templates [] = "page- {$ pagename_decoded }.php " ;
424424 }
425425 $ templates [] = "page- {$ pagename }.php " ;
@@ -613,13 +613,70 @@ function get_attachment_template() {
613613 return get_query_template ( 'attachment ' , $ templates );
614614}
615615
616+ /**
617+ * Determines whether a template found by locate_template() may be loaded.
618+ *
619+ * @since 7.1.2
620+ * @access private
621+ *
622+ * @param string $path Path to an existing template file.
623+ * @return bool Whether the template may be loaded.
624+ */
625+ function _wp_is_template_path_allowed ( $ path ) {
626+ // A file path that exists and does not contain `..` is allowed.
627+ if ( 0 === preg_match ( '#(?:^|/)\.\.[. ]*(?:/|$)# ' , wp_normalize_path ( $ path ) ) ) {
628+ return true ;
629+ }
630+
631+ // Resolve the true location of the requested file for later comparison.
632+ $ real_path = realpath ( $ path );
633+
634+ if ( false === $ real_path ) {
635+ return false ;
636+ }
637+
638+ $ real_path = trailingslashit ( wp_normalize_path ( $ real_path ) );
639+
640+ $ directories = array (
641+ STYLESHEETPATH ,
642+ TEMPLATEPATH ,
643+ ABSPATH . WPINC . '/theme-compat ' ,
644+ );
645+
646+ // If a theme is in a subdirectory, accept templates from its direct parent directory.
647+ if ( false !== strpos ( get_stylesheet (), '/ ' ) ) {
648+ $ directories [] = dirname ( STYLESHEETPATH );
649+ }
650+
651+ // If a parent theme is in a subdirectory, accept templates from its direct parent directory.
652+ if ( false !== strpos ( get_template (), '/ ' ) ) {
653+ $ directories [] = dirname ( TEMPLATEPATH );
654+ }
655+
656+ foreach ( $ directories as $ directory ) {
657+ $ real_directory = realpath ( $ directory );
658+
659+ if ( false === $ real_directory ) {
660+ continue ;
661+ }
662+
663+ // The true location of the requested file must be inside one of the allowed directories.
664+ if ( 0 === strpos ( $ real_path , trailingslashit ( wp_normalize_path ( $ real_directory ) ) ) ) {
665+ return true ;
666+ }
667+ }
668+
669+ return false ;
670+ }
671+
616672/**
617673 * Retrieve the name of the highest priority template file that exists.
618674 *
619675 * Searches in the STYLESHEETPATH before TEMPLATEPATH and wp-includes/theme-compat
620676 * so that themes which inherit from a parent theme can just overload one file.
621677 *
622678 * @since 2.7.0
679+ * @since 7.1.2 A template name containing `..` is only located if it resolves inside the theme.
623680 *
624681 * @param string|array $template_names Template file(s) to search for, in order.
625682 * @param bool $load If true the template file will be loaded if it is found.
@@ -632,13 +689,17 @@ function locate_template($template_names, $load = false, $require_once = true )
632689 if ( !$ template_name )
633690 continue ;
634691 if ( file_exists (STYLESHEETPATH . '/ ' . $ template_name )) {
635- $ located = STYLESHEETPATH . '/ ' . $ template_name ;
636- break ;
692+ $ candidate = STYLESHEETPATH . '/ ' . $ template_name ;
637693 } elseif ( file_exists (TEMPLATEPATH . '/ ' . $ template_name ) ) {
638- $ located = TEMPLATEPATH . '/ ' . $ template_name ;
639- break ;
694+ $ candidate = TEMPLATEPATH . '/ ' . $ template_name ;
640695 } elseif ( file_exists ( ABSPATH . WPINC . '/theme-compat/ ' . $ template_name ) ) {
641- $ located = ABSPATH . WPINC . '/theme-compat/ ' . $ template_name ;
696+ $ candidate = ABSPATH . WPINC . '/theme-compat/ ' . $ template_name ;
697+ } else {
698+ continue ;
699+ }
700+
701+ if ( _wp_is_template_path_allowed ( $ candidate ) ) {
702+ $ located = $ candidate ;
642703 break ;
643704 }
644705 }
0 commit comments