Tech

숨고를 지키는 가디언, AWS WAF 도입기

숨고 서비스의 안정적인 운영을 위해서 WAF를 도입한 계기와 과정 그리고 효과를 기록합니다.

2022-10-13 Ted Jeong

안녕하세요. 숨고 Platform Chapter의 DevOps Engineer Ted 입니다.

숨고 서비스에서는 여러 신규 기능의 개발과 함께 이미 개발된 많은 서비스들에 대한 고도화가 함께 진행되고 있는데요. 그만큼 지켜야할 것들이 더욱 많아지며, 자연스레 서비스의 보안에 대한 중요성이 높아지고 있습니다. 이번 글에서는 불특정 다수에게 노출된 퍼블릭 엔드포인트의 보안을 더욱 강화하기 위해 최근 숨고 서비스에 WAF(Web Application Firewall)를 적용한 사례를 소개드리겠습니다.

WAF에 대해서

WAF는 Web Application Firewall(이하 WAF)의 약자로 직역하면 웹 응용 프로그램 방화벽입니다. 일반적인 방화벽(Firewall)과 달리 WAF는 HTTP(S) 등의 규격으로 숨고와 같은 웹 어플리케이션에게 전달되는 요청이나 외부의 공격 등에 대한 보안을 담당합니다.

주로 SQL Injection이나 교차 사이트 스크립팅(Cross-Site Scripting, XSS), 무차별 대입 로그인, 세션 하이재킹 등의 다양한 웹 공격을 탐지하고 차단하는 역할을 합니다. WAF는 워낙 광범위한 개념인 만큼 이 글에서는 WAF에 대한 자세한 내용을 다루지는 않겠습니다. 더 자세한 내용이 궁금하신 분들은 글 맨 아래의 참고자료를 확인해주세요.

도입 계기

AWS WAF를 선택한 이유

