在安全领域,有个共识是:`eval()` 虽然强大,但用在不可信输入上,无异于开门揖盗。它直接执行任意 Python 表达式,`__import__()`、`open()`、`exec()` 这些危险操作一个不落。即便在沙箱环境里,也几乎找不到彻底封死的路径。某些加固过的 Python 运行时会直接抛出 `SecurityError` 拦住你,但更多时候,它根本不会报错,而是神不知鬼不觉地跑完恶意逻辑。

**直接说结论:处理不可信输入,最稳妥的方案就是 `ast.literal_eval()`。它只允许解析字面量——数字、字符串、列表、字典这些,天然杜绝代码注入。**
### 为什么 `eval()` 会触发 SecurityError 或更糟?
`eval()` 的可怕之处在于它不挑食。举个典型的例子:用户提交了 `"[1, 2, __import__('os').system('rm -rf /')]"`,`eval()` 直接就把命令执行了。还有配置文件里看似无害的 JSON-like 字符串,如果被 `eval()` 解析,意外构造的表达式可能瞬间引爆。更隐蔽的是,`"{'a': 1}.keys().__class__.__mro__[1].__subclasses__()"` 这类反射探测,`eval()` 也能照单全收。
常见错误场景:
- 用户提交的字符串里藏着 `__import__('os').system(...)`,`eval()` 直接执行
- 配置文件中的 JSON-like 数据被 `eval()` 解析,触发意外表达式
- Web 表单传入的 `"{'a': 1} + {'b': 2}"` 看似无害,但背后可能藏着 `"{'a': 1}.keys().__class__.__mro__[1].__subclasses__()"` 这类反射探测
### `ast.literal_eval()` 能解析哪些内容?
`ast.literal_eval()` 只接受 Python 字面量语法,也就是编译器能静态确认“不带副作用”的结构。它不能直接当 JSON 解析器用,但覆盖绝大多数配置和数据场景。
支持的输入示例:
- `"123"` → `123`(int)
- `"'hello'"` 或 `"\"world\""` → `"hello"`(str)
- `"[1, 2, 'a']"` → `[1, 2, 'a']`(list)
- `"{'key': True, 'val': None}"` → `{'key': True, 'val': None}`(dict)
- `"(1, 2, 3)"` → `(1, 2, 3)`(tuple)
不支持的(会抛 `ValueError`):
- `"1 + 2"`(运算表达式)
- `"os.listdir('.')"`(函数调用)
- `"{'a': 1}.items()"`(方法调用)
- `"None.lower()"`(属性访问)
- `"b'bytes'"`(字节字面量,Python 3.6+ 才支持,旧版本直接报错)
### 实际替换时要注意的兼容性细节
从 `eval()` 切到 `ast.literal_eval()`,可不是简单把函数名一换就完事了。有几个细节必须检查:
- **输入格式**:必须是合法 Python 字面量,不是 JSON 格式。JSON 的 `true`/`false`/`null` 要改成 `True`/`False`/`None`;双引号字符串在 Python 字面量里合法,但单引号更常见
- **异常类型**:`eval()` 报 `SyntaxError`、`NameError` 等,而 `ast.literal_eval()` 只抛 `ValueError` 和极其罕见的 `MemoryError`
- **性能差异**:`ast.literal_eval()` 因为要构建 AST 并校验,实际会慢一点点,但对普通配置解析来说可以忽略不计
- **动态作用域**:如果原逻辑依赖 `eval()` 传 `locals` 参数,`ast.literal_eval()` 完全不支持——这反而是好事,说明原来的代码本身就存在隐患
### 别忘了边界场景:空输入、嵌套深度、编码问题
`ast.literal_eval()` 对边缘情况相当严格,容易踩坑:
- **空输入**:传入空字符串 `""` 或仅空白字符,直接抛 `ValueError`。务必先 `.strip()` 处理
- **嵌套深度**:超深嵌套(比如 1000 层列表)可能触发递归限制,报 `ValueError`(非 `RecursionError`)。可通过 `sys.setrecursionlimit()` 调整,但建议先检查数据合理性
- **编码问题**:如果输入来自文件或网络,注意编码。传入 bytes 会报错,必须 decode 成 str;BOM 头(如 `\ufeff`)会导致解析失败,建议用 `input_str.strip('\ufeff')`
- **尾部逗号**:`[1, 2,]` 在 Python 中合法,但 `ast.literal_eval()` 要求严格语法,这种写法会报错
真正棘手的点往往不在函数本身,而在于你如何清洗输入、定义信任边界,以及是否把“解析成功”默认等同于“数据可信”。`ast.literal_eval()` 只解决代码执行风险,不校验业务逻辑合法性。
本文转载于:https://www.php.cn/faq/2426635.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。