NGINX OWASP API Security – 클러스터의 SSRF 공격 방어 구성

이 포스트는 NGINX Plus Ingress Controller와 NGINX App Protect WAF를 통해 2023 OWASP API Security risks – API7 SSRF(Server Side Request Forgery) 공격을 방어하도록 구성하는 방법에 관해 다룹니다.

기존 NGINX Plus Ingress Controller를 NGINX App Protect WAF 기능을 추가하여 구성하는 방법과 NGINX App Protect WAF의 Policy 구성을 통해 SSRF 공격을 방어하도록 설정하는 방법, 그리고 NGINX Instance Manager의 Security Monitoring 기능과 통합하여 대시보드를 통해 공격 방어를 모니터링하는 방법을 단계적으로 알아보겠습니다.

NGINX App Protect WAF를 사용하기 위해선 NGINX의 상업용 구독이 필요합니다. NGINX STORE를 통해 문의해 무료로 NGINX의 상업용 구독을 체험해 보실 수 있습니다.

목차

1. SSRF 공격이란?
2. NGINX OWASP API Security환경 구성
3. NGINX Ingress Controller + NGINX App Protect WAF 구성
4. NGINX Ingress Controller로 OWASP API Security – SSRF 공격 방어

 4-1. OWASP Juice Shop 애플리케이션의 SSRF 취약점
 4-2. NGINX OWASP API Security – SSRF 공격 방어 구성
5. NGINX App Protect WAF + NGINX Instance Manager Security Monitoring
 5-1. NGINX Ingress Controller 구성
 5-2. Security Monitoring
6. 결론

1. SSRF 공격이란?

SSRF(Server Side Request Forgery) 공격은 OWASP Top 10 API Security Risks 2023의 API7으로 선정된 공격으로, 공격자가 서버를 조작하여 의도하지 않은 내부 또는 외부 리소스에 요청을 보내도록 하는 보안 취약점입니다. 이 공격을 통해 공격자는 서버의 신뢰성을 악용하여 일반적으로 접근할 수 없는 시스템에 접근할 수 있습니다.
주로 사용자의 입력을 통해 외부의 자원에 접근하는 웹 훅, URL에서 파일 가져오기, URL 미리보기와 같은 기능을 통해 공격을 수행합니다. 예를 들어,

  • 정상적인 사용 사례:
    • 사용자가 공개 이미지 URL을 제공하면 서버가 해당 이미지를 다운로드하여 처리
    • 외부 API에서 데이터를 가져오기 위해 사용자가 제공한 엔드포인트로 요청

SSRF 공격은 특히 MSA(Microservices Architecture)나 클라우드 환경에서 위험도가 높습니다.
서비스 간 통신 및 신뢰 관계를 악용해 클러스터 내부의 자원에 접근하거나, 보호된 데이터의 유출을 초래할 수 있기 때문입니다.

2. NGINX OWASP API Security – 환경 구성

  • NGINX Plus Ingress Controller : 3.7.0
  • NGINX App Protect WAF : 5.3.0
  • NGINX Agent : 2.39.0
  • NGINX Instance Manager : 2.18.0
  • NGINX Security Monitoring : 1.7.1
  • nap 네임스페이스 Pod 구성
$ kubectl get po -n nap -o wide

NAME                         READY   STATUS    RESTARTS      AGE   IP             NODE     NOMINATED NODE   READINESS GATES
juiceshop-7f48459c4d-q4qsq   1/1     Running   2 (32d ago)   45d   10.244.2.236   worker   <none>           <none>
nginx                        1/1     Running   0             31d   10.244.2.248   worker   <none>           <none>
  • nap 네임스페이스 Service 구성
$ kubectl get svc -n nap

NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
juiceshop-svc   ClusterIP   10.109.251.18    <none>        80/TCP    65d

3. NGINX Ingress Controller + NGINX App Protect WAF 구성

이 포스트에서는 Manifest 방식을 통해 NGINX App Protect WAF가 활성화된 NGINX Plus Ingress Controller를 배포합니다.
자세한 구성 방법은 NGINX Ingress Controller WAF v5와 설치 포스트를 참고하세요.

