VYPR
High severityNVD Advisory· Published Apr 1, 2025· Updated Apr 1, 2025

Path Traversal allowing arbitrary read of files in Yeswiki

CVE-2025-31131

Description

YesWiki is a wiki system written in PHP. The squelette parameter is vulnerable to path traversal attacks, enabling read access to arbitrary files on the server. This vulnerability is fixed in 4.5.2.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
yeswiki/yeswikiPackagist
< 4.5.24.5.2

Affected products

1

Patches

1
f78c915369a6

fix(thememanager): path traversal vulnerability

https://github.com/YesWiki/yeswikiFlorian SchmittMar 24, 2025via ghsa
1 file changed · +55 12
  • includes/services/ThemeManager.php+55 12 modified
    @@ -119,31 +119,74 @@ public function loadTemplates($metadata = []): ?array
                 $this->setFavorite('preset', $this->getConfigAsStringOrDefault('favorite_preset', ''));
             } else {
                 // Sinon, on récupère premièrement les valeurs passées en REQUEST, ou deuxièmement les métasdonnées présentes pour la page, ou troisièmement les valeurs du fichier de configuration
    -            if (isset($_REQUEST['theme']) && (is_dir('custom/themes/' . $_REQUEST['theme']) || is_dir('themes/' . $_REQUEST['theme']))
    -                && isset($_REQUEST['style']) && (is_file('custom/themes/' . $_REQUEST['theme'] . '/styles/' . $_REQUEST['style']) || is_file('themes/' . $_REQUEST['theme'] . '/styles/' . $_REQUEST['style']))
    -                && isset($_REQUEST['squelette']) && (is_file('custom/themes/' . $_REQUEST['theme'] . '/squelettes/' . $_REQUEST['squelette']) || is_file('themes/' . $_REQUEST['theme'] . '/squelettes/' . $_REQUEST['squelette']))
    +            $requested = [];
    +            $keysToVerify = ['theme', 'squelette', 'style', 'preset'];
    +            foreach ($keysToVerify as $val) {
    +                $requested[$val] = null;
    +                if (!empty($_REQUEST[$val])) {
    +                    if (preg_match('/\//', $_REQUEST[$val], $matches)) {
    +                        exit('ERROR: Suspicious path traversal attempt.');
    +                    }
    +                    switch ($val) {
    +                        case 'theme':
    +                            $customThemePath = basename(realpath(getcwd() . '/custom/themes/' . $_REQUEST[$val]));
    +                            $classicThemePath = basename(realpath(getcwd() . '/themes/' . $_REQUEST[$val]));
    +                            $requested[$val] = !empty($customThemePath) ? $customThemePath : $classicThemePath;
    +                            break;
    +
    +                        case 'squelette':
    +                            $customPath = basename(realpath(getcwd() . '/custom/themes/' . $requested['theme'] . '/squelettes/' . $_REQUEST[$val]));
    +                            $classicPath = basename(realpath(getcwd() . '/themes/' . $requested['theme'] . 'squelettes/' . $_REQUEST[$val]));
    +                            $requested[$val] = null;
    +                            if (!empty($customPath) && file_exists(getcwd() . '/custom/themes/' . $requested['theme'] . '/squelettes/' . $customPath)) {
    +                                $requested[$val] = $customPath;
    +                            } elseif (file_exists(getcwd() . '/themes/' . $requested['theme'] . '/squelettes/' . $classicPath)) {
    +                                $requested[$val] = $classicPath;
    +                            }
    +                            if (!preg_match('/\.tpl\.html$/i', $requested[$val] ?? '', $matches)) {
    +                                $requested[$val] = null;
    +                            }
    +
    +                            break;
    +
    +                        default:
    +                            // ugly append of "s" to get the path of styleS, presetS and squeletteS
    +                            $customPath = basename(realpath(getcwd() . '/custom/themes/' . $requested['theme'] . '/' . $val . 's/' . $_REQUEST[$val]));
    +                            $classicPath = basename(realpath(getcwd() . '/themes/' . $requested['theme'] . '/' . $val . 's/' . $_REQUEST[$val]));
    +                            $requested[$val] = null;
    +                            if (!empty($customPath) && file_exists(getcwd() . '/custom/themes/' . $requested['theme'] . '/' . $val . 's/' . $customPath)) {
    +                                $requested[$val] = $customPath;
    +                            } elseif (file_exists(getcwd() . '/themes/' . $requested['theme'] . '/' . $val . 's/' . $classicPath)) {
    +                                $requested[$val] = $classicPath;
    +                            }
    +
    +                            break;
    +                    }
    +                }
    +            }
    +            if (!empty($requested['theme']) && !empty($requested['style']) && !empty($requested['squelette']) && preg_match('/\.tpl\.html$/i', $requested['squelette'], $matches)
                 ) {
    -                $this->setFavorite('theme', $_REQUEST['theme']);
    -                $this->setFavorite('style', $_REQUEST['style']);
    -                $this->setFavorite('squelette', $_REQUEST['squelette']);
    +                $this->setFavorite('theme', $requested['theme']);
    +                $this->setFavorite('style', $requested['style']);
    +                $this->setFavorite('squelette', $requested['squelette']);
     
                     // presets
    -                if (isset($_REQUEST['preset'])
    +                if (!empty($requested['preset'])
                             && (
                                 (
    -                                ($isCustom = (substr($_REQUEST['preset'], 0, strlen(self::CUSTOM_CSS_PRESETS_PREFIX)) == self::CUSTOM_CSS_PRESETS_PREFIX))
    -                                && is_file(self::CUSTOM_CSS_PRESETS_PATH . '/' . substr($_REQUEST['preset'], strlen(self::CUSTOM_CSS_PRESETS_PREFIX)))
    +                                ($isCustom = (substr($requested['preset'], 0, strlen(self::CUSTOM_CSS_PRESETS_PREFIX)) == self::CUSTOM_CSS_PRESETS_PREFIX))
    +                                && is_file(self::CUSTOM_CSS_PRESETS_PATH . '/' . substr($requested['preset'], strlen(self::CUSTOM_CSS_PRESETS_PREFIX)))
                                 )
                                 || (
                                     !$isCustom
                                     && (
    -                                    is_file('custom/themes/' . $_REQUEST['theme'] . '/presets/' . $_REQUEST['preset'])
    -                                    || is_file('themes/' . $_REQUEST['theme'] . '/presets/' . $_REQUEST['preset'])
    +                                    is_file('custom/themes/' . $requested['theme'] . '/presets/' . $requested['preset'])
    +                                    || is_file('themes/' . $requested['theme'] . '/presets/' . $requested['preset'])
                                     )
                                 )
                             )
                     ) {
    -                    $this->setFavorite('preset', $_REQUEST['preset']);
    +                    $this->setFavorite('preset', $requested['preset']);
                     }
     
                     if (isset($_REQUEST['bgimg']) && is_file('files/backgrounds/' . $_REQUEST['bgimg'])) {
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.