T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/migrate_timestamps.py:21
- Finding
- Predictable Temporary File Enables Symlink-Based File Overwrite During Timestamp Migration<![CDATA[ ## Vulnerability Details **File Location**: `scripts/migrate_timestamps.py`, lines 21–43 **Vulnerability Type**: Predictable temporary file and symbolic-link following **Risk Level**: Medium ```python def migrate(): if not LOG_PATH.exists(): print('No log found at', LOG_PATH) return tmp = LOG_PATH.with_suffix('.tmp') count = 0 with LOG_PATH.open('r',encoding='utf-8') as fin, tmp.open('w',encoding='utf-8') as fout: for line in fin: try: obj = json.loads(line) ts = obj.get('timestamp','') if isinstance(ts,str) and ts.endswith('Z'): s = ts.replace('Z','+00:00') try: dt = datetime.fromisoformat(s) obj['timestamp'] = dt.astimezone(TZ).isoformat() count += 1 except Exception: pass fout.write(json.dumps(obj)+"\n") except Exception: fout.write(line) tmp.replace(LOG_PATH) ``` ### Technical Analysis The migration utility derives its temporary filename predictably by replacing the log suffix with `.tmp`. It then opens that path using regular write mode: ```python tmp.open('w', encoding='utf-8') ``` This operation neither creates the file exclusively nor prevents symbolic-link traversal. If an attacker can write to the configured log directory, the attacker can create `token_log.tmp` as a symbolic link to another file before the migration starts. Python's normal file-opening behavior follows that link, opens the linked target in write mode, and truncates it. After writing, `tmp.replace(LOG_PATH)` renames the temporary directory entry over the original log. If the temporary entry is a symbolic link, the link itself is renamed; however, the linked target has already been truncated and overwritten during the preceding write operation. Exploitation requires loca ...[truncated 1409 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Create temporary files securely and unpredictably in the same directory as the destination, using `tempfile.NamedTemporaryFile` or `tempfile.mkstemp`. - Do not reuse a fixed temporary pathname. - Use restrictive file permissions, such as mode `0600`, for token logs and temporary files. - Flush buffered data and call `os.fsync()` before replacing the destination. - Complete the update with `os.replace()` so that replacement remains atomic. - Verify that the configured log directory is owned by the service account and is not writable by untrusted users. - Where supported, use no-follow semantics such as `O_NOFOLLOW` and verify with `fstat()` that the opened object is a regular file. - Validate that `LOG_PATH` and its parent directory are not symbolic links when operating across trust boundaries. A hardened pattern is: ```python import os import tempfile with LOG_PATH.open('r', encoding='utf-8') as fin: fd, tmp_name = tempfile.mkstemp( prefix='.token_log.', suffix='.tmp', dir=LOG_PATH.parent ) try: with os.fdopen(fd, 'w', encoding='utf-8') as fout: # Perform migration and write output. fout.flush() os.fsync(fout.fileno()) os.replace(tmp_name, LOG_PATH) except Exception: try: os.unlink(tmp_name) except FileNotFoundError: pass raise ``` ]]>
