{"id":"IL-0052","org":"JW Player","date":"2019-03-19","docs":[],"title":{"en":"A cryptocurrency miner ran on every node of the development and staging clusters through a public, unauthenticated Weave Scope dashboard","ko":"인증 없이 인터넷에 열린 Weave Scope 대시보드를 통해 암호화폐 채굴기가 개발 클러스터와 스테이징 클러스터의 모든 노드에서 돌았다."},"domain":["kubernetes","aws","security"],"impact":{"scope":"every node, masters included, of the development and staging clusters; one CPU core per node used by the miner; production not affected","users":"no customer data accessed; service uninterrupted","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"Load average was not a useful signal for spotting a problem in the clusters, and muted noisy alerts nearly hid the miner. A load balancer that Kubernetes creates belongs to Kubernetes: a manual security group edit in the AWS console is reverted, so internal exposure must be declared in the Kubernetes YAML. A dashboard that can open shells, in front of privileged agents that mount the host's root filesystem, gives whoever reaches it root on every node.","ko":"load 기반 모니터링은 클러스터의 이상을 가려내는 좋은 지표가 아니었다. 정상 서비스가 낸 경보 소음이 침입 신호를 가렸다. 쿠버네티스가 관리하는 리소스를 손으로 고치면 쿠버네티스가 그 변경을 되돌린다. 로드 밸런서를 내부용으로 정의하거나 보안 그룹 규칙을 쿠버네티스 YAML 에 넣었어야 했다. 인증 없는 내부 대시보드, --privileged 와 root 로 도는 컨테이너, 호스트 파일 시스템 마운트가 겹치면 열린 로드 밸런서 하나로 모든 노드에서 코드가 실행될 수 있다. 컨테이너를 root 로 돌리는 것처럼 예전 인프라에서 물려받은 결정은 다시 검토해야 한다."},"related":[],"sources":[{"url":"https://medium.com/jw-player-engineering/how-a-cryptocurrency-miner-made-its-way-onto-our-internal-kubernetes-clusters-9b09c4704205","kind":"blog","license":"unknown","archived":"https://web.archive.org/web/20191206074634/https://medium.com/jw-player-engineering/how-a-cryptocurrency-miner-made-its-way-onto-our-internal-kubernetes-clusters-9b09c4704205"}],"summary":{"en":"JW Player's DevOps team found a Monero miner, named gcc, running on every node of its development and staging Kubernetes clusters. Production was not affected, no customer data was accessed and service was not interrupted. The clusters had recently been rebuilt on Kubernetes 1.10 with different instance sizes, so some unusual behaviour was expected, and noisy load alerts from a legitimate service delayed the investigation.\n\nThe miner's parent process was Weave Scope, a tool for watching a Kubernetes cluster in real time. No CVE applied and its image hash matched the published Docker Hub image. Rebuilding an isolated cluster with the same deployment method showed the entry point: the Weave Scope service was a LoadBalancer without the annotation that makes the load balancer internal, so its dashboard was reachable from the internet with no login. Weave Scope can open a command line in any container, and the scope-agent container, which runs on every node, was started with --privileged, mounted files from the host's root filesystem and ran as root. The team reproduced the attack by starting a script on a host from a scope-agent shell.\n\nAWS CloudTrail explained why an internal dashboard was public. When Kubernetes created the load balancer, it attached a security group open to 0.0.0.0/0 on port 80. Someone then restricted it to allow-listed IPs by hand in the AWS console, but Kubernetes owns that load balancer and reverted the edit shortly afterwards.\n\nThe damage was one CPU core at 100% on each node in development and staging. Access logs showed no outside logins, the Kubernetes API was not used by the intruder, and RBAC limited Weave Scope to the weave namespace, so a request for secrets would have been denied. The team concluded the crawler's only purpose was mining.","ko":"JW Player 데브옵스 팀은 개발 클러스터와 스테이징 클러스터에서 암호화폐 채굴기를 발견했다. 프로덕션 클러스터는 영향을 받지 않았고, 고객 데이터에 접근하거나 유출된 일도 없었으며, 서비스도 멈추지 않았다.\n\n처음 신호는 Datadog 의 높은 load average 경보였다. 팀은 정상 서비스의 부하로 보고 경보를 뮤트했고, 3일 차에야 노드에 들어가 gcc 라는 이름으로 위장한 채굴기를 찾았다. 채굴기는 마스터를 포함한 모든 노드에서 CPU 코어 하나를 100% 쓰고 있었다. 프로세스의 부모는 Weave Scope 였다. XMRig 를 검색해 찾은 소스로 이 프로그램이 Monero 채굴만 한다는 것을 확인했다.\n\n공격 경로를 찾으려고 같은 배포 방식으로 격리된 새 클러스터를 띄워 보니, Weave Scope 로드 밸런서의 보안 그룹이 인터넷 전체에 열려 있었다. 로드 밸런서 URL 을 아는 사람은 누구나 인증 없이 Weave Scope 대시보드에 들어갈 수 있었다. Weave Scope 에는 실행 중인 컨테이너에 셸을 여는 기능이 있다. 배포에는 로드 밸런서를 내부용으로 만드는 annotation 이 빠져 있었다. weave-scope 컨테이너는 --privileged 로 돌았다. 호스트 루트 파일 시스템의 파일이 컨테이너에 마운트돼 있었고, 컨테이너는 root 사용자로 실행됐다. 이 조합 때문에 컨테이너 밖으로 빠져나가 호스트에서 임의 코드를 실행할 수 있었다. 팀은 scope-agent 컨테이너에서 같은 방식으로 호스트에 root 권한 스크립트를 띄워 공격을 재현했다. scope-agent 는 지표를 모으려고 모든 노드에서 돌기 때문에 채굴기도 모든 노드에서 돌 수 있었다.\n\nAWS CloudTrail 로그를 보니 쿠버네티스가 로드 밸런서를 만들 때 0.0.0.0/0 에서 80 포트를 허용하는 보안 그룹을 연결했다. 팀은 AWS 콘솔에서 이 보안 그룹을 손으로 고쳤지만, 쿠버네티스가 얼마 뒤 그 수정을 되돌렸다. 쿠버네티스 기여자는 GitHub 이슈에서 ELB 는 쿠버네티스가 관리하는 리소스이므로 손으로 고치면 안 된다고 설명했다.\n\n피해 조사에서 호스트에 외부 로그인은 없었고, 침입자는 쿠버네티스 API 에 접근하지 않았다. 로드 밸런서 URL 은 공유된 적이 없었고, 자동화된 크롤러가 대시보드를 찾아냈다. RBAC 가 Weave Scope 의 권한을 weave 네임스페이스로 묶어 두었고, 노드에는 민감한 정보가 없었다. 피해는 개발과 스테이징의 모든 노드에서 CPU 코어 하나가 100% 쓰인 것이 전부였다."},"trigger":{"en":"An automated crawler found the Weave Scope dashboard on a public load balancer that required no authentication. Weave Scope can open a shell in running containers, and its scope-agent containers ran privileged, as root and with the host's root filesystem mounted, so a command started there could run on the host.","ko":"Weave Scope 를 배포할 때 쿠버네티스가 로드 밸런서에 0.0.0.0/0 에서 80 포트로 들어오는 접근을 허용하는 보안 그룹을 연결했다. 팀은 AWS 콘솔에서 이 보안 그룹을 손으로 고쳐 허용 목록 밖의 트래픽을 막았지만, 쿠버네티스가 얼마 뒤 이 수정을 되돌렸다. 그 결과 Weave Scope 대시보드가 인증 없이 인터넷에 열렸고, 자동화된 크롤러가 이를 찾아냈다."},"severity":"unknown","symptoms":["node-high-cpu","unauthorized-access"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":{"en":"Datadog alerts on high normalised load average, muted at first as noise from a legitimate service; on Day 3 an engineer ran top on a node and found the gcc process at 100% CPU.","ko":"Datadog 이 정규화한 load average 경보를 0일 차 21:06 EST 에 스테이징에서, 1일 차 16:53 EST 에 개발 환경에서 보냈다. 팀은 정상 서비스의 부하로 보고 경보를 뮤트했다. 3일 차 17:40 EST 에 부하 증가가 나흘째 이어지자 엔지니어가 노드에 SSH 로 들어가 top 으로 CPU 를 100% 쓰는 gcc 프로세스를 찾았다."},"components":["cluster-dashboard","cloud-load-balancer"],"root_cause":"config-change","remediation":[{"text":{"en":"stopped Weave Scope and removed its deployment from all Kubernetes clusters","ko":"Weave Scope 를 멈추고 모든 쿠버네티스 클러스터에서 그 배포를 지웠다."}},{"text":{"en":"checked access logs for unauthorised access to the instances","ko":"인스턴스에 무단 접근한 흔적이 있는지 접근 로그를 확인했다."}},{"text":{"en":"confirmed the miner's outbound connections went only to a mining pool","ko":"gcc 프로세스의 외부 연결을 조사해 마이닝 풀과만 통신한다는 것을 확인했다."}},{"text":{"en":"isolated one affected node for later investigation","ko":"영향을 받은 노드 하나를 나중에 조사하려고 클러스터에서 격리했다."}},{"text":{"en":"rotated out every node in each cluster so all affected hosts were destroyed and rebuilt","ko":"모든 클러스터의 노드를 전부 새로 만들어 교체하고 감염을 모두 없앴다."}}],"action_items":[{"text":{"en":"Audit all Kubernetes and deployment YAML files and security groups for services that are unintentionally public or misconfigured","ko":"모든 쿠버네티스 설정 파일과 사내 배포 YAML, 보안 그룹을 감사해 의도치 않게 외부에 열린 서비스나 잘못된 설정이 없는지 확인했다."}},{"text":{"en":"Replace load-average alerts with more meaningful monitors and tune them to signal a real anomaly","ko":"load 대신 더 의미 있는 모니터를 정하고, 작업량이 늘 때 경보가 쏟아지지 않고 실제 이상을 가려내도록 다듬는다."}},{"text":{"en":"Research tools such as falcon or sysdig for behavioural monitoring and anomaly and intrusion detection","ko":"falcon 이나 sysdig 같은 도구로 행위 모니터링과 이상 탐지, 침입 탐지를 할 수 있는지 검토한다."}},{"text":{"en":"Evaluate Istio and Linkerd for observing and controlling end-to-end network traffic","ko":"Istio 나 Linkerd 로 종단 간 네트워크 트래픽을 파악하고 통제해 무단 접근을 막는 방안을 검토한다."}},{"text":{"en":"Analyse the cost of scanning Docker images and containers for known vulnerabilities","ko":"Docker 이미지와 컨테이너에서 알려진 취약점을 검사하는 데 드는 계산 비용을 따져 본다."}},{"text":{"en":"Set aside time in architecture discussions to revisit older choices such as running containers as root","ko":"아키텍처 논의 시간 일부를 떼어 컨테이너를 root 로 돌리는 것처럼 예전에 내린 결정을 다시 검토하기로 했다."}},{"text":{"en":"Share knowledge of large projects, such as Kubernetes version migrations, across the whole team","ko":"일부 팀원에게만 있던 정보를 팀 전체와 나눠, 대규모 쿠버네티스 버전 업그레이드 같은 큰 프로젝트를 모든 엔지니어가 볼 수 있게 한다."}}],"contributing":["early-signal-ignored","default-config-unchanged","blast-radius-too-wide","monitoring-blind-spot"],"recurrence_of":null,"symptoms_text":{"en":"Datadog alerted the DevOps team to a high normalised load average, first on the staging cluster (Day 0, 21:06 EST) and the next day on the development cluster (Day 1, 16:53 EST). The load was first put down to a legitimate service that kept scaling up and down in both environments, and because of the volume of identical alerts they were muted until things settled.\n\nOn Day 3 at 17:40 EST, after four days of raised load, an engineer logged onto a node over SSH and ran top. A process named gcc was using 100% of a CPU, and the same process was running on every machine in both clusters, master nodes included.\n\nThe first guess was a faulty container launched by the internal deployment tool, but that did not explain why master nodes were affected, and the infrastructure-as-code repositories contained nothing that ran gcc. Inspecting the process showed that its parent was a Weave Scope container, that the binary ran from the host's root directory rather than inside a container, and that it was in fact a cryptocurrency miner. Weave Scope's own view showed the process holding an outbound connection to a mining pool; a search for the name XMRig led to a Monero miner.","ko":"Datadog 이 스테이징 환경의 정규화한 load average 가 높다고 경보를 보냈다. 팀은 원인을 정상 서비스 하나의 부하 증가로 판단했고, 정상 동작으로 여겼다.\n\n다음 날 같은 경보가 개발 환경에서도 울렸다. 그 서비스는 개발과 스테이징 양쪽에서 스케일 업과 스케일 다운을 계속 되풀이하며 경보를 거듭 냈다. 앞선 경보를 이미 살펴본 데다 같은 경보가 쏟아졌으므로, 팀은 부하가 안정될 때까지 이 경보들을 뮤트했다.\n\n3일 차가 되자 두 클러스터에서 나흘 동안 이어진 부하 증가를 더는 정상으로 볼 수 없었다. 엔지니어가 노드 하나에 SSH 로 들어가 top 을 보니 gcc 프로세스가 CPU 를 100% 쓰고 있었다. 이 프로세스는 개발과 스테이징 클러스터의 모든 머신에서 돌고 있었다.\n\n엔지니어는 처음에 사내 배포 도구로 잘못된 컨테이너가 떠서 gcc 를 돌린다고 의심했다. 마스터 노드도 영향을 받았으므로 이 가설은 맞지 않았다. 인프라를 코드로 관리하는 저장소에도 gcc 를 실행하는 설정이나 배포는 없었다. 프로세스를 따라가 보니 부모가 Weave Scope 컨테이너였고, gcc 라는 이름의 파일은 호스트의 루트 디렉터리에서 실행되는 암호화폐 채굴기였다. Weave Scope 로 보니 이 프로세스는 마이닝 풀로 나가는 연결을 맺고 있었다."},"updated_at":"2026-11-10T00:00:38.848379Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0052","org_slug":"jw-player"}