Skip to content

Commit 7ed49f5

Browse files
committed
Asset path traversal with client that doesn't resolve the ../
1 parent b92d57d commit 7ed49f5

File tree

4 files changed

+85
-13
lines changed

4 files changed

+85
-13
lines changed

jooby/src/main/java/io/jooby/internal/ClassPathAssetSource.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.net.URL;
1515
import java.net.URLConnection;
1616
import java.nio.file.Files;
17+
import java.nio.file.Path;
1718
import java.nio.file.Paths;
1819
import java.util.jar.JarFile;
1920
import java.util.zip.ZipEntry;
@@ -40,7 +41,16 @@ public ClassPathAssetSource(ClassLoader loader, String source) {
4041
}
4142

4243
@Nullable @Override public Asset resolve(@Nonnull String path) {
43-
String fullpath = isDir ? prefix + path : source;
44+
String fullpath;
45+
if (isDir) {
46+
fullpath = safePath(prefix + path);
47+
if (!fullpath.startsWith(prefix)) {
48+
return null;
49+
}
50+
} else {
51+
fullpath = source;
52+
}
53+
4454
URL resource = loader.getResource(fullpath);
4555
if (resource == null) {
4656
return null;
@@ -85,4 +95,23 @@ private boolean isDirectory(ClassLoader loader, String base) {
8595
return true;
8696
}
8797
}
98+
99+
private static String safePath(String path) {
100+
if (path.indexOf("./") > 0) {
101+
return normalize(path.split("/"));
102+
}
103+
return path;
104+
}
105+
106+
private static String normalize(String[] segments) {
107+
Path path = Paths.get(segments[0]);
108+
for (int i = 1; i < segments.length; i++) {
109+
path = path.resolve(segments[i]);
110+
}
111+
StringBuilder buffer = new StringBuilder();
112+
for (Path segment : path.normalize()) {
113+
buffer.append("/").append(segment);
114+
}
115+
return buffer.substring(1);
116+
}
88117
}

modules/jooby-bom/pom.xml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616
<!-- THIS FILE IS AUTO GENERATED. DON'T EDIT -->
1717

1818
<properties>
19-
<jooby.version>2.8.2</jooby.version>
19+
<jooby.version>2.8.3-SNAPSHOT</jooby.version>
2020
<HikariCP.version>3.4.4</HikariCP.version>
2121
<archetype-packaging.version>3.1.2</archetype-packaging.version>
2222
<asm.version>8.0.1</asm.version>
2323
<auto-service.version>1.0-rc6</auto-service.version>
24-
<aws-java-sdk.version>1.11.770</aws-java-sdk.version>
24+
<aws-java-sdk.version>1.11.778</aws-java-sdk.version>
2525
<boringssl.version>2.0.27.Final</boringssl.version>
2626
<bucket4j-core.version>4.10.0</bucket4j-core.version>
2727
<checkstyle.version>8.32</checkstyle.version>
@@ -54,9 +54,9 @@
5454
<jdbi.version>3.12.2</jdbi.version>
5555
<jetty.version>9.4.28.v20200408</jetty.version>
5656
<jfiglet.version>0.0.8</jfiglet.version>
57-
<jmespath-java.version>1.11.779</jmespath-java.version>
58-
<jooby-maven-plugin.version>2.8.2</jooby-maven-plugin.version>
59-
<jooby.version>2.8.2</jooby.version>
57+
<jmespath-java.version>1.11.778</jmespath-java.version>
58+
<jooby-maven-plugin.version>2.8.3-SNAPSHOT</jooby-maven-plugin.version>
59+
<jooby.version>2.8.3-SNAPSHOT</jooby.version>
6060
<json.version>20190722</json.version>
6161
<jsonwebtoken.version>0.11.1</jsonwebtoken.version>
6262
<jsr305.version>3.0.2</jsr305.version>
@@ -93,6 +93,7 @@
9393
<mysql-connector-java.version>8.0.20</mysql-connector-java.version>
9494
<netty.version>4.1.49.Final</netty.version>
9595
<nexus-staging-maven-plugin.version>1.6.8</nexus-staging-maven-plugin.version>
96+
<node.version>v12.16.3</node.version>
9697
<okhttp.version>4.6.0</okhttp.version>
9798
<pac4j.version>4.0.0</pac4j.version>
9899
<pebble.version>3.1.2</pebble.version>
@@ -107,7 +108,6 @@
107108
<spring.version>5.2.5.RELEASE</spring.version>
108109
<stork-maven-plugin.version>3.0.0</stork-maven-plugin.version>
109110
<swagger-parser.version>2.0.19</swagger-parser.version>
110-
<swagger-ui.version>3.25.2</swagger-ui.version>
111111
<swagger.version>2.1.2</swagger.version>
112112
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
113113
<truth.version>1.0.1</truth.version>
@@ -551,12 +551,6 @@
551551
<version>${swagger-parser.version}</version>
552552
<type>jar</type>
553553
</dependency>
554-
<dependency>
555-
<groupId>org.webjars</groupId>
556-
<artifactId>swagger-ui</artifactId>
557-
<version>${swagger-ui.version}</version>
558-
<type>jar</type>
559-
</dependency>
560554
<dependency>
561555
<groupId>org.unbescape</groupId>
562556
<artifactId>unbescape</artifactId>
@@ -941,6 +935,12 @@
941935
<version>${plexus-utils.version}</version>
942936
<type>jar</type>
943937
</dependency>
938+
<dependency>
939+
<groupId>com.amazonaws</groupId>
940+
<artifactId>aws-java-sdk-iotsitewise</artifactId>
941+
<version>${aws-java-sdk.version}</version>
942+
<type>jar</type>
943+
</dependency>
944944
<dependency>
945945
<groupId>com.amazonaws</groupId>
946946
<artifactId>aws-java-sdk-synthetics</artifactId>

