Skip to content

Commit c2f5d68

Browse files
Merge pull request #1227 from rootvector2/addaswebresources-zip-slip
prevent zip-slip in DeploymentEngine.addAsWebResources
2 parents aaf1489 + 3381261 commit c2f5d68

2 files changed

Lines changed: 85 additions & 2 deletions

File tree

modules/kernel/src/org/apache/axis2/deployment/DeploymentEngine.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,7 @@ private static void addAsWebResources(File in,
741741
}
742742
File webLocation = new File(webLocationString);
743743
File out = new File(webLocation, serviceFileName);
744+
String outPath = out.getCanonicalPath();
744745
int BUFFER = 1024;
745746
byte data[] = new byte[BUFFER];
746747
FileInputStream fin = new FileInputStream(in);
@@ -753,10 +754,21 @@ private static void addAsWebResources(File in,
753754
String fileName = zip.getName();
754755
fileName = fileName.substring("WWW/".length(),
755756
fileName.length());
757+
File target = new File(out, fileName);
758+
// A WWW/ entry name can contain ../ sequences; reject any that
759+
// resolve outside the web resource directory so a crafted
760+
// archive cannot write to an arbitrary path (zip slip).
761+
String targetPath = target.getCanonicalPath();
762+
if (!targetPath.equals(outPath)
763+
&& !targetPath.startsWith(outPath + File.separator)) {
764+
log.warn("Skipping web resource entry outside target directory: "
765+
+ zip.getName());
766+
continue;
767+
}
756768
if (zip.isDirectory()) {
757-
new File(out, fileName).mkdirs();
769+
target.mkdirs();
758770
} else {
759-
FileOutputStream tempOut = new FileOutputStream(new File(out, fileName));
771+
FileOutputStream tempOut = new FileOutputStream(target);
760772
int count;
761773
while ((count = zin.read(data, 0, BUFFER)) != -1) {
762774
tempOut.write(data, 0, count);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.axis2.deployment;
21+
22+
import org.apache.axis2.description.AxisServiceGroup;
23+
24+
import junit.framework.TestCase;
25+
26+
import java.io.File;
27+
import java.io.FileOutputStream;
28+
import java.lang.reflect.Method;
29+
import java.nio.charset.StandardCharsets;
30+
import java.util.zip.ZipEntry;
31+
import java.util.zip.ZipOutputStream;
32+
33+
public class AddAsWebResourcesZipSlipTest extends TestCase {
34+
35+
public void testWwwEntryCannotEscapeWebDirectory() throws Exception {
36+
File base = File.createTempFile("axis2-zipslip", "dir");
37+
assertTrue(base.delete());
38+
File webLocation = new File(base, "web");
39+
// out = webLocation/svc — the per-service web dir, created from the
40+
// archive's WWW/ directory entry just as a real deployment would.
41+
assertTrue(new File(webLocation, "svc").mkdirs());
42+
43+
// A WWW/../../ entry resolves to base/, outside the web resource
44+
// directory entirely.
45+
File escapeTarget = new File(base, "pwned.txt");
46+
assertFalse(escapeTarget.exists());
47+
48+
File archive = new File(base, "evil.aar");
49+
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(archive))) {
50+
zos.putNextEntry(new ZipEntry("WWW/"));
51+
zos.closeEntry();
52+
zos.putNextEntry(new ZipEntry("WWW/../../pwned.txt"));
53+
zos.write("owned".getBytes(StandardCharsets.UTF_8));
54+
zos.closeEntry();
55+
}
56+
57+
String previous = DeploymentEngine.getWebLocationString();
58+
DeploymentEngine.setWebLocationString(webLocation.getAbsolutePath());
59+
try {
60+
Method m = DeploymentEngine.class.getDeclaredMethod(
61+
"addAsWebResources", File.class, String.class, AxisServiceGroup.class);
62+
m.setAccessible(true);
63+
m.invoke(null, archive, "svc", new AxisServiceGroup());
64+
} finally {
65+
DeploymentEngine.setWebLocationString(previous);
66+
}
67+
68+
assertFalse("zip slip: WWW/ entry was written outside the web resource directory",
69+
escapeTarget.exists());
70+
}
71+
}

0 commit comments

Comments
 (0)