NGINX Plus Ingress Controller 배포를 위한 기존 deployment.yaml 파일에 추가로 구성이 필요한 부분은 다음과 같습니다.

1. 각 컨테이너에 마운트하기 위한 3개의 볼륨을 구성합니다. 구성 예시는 아래와 같습니다.

volumes:
- name: app-protect-bd-config
  persistentVolumeClaim:
    claimName: waf-bd-conf-pvc
- name: app-protect-config
  persistentVolumeClaim:
    claimName: waf-conf-pvc
- name: app-protect-bundles
 persistentVolumeClaim:
    claimName: waf-bundle-pvc

NGINX App Protect WAF의 설정을 Pod의 재시작에도 영구적으로 저장하기 위해 Kubernetes의 PersistentVolumeClaim을 사용하여 볼륨을 구성합니다.

2. NGINX Plus Ingress Controller의 container 구성 항목에 volumeMounts를 추가합니다.

      - image: YOUR_REGISRTY:npic-waf-3.7.0 # NGINX Plus Ingress Controller 이미지 설정
        imagePullPolicy: IfNotPresent
        name: nginx-plus-ingress

......

        volumeMounts:
        - name: app-protect-bd-config
          mountPath: /opt/app_protect/bd_config
        - name: app-protect-config
          mountPath: /opt/app_protect/config
        - name: app-protect-bundles
          mountPath: /etc/app_protect/bundles

앞서 구성한 3개의 볼륨을 모두 마운트합니다.

3. NGINX Plus Ingress Controller에 NGINX App Protect WAF 활성화를 위해 args 항목에 값을 추가합니다.

        args:
          - -nginx-plus
          - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config
          - -enable-app-protect

4. NGINX App Protect WAF 구성을 위해 enforcer, config-mgr 컨테이너를 추가합니다.

      - name: waf-enforcer
        image: YOUR_REGISRTY:waf-enforcer-5.3.0 # NGINX App Protect WAF enforcer 이미지 설정
        imagePullPolicy: IfNotPresent
        env:
          - name: ENFORCER_PORT
            value: "50000"
        volumeMounts:
          - name: app-protect-bd-config
            mountPath: /opt/app_protect/bd_config
      - name: waf-config-mgr
        image: YOUR_REGISRTY:waf-config-mgr-5.3.0 # NGINX App Protect WAF config-mgr 이미지 설정
        imagePullPolicy: IfNotPresent
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
              - all
        volumeMounts:
        - name: app-protect-bd-config
          mountPath: /opt/app_protect/bd_config
        - name: app-protect-config
          mountPath: /opt/app_protect/config
        - name: app-protect-bundles
          mountPath: /etc/app_protect/bundles

전체 예시 파일(npic-waf.yaml)은 NGINX STORE GitHub 리포지토리에서 확인하실 수 있습니다.

위와 같이 구성 후, NGINX App Protect WAF를 활성화하는 VirtualServer 리소스를 배포하기 전에 NGINX Ingress Controller를 배포하면 waf-enforcer 컨테이너가 정상적으로 시작되지 않습니다. 4-2. NGINX OWASP API Security – SSRF 공격 방어 구성 섹션의 policies가 구성된 VirtualServer 배포 과정 수행 후 Pod를 재시작하면 waf-enforcer 컨테이너가 정상적으로 시작됩니다.

4. NGINX Ingress Controller로 OWASP API Security – SSRF 공격 방어

사전에 구성된 OWASP Juice Shop Pod의 SSRF 공격 취약점을 알아보고, NGINX Ingress Controller와 NGINX App Protect WAF 구성을 통해 OWASP API Security API7 항목인 SSRF 공격을 방어하도록 구성하겠습니다.

4-1. OWASP Juice Shop 애플리케이션의 SSRF 취약점

취약점이 존재하는 OWASP Juice Shop Pod는 다음과 같이 nap 네임스페이스에 nginx Pod와 함께 구성되어 있습니다.

$ kubectl get po -n nap -o wide

