news 2026/9/30 14:45:18

AST 安全求值

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AST 安全求值

AST 安全求值指的是:把表达式/代码先解析成抽象语法树(AST),然后不直接eval/compile执行,而是自己遍历 AST,只允许白名单内的节点,并按预定语义解释执行。核心目标是避免任意代码执行、沙箱逃逸和拒绝服务。

一句话:解析成 AST + 白名单解释器 + 资源限制 + 默认拒绝。


1. 为什么不直接用 eval?

Python 的eval、JS 的eval/new Function都极其危险:

python

eval("__import__('os').system('rm -rf /')")

即使限制__builtins__,也可能通过属性链、类继承、生成器等方式逃逸。
所以安全求值不能依赖“过滤字符串”,而应基于 AST 白名单。


2. 安全求值的基本原则

  1. 默认拒绝:没显式允许的 AST 节点一律报错。

  2. 不调用、不导入、不访问属性:禁止Call、Import、Attribute、Subscript等。

  3. 变量来自受控环境:只允许简单类型,如int/float/str/bool/None。

  4. 运算符白名单:只允许+ - * / // % **、比较、布尔等,并限制**。

  5. 资源限制:输入长度、AST 节点数、递归深度、数值大小、字符串长度、执行时间。

  6. 纯函数、无副作用:求值不能修改外部状态。


3. Python 示例:安全表达式求值

只允许变量、常量、四则运算、比较、布尔、三元表达式。

python

import ast import operator as op _BIN = { ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul, ast.Div: op.truediv, ast.FloorDiv: op.floordiv, ast.Mod: op.mod, } _UNARY = { ast.UAdd: op.pos, ast.USub: op.neg, ast.Not: op.not_, } _CMP = { ast.Eq: op.eq, ast.NotEq: op.ne, ast.Lt: op.lt, ast.LtE: op.le, ast.Gt: op.gt, ast.GtE: op.ge, ast.In: lambda a, b: a in b, ast.NotIn: lambda a, b: a not in b, } _ALLOWED_TYPES = (int, float, str, bool, type(None)) def safe_eval(expr: str, variables=None, max_nodes=1000, max_pow=100): variables = variables or {} tree = ast.parse(expr, mode="eval") if sum(1 for _ in ast.walk(tree)) > max_nodes: raise ValueError("表达式过大") return _eval(tree, variables, max_pow) def _eval(node, env, max_pow): if isinstance(node, ast.Expression): return _eval(node.body, env, max_pow) if isinstance(node, ast.Constant): if isinstance(node.value, _ALLOWED_TYPES): return node.value raise ValueError("不允许的常量") if isinstance(node, ast.Name): if node.id in env and isinstance(env[node.id], _ALLOWED_TYPES): return env[node.id] raise ValueError(f"未知或不允许的变量: {node.id}") if isinstance(node, ast.BinOp): left = _eval(node.left, env, max_pow) right = _eval(node.right, env, max_pow) if isinstance(node.op, ast.Pow): if not isinstance(right, (int, float)) or abs(right) > max_pow: raise ValueError("指数过大") if isinstance(left, (int, float)) and abs(left) > 1e6: raise ValueError("底数过大") return left ** right fn = _BIN.get(type(node.op)) if fn is None: raise ValueError("不允许的二元运算") # 防止字符串乘法耗尽内存 if isinstance(node.op, ast.Mult): if isinstance(left, str) and isinstance(right, int) and len(left) * right > 10000: raise ValueError("字符串过长") if isinstance(right, str) and isinstance(left, int) and len(right) * left > 10000: raise ValueError("字符串过长") return fn(left, right) if isinstance(node, ast.UnaryOp): fn = _UNARY.get(type(node.op)) if fn is None: raise ValueError("不允许的一元运算") return fn(_eval(node.operand, env, max_pow)) if isinstance(node, ast.BoolOp): if isinstance(node.op, ast.And): result = True for v in node.values: result = _eval(v, env, max_pow) if not result: return result return result if isinstance(node.op, ast.Or): result = False for v in node.values: result = _eval(v, env, max_pow) if result: return result return result raise ValueError("不允许的布尔运算") if isinstance(node, ast.Compare): left = _eval(node.left, env, max_pow) for op_node, comparator in zip(node.ops, node.comparators): right = _eval(comparator, env, max_pow) fn = _CMP.get(type(op_node)) if fn is None or not fn(left, right): return False left = right return True if isinstance(node, ast.IfExp): cond = _eval(node.test, env, max_pow) return _eval(node.body if cond else node.orelse, env, max_pow) raise ValueError(f"不允许的语法: {type(node).__name__}")