tests/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@
154154
<artifactId>vue</artifactId>
155155
<version>2.6.11</version>
156156
</dependency>
157+
158+
<dependency>
159+
<groupId>org.apache.httpcomponents</groupId>
160+
<artifactId>fluent-hc</artifactId>
161+
<version>4.5.12</version>
162+
</dependency>
157163
</dependencies>
158164

159165
<build>

tests/src/test/java/io/jooby/Issue1639.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.jooby.junit.ServerTest;
44
import io.jooby.junit.ServerTestRunner;
5+
import org.apache.http.client.fluent.Request;
56

67
import java.nio.file.Path;
78
import java.nio.file.Paths;
@@ -40,6 +41,9 @@ public void shouldNotAccessToClassFromCpAssetSource(ServerTestRunner runner) {
4041
runner.define(app -> {
4142
app.assets("/static/?*", "/static");
4243
}).ready(client -> {
44+
client.get("/static/foo/../js/index.js", rsp -> {
45+
assertEquals("(function () { console.log('index.js');});", rsp.body().string().trim());
46+
});
4347
client.get("/static/js/index.js", rsp -> {
4448
assertEquals("(function () { console.log('index.js');});", rsp.body().string().trim());
4549
});
@@ -54,6 +58,39 @@ public void shouldNotAccessToClassFromCpAssetSource(ServerTestRunner runner) {
5458
});
5559
}
5660

61+
@ServerTest
62+
public void shouldNotAccessToClassFromCpAssetSourceWithDifferentClient(ServerTestRunner runner) {
63+
runner.define(app -> {
64+
app.assets("/static/?*", "/static");
65+
}).ready(client -> {
66+
assertEquals(404,
67+
Request
68+
.Get("http://localhost:" + client.getPort() + "/static/../io/jooby/Issue1639.class")
69+
.execute()
70+
.returnResponse()
71+
.getStatusLine().getStatusCode());
72+
73+
assertEquals("(function () { console.log('index.js');});",
74+
Request.Get("http://localhost:" + client.getPort() + "/static/foo/../js/index.js")
75+
.execute()
76+
.returnContent()
77+
.asString().trim());
78+
79+
assertEquals("(function () { console.log('index.js');});",
80+
Request.Get("http://localhost:" + client.getPort() + "/static/js/index.js")
81+
.execute()
82+
.returnContent()
83+
.asString().trim());
84+
85+
assertEquals(404,
86+
Request.Get(
87+
"http://localhost:" + client.getPort() + "/static/..%252fio/jooby/Issue1639.class")
88+
.execute()
89+
.returnResponse()
90+
.getStatusLine().getStatusCode());
91+
});
92+
}
93+
5794
private static Path userdir(String... segments) {
5895
Path path = Paths.get(System.getProperty("user.dir"));
5996
for (String segment : segments) {

0 commit comments

Comments
 (0)