사례별로 알아본 안전한 S3 사용 가이드

Nov.09.2021 이주호

AWS Infra Security

0x00 들어가며

안녕하세요. 우아한형제들 인프라보안팀에서 근무하고 있는 이주호라고 합니다. 이번 포스팅을 통해 AWS 운영 환경에서 데이터 저장소로 많이 활용하고 있는 AWS S3(Simple Storage Service)에 대해 이야기해 보려 합니다.

S3는 이름 뜻대로 간단하게 사용할 수 있는 저장소이며 사용 방법도 크게 어렵지 않아 서비스 구성에 다양하게 사용되는 서비스입니다. 하지만 사용이 쉬운 만큼 보안 관점에서는 많이 고민해 보아야 하는 서비스라고 생각합니다.
특히 데이터를 보관하는 서비스인 특성상 보안을 충분히 고려하여 설정해야 하지만 안전하게 사용하기 위한 설정은 무엇인지, 어떤 것을 고려해야 하는지, 모범사례는 무엇인지 사례별 가이드는 찾아보기 어렵습니다.

이번 글을 통해 ‘S3를 왜 안전하게 사용해야 하는지 이해’하고 몇 가지 사례를 예시로 들어 ‘더욱 안전하게 사용하기 위해 설정해야 하는 기능’‘사용 가이드’를 제시하여 많은 클라우드 관련 개발 및 보안 엔지니어분들에게 도움이 되고자 합니다.

0x01 S3의 보안 가이드가 어려운 이유

S3는 사용 방법이 쉬우면서도 세부 설정들은 복잡하므로 특별한 기준 없이 사용하도록 방치하다 보면 추후 많은 어려움을 겪게 될 수 있습니다. 특히 S3는 보안사고로 이어지는 경우가 많은데, 대부분 잘못된 설정에 의한 사고입니다. 그리고 S3의 침해사고는 보안 담당자 입장에서 로그를 자세히 분석하지 않는 이상 탐지하기도 어렵습니다. 그러므로 미리미리 취약하지 않도록 잘못된 설정은 관리가 될 수 있도록 조치하여 예방하는 것이 중요합니다.

S3에 의한 데이터 유출 보안사고 목록 : https://github.com/nagwww/s3-leaks

이러한 위험을 해소하기 위해 가이드를 만들고 가이드를 적절하게 따르고 있는지 모니터링하는 것이 필요하다고 생각합니다. 하지만 S3 가이드를 만들고 정책을 수립하는 것은 어려운 일입니다. 왜냐하면 실제 사용 사례를 들여다보면 개발 등 다양한 목적으로 S3를 사용하고 있는 게 현실이고 잠재적인 위협이 될 수 있는 S3를 식별하는 것은 많은 고민과 경험이 필요하기 때문입니다.
예를 들어 S3 보안 가이드에서 S3의 퍼블릭 공개 설정은 취약하다고 무작정 IAM user의 S3의 퍼블릭 공개 설정 권한을 제거하거나 S3 > 퍼블릭 액세스 차단 설정정보 기능을 이용하여 퍼블릭 공개 상태로 운영 중인 S3 버킷을 퍼블릭 비공개 제한으로 변경하게 된다면 이는 또 다른 사고로 이어질 수 있습니다.

