Skip to content

Commit f9a4be4

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.0 branch. git-svn-id: https://develop.svn.wordpress.org/branches/5.0@63827 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 086a8c2 commit f9a4be4

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
@@ -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

Comments
 (0)