Skip to content

Commit aa9ef7f

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.2 branch. git-svn-id: https://develop.svn.wordpress.org/branches/5.2@63825 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 81230b0 commit aa9ef7f

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
@@ -441,7 +441,7 @@ function get_page_template() {
441441
}
442442
if ( $pagename ) {
443443
$pagename_decoded = urldecode( $pagename );
444-
if ( $pagename_decoded !== $pagename ) {
444+
if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
445445
$templates[] = "page-{$pagename_decoded}.php";
446446
}
447447
$templates[] = "page-{$pagename}.php";
@@ -636,13 +636,70 @@ function get_attachment_template() {
636636
return get_query_template( 'attachment', $templates );
637637
}
638638

639+
/**
640+
* Determines whether a template found by locate_template() may be loaded.
641+
*
642+
* @since 7.1.2
643+
* @access private
644+
*
645+
* @param string $path Path to an existing template file.
646+
* @return bool Whether the template may be loaded.
647+
*/
648+
function _wp_is_template_path_allowed( $path ) {
649+
// A file path that exists and does not contain `..` is allowed.
650+
if ( 0 === preg_match( '#(?:^|/)\.\.[. ]*(?:/|$)#', wp_normalize_path( $path ) ) ) {
651+
return true;
652+
}
653+
654+
// Resolve the true location of the requested file for later comparison.
655+
$real_path = realpath( $path );
656+
657+
if ( false === $real_path ) {
658+
return false;
659+
}
660+
661+
$real_path = trailingslashit( wp_normalize_path( $real_path ) );
662+
663+
$directories = array(
664+
STYLESHEETPATH,
665+
TEMPLATEPATH,
666+
ABSPATH . WPINC . '/theme-compat',
667+
);
668+
669+
// If a theme is in a subdirectory, accept templates from its direct parent directory.
670+
if ( false !== strpos( get_stylesheet(), '/' ) ) {
671+
$directories[] = dirname( STYLESHEETPATH );
672+
}
673+
674+
// If a parent theme is in a subdirectory, accept templates from its direct parent directory.
675+
if ( false !== strpos( get_template(), '/' ) ) {
676+
$directories[] = dirname( TEMPLATEPATH );
677+
}
678+
679+
foreach ( $directories as $directory ) {
680+
$real_directory = realpath( $directory );
681+
682+
if ( false === $real_directory ) {
683+
continue;
684+
}
685+
686+
// The true location of the requested file must be inside one of the allowed directories.
687+
if ( str_starts_with( $real_path, trailingslashit( wp_normalize_path( $real_directory ) ) ) ) {
688+
return true;
689+
}
690+
}
691+
692+
return false;
693+
}
694+
639695
/**
640696
* Retrieve the name of the highest priority template file that exists.
641697
*
642698
* Searches in the STYLESHEETPATH before TEMPLATEPATH and wp-includes/theme-compat
643699
* so that themes which inherit from a parent theme can just overload one file.
644700
*
645701
* @since 2.7.0
702+
* @since 7.1.2 A template name containing `..` is only located if it resolves inside the theme.
646703
*
647704
* @param string|array $template_names Template file(s) to search for, in order.
648705
* @param bool $load If true the template file will be loaded if it is found.
@@ -656,13 +713,17 @@ function locate_template( $template_names, $load = false, $require_once = true )
656713
continue;
657714
}
658715
if ( file_exists( STYLESHEETPATH . '/' . $template_name ) ) {
659-
$located = STYLESHEETPATH . '/' . $template_name;
660-
break;
716+
$candidate = STYLESHEETPATH . '/' . $template_name;
661717
} elseif ( file_exists( TEMPLATEPATH . '/' . $template_name ) ) {
662-
$located = TEMPLATEPATH . '/' . $template_name;
663-
break;
718+
$candidate = TEMPLATEPATH . '/' . $template_name;
664719
} elseif ( file_exists( ABSPATH . WPINC . '/theme-compat/' . $template_name ) ) {
665-
$located = ABSPATH . WPINC . '/theme-compat/' . $template_name;
720+
$candidate = ABSPATH . WPINC . '/theme-compat/' . $template_name;
721+
} else {
722+
continue;
723+
}
724+
725+
if ( _wp_is_template_path_allowed( $candidate ) ) {
726+
$located = $candidate;
666727
break;
667728
}
668729
}

0 commit comments

Comments
 (0)