T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/game-engine.mjs:87
- Finding
- Path Traversal in Game and Ranking File Storage<![CDATA[ ## Vulnerability Details **File Location**: `scripts/game-engine.mjs:87-93` **Vulnerability Type**: Path traversal and unrestricted filesystem access **Risk Level**: Medium ### Vulnerable Code ```js function gamePath(groupId) { return path.join(GAMES_DIR, `${groupId}.json`); } function rankingPath(groupId) { return path.join(RANKINGS_DIR, `${groupId}.json`); } ``` The untrusted `groupId` reaches these functions from command handlers at `scripts/game-engine.mjs:142`, `217`, `239`, `273`, `352`, `399`, and `494`. ### Technical Analysis The `--group` command-line argument is used directly as part of a filesystem path without format validation or a containment check. Node.js `path.join()` normalizes traversal sequences such as `../`, allowing a crafted group ID to escape `scripts/data/games` or `scripts/data/rankings`. The generated path is passed to the following filesystem operations: ```js function loadJSON(filePath) { try { return JSON.parse(fs.readFileSync(filePath, 'utf-8')); } catch { return null; } } function saveJSON(filePath, data) { fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8'); } ``` Because `.json` is appended automatically, exploitation is generally limited to paths ending in `.json`. Nevertheless, the application can access or overwrite JSON files outside its designated data directories wherever the Node.js process has filesystem permission. ### Attack Path 1. An attacker supplies a group identifier containing traversal components, such as `../../target`. 2. The application constructs a path equivalent to: ```text scripts/data/games/../../target.json ``` 3. Path normalization resolves this outside the intended `games` directory. 4. A command such as `create` loads the attacker-selected path while checking for an existing game and can subsequently write game data to that path. 5. Other commands similarly use attacker-selected paths for game or ranking reads and writes, subject ...[truncated 681 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Restrict group IDs to a conservative allowlist: ```js function validateGroupId(groupId) { if ( typeof groupId !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(groupId) ) { fail('Invalid group identifier'); } return groupId; } ``` 2. Resolve and verify every generated path remains under its designated directory: ```js function safeJsonPath(baseDir, identifier) { const safeId = validateGroupId(identifier); const base = path.resolve(baseDir); const candidate = path.resolve(base, `${safeId}.json`); if (!candidate.startsWith(`${base}${path.sep}`)) { fail('Resolved path is outside the permitted data directory'); } return candidate; } function gamePath(groupId) { return safeJsonPath(GAMES_DIR, groupId); } function rankingPath(groupId) { return safeJsonPath(RANKINGS_DIR, groupId); } ``` 3. Apply validation before both reads and writes rather than relying only on command-entry validation. 4. Run the game engine under an account with write access limited to its dedicated data directories. 5. Add tests covering `../`, encoded separators, absolute-looking paths, long identifiers, and platform-specific path separators. ]]>