WAF는 설치 방법에 따라 크게 하드웨어형, 소프트웨어형, 클라우드형으로 나눌 수 있습니다. 숨고는 모든 서비스가 물리 서버가 아닌 AWS 기반으로 운영되고 있는 만큼, 하드웨어형이나 소프트웨어형보다는 클라우드형 WAF가 적합하다고 판단했습니다. 클라우드형 WAF에도 다양한 회사의 솔루션이 있었는데, 논의 끝에 AWS의 WAF (이하 “AWS WAF")를 도입하는 것으로 결정했습니다. WAF의 간단한 특징과 함께 저희가 AWS의 WAF를 선택한 이유를 설명드리자면 다음과 같습니다.

AWS WAF 기능

  • 웹 트래픽 필터링 : IP 주소, HTTP 헤더 및 본문 또는 사용자 정의 URI와 같은 조건을 기준으로 웹 트래픽을 필터링
  • SQL Injection, XSS(교차 사이트 스크립팅) 등 여러 규칙을 적용할 수 있음
  • AWS WAF Bot Control : 스크래퍼, 스캐너, 크롤러를 차단하거나 비율을 제한할 수 있음
  • 실시간 가시성 : IP 주소, URI, HTTP 헤더 등의 지표를 실시간으로 조회 가능

AWS WAF 핵심 요소

  • 웹 액세스 제어 목록(Access Control List, ACL) : 보호 대상에 적용될 제어 목록으로 규칙을 추가하여 보호할 수 있는 주체
  • 규칙(Rule) : 검사 기준을 정의하고 기준에 따라서 수행할 작업을 포함
  • 규칙 그룹(Rule Group) : 개별적으로 규칙을 정의하여 그룹화하거나 AWS에서 제공하는 관리형 규칙 그룹(AWS Managed Rule)

AWS WAF 도입 시 장점

  • 별도 설치없이 몇 가지 설정으로 즉시 도입 가능

    AWS WAF 뿐만 아니라 일반적인 클라우드형 WAF이 가지고 있는 장점 중의 하나로서, 솔루션을 LAN이나 VM에 설치할 필요 없이 SaaS 형태로 제공받을 수 있습니다. 특히 숨고 서비스는 AWS 상에서 운영되고 있기 때문에 WAF가 적용되어야 하는 서비스들에 빠르게 도입할 수 있다는 것도 긍정적인 요소였습니다.

  • 비용 효율적

    많은 하드웨어, 소프트웨어 WAF의 경우 설치 비용 및 솔루션 라이센스 구매 비용 등의 초기 투자 비용이 비교적 큰 편입니다. 또한 서비스 규모가 커지게 되면 경우에 따라 초기 설치에 준하는 큰 금액을 지불해야될 수도 있습니다. 하지만 AWS WAF를 사용하게 되면 사용량에 따른 금액만 과금하면 되기 때문에 초기 도입 시 비용 측면에서 유리하다고 생각했습니다.

  • 손쉬운 모니터링 및 알람 발생

    WAF가 잘 동작하고 있는지, 서비스에 침입이나 공격이 들어왔는지에 대한 기본적인 모니터링 외에도 사용량에 따른 과금량도 AWS 콘솔 내에서 빠르게 확인할 수 있다는 점이 긍정적이었습니다.

  • AWS 보안 솔루션들과 조합 가능

    AWS에는 WAF 외에도 다양한 보안 솔루션이 있는데, 추후 숨고 서비스의 안정성을 위해 다양한 보안 솔루션을 도입함에 있어서 AWS의 WAF가 더 많은 확장성을 가질 것으로 판단했습니다.

이에 따라 본격적으로 숨고 서비스에 AWS WAF를 적용하는 작업을 시작했습니다.

서비스 적용 과정

WAF의 적용 과정을 소개하기에 앞서 읽는 분들의 원활한 이해를 돕기 위해 저희가 AWS WAF를 통해 만들고자 했던 구조를 먼저 설명드리겠습니다.

  • 우선 일반 사용자들의 정상적인 요청은 WAF 규칙 검사를 통해 통과 여부를 결정합니다.
  • 이때, 공격자로 추정되는 비정상적인 요청은 WAF 규칙 검사에서 차단합니다.
  • AWS WAF에서 발생된 메트릭 및 로그를 기반으로 Slack에 알람을 보냅니다.
  • 비용 최적화 및 WAF 규칙 고도화를 위해 주기적으로 로그를 AWS S3에 백업합니다.

위의 구조에 따라 우선 AWS WAF를 적용해야하는 서비스를 선정하고, 해당 서비스들에 맞게 AWS WAF가 적용될 수 있도록 사전 준비 작업을 진행했습니다. 이후 스테이징 서버에 우선 적용해서 동작을 확인한 뒤 운영 서버에도 적용하는 것으로 최종 배포 계획을 세웠습니다.

대상 선정

AWS WAF를 도입하기에 앞서 숨고 서비스 내에서 AWS WAF가 적용이 필요한 대상을 선정해야 했는데요. 이를 위해 AWS WAF가 다른 AWS의 서비스 중 어떤 것들과 조합이 가능한지를 체크해보았습니다. 자칫 숨고가 사용하는 서비스와의 호환성이 낮거나 조합이 되지 않을 수도 있기 때문에 공식 문서를 참고한 결과 아래의 다섯 가지 서비스들과 AWS WAF가 조합이 가능한 것을 확인할 수 있었습니다.

  • CloudFront
  • API Gateway (REST API)
  • Application Load Balancer
  • AWS AppSync GraphQL API
  • Amazon Cognito user pool

다행히 숨고는 AWS EKS 기반의 Kubernetes를 통해 서비스하고 있고, Ingress 설정과 AWS Application Load Balancer(이하 ALB) Ingress Controller를 통해서 ALB를 생성하여 사용하고 있으므로 AWS WAF를 무리없이 사용할 수 있었습니다.

이후 AWS WAF 사용에 따른 비용 산정을 위해서 적용 대상이 되는 Public ALB 목록을 리스트업을 하였고, 숨고에서 모니터링 도구로 사용하고 있는 NewRelic을 사용해 약 1년 간의 트래픽을 추출하고, 이를 바탕으로 월 별 평균값 트래픽을 계산하여 예상 비용을 산정했습니다.

적용 준비

이전 숨고 서비스 컨테이너화 통합 테스트를 다룬 포스팅에서 언급한 것처럼 인프라의 변경사항이 있는 경우에도 서비스 변경과 동일하게 통합 테스트를 포함한 테스트 과정을 거친 후 배포하는 것이 필수입니다.

AWS WAF 적용도 동일하게 통합 테스트 환경인 스테이징 환경에서 적용 후 충분한 기간을 두고 운영 환경에 적용하는 것으로 계획하였습니다. 다만 아무리 스테이징 환경이라 할지라도 WAF 외에도 여러 변경사항에 대한 테스트가 이루어지고 있으므로 서비스의 안정성도 고려를 해야했습니다. 때문에 AWS WAF에서 제공하는 기본적이고 핵심적인 요소들을 활용하여 적용을 진행하였습니다.

가장 먼저 웹 ACL을 생성한 후 규칙 검사에서 필터링 되지 않는 경우에 대해 디폴트로 요청되는 값을 설정하였습니다. 저희는 모든 유저에게 원활한 서비스 제공을 위해서 숨고 서비스에서는 이 디폴트 처리를 허용으로 설정하였지만, 상황에 따라 차단을 기본값으로 사용하는 것도 가능합니다.

다음으로 규칙을 추가하는 작업을 진행하였습니다. AWS WAF에서는 규칙 검사에서 필터링 되어 탐지되는 트래픽에 대해 다음 네 가지 작업 중 하나로 선택하여 정의할 수 있습니다. 특히 규칙 검사의 필터링 결과에 대해 Count 작업을 적절히 활용한다면 공격 요청이 아닌 정상적인 요청에 대해서 보다 안정적으로 규칙을 적용할 수 있습니다.

  • Count : 다음 규칙으로 평가를 위해서 통과
  • Allow : 요청을 특정 리소스로 전달
  • Block : 요청 차단
  • CAPTCHA : 요청에 대해 CAPTCHA 검사를 실행

적용

배포에 앞서 기본적인 준비를 마친 후, 배포 계획을 수립했습니다. 큰 틀에서의 배포 계획은 다음과 같습니다.

  • 스테이징 환경 적용
  • 운영 환경 적용
  • 배포 이후 추가 작업

각 환경에 대한 적용은 위에서 설명한 내용을 실제로 실행하는 것이고, 배포 이후 추가 작업은 실제 서비스에서 AWS WAF가 동작하는 과정에서의 간단한 개선 사항을 포함했습니다.

스테이징 환경 적용

첫 1주일은 스테이징 환경에 AWS WAF를 적용해보는 것을 계획하였습니다.

  1. 스테이징 환경용 웹 ACL을 생성합니다.
  2. 웹 ACL 에서 규칙을 정의, 적용하고 이에 따라 적절하게 Count 작업을 적용합니다.
  3. 이때, AWS에서 제공하는 Managed Rule을 사용합니다.
  4. Request Rate Limit에 대해서도 별도로 규칙을 생성해 적용합니다. 이때, 임계치는 최근 1개월 동안의 정상적인 사용자의 최대 요청 수를 기준으로 두고 설정합니다.
  5. AWS Load Balancer Controller 문서를 참고해 어노테이션을 통해서 웹 ACL 적용을 ALB에 설정합니다.
  6. Count 작업에 필터링되는 트래픽을 모니터링합니다. 이때, 정상적인 요청이라고 판단되는 경우에는 Count 작업을 유지하고, 비정상적인 요청에 대해서만 Block 작업으로 전환합니다.
  7. Block 작업을 통해 비정상적인 요청으로 분류된 트래픽을 모니터링하고, 전체 규칙이 잘 적용되는지, 오탐이 많지는 않은지를 모니터링합니다.
  8. 마지막으로 AWS WAF와 AWS CloudWatch(이하 CW)를 연동해 각종 경보를 설정하고 알람이 정상적으로 수신되는지를 체크합니다.

운영 환경 적용

스테이징 환경에서의 WAF 적용에 큰 문제가 없다고 판단하여, 스테이징 환경 적용 이후 1주일 동안 운영 환경에 WAF 적용해보는 것을 계획하였습니다.

Count 작업과 Block 작업을 1주 동안 함께 진행했던 스테이징 환경 적용때와 달리, 더 면밀한 검토를 위해 운영 환경에서는 Count 작업 및 모니터링과 Block 작업 전환 및 모니터링을 각각 1주씩, 총 2주에 걸쳐 진행했습니다.

  1. 스테이징 환경과는 다른 별도의 운영 환경용 웹 ACL을 생성합니다.
  2. 스테이징 에서의 2. 와 3. 항목을 동일하게 수행합니다.
  3. Count 작업에 필터링되는 트래픽을 모니터링합니다.
  4. AWS CW를 사용해 Count 작업에 대해서 경보와 알람을 설정합니다
  5. 1주일 후, Block 작업을 통해 비정상적인 요청으로 분류된 트래픽을 모니터링하면서 전체 규칙이 잘 적용되는지, 혹시 오탐이 많지는 않은지를 확인합니다.
  6. 마지막으로, 5. 의 설정값에 더해 Block 작업에 대하여 마찬가지로 AWS CW를 사용해 각종 경보를 설정하고 알람이 정상적으로 수신되는지를 체크합니다.

추가 작업

계획에 따라 스테이징 환경과 운영 환경에 대해 적용을 끝마친 이후, 본격적인 AWS WAF 사용에 앞서 몇가지 추가 작업을 함께 진행하였습니다. 하지만 AWS WAF를 오랜 기간 사용한 후 고도화를 하는 것이 아니기 때문에, AWS WAF 적용과 함께 작업이 가능하면서도 작업 완료 시 효용성을 체감할 수 있는 것들을 추가 작업 대상으로 선정했습니다. 이에 따라 선정된 추가 작업 사항은 아래와 같습니다.

  • 사용자 지정 응답
  • 로그 백업 자동화

사용자 지정 응답

Managed Rule에 의해서 차단된 요청은 기본적으로 403 (Forbidden) 상태 코드로 응답합니다. 하지만 403 외에도 흔히 알려진 400, 404, 502 등의 HTTP 응답 코드들을 사용할 수 있고, 본문의 응답 형태도 JSON, HTML, Plain text 등 여러 포맷으로 정의할 수 있습니다.

하지만 일반적으로 차단된 요청에 따라 상태 코드와 본문을 확인하는 것은 대부분 악의를 가진 공격자일 것이기 때문에 굳이 친절하고 상세한 응답을 보내지 않아도 됩니다. 하지만 일반 사용자가 의도치 않게 요청한 응답이 차단되었을 때는 상세한 내용을 보내주는 것이 사용자 경험을 더 높일 수 있다고 판단했습니다.

이에 따라, AWS WAF의 Count 작업을 활용해 아래와 같은 과정으로 사용자 지정 응답을 간단하게 만들어보았습니다.

  1. Managed Rule에서 Count 작업을 하도록 설정하고 각각의 작업에 대해 우선 순위를 적절하게 설정하여 나열합니다.
  2. 사용자 지정 응답을 가지는 사용자 지정 규칙을 생성한 후 가장 낮은 우선 순위에 배치합니다.
  3. 사용자 지정 규칙보다 우선 순위가 높은 Managed Rule에서 검사 규칙과 일치하는 경우, 해당 규칙에 해당하는 LABEL이 전달되고 사용자 지정 규칙에서 차단할 때 활용하는 방법으로 동작합니다.
"Statement": {
    "OrStatement": {
      "Statements": [
        {
          "LabelMatchStatement": {
            "Scope": "LABEL",
            "Key": "awswaf:managed:aws:sql-database:SQLi_Body"
          }
        },
        {
          "LabelMatchStatement": {
            "Scope": "LABEL",
            "Key": "awswaf:managed:aws:sql-database:SQLi_URIPath"
          }
        },
    ...
    "Action": {
        "Block": {
          "CustomResponse": {
            "ResponseCode": 403,
            "CustomResponseBodyKey": "response-http-status-403"
          }
        }
    }
}

사용자 지정 규칙 예시

로그 백업 자동화

AWS WAF에서 발생하는 CloudWatch Logs가 누적되면 클라우드 사용에 따른 비용이 빠르게 늘어날 것으로 예상되었습니다. 이에 따라 누적된 Logs를 S3로 자동으로 백업하도록 했습니다. 이때, AWS CLI 를 이용하여 Bash Script 로 백업을 구현하였고 해당 백업을 Docker 이미지로 만들어 Kubernetes CronJob 으로 스케쥴하는 방법으로 구현하였습니다. 이 작업을 통해 기존 방법 대비 약 20% 이상의 비용을 절감할 수 있었습니다.

효과

AWS WAF가 도입되기 전까지는 외부의 DDOS 공격이나 과도한 크롤링으로 인해서 1주일에 한 번 꼴로 서비스의 가용성이 떨어지는 일이 발생하곤 했습니다. 이로 인해 정상적인 사용자들의 서비스 이용에도 불편함이 있었습니다.

사례1) 특정 IP 가 수 분 내에 발생시킨 요청 수 약 20만건 이상 (New Relic Logs 에서 추출)

AWS WAF 도입 이후에는 위와 같은 과도한 요청을 방지하는 Request Rate Limit 규칙이 특정 IP에 대해서 일시적으로 특정 시간 동안 요청 차단(timeout)을 발생시켜 서비스 가용성 확보에 도움을 주고 있습니다.

"httpRequest": {
    "clientIp": "94.182.176.136",
    "country": "IR",
    "headers": [
        {
            "name": "Host",
            "value": "soomgo.com"
        },
        {
            "name": "Content-Type",
            "value": "multipart/form-data; boundary=\"a1217312-4bba-43e1-a5d1-20a29af87d2e\""
        },
        {
            "name": "Content-Length",
            "value": "1109715"
        }
    ],
    "uri": "/",
    "args": "",
    "httpVersion": "HTTP/1.1",
    "httpMethod": "POST"
}

사례2) 일부 차단된 요청의 httpRequest 로그 (CloudWatch Logs 에서 추출)

로그를 확인해보면 이란(IR) 국적의 94.182.176.136 클라이언트가 숨고 서비스(soomgo.com)에 파일 업로드(multipart/form-data, POST)를 시도하는 것으로 볼 수 있습니다. 숨고 서비스에서는 이러한 서비스를 제공하지 않으므로 해당 요청은 비정상 요청이라는 것을 로그를 통한 모니터링을 통해 판단할 수 있었습니다.

위의 예시 외에도 AWS WAF 도입 이후 현재까지 Managed Rule을 통해 다양한 SQL Injection, XSS, Linux/Unix Shell 공격 등에 효과적으로 대응하고 있습니다.

회고

AWS WAF 뿐만 아니라 방화벽이나 보호 프로토콜의 목표는 공격자들의 요청을 효과적으로 차단하는 것 이지만 결국 근본적인 의미는 정상적인 사용자들에게 원활한 서비스를 제공하는 것이라고 생각합니다.

AWS WAF를 도입한 이후로 성능 지표의 개선은 물론 서비스 장애 빈도수가 현격하게 낮아진 것을 체감할 수 있었습니다. 또한 엔지니어의 입장에서 서비스 관리의 유용성 측면을 넘어, 사용자들이 이전보다 더 안정적인 환경에서 숨고 서비스를 이용할 수 있는 환경이 만들어졌다는 것에서 큰 만족감을 느끼고 있습니다.

이 글에서 다룬 AWS WAF가 아니더라도 WAF의 도입을 통해 얻을 수 있는 이점은 비슷하다고 생각합니다. 웹 서비스를 운영하는 엔지니어, 또는 개발 조직에서 서비스의 안정성과 사용성을 높이기 위한 방법을 고민하고 계시다면 WAF의 도입을 추천하고 싶습니다.

긴 글 읽어주셔서 감사합니다.

참고 자료

Ted Jeong Soomgo DevOps Engineer
숨고의 안정적인 인프라를 위해 열심히 고민하며 노력하고 있습니다.