[TikiWiki-commits] [Git][tikiwiki/tiki][24.x] [FIX] Language custom.php: block path traversal and PHP injection in translation editor
"Espoir Baraka \(@esbarakabigega\) via TikiWiki-cvs" <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <6a8309937d61e_388223c870472@gitlab-sidekiq-low-urgency-cpu-bound-v2-7678768db5-fx99w.mail> |
Espoir Baraka pushed to branch 24.x at Tiki Wiki CMS Groupware / Tiki
Commits:
17977134 by Espoir Baraka at 2026-08-17T15:09:18+02:00
[FIX] Language custom.php: block path traversal and PHP injection in translation editor
---
* [FIX] Language custom.php: block path traversal and PHP injection in translation editor
---
* [FIX] Language custom.php: block path traversal and PHP injection in translation editor
---
* [FIX] Language custom.php: block path traversal and PHP injection in translation editor
---
* [FIX] Language custom.php: block path traversal and PHP injection in translation editor
---
* Refactored language path retrieval to ensure custom PHP files are correctly resolved within the language directory.
* Introduced validation methods to prevent path traversal and ensure valid language codes.
(cherry picked from commit 17308932091c13c552c4951cb487aa25a6085139)
See merge request tikiwiki/tiki!10904
(cherry picked from commit a382e8be768d8b9653f9f554da2803fb29f35f48)
See merge request tikiwiki/tiki!10905
(cherry picked from commit ac64780c5cd63a92bc68e1eff1a33e2ee79c8951)
See merge request tikiwiki/tiki!10906
(cherry picked from commit a325dfa57d86015475152a492f66ea0e796e8a19)
See merge request tikiwiki/tiki!10907
- - - - -
3 changed files:
- lib/core/Services/Language/Controller.php
- lib/core/Services/Language/Utilities.php
- + lib/test/core/Services/Language/UtilitiesTest.php
Changes:
=====================================
lib/core/Services/Language/Controller.php
=====================================
@@ -156,8 +156,7 @@ class Services_Language_Controller
$languages = $this->utilities->getLanguages();
//get custom php file location
- $custom_php_file = $this->utilities->getLanguageDirectory($language);
- $custom_php_file .= 'custom.php';
+ $custom_php_file = $this->utilities->getCustomPhpPath($language);
if (! file_exists($custom_php_file)) {
$custom_php_file = null;
}
=====================================
lib/core/Services/Language/Utilities.php
=====================================
@@ -174,6 +174,21 @@ class Services_Language_Utilities
return $languages;
}
+ /**
+ * Ensure $language is a real on-disk language code (no path segments / traversal).
+ *
+ * @param string $language Language code (eg: en)
+ *
+ * @throws Services_Exception
+ */
+ public function assertValidLanguage($language)
+ {
+ $langLib = TikiLib::lib('language');
+ if (! is_string($language) || $language === '' || ! $langLib->is_valid_language($language)) {
+ throw new Services_Exception(tr('Invalid language provided'), 400);
+ }
+ }
+
/**
* Get language directory generally and for a specific language too
*
@@ -181,23 +196,60 @@ class Services_Language_Utilities
*
* @return string $langDir The directory path languages or to a language specifically
*
+ * @throws Services_Exception
*/
public function getLanguageDirectory($language = '')
{
$langDir = "lang/";
if (! empty($language)) {
+ $this->assertValidLanguage($language);
$langDir .= "$language/";
}
global $tikidomain;
if (! empty($tikidomain)) {
+ // tikidomain is an internal install path segment; reject traversal if ever set from untrusted input
+ if (! is_string($tikidomain) || $tikidomain === '' || strpbrk($tikidomain, '/\\') !== false || str_contains($tikidomain, '..')) {
+ throw new Services_Exception(tr('Invalid language provided'), 400);
+ }
$langDir .= "$tikidomain/";
}
return $langDir;
}
+ /**
+ * Resolve custom.php path for a language and ensure it stays under lang/.
+ *
+ * @param string $language Language code (eg: en)
+ *
+ * @return string Relative path to custom.php
+ *
+ * @throws Services_Exception
+ */
+ public function getCustomPhpPath($language)
+ {
+ $this->assertValidLanguage($language);
+
+ $custom_file = $this->getLanguageDirectory($language) . 'custom.php';
+ $langRoot = realpath('lang');
+ $languageDir = realpath(dirname($custom_file));
+
+ if (
+ $langRoot === false
+ || $languageDir === false
+ || (
+ $languageDir !== $langRoot
+ && ! str_starts_with($languageDir, $langRoot . DIRECTORY_SEPARATOR)
+ )
+ ) {
+ throw new Services_Exception(tr('Invalid language provided'), 400);
+ }
+
+ return $custom_file;
+ }
+
/**
* Check if lang/ directory is readable generally and for a specific language too
*
@@ -265,8 +317,7 @@ class Services_Language_Utilities
*/
public function getCustomPhpTranslations($language)
{
- $custom_file = $this->getLanguageDirectory($language);
- $custom_file .= 'custom.php';
+ $custom_file = $this->getCustomPhpPath($language);
if (file_exists($custom_file)) {
include($custom_file);
@@ -308,23 +359,19 @@ class Services_Language_Utilities
*/
public function writeCustomPhpTranslations($language, $data)
{
- //prepare custom file path
- $custom_file = $this->getLanguageDirectory($language);
-
- //add file name
- $custom_file .= 'custom.php';
-
- //prepare php file
- $custom_code = "<?php\r\n\$lang_custom = array(\r\n";
+ $custom_file = $this->getCustomPhpPath($language);
if (! is_array($data)) {
throw new Services_Exception(tr('String translation set is not an array'), 400);
}
- //add translations
+ //prepare php file — escape for double-quoted PHP strings (backslash before quote, etc.)
+ $custom_code = "<?php\r\n\$lang_custom = array(\r\n";
+
foreach ($data as $from => $to) {
- if (! empty($from)) {
- $custom_code .= '"' . str_replace('"', '\\"', $from) . '" => "' . str_replace('"', '\\"', $to) . "\",\r\n";
+ if ($from !== '' && $from !== null) {
+ $custom_code .= '"' . Language::addPhpSlashes((string) $from) . '" => "'
+ . Language::addPhpSlashes((string) $to) . "\",\r\n";
}
}
=====================================
lib/test/core/Services/Language/UtilitiesTest.php
=====================================
@@ -0,0 +1,75 @@
+<?php
+
+// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
+//
+// All Rights Reserved. See copyright.txt for details and a complete list of authors.
+// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
+
+use Services_Exception;
+use Services_Language_Utilities;
+use TikiTestCase;
+
+class Services_Language_UtilitiesTest extends TikiTestCase
+{
+ private $utilities;
+ private $customFile;
+ private $hadCustomFile = false;
+ private $previousCustomContents = '';
+
+ protected function setUp(): void
+ {
+ $this->utilities = new Services_Language_Utilities();
+ $this->customFile = 'lang/en/custom.php';
+ $this->hadCustomFile = file_exists($this->customFile);
+ if ($this->hadCustomFile) {
+ $this->previousCustomContents = file_get_contents($this->customFile);
+ }
+ }
+
+ protected function tearDown(): void
+ {
+ if ($this->hadCustomFile) {
+ file_put_contents($this->customFile, $this->previousCustomContents);
+ } elseif (file_exists($this->customFile)) {
+ unlink($this->customFile);
+ }
+ }
+
+ public function testRejectsPathTraversalLanguage(): void
+ {
+ $this->expectException(Services_Exception::class);
+ $this->utilities->assertValidLanguage('../temp');
+ }
+
+ public function testRejectsPathTraversalOnWrite(): void
+ {
+ $this->expectException(Services_Exception::class);
+ $this->utilities->writeCustomPhpTranslations('../temp', [
+ 'k' => '\");echo \'PWNED\';?>',
+ ]);
+ }
+
+ public function testEscapesBackslashQuoteInjection(): void
+ {
+ $payload = '\");echo \'PWNED-CUSTOM-PHP\';?>';
+ $this->utilities->writeCustomPhpTranslations('en', [
+ 'hello' => $payload,
+ ]);
+
+ $contents = file_get_contents($this->customFile);
+ // Escaped form inside the double-quoted PHP value is \\\");echo (three backslashes then quote)
+ $this->assertStringContainsString('\\\\\\");echo', $contents);
+
+ // Including the file must restore the literal payload, not execute injected PHP
+ $lang = [];
+ $lang_custom = null;
+ ob_start();
+ include $this->customFile;
+ $stdout = ob_get_clean();
+
+ $this->assertSame('', $stdout);
+ $this->assertIsArray($lang_custom);
+ $this->assertSame($payload, $lang_custom['hello']);
+ $this->assertSame($payload, $lang['hello']);
+ }
+}
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/17977134b39db41738abd8feed1a69266c1e3560
--
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/17977134b39db41738abd8feed1a69266c1e3560
You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help
_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs