系列第 2 篇。上篇讲业务、版本、架构;本篇讲安全与隐式耦合;下篇讲协作与一致性。
开场:最危险的,不是“报错”
让 Trae 写一个文件上传接口,Prompt 是:
写一个 FastAPI 文件上传接口,把文件保存到 uploads 目录。
它给出:
fromfastapiimportFastAPI,UploadFileimportos app=FastAPI()@app.post("/upload")asyncdefupload(file:UploadFile):content=awaitfile.read()save_path=os.path.join("./uploads",file.filename)withopen(save_path,"wb")asf:f.write(content)return{"ok":True,"path":save_path}跑得通。上传个小图片,返回{"ok": true},一切正常。
这就是最危险的地方——它“能用”,所以你会直接上生产。
下面三个漏洞,全部可复现。
漏洞一:路径穿越
file.filename是客户端可控的。构造一个请求:
curl-XPOST http://localhost:8000/upload\-F"file=@test.txt;filename=../../etc/cron.d/evil"os.path.join("./uploads", "../../etc/cron.d/evil")会拼成:
>>>os.path.join("./uploads","../../etc/cron.d/evil")'uploads/../../etc/cron.d/evil'os.path.join不会清理..,实际写到/etc/cron.d/evil。配合定时任务,直接 RCE。
漏洞二:类型伪造
curl-XPOST http://localhost:8000/upload\-F"file=@shell.php;filename=shell.php"上传一个.php,如果 uploads 目录被 Web 服务器当静态目录暴露,就是 webshell。Trae 的原始代码完全不看文件类型。
漏洞三:内存 DoS
ddif=/dev/urandomof=big.binbs=1Mcount=10240curl-XPOST http://localhost:8000/upload-F"file=@big.bin"await file.read()把整个文件读进内存。10GB 文件直接打爆进程,服务挂掉——一个极低成本的 DoS。
修复版(可运行)
fromfastapiimportFastAPI,UploadFile,HTTPExceptionfrompathlibimportPurePosixPathimportos,uuid app=FastAPI()UPLOAD_DIR="./uploads"MAX_SIZE=10*1024*1024# 10MBALLOWED_EXT={".png",".jpg",".jpeg",".gif",".pdf"}os.makedirs(UPLOAD_DIR,exist_ok=True)@app.post("/upload")asyncdefupload(file:UploadFile):# 1. 类型校验:扩展名白名单ext=PurePosixPath(file.filenameor"").suffix.lower()ifextnotinALLOWED_EXT:raiseHTTPException(400,"不支持的文件类型")# 2. 重命名,彻底杜绝路径穿越safe_name=f"{uuid.uuid4().hex}{ext}"save_path=os.path.join(UPLOAD_DIR,safe_name)# 3. 分块读取,超限中断size=0withopen(save_path,"wb")asf:whilechunk:=awaitfile.read(1024*1024):size+=len(chunk)ifsize>MAX_SIZE:f.close()os.remove(save_path)raiseHTTPException(413,"文件过大")f.write(chunk)return{"ok":True,"path":save_path}修复点对照:
| 漏洞 | 修复手段 |
|---|---|
| 路径穿越 | 不用原始文件名,uuid重命名 + 扩展名单独校验 |
| 类型伪造 | 扩展名白名单,后续可加 MIME / 文件头校验 |
| 内存 DoS | 分块读取,超限中断并删除残留文件 |
安全追问必须由你发起
Trae 默认只回答“怎么实现”,不回答“怎么防人”。所以正确的用法是:让它先写,然后你明确追问——
这段代码存在哪些安全问题?请逐一列出: 1. 输入校验 2. 路径/权限 3. 资源耗尽 4. 注入 每一类都给出攻击方式与修复代码。它会补得不错。但触发这个追问的,必须是你,不能是它。安全的责任,AI 不会主动接。
延伸坑:跨文件隐式耦合
安全之外,还有一类更隐蔽的漏洞:
# 直接调用(Trae 能看到)utils.format_price(x)# 通过字符串反射调用(Trae 看不到)handler=getattr(utils,"format_price")handler(x)让 Trae 改format_price的签名,它会改掉可见的直接调用,却漏掉getattr这种字符串引用。上线后反射调用报错。
根因:Trae 按静态调用关系判断影响面,对字符串、反射、事件回调注册、配置里的类名识别不可靠。
改进方法:改公共 API 前自己 grep 全仓库:
grep-rn"format_price"--include="*.py".再确认有没有字符串、反射、配置用法。把 AI 的“影响面分析”当线索,不当结论。
小结
- Trae 写的上传接口,逻辑“无懈可击”,安全“千疮百孔”
- 安全追问必须由你发起
- 改公共 API,全仓 grep 不能省—
上篇:业务、版本、架构(见系列上篇)。
下篇:为什么 AI 写的 review 没人信,以及怎么让 AI 长期保持一致。收藏 + 关注,系列不丢。