T09 · Insecure Skill Coding Practices
- Location
scripts/powpow-register.mjs:39- Finding
Predictable Shared Temporary State File Permits Cross-User Session Disclosure and State Injection
- Content
View full analysis
Vulnerability Details
File Location:
scripts/powpow-register.mjs, lines 39–56 and 203–210
Vulnerability Type: Unsafe temporary file handling
Risk Level: MediumVulnerable Code
js const BASE_URL = 'https://global.powpow.online'; const STATE_FILE = join(tmpdir(), 'powpow-register-state.json'); const PASSWORD_ENV = 'POWPOW_REGISTER_PASSWORD'; // ---------- 状态文件 ---------- function loadState() { try { return JSON.parse(readFileSync(STATE_FILE, 'utf8')); } catch { return {}; } } function saveState(patch) { const s = { ...loadState(), ...patch }; writeFileSync(STATE_FILE, JSON.stringify(s, null, 2), { mode: 0o600 }); return s; }The sensitive session token is subsequently written to this fixed path:
js const token = r.cookies['powpow_token'] || ''; if (!token) die('注册成功但未捕获到会话 cookie(平台响应异常)'); saveState({ username: r.data?.user?.username || username, nickname: r.data?.user?.nickname || nickname, userId: r.data?.userId, token, step: 'registered-pending', registeredAt: new Date().toISOString(), });Technical Analysis
The script stores workflow state at the fixed path
os.tmpdir()/powpow-register-state.json. This state includes the user's email, account identifiers, order information, and authenticated PowPow JWT.The
mode: 0o600option only reliably controls permissions when Node.js creates a new file. When the pathname already resolves to an existing file,writeFileSynctruncates and writes that file without changing its existing permissions. The operation also does not explicitly reject symbolic links or verify that the existing object is a regular file owned by the current user.On a multi-user host where the temporary directory is shared and operating-system hardening does not independently block the operation, another local user can prepare the predictable pathname as an attacker-readable and victim-writable file. The victim's registration process then writes its authe ...[truncated 2327 chars]
- Remediation
View remediation
Remediation Suggestions
- Create a private, unpredictable per-user or per-workflow directory with
mkdtempSync, set directory permissions to0700, and place the state file inside that directory. - Pass the randomized state location between invocations through a protected configuration mechanism rather than using one global filename.
- Create state files atomically with exclusive creation semantics, such as
O_CREAT | O_EXCL, and fail if an unexpected object already exists. - Where supported, use no-follow semantics and verify with
lstatorfstatthat the object is a regular file, is owned by the current effective user, and is not hard-linked unexpectedly. - Open the file descriptor first, enforce mode
0600withfchmod, and write through that validated descriptor instead of reopening a pathname. - Use atomic replacement within the private directory for updates, while ensuring replacement cannot cross a symlink boundary.
- Keep separate state files for concurrent workflows so one registration cannot consume another user's or another invocation's account and payment state.
- Continue deleting state at workflow completion, but treat cleanup as defense in depth rather than the primary confidentiality control.
- Create a private, unpredictable per-user or per-workflow directory with
