fix(locale): validate language_code against known locales to block path traversal (#4704)

* fix(locale): validate language_code against known locales to block path traversal

postSaveLocale() stored language_code from user input with no allow-list validation, and it later flows into Language::setLocale()/load() where the locale segment is require()'d. An authenticated config-grant account could store a relative path (e.g. ../../public/uploads) and, combined with a planted file in public/uploads/, achieve unauthenticated RCE on the next request.

Validate the submitted language against array_keys(get_languages()) before storing, and harden languageExists() to reject path separators and dot-dot sequences. Adds regression tests.

* test(locale): give locale fixture valid reference-code min/max defaults

* fix(locale): reject null bytes in languageExists guard

A stored language_code containing a NUL byte passes the existing path-separator and parent-dir checks, then reaches file_exists(). On PHP 8.5+ file_exists() throws a ValueError for NUL-byte paths, which breaks configuration loading. Reject NUL bytes in the guard and add regression tests.
This commit is contained in:
jekkos authored and GitHub committed 2026-09-21 17:36:50 +02:00
1 parent 184918d914
commit 47aade5024
4 files changed
+117 -2

No files matched your search

+6 -1
View File
@@ -506,7 +506,12 @@ class Config extends Secure_Controller
return $this->response->setJSON(['success' => false, 'message' => reset($errors)]);
}
$exploded = explode(":", $this->request->getPost('language'));
$language = $this->request->getPost('language');
if (!in_array($language, array_keys(get_languages()), true)) {
return $this->response->setJSON(['success' => false, 'message' => 'Invalid language']);
}
$exploded = explode(":", $language);
$currency_symbol = $this->request->getPost('currency_symbol');
$batch_save_data = [
'currency_symbol' => htmlspecialchars($currency_symbol ?? ''),