NAME                         READY   STATUS    RESTARTS      AGE   IP             NODE     NOMINATED NODE   READINESS GATES
juiceshop-7f48459c4d-q4qsq   1/1     Running   2 (32d ago)   45d   10.244.2.236   worker   <none>           <none>
nginx                        1/1     Running   0             31d   10.244.2.248   worker   <none>           <none>

구성된 nginx Pod의 내부에는 아래와 같이 image.png 이미지 파일이 있습니다.
Pod 내부의 default.conf 설정에 따라 / 경로로 요청 시 /usr/share/nginx/html/ 디렉터리를 root로 하여 파일을 제공합니다.

root@nginx:/# ls /usr/share/nginx/html/

50x.html  image.png  index.html

OWASP Juice Shop 애플리케이션에는 사용자 프로필 사진을 변경할 수 있는 기능이 있습니다.
이미지 URL을 통해 해당 URL의 이미지로 프로필 사진을 변경할 수 있습니다.

공격자는 이미지 URL 입력란에 클러스터 내부 Pod의 IP를 입력하여 외부에서는 접근할 수 없는 내부 자원에 접근을 시도할 수 있습니다.

위와 같이 클러스터 내부의 nginx Pod IP로 요청을 전송하면, 해당 Pod 내부의 이미지 파일을 프로필 사진으로 변경할 수 있습니다.

4-2. NGINX OWASP API Security – SSRF 공격 방어 구성

1. SSRF 공격을 방어하기 위한 Policy 파일을 컴파일합니다.

ssrf_policy.json
{
    "policy": {
        "name": "custom_sigs",
        "template": { "name": "POLICY_TEMPLATE_NGINX_BASE" },
        "applicationLanguage": "utf-8",
        "enforcementMode": "blocking",
        "signature-sets": [
            {
                 "name": "Other Application Attacks Signatures",
                 "block": true,
                 "alarm": true
            }
        ]
    }
}
  • name: 사용자 지정 Policy의 이름 정의
  • template: NGINX App Protect WAF 기본 제공 설정을 기반으로 함
  • enforcementMode: 위협적인 요청 차단 설정
  • signature-sets: signature-sets에 해당하는 요청의 감지/차단 설정

위 json 파일은 기본적으로 적용되는 Default policy에 추가로 ‘Other Application Attacks Signatures’ signature-sets에 해당하는 요청을 차단하는 Policy를 정의하는 파일입니다. 내부 IP 범위로 요청을 전송하는 SSRF 공격은 Other Application Attacks 타입에 속하며, 관련 정보는 F5의 Attack Signatures 문서에서 확인할 수 있습니다.
위 파일을 NGINX App Protect WAF Compiler을 통해서 컴파일하여, tgz 파일로 컴파일합니다.

이 포스트에선 Docker가 설치된 별개의 VM에서 waf-compiler:5.3.0 이미지를 사용해 컴파일했습니다.
컴파일러의 이미지 버전과 NGINX App Protect WAF 관련 컨테이너(enforcer, config-mgr)의 버전이 일치하지 않으면 오류가 발생함으로 주의가 필요합니다.

$ docker run --rm \
-v $(pwd):$(pwd) \
waf-compiler:5.3.0 \
-p $(pwd)/ssrf_policy.json -o $(pwd)/ssrf_policy.tgz

컴파일된 ssrf_policy.tgz 파일을 NGINX Ingress Controller에 마운트 한 bundles 볼륨으로 이동합니다.

$ ls /mnt/waf/bundles/ssrf_policy.tgz 
/mnt/waf/bundles/ssrf_policy.tgz

이 포스트에선 노드의 /mnt/waf/bundles 디렉터리를 app-protect-bundles 볼륨으로 구성하여, 위와 같이 파일을 이동했습니다.

2. 컴파일한 파일을 사용하는 Policy 리소스를 생성합니다.

ssrf_policy.yaml
apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
  name: ssrf-policy             # 이름 지정
spec:
  waf:                          # NGINX App Protect WAF 관련 Policy
    enable: true                # NGINX App Protect WAF 활성화
    apBundle: "ssrf_policy.tgz" # 컴파일한 파일 이름

