VYPR
High severityNVD Advisory· Published Jan 21, 2025· Updated Feb 12, 2025

YesWiki vulnerable to authenticated arbitrary file deletion

CVE-2025-24019

Description

YesWiki is a wiki system written in PHP. In versions up to and including 4.4.5, it is possible for any authenticated user, through the use of the filemanager to delete any file owned by the user running the FastCGI Process Manager (FPM) on the host without any limitation on the filesystem's scope. This vulnerability allows any authenticated user to arbitrarily remove content from the Wiki resulting in partial loss of data and defacement/deterioration of the website. In the context of a container installation of YesWiki without any modification, the yeswiki files (for example .php) are not owned by the same user (root) as the one running the FPM process (www-data). However in a standard installation, www-data may also be the owner of the PHP files, allowing a malicious user to completely cut the access to the wiki by deleting all important PHP files (like index.php or core files of YesWiki). Version 4.5.0 contains a patch for this issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
yeswiki/yeswikiPackagist
< 4.5.04.5.0

Affected products

1

Patches

1
3ddd833d2270

fix(attach): authenticated arbitrary file deletion

https://github.com/YesWiki/yeswikiFlorian SchmittJan 19, 2025via ghsa
1 file changed · +54 52
  • tools/attach/libs/attach.lib.php+54 52 modified
    @@ -14,27 +14,27 @@
     if (!class_exists('attach')) {
         class attach
         {
    -        public $wiki = ''; //objet wiki courant
    -        public $attachConfig = []; //configuration de l'action
    -        public $file = ''; //nom du fichier
    +        public $wiki = ''; // objet wiki courant
    +        public $attachConfig = []; // configuration de l'action
    +        public $file = ''; // nom du fichier
             public $height;
             public $width;
    -        public $desc = ''; //description du fichier
    -        public $link = ''; //url de lien (image sensible)
    -        public $caption = ''; //texte de la vignette au survol
    -        public $legend = ''; //texte en dessous de l'image
    -        public $nofullimagelink = ''; //mettre un lien vers l'image entiere
    -        public $isPicture = 0; //indique si c'est une image
    -        public $isAudio = 0; //indique si c'est un fichier audio
    -        public $isFreeMindMindMap = 0; //indique si c'est un fichier mindmap freemind
    -        public $isWma = 0; //indique si c'est un fichier wma
    -        public $isPDF = 0; //indique si c'est un fichier pdf
    -        public $displayPDF = 0; //indique s'il faut afficher le fichier pdf
    -        public $classes = 'attached_file'; //classe pour afficher une image
    -        public $attachErr = ''; //message d'erreur
    -        public $pageId = 0; //identifiant de la page
    -        public $isSafeMode = true; //indicateur du safe mode de PHP
    -        public $data = ''; //indicateur du safe mode de PHP
    +        public $desc = ''; // description du fichier
    +        public $link = ''; // url de lien (image sensible)
    +        public $caption = ''; // texte de la vignette au survol
    +        public $legend = ''; // texte en dessous de l'image
    +        public $nofullimagelink = ''; // mettre un lien vers l'image entiere
    +        public $isPicture = 0; // indique si c'est une image
    +        public $isAudio = 0; // indique si c'est un fichier audio
    +        public $isFreeMindMindMap = 0; // indique si c'est un fichier mindmap freemind
    +        public $isWma = 0; // indique si c'est un fichier wma
    +        public $isPDF = 0; // indique si c'est un fichier pdf
    +        public $displayPDF = 0; // indique s'il faut afficher le fichier pdf
    +        public $classes = 'attached_file'; // classe pour afficher une image
    +        public $attachErr = ''; // message d'erreur
    +        public $pageId = 0; // identifiant de la page
    +        public $isSafeMode = true; // indicateur du safe mode de PHP
    +        public $data = ''; // indicateur du safe mode de PHP
             private $params;
     
             /**
    @@ -185,7 +185,7 @@ public function GetFullFilename($newName = false)
                     )
                 );
     
    -            //decompose le nom du fichier en nom+extension ou en page/nom+extension
    +            // decompose le nom du fichier en nom+extension ou en page/nom+extension
                 if (preg_match('`^((.+)/)?(.*)\.(.*)$`', str_replace(' ', '_', $this->file), $match)) {
                     list(, , $file['page'], $file['name'], $file['ext']) = $match;
                     if (!$this->isPicture() && !$this->isAudio() && !$this->isVideo() && !$this->isFreeMindMindMap() && !$this->isWma() && !$this->isFlashvideo()) {
    @@ -194,10 +194,10 @@ public function GetFullFilename($newName = false)
                 } else {
                     return false;
                 }
    -            //recuperation du chemin d'upload
    +            // recuperation du chemin d'upload
                 $path = $this->GetUploadPath($this->isSafeMode);
                 $page_tag = $file['page'] ? $file['page'] : $this->wiki->GetPageTag();
    -            //generation du nom ou recherche de fichier ?
    +            // generation du nom ou recherche de fichier ?
                 if ($newName) {
                     $full_file_name = $file['name'] . '_' . $pagedate . '_' . $this->getDate() . '.' . $file['ext'];
                     if ($this->isSafeMode) {
    @@ -207,12 +207,12 @@ public function GetFullFilename($newName = false)
                     }
                 } else {
                     $isActionBuilderPreview = $this->wiki->GetPageTag() == 'root';
    -                //recherche du fichier
    +                // recherche du fichier
                     if ($isActionBuilderPreview) {
                         // bazar action builder, preview action
                         $searchPattern = '`' . $file['name'] . '_\d{14}_\d{14}\.' . $file['ext'] . '$`';
                     } elseif ($this->isSafeMode) {
    -                    //TODO Recherche dans le cas ou safe_mode=on
    +                    // TODO Recherche dans le cas ou safe_mode=on
                         $searchPattern = '`^' . $page_tag . '_' . $file['name'] . '_\d{14}_\d{14}\.' . $file['ext'] . '$`';
                     } else {
                         $searchPattern = '`^' . $file['name'] . '_\d{14}_\d{14}\.' . $file['ext'] . '$`';
    @@ -330,7 +330,7 @@ public function parseDate($sDate)
                 $pattern = '`^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$`';
                 $res = '';
                 if (preg_match($pattern, $sDate, $m)) {
    -                //list(,$res['year'],$res['month'],$res['day'],$res['hour'],$res['min'],$res['sec'])=$m;
    +                // list(,$res['year'],$res['month'],$res['day'],$res['hour'],$res['min'],$res['sec'])=$m;
                     $res = $m[1] . '-' . $m[2] . '-' . $m[3] . ' ' . $m[4] . ':' . $m[5] . ':' . $m[6];
                 }
     
    @@ -362,17 +362,17 @@ public function decodeLongFilename($filename)
                 $afile['path'] = dirname($filename);
                 if (preg_match('`^(.*)_(\d{14})_(\d{14})\.(.*)(trash\d{14})?$`', $afile['realname'], $m)) {
                     $afile['name'] = $m[1];
    -                //suppression du nom de la page si safe_mode=on
    +                // suppression du nom de la page si safe_mode=on
                     if ($this->isSafeMode) {
                         $afile['name'] = preg_replace('`^(' . $this->wiki->tag . ')_(.*)$`i', '$2', $afile['name']);
                     }
                     $afile['datepage'] = $m[2];
                     $afile['dateupload'] = $m[3];
                     $afile['trashdate'] = preg_replace('`(.*)trash(\d{14})`', '$2', $m[4]);
    -                //suppression de trashxxxxxxxxxxxxxx eventuel
    +                // suppression de trashxxxxxxxxxxxxxx eventuel
                     $afile['ext'] = preg_replace('`^(.*)(trash\d{14})$`', '$1', $m[4]);
                     $afile['ext'] = rtrim($afile['ext'], '_');
    -                //$afile['ext'] = rtrim($m[4],'_');
    +                // $afile['ext'] = rtrim($m[4],'_');
                 }
     
                 return $afile;
    @@ -408,7 +408,7 @@ public function searchFiles($filepattern, $start_dir)
              */
             public function CheckParams()
             {
    -            //recuperation des parametres necessaire
    +            // recuperation des parametres necessaire
                 $this->file = $this->wiki->GetParameter('attachfile');
                 if (empty($this->file)) {
                     $this->file = $this->wiki->GetParameter('file');
    @@ -420,20 +420,20 @@ public function CheckParams()
                 }
                 $this->desc = htmlentities(strip_tags($this->desc)); // avoid XSS
     
    -            $this->link = $this->wiki->GetParameter('attachlink'); //url de lien - uniquement si c'est une image
    +            $this->link = $this->wiki->GetParameter('attachlink'); // url de lien - uniquement si c'est une image
                 if (empty($this->link)) {
                     $this->link = $this->wiki->GetParameter('link');
                 }
     
    -            $this->caption = $this->wiki->GetParameter('caption'); //texte de la vignette (au survol)
    -            $this->legend = $this->wiki->GetParameter('legend'); //texte de la vignette (en dessous)
    +            $this->caption = $this->wiki->GetParameter('caption'); // texte de la vignette (au survol)
    +            $this->legend = $this->wiki->GetParameter('legend'); // texte de la vignette (en dessous)
                 $this->nofullimagelink = $this->wiki->GetParameter('nofullimagelink');
                 $this->height = $this->wiki->GetParameter('height');
                 $this->width = $this->wiki->GetParameter('width');
                 $this->displayPDF = $this->wiki->GetParameter('displaypdf');
    -            $this->data = $this->wiki->services->get(\YesWiki\Templates\Service\Utils::class)->getDataParameter();
    +            $this->data = $this->wiki->services->get(YesWiki\Templates\Service\Utils::class)->getDataParameter();
     
    -            //test de validit&eacute; des parametres
    +            // test de validit&eacute; des parametres
                 if (empty($this->file)) {
                     $this->attachErr = '<div class="alert alert-danger"><strong>' . _t('ATTACH_ACTION_ATTACH') . '</strong> : ' . _t('ATTACH_PARAM_FILE_NOT_FOUND') . '.</div>' . "\n";
                 }
    @@ -510,10 +510,10 @@ public function showAsImage($fullFilename)
                     $height = $height - 20;
                 }
     
    -            //c'est une image : balise <IMG..../>
    +            // c'est une image : balise <IMG..../>
                 $img = '<img loading="lazy" class="img-responsive" src="' . $this->GetScriptPath() . $img_name . '" ' .
                 'alt="' . $this->desc . ($this->link ? "\nLien vers: $this->link" : '') . '" width="' . $width . '" height="' . $height . '" />';
    -            //test si c'est une image sensible
    +            // test si c'est une image sensible
                 $classDataForLinks =
                     strstr($this->classes, 'new-window')
                     ? ' class="new-window"'
    @@ -553,7 +553,7 @@ public function showAsImage($fullFilename)
                 $output = ($notAligned ? '<div>' : '') . (isset($link) ? $link : '') . "<figure class=\"$this->classes\" $data>$img$caption$legend</figure>" . (isset($link) ? '</a>' : '') . ($notAligned ? '</div>' : '');
     
                 echo $output;
    -            //$this->showUpdateLink();
    +            // $this->showUpdateLink();
             }
     
             /**
    @@ -674,13 +674,13 @@ public function doAttach()
                     return;
                 }
                 $fullFilename = $this->GetFullFilename();
    -            //test d'existance du fichier
    +            // test d'existance du fichier
                 if ((!file_exists($fullFilename)) || ($fullFilename == '')) {
                     $this->showFileNotExits();
     
                     return;
                 }
    -            //le fichier existe : affichage en fonction du type
    +            // le fichier existe : affichage en fonction du type
                 if ($this->isPicture()) {
                     $this->showAsImage($fullFilename);
                 } elseif ($this->isVideo() || $this->isFlashvideo()) {
    @@ -751,8 +751,8 @@ public function performUpload()
                 if ($this->wiki->config['authorized-extensions'] && !in_array($ext, array_keys($this->wiki->config['authorized-extensions']))) {
                     $_FILES['upFile']['error'] = 5;
                 }
    -            $destFile = $this->GetFullFilename(true); //nom du fichier destination
    -            //test de la taille du fichier recu
    +            $destFile = $this->GetFullFilename(true); // nom du fichier destination
    +            // test de la taille du fichier recu
                 if ($_FILES['upFile']['error'] == 0) {
                     $size = filesize($_FILES['upFile']['tmp_name']);
                     if ($size > $this->attachConfig['max_file_size']) {
    @@ -823,8 +823,8 @@ public function doDownload()
                     header('Cache-Control: no-store, no-cache, must-revalidate'); // HTTP/1.1
                     header('Cache-Control: pre-check=0, post-check=0, max-age=0'); // HTTP/1.1
                     header('Content-Transfer-Encoding: none');
    -                header('Content-Type: application/octet-stream; name="' . $dlFilename . '"'); //This should work for the rest
    -                header('Content-Type: application/octetstream; name="' . $dlFilename . '"'); //This should work for IE & Opera
    +                header('Content-Type: application/octet-stream; name="' . $dlFilename . '"'); // This should work for the rest
    +                header('Content-Type: application/octetstream; name="' . $dlFilename . '"'); // This should work for IE & Opera
                     if (in_array(preg_replace("/^.*\.([^.]+$)/", '$1', $dlFilename), ['txt', 'md', 'png', 'svg', 'jpeg', 'jpg', 'mp3'])) {
                         header('Content-Type: ' . mime_content_type($fullFilename) . '; name="' . $dlFilename . '"');
                     }
    @@ -866,7 +866,7 @@ public function doFileManager($isAction = false)
                         $this->fmShow(true, $isAction);
                         break;
                     case 'emptytrash':
    -                    $this->fmEmptyTrash(); //pas de break car apres un emptytrash => retour au gestionnaire
    +                    $this->fmEmptyTrash(); // pas de break car apres un emptytrash => retour au gestionnaire
                         // no break
                     default:
                         $this->fmShow(false, $isAction);
    @@ -999,8 +999,10 @@ public function fmEmptyTrash()
             public function fmErase()
             {
                 $path = $this->GetUploadPath();
    -            $filename = $path . '/' . ($_GET['file'] ? $_GET['file'] : '');
    -            if (file_exists($filename)) {
    +            // Sanitize file path
    +            $filename = $this->GetUploadPath() . '/' . basename(realpath($_GET['file'] ? $_GET['file'] : ''));
    +            // Make sure that the filename ends with trash and a date
    +            if (file_exists($filename) && preg_match('/trash\d{14}$/', $filename)) {
                     unlink($filename);
                 }
             }
    @@ -1077,7 +1079,7 @@ function ByNameByRevFile($f1, $f2)
                         $f2Name = $f2['name'] . '.' . $f2['ext'];
                         $res = strcasecmp($f1Name, $f2Name);
                         if ($res == 0) {
    -                        //si meme nom => compare la revision du fichier
    +                        // si meme nom => compare la revision du fichier
                             $res = strcasecmp($f1['dateupload'], $f2['dateupload']);
                         }
     
    @@ -1140,13 +1142,13 @@ public function redimensionner_image($image_src, $image_dest, $largeur, $hauteur
                     // get image info except for webp (code copier from Zebra_Image)
                     if (
                         !(
    -                        version_compare(PHP_VERSION, '7.0.0') >= 0 &&
    -                        version_compare(PHP_VERSION, '7.1.0') < 0 &&
    -                        (
    +                        version_compare(PHP_VERSION, '7.0.0') >= 0
    +                        && version_compare(PHP_VERSION, '7.1.0') < 0
    +                        && (
                                 $imgTrans->source_type = strtolower(substr($imgTrans->source_path, strrpos($imgTrans->source_path, '.') + 1))
                             ) === 'webp'
    -                    ) &&
    -                    !list($sourceImageWidth, $sourceImageHeight, $sourceImageType) = @getimagesize($imgTrans->source_path)
    +                    )
    +                    && !list($sourceImageWidth, $sourceImageHeight, $sourceImageType) = @getimagesize($imgTrans->source_path)
                     ) {
                         return false;
                     }
    

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.