Skip to content

fix(wechat): sanitize media file_name to block path traversal in _dl_media#609

Open
kevinchennewbee wants to merge 1 commit into
lsdefine:mainfrom
kevinchennewbee:fix/wechat-media-path-traversal
Open

fix(wechat): sanitize media file_name to block path traversal in _dl_media#609
kevinchennewbee wants to merge 1 commit into
lsdefine:mainfrom
kevinchennewbee:fix/wechat-media-path-traversal

Conversation

@kevinchennewbee

Copy link
Copy Markdown
Contributor

现象

frontends/wechatapp.py_dl_media 把入站消息里的 file_name 直接用来落盘解密后的媒体内容。file_name 来自对方消息,攻击者可控。

根因

fname = sub.get('file_name') or f'{uuid...}{ext}'
p = os.path.join(_TEMP_DIR, fname); open(p, 'wb').write(pt)

os.path.join(base, '../../x')(或绝对路径)会逃出 _TEMP_DIR。任何能给 bot 发消息的人,就能把自己控制的内容写到宿主机任意路径——比如丢进自启动目录。这是一个远程可达的任意写原语。

修法

落盘前把入站名收敛成 basename。正常文件名没有目录部分,basename 是恒等,合法行为完全不变;只有 ../ / 绝对路径被中和。空名仍走原来的 uuid 兜底。

-                fname = sub.get('file_name') or f'{uuid.uuid4().hex[:8]}{ext or ".bin"}'
+                fname = os.path.basename(sub.get('file_name') or '') or f'{uuid.uuid4().hex[:8]}{ext or ".bin"}'

实测

  • python3 -m py_compile frontends/wechatapp.py 通过
  • 行为快测:../../../../etc/cron.d/evilevil/root/.bashrc.bashrcphoto.jpgphoto.jpg(恒等)、空名 → uuid 兜底

单文件单行改动,行为对合法文件名不变。

…media

`_dl_media` takes `file_name` straight from the inbound message payload
(attacker-controllable) and joins it onto `_TEMP_DIR` before writing the
decrypted bytes:

    fname = sub.get('file_name') or f'{uuid...}{ext}'
    p = os.path.join(_TEMP_DIR, fname); open(p, 'wb').write(pt)

`os.path.join(base, '../../x')` (or an absolute path) escapes `_TEMP_DIR`,
so a remote sender can write attacker-chosen content to an arbitrary path
on the bot host — e.g. drop a file into an autoload location. It is a write
primitive reachable by anyone who can message the bot.

Fix: collapse the inbound name to its basename before use. A normal
filename has no directory component, so basename is the identity for
legitimate names and behavior is unchanged; only `../` / absolute paths
are neutralized. Empty names still fall through to the uuid fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant