From 571c24e5b78fba9e79ad2080b1f9817276b90528 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 27 Nov 2022 18:19:05 +0100 Subject: [PATCH] Validate identifiers and file paths --- src/SeriousJSON/JsonDatabase.php | 53 ++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/src/SeriousJSON/JsonDatabase.php b/src/SeriousJSON/JsonDatabase.php index 78f0e85..64a9bf1 100644 --- a/src/SeriousJSON/JsonDatabase.php +++ b/src/SeriousJSON/JsonDatabase.php @@ -55,12 +55,6 @@ class JsonDatabase implements IJsonUnflattener } } - private function migrate() - { - // TODO: This is a stub - // Make this function migrate between different settings - } - private function getObjectPath($class, $name, ?int $time = null) { $retPath = $this->storagePath; @@ -75,6 +69,10 @@ class JsonDatabase implements IJsonUnflattener $segments = array_filter(explode('\\', $class)); + for ($i = 0; $i < count($segments); $i++) { + $segments[$i] = JsonDatabase::SanitizeIdentifier($segments[$i]); + } + if ($this->fullDomain) $retPath .= DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $segments); else @@ -92,15 +90,24 @@ class JsonDatabase implements IJsonUnflattener public function load(string $class, string $name, ?int $timestamp = null): JsonSerializable|null { - // TODO: Do some basic validation of class and name to - // avoid path traversal and make sure a class is specified - // This is really fucking important, implement it, or don't and watch someone use your code - // and get their production machine hacked + if ($class == null || empty(trim($name))) + return null; + + if ($name != null && !empty(trim(strval($name)))) + $name = JSONDatabase::SanitizeIdentifier($name); + else + return null; + + $class = trim($class); + if (!$this->audit && $timestamp != null) return null; $objPath = $this->getObjectPath($class, $name, ($timestamp != null)); + if (strlen($objPath) > PHP_MAXPATHLEN) + return null; + if (!is_file($objPath)) return null; @@ -115,22 +122,29 @@ class JsonDatabase implements IJsonUnflattener return JsonSerializable::Deserialize($json, $this->nested, $this, $class); } - public function save(JsonSerializable $obj, string $name = null) + public function save(JsonSerializable $obj, ?string $name = null) { - if ($name == null || empty(trim(strval($name)))) + if ($name != null && !empty(trim($name))) { + $name = JSONDatabase::SanitizeIdentifier($name); + } else { if ($obj instanceof IJsonIdentifiable) - $name = $obj->flatIdentifier() ?? uniqid(); + $name = JSONDatabase::SanitizeIdentifier($obj->flatIdentifier() ?? uniqid()); else $name = uniqid(); - - $name = JSONDatabase::SanitizeIdentifier($name); - + } + $objPath = $this->getObjectPath(get_class($obj), $name); + if (strlen($objPath) > PHP_MAXPATHLEN) + throw new Exception('JsonDatabase: Specified Path ' . $objPath . ' is longer than allowed.'); + $objJson = $obj->Serialize(nested: $this->nested, to_array: false); if ($this->audit) { $historyPath = $this->getObjectPath(get_class($obj), $name, time()); + if (strlen($historyPath) > PHP_MAXPATHLEN) + throw new Exception('JsonDatabase: Specified Path ' . $objPath . ' is longer than allowed.'); + // For Historic files we always save the whole Object Tree since // dependencies might have different timestamps // and resolving that would be annoying @@ -149,8 +163,15 @@ class JsonDatabase implements IJsonUnflattener return $name; } + /** + * Restore a JsonSerializable from the Database by using it's Identifier reference. + * @param string $className the full class name with domain of the JsonSerializable property + * @param $identifer The identifier that uniquely identifies the object + * @return JsonSerializable|null + */ public function unflattenByID(string $className, $identifier) : JsonSerializable | null { + // Fetch JsonSerializable Object from Database using given Identifier return $this->load($className, strval($identifier)); } } \ No newline at end of file