Policy 리소스에 사용할 수 있는 NGINX App Protect WAF 관련 추가 내용은 정책 리소스 문서를 참고하세요.

$ kubectl apply -f ssrf_policy.yaml

policy.k8s.nginx.org/ssrf-policy created


$ kubectl get policy ssrf-policy

NAME          STATE   AGE
ssrf-policy   Valid   18s

yaml 파일을 통해 리소스를 생성하고, Valid 상태를 확인합니다.

3. OWASP Juice Shop Pod로 연결하는 VirtualServer 리소스를 생성합니다.

juiceshop-vs.yaml
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
  name: juiceshop  # 리소스 이름
spec:
  host: juiceshop.example.com
  policies:
  - name: ssrf-policy
  upstreams: # 연결될 Service를 upstream으로 정의
  - name: juiceshop
    service: juiceshop-svc
    port: 80
  routes:
  - path: /
    action:
      pass: juiceshop 

juiceshop.example.com으로의 요청이 juiceshop-svc를 통해 Pod로 전달됩니다. policies 항목에 앞서 구성한 Policy 리소스의 이름을 정의하여 해당 Policy를 적용합니다.

$ kubectl apply -f juiceshop-vs.yaml

virtualserver.k8s.nginx.org/juiceshop created


$ kubectl get vs

NAME        STATE   HOST                    IP    PORTS   AGE
juiceshop   Valid   juiceshop.example.com                 68s

Valid 상태를 확인합니다.

  Warning  AddedOrUpdatedWithWarning  14s    nginx-ingress-controller  Configuration for nap/juiceshop was added or updated ; with warning(s): WAF policy nap/invalid-policy references an invalid or non-existing App Protect bundle /etc/app_protect/bundles/non_existance_policy.tgz

VirtualServer 리소스가 참조하는 Policy 리소스, 혹은 해당 Policy 리소스에 정의된 파일(컴파일된 tgz 파일)에 문제가 있으면 위와 같이 경고 상태로 표시됩니다.

4. 다시 프로필 변경 URL을 클러스터 내부 Pod의 IP로 입력 후 변경을 시도하면 사진과 같이 해당 요청이 차단됩니다.

5. NGINX App Protect WAF + NGINX Instance Manager Security Monitoring

NGINX App Protect WAF를 NGINX Instance Manager의 Security Monitoring과 통합하면, NGINX App Protect WAF를 통해 감지된 위협적인 요청과 차단된 요청의 상세한 정보를, 대시보드를 통해 편리하게 확인할 수 있습니다.

이 포스트는 NGINX Instance Manager와 Security Monitoring이 사전 구성된 환경에서 진행됩니다.
NGINX Plus Ingress Controller에 NGINX Agent를 구성하여 NGINX Instance Manager로 NGINX App Protect WAF를 통해 감지/차단된 요청의 정보를 전송하는 방법과 대시보드로 차단된 요청을 확인해 보겠습니다.

 5-1. NGINX Ingress Controller 구성

1. NGINX Agent의 설정 파일을 ConfigMap 리소스로 배포합니다.

agent.yaml
kind: ConfigMap
apiVersion: v1
metadata:
  name: agent-config
  namespace: nginx-ingress
data:
  nginx-agent.conf: |-
    log:
      level: error
      path: ""
    server:
      host: "<FQDN or IP address of NGINX Instance Manager>"  # 구성된 NGINX Instance Manager에 맞춰 변경
      grpcPort: 443
    tls:
      enable: true
      skip_verify: true
    features:
    - registration
    - nginx-counting
    - metrics-sender
    - dataplane-status
    extensions:
    - nginx-app-protect
    - nap-monitoring
    nginx_app_protect:
      report_interval: 15s
      precompiled_publication: true
    nap_monitoring:
      collector_buffer_size: 20000
      processor_buffer_size: 20000
      syslog_ip: "127.0.0.1"
      syslog_port: 1514

