[CloudGoat] Scenario "ecs_takeover" - solution
시나리오 목표
"vault" 컨테이너에 Access해 플래그 획득
시나리오 세팅
./cloudgoat.py create ecs_takeover
EC2 인스턴스의 Public IP가 연결된 DNS 주소 하나가 주어진다.
ECS 클러스터 구조
문제에 돌입하기 전에 ECS 서비스에 대한 구조를 간략하게 알고 넘어가보자.
ECS의 리소스는 cluster, service, task, container로 구성되어있다. 이러한 구조는 k8s와도 살짝 닮아있는 것을 알 수 있다.
Component | 설명 |
Cluster | ECS Service와 Task를 실행하는 인프라, service와 task를 묶는 논리적인 단위가 된다. ECS의 가장 상위 수준의 리소스 |
Service | Task의 지속적인 실행을 관리하는 개체로, Task를 실행하는 방법을 담고있다. 이러한 점에 의해 k8s에서의 Deployment와 유사한 개념으로 매핑해볼 수 있다. Service는 Task의 실행하고 관리하는 단위로, Task의 스케일링, 로드 밸런싱, 실패 대응 등을 관리한다. |
Task | 하나 이상의 Container로 구성된 각 Task는 하나 이상의 컨테이너를 실행한다. 마치 k8s의 Pod 정도의 개념으로 생각할 수 있다. Task는 ECS에서 컨테이너의 실행을 관리하는 단위이며, 하나의 Task 상의 모든 컨테이너들은 동일한 Task 정의(Task Definition)를 공유한다. |
Container | 소프트웨어 Application을 패키지화하고 실행하기 위한 최소 사이즈의 독립/격리된 환경 |
Solution
1. 주어진 시작 사이트 탐색 및 취약 구간 탐지
주어진 사이트로 접속하면 URL 입력구간이 하나 나온다.
URL을 submit하면 해당 URL에 Request를 전송하고 Response를 받아와 그대로 출력해주는 것으로 보인다. cURL을 수행한 결과로 판단된다.
여기서 사용자 입력값을 검증하는 로직이 존재하는 지 확인해본 결과, 입력값이 그대로 첫번째 줄에 출력되고 그 결과가 다음 줄로 출력되는 형태임을 알 수 있었다.
이후 이 URL 입력이 Shell Command Injection이 가능한 구간임을 확인할 수 있다.
먼저 이 웹 사이트가 올라간 인스턴스에 연결되어있는 IAM Role이 있나 확인을 해보았고, 하나의 Role을 확인할 수 있었다. (이하 ec2_role)
이 Role의 Permission을 체크해본다. enumerate-iam을 이용해 이 Role의 permission 목록을 열거했다.
다음으로 웹 서비스가 올라간 환경의 환경변수를 확인해보았다.
ECS Container와 연관된 내용을 확인할 수 있었고, 현재 이 웹 서비스는 컨테이너환경으로 파악할 수 있었다.
그 다음으로 docker 커맨드가 먹히는지 테스트를 해보았는데, 커맨드가 동작했다. 분명 웹 서비스는 ECS 컨테이너 환경인데 어떻게 docker 커맨드를 사용할 수 있었을까?
+) Contanier Escaping
웹 서비스 컨테이너를 둘러보면 마운트된 docker socket 파일을 확인할 수 있었다.
이를 통해 docker 컨테이너를 escape할 수 있는 취약점이 발생한다.
컨테이너 내부에서 docker CLI 커맨드를 이용해 host의 container들의 상세정보들을 확인 가능한 것이다.
결국 웹 서비스는 컨테이너 환경이 맞고, host의 docker socket이 웹 서비스 컨테이너 볼륨에 마운트되어 컨테이너 내부에서 docker CLI API를 사용할 수 있던 것이다.
2. Exploit - ECS Container Credential 탈취
우선 이 인스턴스에 존재하는 컨테이너를 먼저 확인했다.
;docker ps -a
이 인스턴스에는 총 3개의 컨테이너가 동작 중인 것을 확인할 수 있었다.
- ecs-takeover-vulnsite
- sleep되어있는 busybox(privd)
- ecs-agent
하지만 Exploit 대상인 "vault" 컨테이너는 아직 볼 수 없었다.
컨테이너들 중 vulnsite는 현재 우리가 보고있는 웹일 것이고, 그리고 ecs-agent는 인스턴스에 ecs를 구동시키기 위한 Agent가 될 것이다.
그렇다면 마지막으로 남은 busybox는 무슨 용도일까.
우선 privd 컨테이너를 타깃으로 전환하기로 했다.
docker CLI를 통해 타 컨테이너 내부의 환경변수 값을 확인할 수 있다. 그 결과, 일반 인스턴스때와는 다른 환경변수 내용을 볼 수 있다.
EC2 인스턴스에 IAM Role을 할당하는 것과 같이 ECS의 Task에도 IAM Role을 할당할 수 있다. 이 Task에 할당되는 Role을 통해 Task의 컨테이너가 AWS 리소스에 접근할 수 있게된다.
인스턴스에서 메타데이터를 확인했던 방법(IMDS)과 마찬가지로, ECS는 컨테이너 내부에서 액세스할 수 있는 메타데이터 엔드포인트를 제공한다.
컨테이너의 환경변수에 등록된 내용(AWS_CONTAINER_CREDENTIALS_RELATIVE_URI)을 이용해 privd 컨테이너의 Role Credential을 획득할 수 있게된다. (이하 privd_role)
이 Role 역시 permission 열거를 진행해본다.
3. 탈취한 Container Credential을 통한 ECS cluster 정보확인
먼저 ECS 클러스터의 정보부터 살펴보도록 한다.
# cluster 목록 확인
aws ecs list-clusters --profile ecs-takeover
# cluster 내용 확인
aws ecs describe-clusters --clusters [cluster ARN] --profile ecs-takeover
위를 통해 하나의 클러스터에 2개의 인스턴스가 등록되어있고, 4개의 task, 3개의 service가 있는 것을 확인할 수 있다.
그 다음으로 하위 단계의 service를 확인한다.
aws ecs list-services --cluster [cluster ARN] --profile ecs-takeover
Service들의 Describe 커맨드는 아래와 같다.
aws ecs describe-services --cluster [cluster ARN] --services [service명] --profile ecs-takeover
vault Service의 events 내용을 보면, 실행로그를 확인할 수 있다. schedule된 Task는 실행이 되었으나 최종적으로는 컨테이너 인스턴스의 조건에 부합하지 않아 Service가 실행되지 않은 것을 볼 수 있다.
그리고 클러스터의 Task 목록을 확인한다.
aws ecs list-tasks --cluster [cluster명] --profile ecs-takeover
이 클러스터에는 총 4개의 task가 존재한다는 것을 확인할 수 있다. 다음으로 각 Task들을 describe해본다.
aws ecs describe-tasks --cluster [cluster 이름] --task [task 번호] --profile ecs-takeover
Task까지 확인한 결과, task 4개 중 2개는 privd 서비스, 하나는 vault, 나머지 하나는 vulnsite 서비스인 것을 볼 수 있었다.
또한 vault와 privd Task가 하나의 인스턴스(36dfb41eb94f4b2f955e8ba259c7f2f5), vulnsite와 다른 privd Task가 하나의 인스턴스(654b615a57c44934b17d4029e6a023ea)에서 컨테이너로 실행되는 것까지도 알 수 있었다.
4. Exploit - Container Rescheduling & Access Container
우리의 목표인 vault 컨테이너에 접근하기 위해서는 앞서 확인한 vault Task를 실행시켜 vault 컨테이너를 deploy 시켜야 한다. 그러기 위해서 vault Service를 활성화시켜야한다.
앞서 vault Service에서 확인한 에러메시지에 따르면, 이 서비스에 대한 컨테이너 인스턴스가 없어서 실행에 실패한 것을 알 수 있었다. 또한 이 서비스는 REPLICA scheduling으로 설정되어있음을 알 수 있다.
그렇다면 vault task가 할당된 인스턴스(36dfb41eb94f4b2f955e8ba259c7f2f5)를 종료시키는 등의 행동을 하면, vault Service가 스케쥴링을 다시 해서 남은 하나의 인스턴스(654b615a57c44934b17d4029e6a023ea)에 vault task를 할당하지 않겠는가?
그러면 vault task는 남은 하나의 인스턴스(vulnsite가 올라간 인스턴스, 우리가 접근할 수 있는 인스턴스)에서 실행되고, vault 컨테이너가 이 인스턴스에 deploy될 것이다.
앞서 탈취한 인스턴스의 IAM Role(ec2_role)에서 update-container-instances-state를 확인할 수 있었다.
이 API는 ECS 클러스터 내의 컨테이너 인스턴스 상태를 업데이트하는 데 사용된다. ECS 클러스터에 등록된 컨테이너 인스턴스를 활성화(Active) 상태에서 비활성화(Draining) 상태로 변경하거나, 반대로 비활성화 상태에서 활성화 상태로 변경할 수 있다.
vault task가 할당된 인스턴스(36dfb41eb94f4b2f955e8ba259c7f2f5)를 Draining하도록 한다.
aws ecs update-container-instances-state --cluster [cluster ARN] \
--container-instances [conatiner instance명] \
--status DRAINING \
--region us-east-1 \
--profile ecs-takeover
DRAINING 처리 이후, vulnsite 인스턴스에서 컨테이너 목록을 확인하면 vault 컨테이너가 올라온 것을 볼 수 있다.
vault 컨테이너 내에 위치한 FLAG.txt를 확인할 수 있게된다.