[TikiWiki-commits] [Git][tikiwiki/tiki][30.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 | <6a82d53e2a1ec_3818c11c188a7@gitlab-sidekiq-low-urgency-cpu-bound-v2-788fd898f-gswd9.mail> |
Espoir Baraka pushed to branch 30.x at Tiki Wiki CMS Groupware / Tiki
Commits:
ac64780c by Espoir Baraka at 2026-08-17T11:27:02+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
---
* 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
- - - - -
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
=====================================
@@ -155,8 +155,7 @@ class Services_Language_Controller
$languages = $this->utilities->getLanguages();
//get custom php file location
- $custom_php_file = $this->utilities->getLanguageDirectory($language);
- $custom_php_file .= LANG_CUSTOM_PHP_BASENAME;
+ $custom_php_file = $this->utilities->getCustomPhpPath($language);
if (! file_exists($custom_php_file)) {
$custom_php_file = null;
}
=====================================
lib/core/Services/Language/Utilities.php
=====================================
@@ -173,6 +173,20 @@ 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)
+ {
+ if (! is_string($language) || $language === '' || ! Language::is_valid_language($language)) {
+ throw new Services_Exception(tr('Invalid language provided'), 400);
+ }
+ }
+
/**
* Get language directory generally and for a specific language too
*
@@ -180,23 +194,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) . LANG_CUSTOM_PHP_BASENAME;
+ $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
*
@@ -264,8 +315,7 @@ class Services_Language_Utilities
*/
public function getCustomPhpTranslations($language)
{
- $custom_file = $this->getLanguageDirectory($language);
- $custom_file .= LANG_CUSTOM_PHP_BASENAME;
+ $custom_file = $this->getCustomPhpPath($language);
if (file_exists($custom_file)) {
global $lang;
@@ -312,23 +362,19 @@ class Services_Language_Utilities
*/
public function writeCustomPhpTranslations($language, $data)
{
- //prepare custom file path
- $custom_file = $this->getLanguageDirectory($language);
-
- //add file name
- $custom_file .= LANG_CUSTOM_PHP_BASENAME;
-
- //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,77 @@
+<?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.
+
+namespace Tiki\Lib\Test\Core\Services\Language;
+
+use Services_Exception;
+use Services_Language_Utilities;
+use TikiTestCase;
+
+class UtilitiesTest extends TikiTestCase
+{
+ private Services_Language_Utilities $utilities;
+ private string $customFile;
+ private bool $hadCustomFile = false;
+ private string $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/ac64780c5cd63a92bc68e1eff1a33e2ee79c8951
--
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/ac64780c5cd63a92bc68e1eff1a33e2ee79c8951
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