使用:

python

safe_eval("a + 2 * (b - 1)", {"a": 3, "b": 5}) # 11

被禁止的语法会直接报错:

python

safe_eval("__import__('os').system('ls')") # ValueError: 不允许的语法: Call

4. 如果只处理字面量

Python 自带ast.literal_eval:

python

import ast ast.literal_eval("{'a': [1, 2, 3]}")

它只允许字面量,安全边界较窄,但不能计算变量、表达式。


5. JS 中的思路

不要用:

js

eval(expr) new Function(expr)

应使用acorn、espree等解析成 ESTree,然后白名单解释:

  • 允许:Literal、Identifier、BinaryExpression、LogicalExpression、UnaryExpression、ConditionalExpression

  • 禁止:CallExpression、MemberExpression、AssignmentExpression、NewExpression、FunctionExpression、ArrowFunctionExpression、TemplateLiteral、ImportExpression、ThisExpression


6. 关键限制与结论

AST 安全求值不是“绝对安全”,只是大幅缩小攻击面。生产环境还应:

  • 限制表达式长度、AST 节点数、执行时间、内存;

  • 禁止Call、Attribute、Subscript、Import、Lambda、推导式、f-string;

  • 不把模块、函数、对象、类实例传入变量环境;

  • 必要时放到独立进程、容器、WASM 或 seccomp 沙箱中执行。

结论:
AST 安全求值 = 解析 + 白名单遍历 + 纯解释器 + 资源限制 + 默认拒绝。
复杂场景建议使用专用表达式引擎,如 CEL、JsonLogic、simpleeval、asteval等,而不是自己从零实现。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 14:39:36

【数据集】分省及地级市城投债信用利差数据集(2011-2026年)

数据简介:城投债分省份、地级市信用利差跟踪包括公募债、私募债数据库据库,信用利差个券估值-同期限国开债收益率。剔除剩余期限半年以内或五年以上的个券,估值采用不行权估值,匹配同期限国开债采用插值法。在债券市场中&#xff…

作者头像 李华
网站建设 2026/9/30 14:39:08

提示微调(Prompt Tuning/Prefix Tuning/P-Tuning)技术总结

提示微调属于参数高效微调 PEFT,核心思路:冻结大模型全部主干权重,只训练少量可学习的软提示向量,相比全参数微调显存开销、训练成本大幅下降,是现在大模型落地最常用的方案之一。 1、 核心技术原理提示微调的关键是将…

作者头像 李华
网站建设 2026/9/30 14:35:00

零基础嵌入式学习第三周

嵌入式基础作业三:STM32F103C8T6 三种方式实现流水灯 课程作业范围:实验1(寄存器方式)、实验2(标准外设库方式及 Keil 逻辑分析仪)、实验3(HAL 库 按键外部中断暂停/恢复)。 目标器…

作者头像 李华
网站建设 2026/9/30 14:29:53

Bayesian Theory

一. Probability1. 条件概率与独立性, with Equivalently,If A and B are independent,If,则称B对A有利(favourable/ probability-increasing)If,则称B对A不利(unfavourable/ probability-decreasing)Favourability is symmetric, B is probability-incr…

作者头像 李华
网站建设 2026/9/30 14:28:50

网易智企 AgentOS 正式上岗,这就给企业配个“AI 总管”!

当所有人都在谈论「Agent 能做什么」的时候,我们更想回答另一个问题—— Agent 到底能不能被企业托付?正是基于这样的思考,我们做了——AgentOS,即企业智能体运行操作系统,一款面向企业智能体规模化生产落地的新一代底…

作者头像 李华