나봄. “세상에서 가장 어려운 것.” 그라폴리오. 20190601.
(https://grafolio.naver.com/works/885433)

보안 편향적 사고의 주의점
정보보호 관련 부서의 가장 큰 목표는 보안 침해사고가 발생하지 않는 것이고 이를 위해 모든 역량을 집중하는 게 맞으나 항상 위험만을 동기로 한 보안 편향적 사고에서 벗어나려 노력해야 합니다. 어렵겠지만 ‘사용성까지 고려한 이해하기 쉽고 안전한 방법’ 을 적절히 제시할 수 있다면 구성원의 보안 준수 행동에 긍정적인 영향을 줄 수 있습니다.
보안팀에서 먼저 모범 사례에 대한 고민과 사용자 입장을 염두에 두며 보안 업무를 수행한다면 다양한 직무의 구성원이 자연스럽게 ‘보안이 반드시 불편한 것은 아니구나’ 하며 보안을 준수하려 노력하는 문화를 형성할 것으로 믿고 있습니다.

0x02 S3 사용 사례별 보안 가이드

S3는 데이터 저장이라는 기술을 기반으로 한 서비스이지만 단순 데이터 저장 외에도 다양한 목적으로 사용할 수 있습니다. 가장 대표적인 예는 정적(static) 웹 호스팅(web hosting) 기능입니다. 이 기능은 서버 측 동적 처리가 불필요한 간단한 정적 웹 페이지로만 구성된 웹을 쉽게 구성하여 호스팅할 수 있는 기능입니다. 이 사례에도 물론 보안을 위해 챙겨야 하는 부분들이 있습니다. 모든 사례를 전부 다룰 수 없기에 정적 웹 호스팅 기능을 포함한 다음 네 가지 사례를 살펴보고 S3 사용 목적별로 챙겨야 하는 보안 설정은 무엇이 있는지 정리하고자 합니다.

  • 0x02-1 정적 웹 호스팅 버킷
  • 0x02-2 정적 리소스 파일 서빙용 버킷
  • 0x02-3 원격 파일 저장용 버킷
  • 0x02-4 민감한 정보 저장용 버킷

0x02-1 정적 웹 호스팅 버킷

staticbucket
[S3 버킷 생성 후 속성 > 정적 웹 사이트 호스팅 기능]

위 그림처럼 S3의 정적 웹 호스팅 기능을 활성화하면 S3 버킷의 웹 사이트 엔드포인트 주소를 할당받게 됩니다. 이후 버킷의 모든 퍼블릭 액세스 차단을 비활성화한 다음 버킷 정책에서 모든 접속을 허용 할 수 있도록 아래 정책을 설정하면 웹 호스팅처럼 이용할 수 있습니다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::{BucketName}/*"
        }
    ]
}

[any access 버킷 정책 예시]

하지만 이렇게 S3 버킷만으로 정적 웹을 호스팅으로 서비스하기에는 아래와 같이 몇 가지 문제가 있습니다.

  • A. https가 아닌 http 통신을 해야 한다는 점
  • B. 버킷이 퍼블릭 공개라는 점
  • C. AWS S3의 엔드포인트 주소를 그대로 사용해야 한다는 점

정확히는 위 잠재적 위험성을 제거할 목적으로 추가 설정이 필요하다고 이해하면 좋습니다. 그럼 위 문제들을 해결하기 위해 어떤 설정 들을 하여야 하는지 알아보겠습니다.

0x02-1-1 Cloudfront 연결하기

정적 웹 호스팅을 구성하는 데 있어 A, B, C 문제들을 해결하기 위해 S3 버킷에 연결할 AWS Cloudfront 서비스가 필요합니다. Cloudfront는 AWS Global Edge Server를 통해 CDN(Content Delivery Network) 역할을 해주는 AWS 서비스입니다.

cloudfront_deploy_set
[Cloudfront 배포하기]

위 그림처럼 정적 웹 호스팅으로 만든 S3 버킷(test-ljh.beta.baemin.com)과 Cloudfront를 연결 후 OAI 설정으로 연결한 Cloudfront를 통해서만 접근할 수 있도록 하겠습니다.

OAI
[OAI 개념도]

이 OAI 설정은 S3를 퍼블릭으로 공개하지 않고도 Cloudfront를 통해서 S3에 퍼블릭하게 접근할 수 있음과 동시에 Cloudfront를 우회하여 S3에 직접 액세스할 수 없음(중요)을 의미합니다. 특별한 사유가 없다면 Cloudfront를 배포함과 동시에 OAI가 버킷 정책에 업데이트되도록 선택합니다.

redirect
[Redirect HTTP to HTTPS]

기본 캐시 동작에서 Redirect HTTP to HTTPS를 설정해줌으로써 http://를 이용하여 접근하는 사용자도 https://로 redirect 될 수 있도록 해줍니다. Cloudfront 배포과정에서 설정할 부분은 전부 완료되었으니 이대로 진행 완료하면 됩니다.

OAIRule_in_bucket
[test-ljh.baemin.com 버킷의 OAI 정책]

업데이트된 S3 버킷의 버킷 정책을 확인해 보면 배포한 Cloudfront의 OAI 설정값이 업데이트된 것을 확인할 수 있습니다.


[OAI 설정 후 S3 앤드포인트 주소 접근 테스트 결과 – 403 Forbidden]

테스트 결과 Cloudfront 배포와 함께 S3 버킷 정책에 OAI 설정 시 S3의 엔드포인트 주소로는 접근할 수 없는 것을 확인할 수 있습니다.

0x02-1-2 Route53으로 도메인 연결하기

route53
[Route53 도메인 연결]

Cloudfront의 엔드포인트 주소는 가독성 등의 문제로 서비스에 사용하기 적절하지 않습니다. 도메인을 연결하기 위해 Route53에서 test-ljh.beta.baemin.com in CNAME 레코드를 지정하였습니다.

Cloudfront SSL
[Cloudfront SSL 인증서 설정]

Route53 설정 이후 위 그림처럼 Cloudfront에서도 CNAME 설정과 ACM(AWS Certificate Manager)을 이용하여 발급한 beta.baemin.com의 SSL 인증서를 적용합니다.

Cloudfront를 S3에 연결함과 동시에 여러 설정으로 위에서 제시한 세 가지 문제(A, B, C)를 해결했습니다.

  • A. https가 아닌 http 통신을 해야 한다는 점
    — a. Cloudfront에 ACM(인증서)을 적용하여 해결
  • B. 버킷이 퍼블릭 공개라는 점
    — b. Cloudfront를 S3에 연결하여 S3는 Cloudfront 뒤에 숨기고 Cloudfront를 통해서만 접근이 가능하도록 설정하여 해결
  • C. AWS S3의 엔드포인트 주소를 그대로 이용해야 한다는 점
    — c. Cloudfront의 엔드포인트 주소를 Route53과 연결하여 적절한 서비스 도메인 주소 값을 할당하여 해결

0x02-1-3 AWS WAF 사용하기 – (선택사항)

정적 웹 호스팅 서비스가 특정 대상에게만 제공할 서비스라면 IP 제어가 필요합니다. 물론, S3 버킷 정책에서도 직접 IP 제어가 가능합니다.

{
  "Id": "SourceIPAccess",
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "SourceIPAccess",
      "Action": "s3:*",
      "Effect": "Deny",
      "Resource": [
        "arn:aws:s3:::BUCKET-Name",
        "arn:aws:s3:::BUCKET-Name/*"
      ],
      "Condition": {
        "NotIpAddress": {
          "aws:SourceIp": [
            "123.123.123.123/32"
          ]
        }
      },
      "Principal": "*"
    }
  ]
}

[버킷 정책 IP 제어 예시 – IP 123.123.123.123/32 외에는 버킷에 접근 불가]

IP 기반의 접근 통제를 S3 버킷 정책에서 관리하게 되면 S3 버킷 정책 수정 권한(PutBucketPolicy)이 있는 IAM User는 모두 버킷 정책을 수정할 수 있으므로 정책이 수정되는 것을 방지할 수 없게 됩니다. 그렇다고 PutBucketPolicy 권한을 IAM user에게서 제거하는 것은 S3 관련 업무 효율성을 떨어뜨릴 수 있습니다. PutBucketPolicy Action을 감시하기 위해 Cloudwatch 혹은 Cloudtrail 로그를 이용하여 모니터링할 수는 있으나 PutBucketPolicy 이벤트가 발생할 때마다 변경된 버킷 정책을 보안 담당자가 직접 확인하고 대응해야 하여 비효율적입니다.

제안하는 방법은 AWS WAF서비스를 이용하여 IP 기반의 접근 통제 정책을 관리하는 것입니다. 일반적으로 AWS WAF는 보안(Security Role)팀에서 관리하므로 임의대로 변경되는 것을 모니터링하기 위한 리소스를 아낄 수 있고 일관된 허용 리스트(allow list) IP 관리가 가능합니다. 그리고 허용 리스트 IP는 등록한 후에는 변경되는 일이 많지 않으므로 보안팀에서 관리한다고 하더라도 크게 비효율적이지도 않습니다.
AWS WAF는 Cloudfront에 연결할 수 있습니다. 앞서 살펴본 0x02-1-1 Cloudfront 연결하기 장에서 Cloudfront를 버킷에 연결해두었으니 해당 Cloudfront에 WAF를 연결하고 IP Set을 만들어 특정 IP에서만 접속할 수 있도록 제어하도록 하겠습니다.

waf-ipset
[IP Set 룰 만들기]

먼저, 허용할 IP를 정한 뒤 IP Set(Woowa-UD-Rule-Allow-OFFICE-IP)으로 규칙을 생성해줍니다. AWS 콘솔에서 AWS WAF > IP Set > Create IP set 에서 생성 가능합니다. 이때 Region은 Global (Cloudfront)을 선택하여 생성해 주어야 합니다.

{
  "Name": "Only-Access-Office",
  "Priority": 0,
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "Only-Access-Office"
  },
  "Statement": {
    "NotStatement": {
      "Statement": {
        "IPSetReferenceStatement": {
          "ARN": "arn:aws:wafv2:us-east-1:888888888888:global/ipset/Woowa-UD-Rule-Allow-OFFICE-IP/88daaddb-8888-8888-8888-888888888888"
        }
      }
    }
  }
}

[AWS WAF 특정 IP 허용 룰 예시 – 123.123.123.123/32 IP외 에는 AWS WAF를 통과할 수 없음]

생성 완료 후 AWS WAF > Web ACLs > Create web ACL 에서 S3와 연결된 Cloudfront를 선택하고 Rule builder에서 규칙을 적용하여 룰을 만들 수 있습니다. 위 예시처럼 룰(Json)을 만들어 Woowa-UD-Rule-Allow-OFFICE-IP 규칙에 속한 IP(123.123.123.123/32)가 아니면 모든 트래픽이 Block 되도록 설정할 수 있습니다.

s3_web_arch
[안전한 S3 정적 웹 호스팅을 위한 보안 구성도]

이번 장에서 제안하는 보안 설정을 모두 적용한 S3 정적 웹 호스팅 구성도는 위 그림과 같습니다.

0x02-2 정적 리소스 파일 서빙용 버킷

이번 사례는 S3 버킷을 정적 리소스 데이터인 css, js, jpg 등을 서빙하기 위한 웹 리소스 저장소로 사용하는 경우입니다. 이 경우에는 S3 버킷 자체를 정적 웹 사이트로 만드는 것이 아니라 서빙이 필요한 정적 파일을 S3 버킷에 담고 버킷을 퍼블릭 공개 설정하여 GetObject를 any access로 허용하고 사용하여 운영하는 경우입니다.

dynamic_and_static
[동적 서비스 리소스와 정적 리소스 도메인 분리 구성]

간략하게 정적 리소스 파일 서빙용 버킷 구성 사례를 그림으로 표현하였습니다. 이 구성은 MSA 아키텍처(MSA=MicroService Architecture) 방식을 지향하는 구성이라고 볼 수 있습니다. 예를 들어 test-ljh.beta.baemin.com(동적 서비스 리소스)와 test-ljh-cdn.beta.baemin.com(정적 리소스)을 구분하여 동적 서비스 리소스의 서버와 정적 리소스를 서빙하는 대상을 구분하여 사용자가 바라보는 서비스 리소스 간의 의존성 감소, EC2 리소스 낭비 방지, 불필요한 로그 수집 방지, 비용 감소 등 여러 이점을 얻기 위한 구성입니다. 물론, 도메인을 분리하지 않고 test-ljh.beta.baemin.com/cdn/* 은 정적 리소스 S3를 바라보게 하는 방식으로 동적 리소스 대상 서버(EC2)와 정적 리소스 대상 버킷을 Cloudfront 단에서 구분할 수도 있습니다.

WAF, Shield 서비스까지 고려한다면
하지만 Cloudfront에 AWS WAF를 연결하거나 Cloudfront에 Shield Advanced(DDoS)를 적용할 경우까지 고려한다면 정적 리소스를 요청하는 건도 WAF, Shield Advanced 사용 비용에 추가될 수 있습니다. 따라서 정적 리소스를 서빙하는 S3는 WAF(웹 해킹), Shield Advanced(DDoS) 보호 대상이 아니므로 Cloudfront 단에서 트래픽을 분리하는 것이 아니라 도메인으로 분리하여 실제 보호해야 할 서버와 정적 리소스로 흘러가는 트래픽을 분리하는 것이 여러모로 유리하다고 생각합니다.

이 사례(0x02-2 정적 리소스 파일 서빙용 버킷)에서 발생하는 보안 문제는 0x02-1 정적 웹 호스팅 버킷 사례에서 다룬 문제들과 크게 다르지 않습니다.

  • A. https가 아닌 http 통신을 해야 한다는 점
  • B. 버킷이 퍼블릭 공개라는 점
  • C. AWS S3의 엔드포인트 주소를 그대로 사용해야 한다는 점

그러므로 0x02-1 정적 웹 호스팅 버킷 사례에서 보안 문제를 해결하기 위해 조치한 것처럼 0x02-1-1 Cloudfront 연결하기, 0x02-1-2 Route53으로 도메인 연결하기 작업을 참고하여 https 사용, SSL인증서 적용, 서비스 도메인 연결, OAI 설정을 진행하면 됩니다.

0x02-3 원격 파일 저장용 버킷

원격지에서 S3로 파일을 업로드하거나 공유 목적으로 읽기를 허용해야 하는 경우입니다. 이 경우에 허용 IP를 제어하는 것이 가장 좋지만, 간혹 불가능한 상황도 있습니다. 좀 더 이해하기 위해서 다음 예시를 준비하였습니다.

Case. 우리는 고객 PC에 설치된 프로그램을 통해 발생하는 로그 파일을 put 하고 있어요.

위 Case 예시는 버킷에 접근하는 대상을 특정할 수 없어 불특정 다수에게 열려야 하는 상황입니다. 만약, 대상을 지정할 수 있었다면 0x02-1-3 AWS WAF 사용하기 – (선택사항) 작업을 참고하여 IP로 대상을 제어하는 것이 모범사례입니다.
하지만 이 경우에는 불특정 다수에게 열려야 하므로 위 방법처럼 IP 제어가 불가능하고 퍼블릭한 상황에서 버킷명을 알고 있다면 누구나 S3에 접근할 수 있는 위험을 갖게 됩니다. 이때 완벽한 보안 방법은 아니지만, 차선책으로 사전에 약속된 웹 헤더 문자열 값을 검사하도록 하여 그 헤더에 문자열이 없는 경우는 비정상 접근으로 판단하고 차단하는 방법을 사용할 수 있습니다.

{
    "Version": "2012-10-17",
    "Id": "Policy1574678695789",
    "Statement": [
        {
            "Sid": "Stmt1574678689238",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::BucketName/*",
            "Condition": {
                "StringLike": {
                    "aws:Referer": "https://CheckValue"
                }
            }
        }
    ]
}

[버킷 정책 Referer 제어 예시 – Referer : https://CheckValue 값을 포함하지 않으면 접근 불가]

위 예시처럼 버킷 정책에서 Condition값을 이용하여 지정한 웹 헤더(Referer)의 문자열 검사를 할 수 있습니다. 하지만 0x02-1-3 AWS WAF 사용하기 – (선택사항) 장에서 언급한 ‘버킷에서 제어 정책을 설정하는 것보다 WAF에서 설정하는 게 좋은 이유’와 같이 문자열 검사 및 차단하는 역할은 AWS WAF에서 진행하는 것이 좋습니다.

이번 예시도 AWS WAF를 사용하기 위해 Cloudfront를 이용합니다. 따라서 0x02-1 정적 웹 호스팅 버킷, 0x02-2 정적 리소스 파일 서빙용 버킷 예시에서 Cloudfront를 적용했던 것과 같이 0x02-1-1 Cloudfront 연결하기 작업을 참고하여 Cloudfront 연결을 선행합니다.

{
  "Name": "woowa-ud-rule-referer-check",
  "Priority": 0,
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "woowa-ud-rule-referer-check"
  },
  "Statement": {
    "NotStatement": {
      "Statement": {
        "ByteMatchStatement": {
          "FieldToMatch": {
            "SingleHeader": {
              "Name": "check"
            }
          },
          "PositionalConstraint": "CONTAINS",
          "SearchString": "woowa-check",
          "TextTransformations": [
            {
              "Type": "NONE",
              "Priority": 0
            }
          ]
        }
      }
    }
  }
}

[AWS WAF 헤더 검사 룰 예시 – check : woowa-check 값을 포함하지 않으면 AWS WAF를 통과할 수 없음]

S3와 연결된 Cloudfront에 AWS WAF를 적용하여 문자열 검사 룰을 만들어 적용합니다. AWS 콘솔 AWS WAF > Web ACLs > Rule > Rule builder 에서 위 예시를 참고하여 웹 헤더의 문자열 검사를 수행하는 룰을 생성하여 WAF에 적용합니다.


[WAF를 이용한 특정 헤더의 문자열 검사 구성도]

정상 사용자(Normal user, 고객)의 PC에서는 구현된 대로 PC 프로그램에서 원격 저장소(S3)에 파일을 put할 때 약속된 문자열을 추가한 http 패킷을 이용해 업로드하도록 패치/배포합니다. (위 그림에서 Check : woowa-check)
웹 패킷으로 전송되는 파일을 S3에 저장하기 위해서는 Cloudfront를 반드시 거치도록 OAI 설정이 되어 있다면 모든 인입 트래픽은 Cloudfront에 연결된 AWS WAF의 검사를 받게 됩니다. 이때 AWS WAF에서 사전 협의된 문자열이 추가된 패킷인지 확인합니다. 즉, check 헤더에 woowa-check 문자열이 없다면 WAF에 의해 Block이 됩니다.

WAF 헤더 검사 내용 정리
공격자(Bad User)가 버킷 이름을 알고 있다고 하여도, Cloudfront와 S3는 OAI(Origin Access Identity)설정이 되어 있어 버킷으로 바로 접근하지 못합니다.
S3는 Cloudfront와 연결함과 동시에 퍼블릭 비공개 설정(Private)으로 전환합니다.
공격자가 Cloudfront에 할당된 도메인 주소(그림에서 test-ljh.beta.baemin.com)를 알고 있다 하여도 특정 웹 헤더 검사를 하는 것은 확인하기 어렵습니다.*

따라서, 버킷에 연결된 도메인 주소와 검사하고 있는 임의의 웹 헤더, 웹 헤더 값을 모두 알고 있어야 우회가 가능합니다. 따라서, 버킷에 연결된 도메인 주소와 검사하고 있는 임의의 웹 헤더, 웹 헤더 값을 모두 알고 있어야 우회할 수 있습니다.

다만, 이 방법이 완벽한 방법은 아니라고 표현한 이유는 공격자 입장에서 PC 프로그램을 리버스 엔지니어링 혹은 동적 분석(프록시)을 통해 분석할 역량이 된다면 웹 헤더에 특정 헤더를 추가하여 원격 파일 저장소와 통신하는 것을 아는 것이 가능하기 때문입니다. 하지만 보안 관점에서 이 방법을 적용한 보안 수준은 단순히 버킷 명만 알고 있는 상황에서 공격자가 PUT, GET 메소드 등으로 접근되도록 구성하는 것과 하늘과 땅 차이라고 생각합니다.

0x02-4 민감한 정보 저장용 버킷

이 사례에서 민감한 정보는 기업 입장에서 보안사고 등에 의해 유출되면 위험이 커지는 정보나 소홀히 관리되지 않아야 하는 중요한 정보를 의미합니다. 예를 들면 고객 개인정보가 포함된 정보인 경우입니다. 물론, 정보보안 최고 책임자 등 판단에 따라서 민감한 정보의 범위는 개인정보 외에도 다양한 정보가 될 수도 있습니다.
이번 민감한 정보 저장용 버킷 사례에서 다뤄볼 예시는 AWS 멀티 계정(Multi Account)환경에서 서비스를 운영하는 상황을 가정하고 다루었습니다.


[여러 계정에 방치된 민감 정보 버킷]

위 그림과 같이 서비스 운영 ‘계정 1’, ‘계정 2’에서 서비스에 필요한 S3 버킷들이 만들어진 상태라고 가정해 보겠습니다. 이 상황에서 버킷 운영 및 관리에서 다음과 같은 문제가 생길 수 있습니다.

  1. 운영 버킷 중에서는 보안사고에 민감하지 않은 파일들이 저장된 버킷들도 있겠지만 일부는 민감한 정보를 담고 있거나 민감 정보와 비 민감 정보를 함께 담은 버킷도 존재할 것입니다. 이 경우에는 하나의 버킷에 하위 경로(PATH)를 구분하여 사용하는데, 장기적으로 관리의 부재에 의해 방치되거나 구성상 취약하게 설정되어 위험이 높아질 수 있습니다.
  2. 버킷에 태그(Tag) 정보가 누락되었을 수 있습니다. 물론, 기업 내 태그 정책이 강제화되어있다면 문제없겠지만 버킷 관리만을 위한 태그 정책 기준이 뚜렷하지 않다면 추후 식별이 어려워지거나 관리가 어려울 수 있습니다.
  3. 버킷의 태그 기준 정보가 확실하다면 버킷의 이름은 크게 문제 되지 않지만 그렇지 않은 경우에는 버킷의 이름에 일관성이 없어 추후 식별이 어렵거나 관리에 어려움이 생길 수 있습니다.
  4. 버킷의 문서화 혹은 태그가 제대로 되어 있지 않은 상태에서 버킷 안에 민감 정보가 저장되어 있는지 아닌지 버킷 관련 담당자가 아니라면 알기 어렵습니다. 담당자가 퇴사하거나 조직 이동이 발생하면 추적 관리가 어려워질 수 있습니다.
  5. 서비스 계정에는 다양한 직무(IAM Role)의 IAM user가 접근할 수 있습니다. 이때 내부 사용자의 실수 등에 의해 민감 버킷 내 정보가 삭제되거나 탈취될 수 있습니다.
  6. 많은 IAM 권한을 가진 EC2(Server) 혹은 Accesskey 등이 해킹되었을 때 같은 계정 내에 있는 버킷들이 탈취될 수 있습니다. 이때 민감한 버킷들에 담긴 정보가 공격자에 의해 유출될 수 있습니다.

0x03-1 민감 버킷 분리 보관


[제안하는 민감 버킷 관리 구성]

위 문제를 해결하기 위해 제안하는 방법은 제한된 계정(보안 계정)으로 민감 정보를 담는 버킷(이하, 민감 버킷)을 분리하여 관리하는 방법입니다. 이때 제한된 계정은 IAM User 중 Security Role이 할당된 사용자만 접근이 가능한 계정이어야 하며 대외 서비스 목적으로 운영되는 리소스는 없어야 합니다. 제안하는 방법대로 분리할 때 다음과 같은 이점이 있습니다.

  1. 서비스 팀에서 민감한 정보를 다룰 때 보안팀 담당자가 직접 보안 계정에 버킷을 생성합니다. 민감 버킷 담당자가 직접 이름 규칙, 태그 규칙, 버킷 정보 문서를 관리하게 되므로 일관성 있게 버킷을 관리할 수 있습니다.
  2. 컴플라이언스 이슈로 인해 어떤 정보들은 2년 이상 보관을 해야 하거나 짧게 보관하고 파기해야 하는 경우가 있을 수 있습니다. 이러한 경우 보안팀 주관으로 분리된 계정에서 관리하고 있으므로 일괄적인 관리에 용이합니다.
  3. 서비스 계정 콘솔로 접근할 수 있는 IAM User가 제한된 보안 계정에 접근할 수 없으므로 버킷이 내부 사용자의 실수 등에 의해 삭제되거나 탈취되는 경우를 예방할 수 있습니다.
  4. 서비스 계정의 서버나 Accesskey가 해킹된 상황이어도 분리된 계정에 접근할 권한이 없는 경우 민감한 정보를 담고 있는 버킷까지는 접근이 불가합니다.

여기서 한 가지 고민해야 할 부분이 있습니다. 민감 버킷을 보안 계정에 분리하더라도 서비스 계정에 있는 서버에서 민감 버킷에 접근해야 하는 경우입니다. 제한된 계정인 만큼 다른 계정과의 연결은 지양하는 게 좋으나 서비스 목적으로 연결이 불가피하다면 민감 버킷과 각 서비스 계정의 서버 간의 교차 계정(cross account/multi account) 접근 시 최소 권한을 제공하여 필요한 서버에서만 제한적으로 접근이 되도록 할 수 있습니다.

계정 1의 ‘ec2-baemin’ 서버가 보안 계정의 ‘best-ljh-test-bucket’ 버킷에 접근하는 상황을 가정해 보겠습니다. 교차 계정 환경에서 타 계정에 있는 리소스가 접근할 수 있도록 하는 방법은 여러 가지가 있지만, 그중 IAM Role 기반의 버킷 허용 정책을 이용하여 작업을 진행하도록 하겠습니다.

IAM Role 이름 : woowa-iam-ec2-baemin-role
inline policy 이름 : iam-01-to-security-s3-policy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": [
        "s3:Put*",
        "s3:List*",
        "s3:Get*"
      ],
      "Resource": [
        "arn:aws:s3:::{{보안 계정 민감 대상 버킷 이름=best-ljh-test-bucket}}/*",
        "arn:aws:s3:::{{보안 계정 민감 대상 버킷 이름=best-ljh-test-bucket}}"
      ]
    }
  ]
}

[iam-01-to-security-s3-policy의 보안 계정 s3접근을 위한 정책]

먼저, 계정 1의 ‘ec2-baemin’ 서버가 사용할 IAM Role(woowa-iam-ec2-baemin-role)과 Inline Policy(iam-01-to-security-s3-policy)를 위와 같이 만듭니다. 이제 ‘ec2-baemin’이 사용할 IAM Role을 지정해 주기 위해 ec2 콘솔로 이동하여 ‘ec2-baemin’에서 ec2 > 보안 > IAM 역할 수정 에서 위 IAM Role(woowa-iam-ec2-baemin-role)을 지정합니다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowRole",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::{{서비스계정 번호}}:role/{{서비스계정 ec2 전용 IAM Role = woowa-iam-ec2-baemin-role  }}"
            },
            "Action": [
                "s3:Put*",
                "s3:List*",
                "s3:Get*"
            ],
            "Resource": [
                "arn:aws:s3:::{{해당 민감 버킷 이름 = best-ljh-test-bucket}}",
                "arn:aws:s3:::{{해당 민감 버킷 이름 = best-ljh-test-bucket}}/*"
            ]
        }
    ]
}

[보안 계정의 대상 버킷에서 ec2-baemin이 접근할 수 있는 허용 정책]

보안 계정에서 버킷(priavte-baemin)을 생성하고 버킷 정책에서 ‘ec2-baemin’서버가 사용하는 IAM Role(woowa-iam-ec2-baemin-role)이 접근할 수 있도록 버킷 정책을 수정해 줍니다.
테스트를 위해 보안 계정 버킷 best-ljh-test-bucket에 파일 ljh0209.html을 올려두었습니다.


[best-ljh-test-bucket 버킷에 업로드된 ljh0209.html]

계정 1에 있는 ‘ec2-baemin’ 서버에 CLI로 접속하여 IAM Role (woowa-iam-ec2-baemin-role)을 이용하여 보안 계정에 있는 best-ljh-test-bucket의 파일을 조회하였습니다.

> $ aws s3api list-objects –bucket best-ljh0209-test-bucket


[ 계정 1의 서버에서 보안 계정 버킷(best-ljh-test-bucket) 조회 결과]

테스트 결과 보안 계정 best-ljh-test-bucket 버킷에 업로드되어 있는 ljh0209.html 파일의 정보가 반환되는 것을 확인할 수 있습니다.


[IAM Role 기반 교차 계정 버킷 접근 구성]

IAM Role 기반 교차 계정에서의 접근 구성을 그림으로 표현하면 위와 같습니다. 이 구성은 반드시 서비스상 분리된 계정에서 보안 계정의 버킷(best-ljh0209-test-bucket)에 접근할 수 있는 IAM Role(woowa-iam-ec2-baemin-role)을 가진 리소스(ec2-baemin)만 보안 계정의 버킷 정책에 의한 허용된 권한에 의해서 접근할 수 있습니다.

0x03-2 침해사고대응 관점에서 바라본 민감 정보 버킷 분리 보관의 이점

침해사고대응 관점에서 민감 정보가 담긴 버킷을 별도 계정에 분리 보관하면 여러 보안사고 상황을 대비할 수 있습니다.


[A 서버 침해사고 시나리오 사례]

위 그림처럼 EC2(Server)에 적용된 IAM Role에 최소한의 권한보다 많은 권한이 부여된 경우를 가정해 보겠습니다. A 서버와 B 서버에 할당된 IAM Role (S3:*)은 A 서버가 해킹당한 상황에서 의도하지 않게 민감한 정보를 담은 버킷(Sensitive info bucket)까지 공격자(Hacker)가 도달할 수 있게 하는 도구가 될 수 있습니다. 공격자 입장에서 AWS 서버를 해킹할 경우 먼저 해당 계정에 있는 모든 버킷을 목록화한 다음 해당 버킷들의 정보들을 모두 탈취하는 방식으로 개인정보 유출 등을 수행하기 때문입니다.


[A 서버 침해사고 상황에서 보안 계정으로 전이 실패 사례]

민감 정보가 담긴 버킷을 별도 계정에 분리하였다면 이러한 침해사고 상황에서 민감한 정보가 버킷에 의해 유출되는 위험을 감소시킬 수 있습니다. 만약, 서비스 목적으로 계정 1의 A 서버와 보안 계정의 민감 버킷이 IAM Role 기반으로 교차계정 허용 연결이 되어 있더라도 보안 계정의 버킷 정보를 알 수 없다면 접근이 불가합니다.

A 서버에 해당 IAM Role의 Policy 정보를 확인(Describe)할 수 있는 권한이 부여되어 있다면 알 수 있겠지만 최소권한 부여 원칙을 지켰다면 서버가 사용할 IAM Role에 Describe IAM Policy이 포함된 IAM Role을 부여할 일은 없습니다.

정리하면 계정 1의 A 서버 해킹에 성공한 공격자(Hacker)는 보안 계정의 민감 정보 버킷에 접근하기 위해서는 민감 정보 버킷 정보를 미리 알고 있어야 하므로 단순히 s3 list-bucket으로 같은 계정에서 민감 정보 버킷이 노출되는 상황보다는 훨씬 안전한 상황이라고 할 수 있습니다.


[Accesskey 유출에 의한 버킷 침해사고 시나리오]

위 그림처럼 Accesskey에 의한 버킷 침해사고 시나리오로 예를 들어보겠습니다. 개발자 A 님이 소유한 계정 1의 ‘S3:*’ 권한이 부여된 IAM user의 Accesskey를 공개된 개인 블로그에 그대로 올리게 되어 키는 불특정 다수에게 유출된 상황입니다. 이 Accesskey를 획득한 공격자는 나쁜 마음을 먹고 돈이 될만한 정보를 찾거나 추가 해킹을 시도하기 위해 여러 명령어를 수행하게 됩니다. 그중에서도 가장 쉽고 효과가 좋은 Accesskey가 발급된 계정에서의 S3 버킷 정보들을 먼저 확인해 볼 것입니다.
하지만 민감 정보가 담긴 버킷(공격자 입장에서 돈이 될만한, 흥미로운)은 전부 별도 보안 계정에서 보관하고 있으므로 개발자 A님의 Accesskey로는 접근이 불가합니다. Accesskey가 접근할 수 있는 계정 1에서는 Garbage data, Test data뿐인 버킷밖에 없습니다. 기업 입장에서는 침해사고 상황이 발생하였지만 개인정보가 유출되었거나 기업의 이미지가 크게 실추되는 상황은 발생하지 않았습니다.

0x03 마무리

이번 글에서 S3를 사용하는 네 가지 사례(0x02-1 정적 웹 호스팅 버킷, 0x02-2 정적 리소스 파일 서빙용 버킷, 0x02-3 원격 파일 저장용 버킷, 0x02-4 민감한 정보 저장용 버킷) 에 대해서 안전하게 사용하도록 구성하는 방법을 알아보았습니다. 사례별 보안 가이드를 통해 이 글을 접하신 여러 회사에서 근무 중인 보안 엔지니어분들께서도 사내에서 운영하는 S3를 안전하게 운영할 수 있도록 가이드 하시길 바라는 마음이 있습니다. 물론, AWS에서 제공하는 여러 서비스를 통한 S3 구성이나 설정상의 자유도 때문에 이보다 더욱 다양한 침해 예상 시나리오와 사용 사례들이 있을 수 있습니다. 하지만 이번 글을 통해 다룬 사례들을 충분히 학습하였다면 다양한 사례에도 유연하게 대응 가능하실 것으로 생각됩니다.

이 글을 읽고 우아한형제들의 다양한 환경에서 보안 위협을 분석하고 예방 및 대응방안을 고민하는 [정보보호실] 인프라보안팀 CERT 직무 채용 에 관심이 생기셨다면 많은 지원을 부탁드립니다.

여기까지 긴 글 읽어주셔서 감사합니다.