해당 ConfigMap은 NGINX Ingress Controller에 마운트되므로, 동일한 네임스페이스에 배포해야 합니다.
하단의 syslog 설정은 이후 security logs 구성에 필요합니다.

2. NGINX Agent가 설치된 NGINX Ingress Controller 이미지를 빌드합니다.
25.1.3 기준 공식적으로 NGINX Agent가 설치된 이미지가 없어 Dockerfile을 통해 빌드했습니다.

Dockerfile
FROM YOUR_REGISRTY:npic-waf-3.7.0  # 베이스가 될 NGINX Ingress Controller 이미지 지정

USER root

RUN apt-get update && apt-get install -y \
    curl \
    gnupg \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

RUN curl -fsSL https://github.com/nginx/agent/releases/download/v2.39.0/nginx-agent-2.39.0.bookworm_amd64.deb -o /tmp/nginx-agent.deb && \
    dpkg -i /tmp/nginx-agent.deb && \
    rm -f /tmp/nginx-agent.deb

RUN touch /var/lib/nginx-agent/agent-dynamic.conf && chown nginx:nginx /var/lib/nginx-agent/agent-dynamic.conf

USER nginx

CMD ["nginx-agent"]

해당 파일의 강조된 부분은 NGINX Agent GitHub의 릴리즈 페이지를 참고하여 베이스가 되는 NGINX Ingress Controller 이미지의 OS, 버전 정보에 맞춰 수정하여 사용하세요.

예시의 경우 Debian bookworm 기반의 NGINX Plus Ingress Controller 이미지를 사용하여 위와 같이 구성했습니다.

3. 변경된 구성에 맞춰 NGINX Ingress Controller의 Deployment yaml 파일을 수정하고, 재배포합니다.

npic-waf-nim.yaml
      containers:
      - image: YOUR_REGISRTY:npic-waf-nim-5.3.0  # NGINX Agent 추가 이미지로 변경
        imagePullPolicy: IfNotPresent

......

# args 구성 추가

        args:
          - -nginx-plus
          - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config
          - -enable-app-protect
          - -agent=true  # NGINX Agent 구성
          - -agent-instance-group=nginx-ingress-waf # Deployment 이름으로 구성

# NGINX Agent 설정 마운트 볼륨 추가

        volumeMounts:
        - name: app-protect-bd-config
          mountPath: /opt/app_protect/bd_config
        - name: app-protect-config
          mountPath: /opt/app_protect/config
        - name: app-protect-bundles
          mountPath: /etc/app_protect/bundles
        - name: agent-config
          mountPath: /etc/nginx-agent/nginx-agent.conf
          subPath: nginx-agent.conf

......

      volumes:
      - name: app-protect-bd-config
        persistentVolumeClaim:
          claimName: waf-bd-conf-pvc
      - name: app-protect-config
        persistentVolumeClaim:
          claimName: waf-conf-pvc
      - name: app-protect-bundles
        persistentVolumeClaim:
          claimName: waf-bundle-pvc
      - name: agent-config
        configMap:
          name: agent-config

4. Security Log 전송을 위한 logging profile을 컴파일합니다.

log_sm.json
{
    "filter": {
        "request_type": "illegal"
    },
    "content": {
        "format": "user-defined",
        "format_string": "%blocking_exception_reason%,%dest_port%,%ip_client%,%is_truncated_bool%,%method%,%policy_name%,%protocol%,%request_status%,%response_code%,%severity%,%sig_cves%,%sig_set_names%,%src_port%,%sub_violations%,%support_id%,%threat_campaign_names%,%violation_rating%,%vs_name%,%x_forwarded_for_header_value%,%outcome%,%outcome_reason%,%violations%,%violation_details%,%bot_signature_name%,%bot_category%,%bot_anomalies%,%enforced_bot_anomalies%,%client_class%,%client_application%,%client_application_version%,%transport_protocol%,%uri%,%request%",
        "escaping_characters": [
            {
                "from": ",",
                "to": "%2C"
            }
        ],
        "max_request_size": "2048",
        "max_message_size": "5k",
        "list_delimiter": "::"
    }
}
$ docker run \
 -v $(pwd):$(pwd) \
 waf-compiler:5.3.0 \
 -l $(pwd)/log_sm.json -o $(pwd)/log_sm.tgz

