From 237cc67b241e2e897d5067bd6e50b3407de2473f Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 07:34:09 -0700 Subject: [PATCH] fix(wcp): sanitize zone name before using it as a directory path The unpacker used info.name verbatim as the destination subdirectory. A malicious WCP could carry a name like '../etc' or '/usr/bin' to write extracted files outside destDir. Now slugified to alphanumeric + underscore/dash, matching the server module slug rule. --- tools/editor/content_pack.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tools/editor/content_pack.cpp b/tools/editor/content_pack.cpp index 58dcd3f0..8fa1c56e 100644 --- a/tools/editor/content_pack.cpp +++ b/tools/editor/content_pack.cpp @@ -137,8 +137,26 @@ bool ContentPacker::unpackZone(const std::string& wcpPath, const std::string& de zoneName = info.value("name", ""); } catch (...) {} + // The zone name becomes a directory name. A malicious WCP could carry a + // name with traversal sequences ("../etc") or an absolute path + // ("/etc/passwd") that would write outside destDir. Strip to a safe + // identifier — same alphabet as the server module slug. + std::string safeZoneName; + for (char c : zoneName) { + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '_' || c == '-') { + safeZoneName += c; + } else if (c == ' ') { + safeZoneName += '_'; + } + } + if (safeZoneName != zoneName && !zoneName.empty()) { + LOG_WARNING("WCP zone name sanitized: '", zoneName, "' -> '", + safeZoneName, "'"); + } + namespace fs = std::filesystem; - std::string zoneDir = zoneName.empty() ? destDir : destDir + "/" + zoneName; + std::string zoneDir = safeZoneName.empty() ? destDir : destDir + "/" + safeZoneName; fs::create_directories(zoneDir); for (uint32_t i = 0; i < fileCount; i++) {