CVE-2018-9159 (Java, published 2018-03-31T21:29Z)
Description: value: In Spark before 2.7.2, a remote attacker can read unintended static files via various representations of absolute or relative pathnames, as demonstrated by file: URLs and directory traversal sequences. NOTE: this product is unrelated to Ignite Realtime Spark.
Severity: MEDIUM (CVSS v3 base score: 5.3)
Weakness: CWE-22 \u2014 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
Repository: https://github.com/perwendel/spark
Files changed:
src/main/java/spark/resource/ClassPathResource.java
src/test/java/spark/embeddedserver/jetty/EmbeddedJettyFactoryTest.java
src/test/java/spark/examples/staticresources/StaticResources.java
Commit message: Fix for #981, patch 2 (#988)



Vulnerable code:
        Assert.state(doesNotContainFileColon(path), "Path must not contain 'file:'");
    private static boolean doesNotContainFileColon(String path) {
        return !path.contains("file:");
                thisLoader.equals(otherLoader) &&
                this.clazz.equals(otherRes.clazz));
import static org.mockito.Mockito.*;
        when(jettyServerFactory.create(100,10,10000)).thenReturn(new Server());
        embeddedServer.ignite("localhost", 8080, null, 100,10,10000);
        verify(jettyServerFactory, times(1)).create(100,10,10000);
        embeddedServer.ignite("localhost", 8080, null, 0,0,0);
        when(jettyServerFactory.create(100,10,10000)).thenReturn(new Server());
        embeddedServer.ignite("localhost", 8080, null, 100,10,10000);
        verify(jettyServerFactory, times(1)).create(100,10,10000);
        if(embeddedServer != null) embeddedServer.extinguish();
import static spark.Spark.staticFileLocation;
        staticFileLocation("/public");

Fixed code:
import spark.utils.ResourceUtils;
        Assert.isTrue(isValid(path), "Path is not valid");
    private static boolean isValid(final String path) {
        return !isInvalidPath(path);
    }

    private static boolean isInvalidPath(String path) {
        if (path.contains("WEB-INF") || path.contains("META-INF")) {
            return true;
        }
        if (path.contains(":/")) {
            String relativePath = (path.charAt(0) == '/' ? path.substring(1) : path);
            if (ResourceUtils.isUrl(relativePath) || relativePath.startsWith("url:")) {
                return true;
            }
        }
        if (path.contains("")) {
            path = StringUtils.cleanPath(path);
            if (path.contains("../")) {
                return true;
            }
        }
        return false;
                    thisLoader.equals(otherLoader) &&
                    this.clazz.equals(otherRes.clazz));

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

        when(jettyServerFactory.create(100, 10, 10000)).thenReturn(new Server());
        embeddedServer.ignite("localhost", 6757, null, 100, 10, 10000);
        verify(jettyServerFactory, times(1)).create(100, 10, 10000);
        embeddedServer.ignite("localhost", 6758, null, 0, 0, 0);
        when(jettyServerFactory.create(100, 10, 10000)).thenReturn(new Server());
        embeddedServer.ignite("localhost", 6759, null, 100, 10, 10000);
        verify(jettyServerFactory, times(1)).create(100, 10, 10000);
        if (embeddedServer != null) {
            embeddedServer.extinguish();
        }
import static spark.Spark.staticFiles;
        staticFiles.location("/public");