ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • AWS Access Key 유출 및 대응 과정
    AWS 2026. 9. 20. 23:24

    문제 발생

    Amazon Web Services 에서 발송된 이메일

     

    코딩 테스트를 준비하던 밤 11시 10분쯤, 약 28분 전에 AWS에서 발송된 보안 경고 메일을 발견했습니다.

    메일의 내용은 제 AWS Access Key와 Secret Access Key가 제3자에게 부적절하게 노출되었을 가능성이 있으며, 실제로 비정상적인 접근이 탐지되었다는 것이었습니다.

    AWS 콘솔의 알림을 확인한 결과 문제가 발생한 IAM 사용자를 특정할 수 있었습니다. 해당 IAM 사용자는 작년 프로젝트에서 사용했던 계정이었고, 최근에는 사용하지 않았지만 Access Key가 활성 상태로 남아 있었습니다.

     

    문제 해결

    가장 먼저 해당 Access Key를 비활성화한 뒤 삭제했습니다.

    해당 IAM 사용자는 더 이상 사용하지 않는 계정이었기 때문에, 연결되어 있던 정책을 확인한 뒤 IAM 사용자 자체도 삭제했습니다.

    AWS에서 비정상적인 자격증명 사용을 이미 탐지한 상황이었기 때문에, 원인을 먼저 조사하기 이전에 노출된 자격증명으로 추가 요청을 보낼 수 없도록 차단하는 것을 우선했습니다.

     

    CloudTrail을 통한 활동 조사

    CloudTrail 에 기록된 이벤트. 조회성 API가 짧은 시간 동안 연속으로 호출되고 있다.

     

    Access Key를 폐기한 뒤에는 CloudTrail의 Event history를 확인했습니다.

    해당 Access Key와 관련된 이벤트는 미국 동부 버지니아 북부(us-east-1) 리전에 기록되어 있었습니다.

    GetCallerIdentity는 현재 사용하고 있는 AWS 자격증명이 어느 AWS 계정의 어떤 사용자 또는 역할인지 확인하는 API입니다. 따라서 외부에서 해당 Access Key가 실제로 사용되었다는 정황을 확인할 수 있었습니다.


    CloudTrail 이벤트 JSON 확인

    {
        "userIdentity": {
            "type": "IAMUser",
            "userName": "d------",
            "accessKeyId": "..."
        },
        "eventTime": "2026-09-17T12:26:00Z",
        "eventSource": "secretsmanager.amazonaws.com",
        "eventName": "ListSecrets",
        "awsRegion": "us-east-1",
        "sourceIPAddress": "...",
        "userAgent": "Boto3 ... Python ... takeovercloud-bbp-recon",
        "errorCode": "AccessDenied",
        "errorMessage":
            "...is not authorized to perform secretsmanager:ListSecrets",
        "responseElements": null,
        "readOnly": true,
        "eventType": "AwsApiCall"
    }

     

    CloudTrail에서는 각 이벤트의 상세 내용을 JSON 형태로 확인할 수 있습니다. ListSecrets 이벤트는 위 같이 기록되어 있었습니다.

    2026년 9월 17일 12시 26분 UTC에 d------ IAM 사용자의 Access Key를 이용하여 외부에서 Secrets Manager의 ListSecrets API를 호출했습니다. userAgent에는 Boto3, Botocore, Python이 기록되어 있어 Python용 AWS SDK를 이용한 요청이었음을 확인할 수 있었습니다. 하지만 해당 IAM 사용자에게 secretsmanager:ListSecrets 권한이 없었기 때문에 요청은 AccessDenied로 실패했습니다.

    ListSecrets는 Secrets Manager에 저장된 Secret의 목록을 조회하는 API이며, Secret의 실제 값을 가져오는 GetSecretValue와는 별개의 API입니다.

     

    자동화된 정찰 스크립트로 추정!

    CloudTrail 로그를 처음 봤을 때 가장 눈에 띈 건, 짧은 시간 안에 여러 AWS 서비스의 조회 API가 연속으로 호출됐다는 점이었습니다. ListUsers, ListBuckets, ListHostedZones, ListSecrets처럼 대부분 계정이나 리소스의 상태를 확인하는 요청이었고, 제가 직접 실행한 작업도 아니었습니다. 여기에 userAgent를 확인해보니 Python, Boto3가 기록되어 있었습니다. 즉 AWS 콘솔에서 사람이 하나씩 눌러본 것이 아니라, Python에서 AWS API를 호출한 흔적이었습니다.

     

    비슷한 패턴이 AWS GuardDuty 문서의 Discovery:IAMUser/AnomalousBehavior 항목에도 설명되어 있었습니다. AWS는 Get, Describe, List 같은 API를 비정상적으로 호출하며 계정과 리소스 정보를 수집하는 행동을 Discovery 단계와 연관 지어 설명합니다.

    이번 로그도 여러 서비스를 짧은 시간 안에 훑고 있었고, Python/Boto3를 통해 호출된 요청이라는 점이 확인됐기 때문에, 특정 공격 도구까지는 알 수 없지만 AWS 환경을 자동으로 탐색하는 정찰 스크립트로 추정했습니다.

     

     

     

    피해 범위와 원인 조사

    해당 키를 통해 가능했던 작업

    조사하면서 가장 먼저 확인한 것은 유출된 Access Key로 어디까지 할 수 있었는지였습니다. 해당 IAM 사용자에는 EC2, ECS, Public ECR, RDS, S3, IAM에 대한 FullAccess 정책이 연결되어 있었습니다. 즉 단순히 리소스 목록을 조회하는 것뿐 아니라, 해당 서비스의 리소스를 생성하거나 수정·삭제할 수 있을 정도로 권한 범위가 넓었습니다.

     

    해당 키를 통해 실제로 수행된 작업

    S3의 경우, 현재 진행 중인 프로젝트의 버킷이 남아있어 가렸습니다.

     

    CloudTrail에서는 RunInstances와 같은 리소스 생성 이벤트나 IAM 사용자, Access Key 생성, 정책 변경 등의 관리 이벤트를 찾지 못했습니다. AWS 콘솔에서도 EC2와 RDS에 추가 리소스가 없었고, Public ECR에도 새 Repository가 존재하지 않았습니다. S3 역시 기존 프로젝트에서 사용하던 Bucket만 남아 있었으며, Billing에서도 확인 당시 추가 비용은 발생하지 않았습니다.

    따라서 확인 가능한 범위에서는 정찰 이후 AWS 리소스를 생성하거나 변경한 흔적은 발견하지 못했습니다.

     

    아쉬운 점

    객체 접근에 대한 확인

    이번 조사에서 사용한 CloudTrail Event history는 Management Event*를 확인할 수 있지만, S3의 GetObject, PutObject, DeleteObject와 같은 객체 수준의 Data Event**는 별도 로깅이 필요하다는 것을 알게 되었습니다. 당시 Data Event 로깅을 설정하지 않았기 때문에, S3 내부 객체까지 실제로 조회하거나 변경했는지는 사후에 확인할 수 없었습니다.

    * AWS 리소스 자체를 관리하는 행동

    ** 리소스 안의 실제 데이터를 사용하는 행동

     

    원인 특정 불가

    해당 Access Key를 GitHub Actions Secret으로 사용했던 만큼 Git history, workflow 로그, artifact, third-party Action 등을 우선적인 확인 대상으로 두고 있습니다. 다만 현재로서는 정확히 어디에서 키가 유출됐는지는 알 수 없습니다.

     

    배운 점 및 앞으로의 TODO

     

    장기 Access Key 사용 최소화

    GitHub Actions 등에서 AWS 인증이 필요한 경우 장기 Access Key를 Secret으로 저장하는 방식은 가능한 한 줄이려고 합니다.

    대신 OIDC를 통해 IAM Role을 Assume하고, 실행 시점에 만료되는 임시 자격증명을 발급받는 방식을 우선적으로 사용하려 합니다.

    과거에도 장기 Access Key가 유출될 가능성을 생각해 OIDC를 사용해본 적은 있었지만, 이번 사고를 통해 그 필요성을 훨씬 직접적으로 체감했습니다.

     

    FullAccess 정책 제거

    AWS를 처음 사용할 당시에는 필요한 권한을 정확하게 구분하지 못해 FullAccess 정책을 여러 개 사용했습니다. 앞으로는 서비스 단위의 FullAccess를 사용하는 대신, 실제로 필요한 리소스와 Action만 허용하는 최소 권한 정책을 구성하려 합니다.

    예를 들어 특정 S3 Bucket에 이미지를 업로드해야 한다면 전체 S3에 대한 s3:* 권한을 주는 것이 아니라, 필요한 Bucket과 PutObject 등 필요한 Action만 허용하도록 하려고 합니다.

     

    감사 가능성 확보

    CloudTrail이 있다고 해서 모든 활동을 사후에 확인할 수 있는 것은 아니었습니다.

    특히 S3 객체와 같이 Data Event에 해당하는 작업까지 추적해야 하는 중요한 리소스라면, 사고가 발생하기 전에 어떤 로그를 남길 것인지도 함께 설계해야 한다는 점을 배웠습니다. 앞으로는 단순히 “정상적으로 동작하는가?”뿐 아니라, “문제가 발생했을 때 무엇을 근거로 원인을 추적할 수 있는가?”까지 고려해서 인프라를 구성하려 합니다.

     


    [Reference]

     

    'AWS' 카테고리의 다른 글

    AWS 다 지웠는데 왜 과금되지? : RDS 스냅샷 삭제하기  (0) 2025.09.11
Designed by Tistory.