컴파일된 파일은 이전의 Policy 파일과 동일한 bundles 디렉터리로 이동합니다.

5. 새로운 Policy 리소스를 클러스터에 배포합니다.

security-monitor.yaml
apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
  name: monitor-policy
spec:
  waf:
    enable: true
    apBundle: "ssrf_policy.tgz" # 적용할 Policy
    securityLogs:
    - enable: true
      apLogBundle: "log_sm.tgz" # 적용할 logging profile
      logDest: "syslog:server=127.0.0.1:1514" # NGINX Agent 설정 파일에 정의된 syslog IP:Port
$ kubectl apply -f security-monitor.yaml

policy.k8s.nginx.org/monitor-policy created

$ kubectl edit vs juiceshop

virtualserver.k8s.nginx.org/juiceshop edited

새로 배포한 Policy 리소스의 설정을 적용하기 위해 VirtualServer 리소스도 수정합니다.

5-2. Security Monitoring

NGINX OWASP API Security - NGINX Ingress Controller Instance

정상적으로 구성이 완료되었다면, NGINX Incetance Manager의 Instances 탭에서 NGINX Ingress Controller Pod를 확인할 수 있습니다.

NGINX OWASP API Security - Security Monitoring Dashboard main

좌측 상단의 Select module 탭을 통해 Security Monitoring 모듈을 선택하면 NGINX App Protect를 통해 확인된 공격 요청을 대시보드로 확인할 수 있습니다.

NGINX OWASP API Security - ID Details
NGINX OWASP API Security - Local Network IP

이전과 동일한 공격 요청을 전송하고, 화면에 출력되는 ID를 좌측의 Support ID Details 메뉴에 입력하면 차단된 요청에 대한 상세한 정보를 확인할 수 있습니다.

NGINX OWASP API Security - Event Logs

대시보드 메인 화면의 Event Logs 탭으로 이동하면 감지/차단된 전체 요청을 확인할 수 있습니다.

NGINX OWASP API Security - Local Network IP
NGINX OWASP API Security - Local Network IP
NGINX OWASP API Security - Local Network IP
NGINX OWASP API Security - CSP Metadata
NGINX OWASP API Security - CSP Metadata
NGINX OWASP API Security - CSP Metadata

SSRF 공격 차단 Policy 구성으로 인해 로컬 네트워크 IP 범위의 요청이나, CSP의 메타데이터 URL을 차단하는 것을 확인할 수 있습니다.

6. 결론

이번 포스트에서는 NGINX Plus Ingress Controller와 NGINX App Protect WAF를 통해 2023 OWASP API Security risks – API7 SSRF(Server Side Request Forgery) 공격을 방어하도록 구성하는 방법과, 추가로 NGINX Instance Manager의 Security Monitoring 대시보드를 통해 차단된 요청의 상세한 정보를 확인하는 방법을 알아봤습니다.

SSRF(Server Side Request Forgery) 공격은 MSA 및 클러스터 환경에서 서비스 간 신뢰 관계와 내부 네트워크 접근을 악용하여 민감한 데이터 유출, 내부 서비스 스캔, 명령 실행 등 심각한 피해를 초래할 수 있는 취약점으로, 이를 방어하기 위한 철저한 보안 구성은 필수적입니다.
NGINX App Protect WAF는 기본적으로 적용되는 Default Policy를 제공하며, 해당 Policy를 기반으로 사용자의 필요에 맞춰 SSRF 공격을 비롯한 다양한 공격에 대한 설정을 컴파일하여 적용할 수 있습니다.

포스트에서 사용한 NGINX Plus Ingress Controller, NGINX App Protect WAF, NGINX Instance Manager를 모두 포함하는 NGINX의 상업용 구독을 NGINX STORE를 통해 문의해 무료로 체험해 보세요.

NGINX STORE를 통한 솔루션 도입 및 기술지원 무료 상담 신청

* indicates required