eval()和exec()对不可控输入必然导致远程代码执行,应禁用并改用ast.literal_eval()或simpleeval;subprocess需禁用shell=true或严格转义;模板渲染须避免|safe,sql查询必须参数化。

为什么 eval() 和 exec() 一用就中招
它们不是“写错才危险”,而是只要输入不可控,就必然存在远程代码执行风险。Python 的动态特性让字符串直接变成可执行逻辑,攻击者只需传入 "__import__('os').system('rm -rf /')" 这类字符串,就能绕过所有业务校验。
常见错误现象:后台管理页提供“自定义公式计算”或“规则引擎调试框”,用户输入后服务端用 eval() 解析;或者日志里看到 SyntaxError: invalid syntax 以外的异常,实际是攻击尝试失败留下的痕迹。
- 永远不要对任何外部输入(HTTP 参数、数据库字段、文件内容)调用
eval()或exec() - 替代方案优先选
ast.literal_eval()—— 它只允许基础字面量(dict、list、str、int等),拒绝函数调用和属性访问 - 如果真要表达式求值(如配置里的阈值公式),用专门的安全库如
simpleeval,并显式禁用__builtins__和危险模块
subprocess.run() 不加 shell=False 会怎样
默认 shell=True(尤其在 Windows 上容易被忽略)会让命令字符串经系统 shell 解析,导致参数注入。比如拼接路径:subprocess.run(f"ls {user_input}"),当 user_input 是 "; rm -rf /",后果直接失控。
使用场景集中在调用外部工具(git、ffmpeg、curl)或执行系统命令时,尤其在 CI/CD 脚本、运维工具里高频出现。
立即学习“Python免费学习笔记(深入)”;
- 强制设
shell=False(这是默认值,但显式写出更安全) - 命令和参数必须拆成列表:
subprocess.run(["ls", "-l", user_path]),而非字符串拼接 - 若必须用
shell=True(如需要管道、通配符),先用shlex.quote()对每个变量转义,再拼接
Django/Flask 模板里 {{ user_input }} 为什么不能信
模板引擎默认做 HTML 转义,但一旦用了 |safe、|mark_safe 或 Jinja2 的 |safe 过滤器,就等于把 XSS 大门敞开。攻击者传入 <script>alert(1)</script>,后端没清洗就存进数据库,前端一渲染就执行。
容易踩的坑是“我只在后台显示,又不给用户看”,但后台页面本身也是 Web 页面,管理员账号一旦泄露,XSS 可窃取 session、提权操作。
- 永远假设所有用户输入都是恶意的,包括管理员自己填的富文本、备注字段
- 避免全局用
|safe;如需渲染 HTML,用bleach.clean()白名单过滤标签和属性 - 敏感操作(删除、权限变更)必须用 CSRF token + POST + 服务端鉴权,不能靠前端隐藏按钮或 JS 判断
SQL 注入在 Python 里不是只有 sqlite3 才有
用 mysql-connector-python、psycopg2、甚至 ORM 的原生查询(session.execute("SELECT * FROM users WHERE name = '" + name + "'"))一样中招。根本原因不是数据库类型,而是字符串拼接 SQL。
性能影响常被忽视:预编译语句(parameterized query)由数据库缓存执行计划,而拼接 SQL 每次都要重新解析,高并发下 CPU 明显升高。
- 所有参数一律用占位符:
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))(PostgreSQL/MySQL)或?(SQLite) - ORM 用户别碰
.filter(name__contains=request.GET['q'])这种裸输入,应先校验格式(如只允许字母数字)或用Q对象组合条件 - 动态表名/字段名无法参数化,必须白名单校验:
if table_name not in ["users", "orders"]:再拼接
最麻烦的其实是“半信半疑”的情况——比如认为“这个字段只来自内部 API,不会被篡改”,结果上游服务被攻破,信任链就断了。安全不是挑地方设防,是每层都按最坏情况设计。










