
sympy.parse_expr pode executar comandos do sistema se não for controlado
sympy.parse_expr irá executar os.system se você permitir. Aqui está o portão AST que me impediu de enviar o RCE.
Eu estava construindo um servidor MCP que aceita uma fórmula de medição como uma string de um LLM, a analisa com sympy e a avalia via Monte Carlo. Cinco minutos de integração. Treze testes. Doze passaram.
O décimo terceiro foi um teste de segurança. A fórmula que passou foi:
"__import__('os').system('echo PWNED')"
O teste esperava um ValueError. Em vez disso, recebi uma mensagem de falha no teste e a string PWNED impressa no meu terminal.
Deixe-me deixar isso claro. sympy.parse_expr, com os argumentos padrão que eu estava usando, realmente invocou os.system na minha máquina. Meu próprio parser, rodando no meu próprio processo de teste, executou um shell e ecoou texto no meu terminal. Se eu tivesse enviado isso para produção, qualquer usuário de LLM com um prompt suficientemente criativo poderia ter feito esse mesmo shell-out acontecer em uma instância do Cloud Run sob minha conta de cobrança.
Por que isso acontece
sympy.parse_expr é implementado em cima do eval() do Python. Quando você não bloqueia explicitamente o dicionário global em que ele avalia, eval herda os __builtins__ do módulo atual, que inclui __import__, open, compile, exec, getattr, e amigos. Uma string que parece uma expressão matemática, mas que faz referência a __import__, é resolvida como uma expressão Python — e é executada.
Isso é um comportamento documentado do sympy com avisos na documentação. Avisos não salvam você quando você está se movendo rápido no dia do lançamento.
A superfície de vulnerabilidade é mais ampla do que __import__. Qualquer um desses pode dar a um atacante execução de código dependendo do que está em escopo:
-
__import__('os').system(...)— shell direto -
__builtins__.__dict__['eval'](...)— reentrar no eval -
(0).__class__.__bases__[0].__subclasses__()— escapar para instâncias de classe arbitrárias -
getattr(__builtins__, 'open')('/etc/passwd').read()— leitura de arquivo -
''.join.__globals__['__builtins__']['exec']('...')— exec via método de string walk
A correção que realmente funciona: valide no nível AST antes que o sympy veja a string
import ast
_DANGEROUS_NAMES = frozenset({
"__import__", "__builtins__", "__class__", "__subclasses__",
"eval", "exec", "compile", "open", "globals", "locals",
"getattr", "setattr", "delattr", "exit", "quit",
})
_FORBIDDEN_AST_NODES = (
ast.Attribute, # bloqueia "os.system" e "obj.__class__"
ast.Subscript, # bloqueia "x[0]" indexando em dunders
ast.Lambda,
ast.GeneratorExp,
ast.ListComp,
ast.DictComp,
ast.Starred,
ast.JoinedStr,
ast.FormattedValue,
ast.NamedExpr,
)
def _validate_formula_ast(formula: str) -> None:
tree = ast.parse(formula, mode="eval")
for node in ast.walk(tree):
if isinstance(node, _FORBIDDEN_AST_NODES):
raise ValueError(
f"Construto de fórmula não permitido: {type(node).__name__}"
)
if isinstance(node, ast.Name):
if node.Empresas brasileiras que utilizam LLMs para processamento de fórmulas devem estar cientes das vulnerabilidades associadas ao uso de bibliotecas como sympy. A implementação de validações adequadas é crucial para evitar a execução de código malicioso em ambientes de produção.
Noticias relacionadas

Protocolo de Contexto do Modelo (MCP) para Websites
O MCP é um protocolo aberto que padroniza como agentes de IA se conectam a fontes de dados externas. Este artigo detalha a implementação do MCP em sites, essencial para a integração com agentes de IA.

Criei um servidor MCP que pesquisa na web
Servidores MCP se tornam mais úteis ao acessar a internet. Desenvolvi uma ferramenta de busca que utiliza DuckDuckGo e retorna resultados formatados para LLMs.

Conecte seu servidor MCP ao Claude Desktop em 5 minutos
Aprenda a conectar seu servidor MCP ao Claude Desktop para utilizar ferramentas de IA de forma prática e eficiente. O artigo traz um guia passo a passo.
Gostou do conteudo?
Receba toda semana as principais novidades sobre WebMCP.