flawopen.com/AI Security/wazuh-mcp-server-webshell-check-ssrf-ghsa-pw2j-pj4h-f5vg
Wazuh MCP webshell 검사 도구의 SSRF 취약점 — GHSA-pw2j-pj4h-f5vg
포렌식 도구가 요청하기 전에 호스트 이름이 Wazuh-MCP-Server의 리터럴 IP 검사를 우회했습니다. GHSA는 심각도를 High, CWE를 CWE-918로 지정했으며 CVE는 없습니다. commit 2bbfe12에서 수정되었습니다.[1], [2], [3]
안내 데스크 직원이 방문자 배지를 차단 목록과 대조한 뒤 방문자가 사무실을 고르게 한다고 생각해 보세요. 인쇄된 배지만 확인하면 그 이름이 제한된 방을 가리키는지 알 수 없습니다. 수정 사항은 이름이 해석되는 주소를 확인하고 연결을 확인된 주소에 고정합니다.[1], [3]
핵심 개념 및 용어
서버 측 요청 위조(SSRF)- 호출자가 영향을 준 대상에 서버가 네트워크 요청을 보내는 문제입니다. 대상 검사가 불완전하면 호출자가 직접 접근하지 못하는 네트워크에 서버가 도달할 수 있습니다.[4], [1]
DNS 해석 및 rebinding- 호스트 이름은 하나 이상의 IP 주소로 해석됩니다. 이름 문자열만 검사하거나 HTTP 클라이언트가 나중에 다시 해석하면 실제 연결 주소가 검사한 주소와 달라질 수 있습니다.[1], [3]
대상 주소 고정- 검증한 IP를 송신 연결에 연결하면서 HTTP와 TLS 검사에는 호스트 이름을 유지합니다. 검증과 연결 사이의 차이를 없앱니다.[3]
프레임워크 매핑
- 이 가이드에서 설명하는 약점의 CWE 참조: CWE-918
- D3-OTF 아웃바운드 트래픽 필터링 — 이 가이드의 아웃바운드 네트워크 제한은 서비스 또는 호스트가 접근할 수 있는 대상을 줄입니다.
단계별 공격 실행 흐름
포렌식 데이터의 URL이 검사 도구로 전달됨
영향을 받는 blueteam_check_webshell 도구는 webshell이 있을 수 있는 주소를 분석가가 확인하도록 URL을 받습니다. 권고문은 이 도구만 영향을 받는다고 명시하며 Wazuh-MCP-Server의 모든 기능으로 범위를 넓히지 않습니다.[1]
소스 코드 비교: 취약한 구현 vs 보안 패치
취약 코드 발췌는 commit 6bd1fa8의 WebshellCheckInput.validate_url 관련 로직을 재현하며 import와 클래스 필드는 생략했습니다. 공개되지 않은 리터럴 IP는 거부하지만 호스트 이름에서 발생한 ValueError를 도메인으로 취급해 DNS 조회 없이 허용합니다. 수정 코드 발췌는 fix commit 2bbfe12의 _pinned_curl_args를 재현합니다. 수정된 validator도 _host_pins를 호출하며, 요청 루프는 --resolve 인수를 curl에 전달하고 다음 이동 전에 각 리디렉션 대상을 검증합니다. 내부 대상은 명시적으로 설정된 도메인에만 허용됩니다. 이는 핵심을 보여 주는 소스 발췌이며 URL 요청 흐름 전체를 대체하지 않습니다.[2], [3]
소스 코드 발췌: mcp_server/tools/webshell_check.py — 검증 6bd1fa8; 수정 2bbfe12
@field_validator("url")
@classmethod
def validate_url(cls, v: str) -> str:
v = v.strip()
if not v.startswith(("http://", "https://")):
raise ValueError(f"URL must start with http:// or https://, got: {v!r}")
# URL 호스트의 사설/예약 IP 주소 거부.
from urllib.parse import urlparse
import ipaddress
host = urlparse(v).hostname
if host:
try:
ip = ipaddress.ip_address(host)
except ValueError:
ip = None # not an IP - domain name, allowed
if ip is not None and (ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local):
raise ValueError(f"URL host is a private/reserved IP: {host}. This tool only accepts public hosts.")
return v
def _pinned_curl_args(url: str) -> tuple[list[str], str | None]:
"""Build curl --resolve args for an already-validated destination."""
import ipaddress
u = urlparse(url)
host = u.hostname
if not host:
return [], "URL has no host."
try:
ipaddress.ip_address(host)
return [], None # Literal IP: no DNS lookup to rebind.
except ValueError:
pass
port = u.port or (443 if u.scheme == "https" else 80)
ips, err = _host_pins(host, _allowed_internal_domains())
if err:
return [], err
args: list[str] = []
for ip in ips:
addr = f"[{ip}]" if ":" in ip else ip
args += ["--resolve", f"{host}:{port}:{addr}"]
return args, None
엔지니어링 및 시스템 보안 강화 체크리스트
- ✓영향을 받는
blueteam_check_webshell구현이 포함된 배포를 찾습니다. 권고문은 패키지 버전 범위가 아니라 commit을 명시합니다.[1], [2] - ✓commit
2bbfe12또는 수정이 포함된 이후 버전으로 업데이트하고 실제 배포 소스를 확인합니다.[3] - ✓모든 A/AAAA 결과를 해석하고 좁게 정의한 내부 도메인 예외가 필요한 경우를 제외하고 비공개 주소를 거부합니다.[3]
- ✓검증한 IP를 실제 연결에 고정해 후속 DNS 조회로 대상이 바뀌지 않게 합니다.[3]
- ✓자동 리디렉션을 비활성화하거나 모든 리디렉션 대상을 다시 검증하고 고정합니다.[1], [3]
- ✓도구 호출 권한과 서버의 송신 네트워크를 제한하고 신뢰할 수 없는 데이터가 URL 인수를 제어할 수 있는지 확인합니다.[1]
- ✓실제 내부 서비스에 요청하지 않고 혼합 DNS 응답, IPv4, IPv6, 내부 예외, 리디렉션 및 주소 고정을 회귀 테스트합니다.[3]
출처
- Wazuh-MCP-Server 보안 권고 GHSA-pw2j-pj4h-f5vg (CVE 미할당)
- commit 6bd1fa8의 취약한 소스 — WebshellCheckInput.validate_url
- Wazuh-MCP-Server 수정 commit 2bbfe12
- MITRE CWE-918: Server-Side Request Forgery
- The Next Web: MCP 구현별 독립적인 SSRF 취약점
- Ars Technica: Vulnerability in agents from Google and others exposes structural flaw in MCP