CVE-2022-41967 (Java, published 2022-12-28T00:15Z)
Description: value: Dragonfly is a Java runtime dependency management library. Dragonfly v0.3.0-SNAPSHOT does not configure DocumentBuilderFactory to prevent XML external entity (XXE) attacks. This issue is patched in 0.3.1-SNAPSHOT. As a workaround, since Dragonfly only parses XML `SNAPSHOT` versions are being resolved, this vulnerability may be avoided by not trying to resolve `SNAPSHOT` versions.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-611 \u2014 Improper Restriction of XML External Entity Reference
The product processes an XML document that can contain XML entities with URIs that resolve to documents outside of the intended sphere of control, causing the product to embed incorrect documents into its output.
Repository: https://github.com/HyperaDev/Dragonfly
Files changed:
LICENSE
SECURITY.md
pom.xml
src/main/java/dev/hypera/dragonfly/Dragonfly.java
src/main/java/dev/hypera/dragonfly/DragonflyBuilder.java
src/main/java/dev/hypera/dragonfly/downloaders/impl/MavenDownloader.java
src/main/java/dev/hypera/dragonfly/exceptions/DownloadFailureException.java
src/main/java/dev/hypera/dragonfly/exceptions/DragonflyException.java
src/main/java/dev/hypera/dragonfly/exceptions/LoadFailureException.java
src/main/java/dev/hypera/dragonfly/exceptions/RelocationFailureException.java
src/main/java/dev/hypera/dragonfly/exceptions/ResolveFailureException.java
src/main/java/dev/hypera/dragonfly/loading/DependencyLoader.java
src/main/java/dev/hypera/dragonfly/loading/DragonflyClassLoader.java
src/main/java/dev/hypera/dragonfly/relocation/DependencyRelocator.java
src/main/java/dev/hypera/dragonfly/relocation/Relocation.java
src/main/java/dev/hypera/dragonfly/resolvers/impl/MavenResolver.java
src/main/java/dev/hypera/dragonfly/resolvers/impl/MavenSnapshotResolver.java
Commit message: fix: CVE-2022-41967

Dragonfly v0.3.0-SNAPSHOT fails to properly configure the
DocumentBuilderFactory to prevent XML enternal entity (XXE) attacks when
parsing maven-metadata.xml files provided by external Maven repositories
during "SNAPSHOT" version resolution.

This patches CVE-2022-41967 by disabling features which may lead to XXE.
If you are currently using v0.3.0-SNAPSHOT it is STRONGLY advised to
update Dragonfly to v0.3.1-SNAPSHOT just to be safe.


Vulnerable code:
#  This is a list of entities who own copyright to portions of Chameleon's code.
#  Please keep this list sorted alphabetically.
#
#  Acceptable formats:
#    Organization Name
#    My Name <myname@email.com>

