Skip to content

Commit 86378f3

Browse files
committed
Themes: Restrict path traversal in locate_template().
Props jeremyfelt, marcs0h, vortfu, jorbin, fiocavallari, xknown, sirlouen, joemcgill, pypwalters, swissspidy, shailu25, oglekler, peterwilsoncc, mukesh27, audrasjb, wildworks, rajinsharwar, martinkrcho, jeffpaul, ressl. Merges r63792 into the 5.7 branch. git-svn-id: https://develop.svn.wordpress.org/branches/5.7@63820 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 6e1f16b commit 86378f3

1 file changed

Lines changed: 67 additions & 6 deletions

File tree

‎src/wp-includes/template.php‎

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ function get_page_template() {
442442
}
443443
if ( $pagename ) {
444444
$pagename_decoded = urldecode( $pagename );
445-
if ( $pagename_decoded !== $pagename ) {
445+
if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
446446
$templates[] = "page-{$pagename_decoded}.php";
447447
}
448448
$templates[] = "page-{$pagename}.php";
@@ -637,6 +637,62 @@ function get_attachment_template() {
637637
return get_query_template( 'attachment', $templates );
638638
}
639639

640+
/**
641+
* Determines whether a template found by locate_template() may be loaded.
642+
*
643+
* @since 7.1.2
644+
* @access private
645+
*
646+
* @param string $path Path to an existing template file.
647+
* @return bool Whether the template may be loaded.
648+
*/
649+
function _wp_is_template_path_allowed( $path ) {
650+
// A file path that exists and does not contain `..` is allowed.
651+
if ( 0 === preg_match( '#(?:^|/)\.\.[. ]*(?:/|$)#', wp_normalize_path( $path ) ) ) {
652+
return true;
653+
}
654+
655+
// Resolve the true location of the requested file for later comparison.
656+
$real_path = realpath( $path );
657+
658+
if ( false === $real_path ) {
659+
return false;
660+
}
661+
662+
$real_path = trailingslashit( wp_normalize_path( $real_path ) );
663+
664+
$directories = array(
665+
STYLESHEETPATH,
666+
TEMPLATEPATH,
667+
ABSPATH . WPINC . '/theme-compat',
668+
);
669+
670+
// If a theme is in a subdirectory, accept templates from its direct parent directory.
671+
if ( false !== strpos( get_stylesheet(), '/' ) ) {
672+
$directories[] = dirname( STYLESHEETPATH );
673+
}
674+
675+
// If a parent theme is in a subdirectory, accept templates from its direct parent directory.
676+
if ( false !== strpos( get_template(), '/' ) ) {
677+
$directories[] = dirname( TEMPLATEPATH );
678+
}
679+
680+
foreach ( $directories as $directory ) {
681+
$real_directory = realpath( $directory );
682+
683+
if ( false === $real_directory ) {
684+
continue;
685+
}
686+
687+
// The true location of the requested file must be inside one of the allowed directories.
688+
if ( str_starts_with( $real_path, trailingslashit( wp_normalize_path( $real_directory ) ) ) ) {
689+
return true;
690+
}
691+
}
692+
693+
return false;
694+
}
695+
640696
/**
641697
* Retrieve the name of the highest priority template file that exists.
642698
*
@@ -645,6 +701,7 @@ function get_attachment_template() {
645701
*
646702
* @since 2.7.0
647703
* @since 5.5.0 The `$args` parameter was added.
704+
* @since 7.1.2 A template name containing `..` is only located if it resolves inside the theme.
648705
*
649706
* @param string|array $template_names Template file(s) to search for, in order.
650707
* @param bool $load If true the template file will be loaded if it is found.
@@ -661,13 +718,17 @@ function locate_template( $template_names, $load = false, $require_once = true,
661718
continue;
662719
}
663720
if ( file_exists( STYLESHEETPATH . '/' . $template_name ) ) {
664-
$located = STYLESHEETPATH . '/' . $template_name;
665-
break;
721+
$candidate = STYLESHEETPATH . '/' . $template_name;
666722
} elseif ( file_exists( TEMPLATEPATH . '/' . $template_name ) ) {
667-
$located = TEMPLATEPATH . '/' . $template_name;
668-
break;
723+
$candidate = TEMPLATEPATH . '/' . $template_name;
669724
} elseif ( file_exists( ABSPATH . WPINC . '/theme-compat/' . $template_name ) ) {
670-
$located = ABSPATH . WPINC . '/theme-compat/' . $template_name;
725+
$candidate = ABSPATH . WPINC . '/theme-compat/' . $template_name;
726+
} else {
727+
continue;
728+
}
729+
730+
if ( _wp_is_template_path_allowed( $candidate ) ) {
731+
$located = $candidate;
671732
break;
672733
}
673734
}

0 commit comments

Comments
 (0)