
Check Point CPM RCE CVE-2026-93616에 대한 안전한 비인증 패치 상태 검사기로, 익스플로잇 없이 TCP 19009에서 UpgradeSvcRemote SOAP 로그인을 프로브합니다.
CVE-2026-93616에 대한 인증되지 않은 패치 상태 점검으로, Check Point Security Management(CPM) 서버에서 Runtime.exec()에 도달하는 인증되지 않은 디렉터리 트래버설이며, 2026-09-22에 sk1000171에서 수정되었습니다. CWE-22, CVSS 9.8 Critical(CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H), 실제 공격에서 악용됨. Check Point가 CNA이며, 이 문제는 외부 보고가 아닌 실제 공격에서 식별되었습니다.
CPM 서버는 TCP 19009에서 SOAP 웹 서비스를 노출합니다. UpgradeSvcRemote 로그인 메서드 loginAsApplicationReadOnlyPublicSession은 targetVersion 매개변수를 검증 없이 Runtime.exec에 전달되는 파일 시스템 경로로 전달하므로, 인증되지 않은 호출자가 실행을 공격자가 선택한 스크립트로 리디렉션할 수 있습니다. 수정 사항은 targetVersion에 경로 구분자를 거부하는 Bean Validation 패턴을 추가합니다.
이 스크립트는 각 대상에 해당 수정 사항이 존재하는지 보고합니다. targetVersion에 불법 문자 하나를 전송하며, 트래버설 시퀀스는 절대 전송하지 않고, 인증하거나 파일을 업로드하거나 대상에서 무엇인가를 실행하지 않습니다. VULNERABLE 이외의 결과는 그 자체로 대상이 패치되었음을 나타내지 않습니다. 취약하지 않은 결과 해석을 참조하십시오.
지원 종료 빌드는 명시적으로 처리됩니다. R81.10보다 오래된 트레인에서는 로그인 메서드가 더 적은 매개변수를 받으므로, 기본 프로브는 패치 상태를 읽기 전에 XML 언마샬링에 의해 거부됩니다. Check Point 서버가 그런 방식으로 프로브를 거부하면, 스크립트는 이전 매개변수 형태로 후속 호출 하나를 전송합니다. 그 후 로그인 메서드가 디스패치되면, 대상은 Check Point가 수정 사항을 게시한 적이 없는 영향을 받는 빌드이며, 판정은 VULNERABLE이고 이유는 affected-eos-no-fix입니다. 후속 호출은 mode도 targetVersion도 전달하지 않으므로, 검증할 것도 트래버설할 것도 전송하지 않습니다. 이는 레거시 서버가 그렇지 않으면 식별 불가로 읽힐 수 있는 공백을 메웁니다.
# 단일 대상 (포트 기본값은 19009)
./cve_2026_93616_check.py mgmt.example.com
# 포트 명시
./cve_2026_93616_check.py mgmt.example.com:19009
# 여러 대상
./cve_2026_93616_check.py mds-a.example.com mds-b.example.com
# 목록 스캔, 한 줄에 대상 하나 ('#' 주석 허용), 간결한 출력
./cve_2026_93616_check.py -f targets.txt --brief
# 파이프라인용 기계 판독 가능 출력
./cve_2026_93616_check.py -f targets.txt --json > results.json
Python 3.8+, 표준 라이브러리만 사용 — 서드파티 패키지 없음.
게이트웨이가 아닌 관리 서버(Security Management, Multi-Domain Management, Log, Multi-Domain Log, SmartEvent)에 대해 도구를 실행하십시오. 이 점검은 게이트웨이가 실행하지 않는 TCP 19009의 CPM SOAP 리스너를 대상으로 합니다.
| 플래그 | 설명 |
|---|---|
TARGET | 하나 이상의 HOST[:PORT] 대상; 포트 기본값은 19009 |
-f, --targets-file FILE | 파일에서 대상 읽기 (한 줄에 하나; # 주석) |
-p, --port PORT | 대상이 포트를 생략할 때의 기본 포트 (기본값: 19009) |
--timeout SECS | 프로브당 타임아웃 (기본값: 15) |
--workers N | 동시 대상 수 (기본값: 16); 출력은 입력 순서 유지 |
-b, --brief | 대상당 정렬된 한 줄, 많은 호스트 스캔용 |
--json | 대상별 프로브 마커를 포함한 구조화된 JSON 출력 |
--no-color | 색상 출력 비활성화 (NO_COLOR 및 비 TTY도 존중) |
취약한 서버 (기본 출력; [!] 마커와 VULNERABLE은 TTY에서 빨간색으로 표시됨):
$ ./cve_2026_93616_check.py mgmt.example.com
[!] mgmt.example.com:19009: VULNERABLE [dispatched-without-validation]
loginAsApplicationReadOnlyPublicSession accepted an illegal targetVersion and dispatched it; the fix's input validation is absent
패치된 서버:
$ ./cve_2026_93616_check.py mgmt-dr.example.com
[+] mgmt-dr.example.com:19009: PATCHED [validation-constraint-present]
the server rejected an illegal targetVersion with the input-validation constraint added by the fix (Jumbo Hotfix R82.10 Take 45 or equivalent)
응답했지만 로그인 메서드를 디스패치하지 않아 패치 상태를 읽을 수 없었던 Check Point 관리 서버:
$ ./cve_2026_93616_check.py log.example.com
[?] log.example.com:19009: INCONCLUSIVE [cpm-no-dispatch]
a Check Point management fault came back, but the UpgradeSvcRemote login method did not dispatch, so patch state could not be read
--brief를 사용한 여러 대상:
$ ./cve_2026_93616_check.py -f targets.txt --brief; echo "exit: $?"
VULNERABLE mgmt.example.com:19009 dispatched-without-validation
PATCHED mgmt-dr.example.com:19009 validation-constraint-present
INCONCLUSIVE log.example.com:19009 cpm-no-dispatch
UNAFFECTED web.example.com:19009 not-cpm
ERROR offline.example.com:19009 unreachable
exit: 1
--json을 사용한 JSON 출력. 판정을 응답이 실제로 포함한 내용과 대조해 확인할 수 있도록 프로브 마커가 포함됩니다:
$ ./cve_2026_93616_check.py mgmt.example.com mgmt-dr.example.com --json
[
{
"target": "mgmt.example.com:19009",
"verdict": "VULNERABLE",
"reason": "dispatched-without-validation",
"detail": "loginAsApplicationReadOnlyPublicSession accepted an illegal targetVersion and dispatched it; the fix's input validation is absent",
"http_status": 500,
"probe": {
"http_status": 500,
"saw_validation": false,
"saw_dispatch": true,
"saw_cpm": true,
"note": ""
}
},
{
"target": "mgmt-dr.example.com:19009",
"verdict": "PATCHED",
"reason": "validation-constraint-present",
"detail": "the server rejected an illegal targetVersion with the input-validation constraint added by the fix (Jumbo Hotfix R82.10 Take 45 or equivalent)",
"http_status": 500,
"probe": {
"http_status": 500,
"saw_validation": true,
"saw_dispatch": true,
"saw_cpm": true,
"note": ""
}
}
]
이 점검은 프로덕션 시스템에 사용하는 것을 목적으로 합니다:
targetVersion에 불법 문자 하나(!)를 넣습니다. 그 한 문자는 패치된 빌드의 검증을 트립하기에 충분하며, 경로 구분자처럼 디렉터리를 탈출할 수 없습니다. 레거시 후속 프로브는 targetVersion을 전혀 전달하지 않습니다.domainId 요소를 생략하므로, 패치되지 않은 서버에서 메서드는 매개변수를 구성하는 중에 NullPointerException을 발생시키며, ReflectionUtils가 Runtime.exec에 도달하기 전입니다. 그렇지 않더라도 프로브 값은 존재하지 않는 디렉터리로 해석되므로 실행할 스크립트가 없습니다. 레거시 프로브는 애플리케이션 이름과 타임아웃만 전송하므로 경로를 구성할 것이 없습니다.| # | 호출 | 시점 | 목적 |
|---|---|---|---|
| 1 | UpgradeSvcRemote.loginAsApplicationReadOnlyPublicSession (현재 형태) | 항상 | 패치 상태 프로브 |
| 2 | UpgradeSvcRemote.loginAsApplicationReadOnlyPublicSession (레거시 형태) | 호출 1이 Check Point 서버에 의해 언마샬링에서 거부된 경우에만 | 지원 종료 빌드 확인 |
프로브는 $MDS_FWDIR/log/cpm.elg에 기록됩니다. 패치된 서버는 검증 거부를 기록하고, 패치되지 않은 서버는 디스패치와 그 NullPointerException을 기록하며, 지원 종료 서버는 호출 1에 대한 언마샬링 오류와 호출 2에 대한 디스패치를 기록합니다. 두 프로브 모두 애플리케이션 이름 bf-check를 전송하며(호출 1은 값 bf-check!도 전송), 이는 로그에서 공격이 아닌 이 도구로 인식 가능합니다.
수정 사항은 이를 받는 모든 UpgradeSvcRemote 메서드의 targetVersion 매개변수에 Bean Validation 패턴 ^[A-Za-z0-9._-]*$를 추가합니다. 이 패턴은 버전 문자열은 허용하지만 경로 구분자는 허용하지 않아, Runtime.exec으로의 트래버설을 차단합니다. 점검은 불법 문자 하나를 전송하고 서버가 어느 방향으로 응답하는지 읽습니다:
| 불법 문자 하나에 대한 응답 | 빌드 | 판정 |
|---|---|---|
targetVersion contains illegal characters | 수정됨 (Take 45 및 동등 버전) | PATCHED |
| 메서드 디스패치됨, 검증 메시지 없음 | 영향받음 (R81.10 이상) | VULNERABLE |
| 프로브가 XML 언마샬링에 의해 거부된 후 레거시 형태가 디스패치됨 | 영향받음, 지원 종료 | VULNERABLE |
| Check Point 결함, 메서드가 디스패치되지 않음 | 어느 쪽이든 | INCONCLUSIVE |
디스패치는 응답에서 전체 메서드 이름 UpgradeSvcRemote.loginAsApplicationReadOnlyPublicSession으로 확인됩니다. 단순 문자열 UpgradeSvcRemote는 사용할 수 없습니다. Check Point 결함은 네임스페이스 맵의 모든 서비스를 나열하므로, 이 메서드가 실행되었는지 여부와 관계없이 그 부분 문자열은 모든 CPM 응답에 나타납니다. 점검은 전체 메서드 이름만 일치시킵니다.
loginAsApplicationReadOnlyPublicSession의 매개변수 형태는 릴리스에 따라 변경되었습니다. R81.10 이상은 기본 프로브가 전송하는 mode 및 targetVersion 요소를 받아들이지만, R81.10보다 오래된 빌드는 그렇지 않으며, 패치 상태 신호가 생성되기 전에 XML 언마샬링(서버 스택이 JAXB/CXF 언마샬러에서 시작하는 generalRemoteFaultException)에서 기본 프로브를 거부합니다. 그 오래된 트레인은 모두 지원 종료이며 수정 사항이 제공된 적이 없으므로, 그 거부 자체가 발견 사항입니다.
스크립트는 이를 인식하고(Check Point 결함 + 언마샬러 스택) 이전 형태(applicationName 및 timeout만)로 후속 호출 하나를 전송합니다. 그 형태에 대해 로그인 메서드가 디스패치되면, 대상은 영향받는 패치 불가능 빌드로 확인되어 VULNERABLE / affected-eos-no-fix로 보고됩니다. 후속 호출은 이 경우에만 전송되므로, 현재 지원되는 서버는 여전히 단일 요청으로 점검됩니다.
점검은 패치된 코드 경로의 동작을 관찰하며 버전 문자열을 읽지 않습니다. 패치된 서버가 실행 중인 Jumbo Hotfix take를 보고하지 않습니다.
VULNERABLE과 PATCHED는 각각 관찰된 서버 응답에 기반합니다. INCONCLUSIVE와 ERROR는 프로브가 대상을 분류하지 못했으므로 패치 상태를 알 수 없음을 의미합니다. 이러한 이유로 INCONCLUSIVE는 PATCHED와 별도로 보고됩니다.
모든 판정은 짧은 reason 태그를 가집니다. --brief는 이를 세 번째 열로 출력하고 --json은 이를 reason으로 전달합니다.