Joshua Sing <joshua@hypera.dev>
Copyright (c) 2021 Joshua Sing <joshua@hypera.dev>
| ------- | ------------------ |
| 0.3.0   | :white_check_mark: |
  <version>0.3.0-SNAPSHOT</version>
      <version>22.0.0</version>
	private static final String VERSION = "0.3.0-SNAPSHOT";
	private final Path directory;
	private final Set<String> repositories;
	private final Consumer<Status> statusHandler;
	private final DependencyDownloader dependencyDownloader = new DependencyDownloader(this);
	private final DependencyRelocator dependencyRelocator;
	private final DependencyLoader dependencyLoader;
	public static String getVersion() {
	public CompletableFuture<Boolean> load(Dependency... dependencies) {
	public Path getDirectory() {
	public Set<String> getRepositories() {
	public DependencyDownloader getDependencyDownloader() {
	private final MavenResolver resolver = new MavenResolver();
	private final MavenSnapshotResolver snapshotResolver = new MavenSnapshotResolver();
	public DownloadFailureException(String message) {
	public DownloadFailureException(String message, Throwable cause) {
	public DownloadFailureException(Throwable cause) {
	protected DownloadFailureException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
	public DragonflyException(String message) {
	public DragonflyException(String message, Throwable cause) {
	public DragonflyException(Throwable cause) {
	protected DragonflyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
	public LoadFailureException(String message) {
	public LoadFailureException(String message, Throwable cause) {
	public LoadFailureException(Throwable cause) {
	protected LoadFailureException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
	public RelocationFailureException(String message) {
	public RelocationFailureException(String message, Throwable cause) {
	public RelocationFailureException(Throwable cause) {
	protected RelocationFailureException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
	public ResolveFailureException(String message) {
	public ResolveFailureException(String message, Throwable cause) {
	public ResolveFailureException(Throwable cause) {
	protected ResolveFailureException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
	private final Dragonfly dragonfly;
	private final IClassLoader classLoader;
	public DependencyLoader(Dragonfly dragonfly, IClassLoader classLoader) {
	public void load(List<Dependency> dependencies) throws LoadFailureException {
	private void load(Dependency dependency) throws LoadFailureException {
	public DragonflyClassLoader(ClassLoader classLoader) {
	public void addURL(URL url) {
	protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
	private static final String RELOCATED_FILENAME = "%s-relocated.jar";
	private final Dragonfly dragonfly;
	private final DragonflyClassLoader classLoader;
	private final DependencyLoader dependencyLoader;
	private Constructor<?> constructor;
	private Method method;
	public DependencyRelocator(Dragonfly dragonfly, boolean deleteOld) {
	private final String from;
	private final String to;
	private Relocation(String from, String to) {
	public String getFrom() {
	public String getTo() {
	private static final String FORMAT = "%s%s/%s/%s/%s-%s.jar";
	private static final String FORMAT = "%s%s/%s/%s/maven-metadata.xml";
	private static final String OUTPUT_FORMAT = "%s/%s-%s-%s-%s.jar";
				DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();

Fixed code:
Copyright (c) 2021-2022 Joshua Sing <joshua@hypera.dev>
|---------| ------------------ |
| 0.3.x   | :white_check_mark: |
  <version>0.3.1-SNAPSHOT</version>
      <version>23.1.0</version>
import org.jetbrains.annotations.NotNull;
	private static final @NotNull String VERSION = "0.3.1-SNAPSHOT";
	private final @NotNull Path directory;
	private final @NotNull Set<String> repositories;
	private final @NotNull Consumer<Status> statusHandler;
	private final @NotNull DependencyDownloader dependencyDownloader = new DependencyDownloader(this);
	private final @NotNull DependencyRelocator dependencyRelocator;
	private final @NotNull DependencyLoader dependencyLoader;
	public static @NotNull String getVersion() {
	public @NotNull CompletableFuture<Boolean> load(@NotNull Dependency... dependencies) {
	public @NotNull Path getDirectory() {
	public @NotNull Set<String> getRepositories() {
	public @NotNull DependencyDownloader getDependencyDownloader() {
import org.jetbrains.annotations.Contract;
	@Contract(value = "-> new", pure = true)
	private final @NotNull MavenResolver resolver = new MavenResolver();
	private final @NotNull MavenSnapshotResolver snapshotResolver = new MavenSnapshotResolver();
import org.jetbrains.annotations.NotNull;

	private static final long serialVersionUID = 5648475409314204882L;

	public DownloadFailureException(@NotNull String message) {
	public DownloadFailureException(@NotNull String message, @NotNull Throwable cause) {
	public DownloadFailureException(@NotNull Throwable cause) {
	protected DownloadFailureException(@NotNull String message, @NotNull Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
import org.jetbrains.annotations.NotNull;

	private static final long serialVersionUID = 3565376065959848642L;

	public DragonflyException(@NotNull String message) {
	public DragonflyException(@NotNull String message, @NotNull Throwable cause) {
	public DragonflyException(@NotNull Throwable cause) {
	protected DragonflyException(@NotNull String message, @NotNull Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
import org.jetbrains.annotations.NotNull;

	private static final long serialVersionUID = -8555618454694039541L;

	public LoadFailureException(@NotNull String message) {
	public LoadFailureException(@NotNull String message, @NotNull Throwable cause) {
	public LoadFailureException(@NotNull Throwable cause) {
	protected LoadFailureException(@NotNull String message, @NotNull Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
import org.jetbrains.annotations.NotNull;

	private static final long serialVersionUID = -2817144091943660838L;

	public RelocationFailureException(@NotNull String message) {
	public RelocationFailureException(@NotNull String message, @NotNull Throwable cause) {
	public RelocationFailureException(@NotNull Throwable cause) {
	protected RelocationFailureException(@NotNull String message, @NotNull Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
import org.jetbrains.annotations.NotNull;

	private static final long serialVersionUID = -8393996338726021911L;

	public ResolveFailureException(@NotNull String message) {
	public ResolveFailureException(@NotNull String message, @NotNull Throwable cause) {
	public ResolveFailureException(@NotNull Throwable cause) {
	protected ResolveFailureException(@NotNull String message, @NotNull Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
import org.jetbrains.annotations.NotNull;
	private final @NotNull Dragonfly dragonfly;
	private final @NotNull IClassLoader classLoader;
	public DependencyLoader(@NotNull Dragonfly dragonfly, @NotNull IClassLoader classLoader) {
	public void load(@NotNull List<Dependency> dependencies) throws LoadFailureException {
	private void load(@NotNull Dependency dependency) throws LoadFailureException {
import org.jetbrains.annotations.NotNull;
	public DragonflyClassLoader(@NotNull ClassLoader classLoader) {
	public void addURL(@NotNull URL url) {
	protected @NotNull Class<?> loadClass(@NotNull String name, boolean resolve) throws ClassNotFoundException {
import org.jetbrains.annotations.Nullable;
	private static final @NotNull String RELOCATED_FILENAME = "%s-relocated.jar";
	private final @NotNull Dragonfly dragonfly;
	private final @NotNull DragonflyClassLoader classLoader;
	private final @NotNull DependencyLoader dependencyLoader;
	private @Nullable Constructor<?> constructor;
	private @Nullable Method method;
	public DependencyRelocator(@NotNull Dragonfly dragonfly, boolean deleteOld) {
	private final @NotNull String from;
	private final @NotNull String to;
	private Relocation(@NotNull String from, @NotNull String to) {
	public @NotNull String getFrom() {
	public @NotNull String getTo() {
	private static final @NotNull String FORMAT = "%s%s/%s/%s/%s-%s.jar";
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import sun.tools.jstat.ParserException;
	private static final @NotNull String FORMAT = "%s%s/%s/%s/maven-metadata.xml";
	private static final @NotNull String OUTPUT_FORMAT = "%s/%s-%s-%s-%s.jar";
	private final @NotNull DocumentBuilderFactory documentBuilderFactory;

	public MavenSnapshotResolver() {
		try {
			/* The below is an attempt to create an XML parser while preventing XML External Entity attacks */
			/* Read more: https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#java */
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
			factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
			factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
			factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
			factory.setXIncludeAware(false);
			factory.setExpandEntityReferences(false);
			this.documentBuilderFactory = factory;
		} catch (ParserConfigurationException ex) {
			throw new RuntimeException("Failed to create DocumentBuilderFactory", ex);
		}
	}
				DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();