|
|
719156 |
commit 78cf73473dda5ceee3eecda5169621f36b93c3db
|
|
|
719156 |
Author: Jiri Vanek <jvanek@redhat.com>
|
|
|
719156 |
Date: Tue Jun 18 15:37:47 2019 +0200
|
|
|
719156 |
|
|
|
719156 |
Fixed bug when relative path (..) could leak up (even out of cache)
|
|
|
719156 |
|
|
|
719156 |
--- a/netx/net/sourceforge/jnlp/cache/CacheUtil.java
|
|
|
719156 |
+++ a/netx/net/sourceforge/jnlp/cache/CacheUtil.java
|
|
|
719156 |
@@ -696,46 +696,68 @@
|
|
|
719156 |
path.append(location.getPort());
|
|
|
719156 |
path.append(File.separatorChar);
|
|
|
719156 |
}
|
|
|
719156 |
- path.append(location.getPath().replace('/', File.separatorChar));
|
|
|
719156 |
- if (location.getQuery() != null && !location.getQuery().trim().isEmpty()) {
|
|
|
719156 |
- path.append(".").append(location.getQuery());
|
|
|
719156 |
- }
|
|
|
719156 |
-
|
|
|
719156 |
- File candidate = new File(FileUtils.sanitizePath(path.toString()));
|
|
|
719156 |
- if (candidate.getName().length() > 255) {
|
|
|
719156 |
- /**
|
|
|
719156 |
- * When filename is longer then 255 chars, then then various
|
|
|
719156 |
- * filesytems have issues to save it. By saving the file by its
|
|
|
719156 |
- * summ, we are trying to prevent collision of two files differs in
|
|
|
719156 |
- * suffixes (general suffix of name, not only 'filetype suffix')
|
|
|
719156 |
- * only. It is also preventing bug when truncate (files with 1000
|
|
|
719156 |
- * chars hash in query) cuts to much.
|
|
|
719156 |
- */
|
|
|
719156 |
+ String locationPath = location.getPath().replace('/', File.separatorChar);
|
|
|
719156 |
+ if (locationPath.contains("..")){
|
|
|
719156 |
try {
|
|
|
719156 |
- MessageDigest md = MessageDigest.getInstance("SHA-256");
|
|
|
719156 |
- byte[] sum = md.digest(candidate.getName().getBytes(StandardCharsets.UTF_8));
|
|
|
719156 |
- //convert the byte to hex format method 2
|
|
|
719156 |
- StringBuilder hexString = new StringBuilder();
|
|
|
719156 |
- for (int i = 0; i < sum.length; i++) {
|
|
|
719156 |
- hexString.append(Integer.toHexString(0xFF & sum[i]));
|
|
|
719156 |
- }
|
|
|
719156 |
- String extension = "";
|
|
|
719156 |
- int i = candidate.getName().lastIndexOf('.');
|
|
|
719156 |
- if (i > 0) {
|
|
|
719156 |
- extension = candidate.getName().substring(i);//contains dot
|
|
|
719156 |
- }
|
|
|
719156 |
- if (extension.length() < 10 && extension.length() > 1) {
|
|
|
719156 |
- hexString.append(extension);
|
|
|
719156 |
- }
|
|
|
719156 |
- candidate = new File(candidate.getParentFile(), hexString.toString());
|
|
|
719156 |
+ /**
|
|
|
719156 |
+ * if path contains .. then it can harm lcoal system
|
|
|
719156 |
+ * So without mercy, hash it
|
|
|
719156 |
+ */
|
|
|
719156 |
+ String hexed = hex(new File(locationPath).getName(), locationPath);
|
|
|
719156 |
+ return new File(path.toString(), hexed.toString());
|
|
|
719156 |
} catch (NoSuchAlgorithmException ex) {
|
|
|
719156 |
- // should not occure, cite from javadoc:
|
|
|
719156 |
- // every java iomplementation should support
|
|
|
719156 |
+ // should not occur, cite from javadoc:
|
|
|
719156 |
+ // every java implementation should support
|
|
|
719156 |
// MD5 SHA-1 SHA-256
|
|
|
719156 |
throw new RuntimeException(ex);
|
|
|
719156 |
}
|
|
|
719156 |
- }
|
|
|
719156 |
- return candidate;
|
|
|
719156 |
+ } else {
|
|
|
719156 |
+ path.append(locationPath);
|
|
|
719156 |
+ if (location.getQuery() != null && !location.getQuery().trim().isEmpty()) {
|
|
|
719156 |
+ path.append(".").append(location.getQuery());
|
|
|
719156 |
+ }
|
|
|
719156 |
+
|
|
|
719156 |
+ File candidate = new File(FileUtils.sanitizePath(path.toString()));
|
|
|
719156 |
+ try {
|
|
|
719156 |
+ if (candidate.getName().length() > 255) {
|
|
|
719156 |
+ /**
|
|
|
719156 |
+ * When filename is longer then 255 chars, then then various
|
|
|
719156 |
+ * filesystems have issues to save it. By saving the file by its
|
|
|
719156 |
+ * sum, we are trying to prevent collision of two files differs in
|
|
|
719156 |
+ * suffixes (general suffix of name, not only 'filetype suffix')
|
|
|
719156 |
+ * only. It is also preventing bug when truncate (files with 1000
|
|
|
719156 |
+ * chars hash in query) cuts to much.
|
|
|
719156 |
+ */
|
|
|
719156 |
+ String hexed = hex(candidate.getName(), candidate.getName());
|
|
|
719156 |
+ candidate = new File(candidate.getParentFile(), hexed.toString());
|
|
|
719156 |
+ }
|
|
|
719156 |
+ } catch (NoSuchAlgorithmException ex) {
|
|
|
719156 |
+ // should not occur, cite from javadoc:
|
|
|
719156 |
+ // every java implementation should support
|
|
|
719156 |
+ // MD5 SHA-1 SHA-256
|
|
|
719156 |
+ throw new RuntimeException(ex);
|
|
|
719156 |
+ }
|
|
|
719156 |
+ return candidate;
|
|
|
719156 |
+ }
|
|
|
719156 |
+ }
|
|
|
719156 |
+
|
|
|
719156 |
+ private static String hex(String origName, String candidate) throws NoSuchAlgorithmException {
|
|
|
719156 |
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
|
|
|
719156 |
+ byte[] sum = md.digest(candidate.getBytes(StandardCharsets.UTF_8));
|
|
|
719156 |
+ //convert the byte to hex format method 2
|
|
|
719156 |
+ StringBuilder hexString = new StringBuilder();
|
|
|
719156 |
+ for (int i = 0; i < sum.length; i++) {
|
|
|
719156 |
+ hexString.append(Integer.toHexString(0xFF & sum[i]));
|
|
|
719156 |
+ }
|
|
|
719156 |
+ String extension = "";
|
|
|
719156 |
+ int i = origName.lastIndexOf('.');
|
|
|
719156 |
+ if (i > 0) {
|
|
|
719156 |
+ extension = origName.substring(i);//contains dot
|
|
|
719156 |
+ }
|
|
|
719156 |
+ if (extension.length() < 10 && extension.length() > 1) {
|
|
|
719156 |
+ hexString.append(extension);
|
|
|
719156 |
+ }
|
|
|
719156 |
+ return hexString.toString();
|
|
|
719156 |
}
|
|
|
719156 |
|
|
|
719156 |
/**
|
|
|
719156 |
diff --git a/netx/net/sourceforge/jnlp/util/FileUtils.java b/netx/net/sourceforge/jnlp/util/FileUtils.java
|
|
|
719156 |
index 89216375..a5356e08 100644
|
|
|
719156 |
--- a/netx/net/sourceforge/jnlp/util/FileUtils.java
|
|
|
719156 |
+++ b/netx/net/sourceforge/jnlp/util/FileUtils.java
|
|
|
719156 |
@@ -183,6 +183,13 @@
|
|
|
719156 |
*/
|
|
|
719156 |
public static void createParentDir(File f, String eMsg) throws IOException {
|
|
|
719156 |
File parent = f.getParentFile();
|
|
|
719156 |
+ // warning, linux and windows behave differently. Below snippet will pass on win(security hole), fail on linux
|
|
|
719156 |
+ // warning mkdir is canonicaling, but exists/isDirectory is not. So where mkdirs return true, and really creates dir, isDirectory can still return false
|
|
|
719156 |
+ // can be seen on this example
|
|
|
719156 |
+ // mkdirs /a/b/../c
|
|
|
719156 |
+ // where b do not exists will lead creation of /a/c
|
|
|
719156 |
+ // but exists on /a/b/../c is false on linux even afterwards
|
|
|
719156 |
+ // without hexing of .. paths,
|
|
|
719156 |
if (!parent.isDirectory() && !parent.mkdirs()) {
|
|
|
719156 |
throw new IOException(R("RCantCreateDir",
|
|
|
719156 |
eMsg == null ? parent : eMsg));
|
|
|
719156 |
diff --git a/tests/netx/unit/net/sourceforge/jnlp/cache/CacheUtilTest.java b/tests/netx/unit/net/sourceforge/jnlp/cache/CacheUtilTest.java
|
|
|
719156 |
index 6422246b..0d2d9811 100644
|
|
|
719156 |
--- a/tests/netx/unit/net/sourceforge/jnlp/cache/CacheUtilTest.java
|
|
|
719156 |
+++ b/tests/netx/unit/net/sourceforge/jnlp/cache/CacheUtilTest.java
|
|
|
719156 |
@@ -88,6 +88,53 @@ public class CacheUtilTest {
|
|
|
719156 |
final File expected = new File("/tmp/https/example.com/5050/applet/e4f3cf11f86f5aa33f424bc3efe3df7a9d20837a6f1a5bbbc60c1f57f3780a4");
|
|
|
719156 |
Assert.assertEquals(expected, CacheUtil.urlToPath(u, "/tmp"));
|
|
|
719156 |
}
|
|
|
719156 |
+
|
|
|
719156 |
+ @Test
|
|
|
719156 |
+ public void tesPathUpNoGoBasic() throws Exception {
|
|
|
719156 |
+ final URL u = new URL("https://example.com/applet/../my.jar");
|
|
|
719156 |
+ final File expected = new File("/tmp/https/example.com/abca4723622ed60db3dea12cbe2402622a74f7a49b73e23b55988e4eee5ded.jar");
|
|
|
719156 |
+ File r = CacheUtil.urlToPath(u, "/tmp/");
|
|
|
719156 |
+ Assert.assertEquals(expected, r);
|
|
|
719156 |
+ }
|
|
|
719156 |
+
|
|
|
719156 |
+ @Test
|
|
|
719156 |
+ public void tesPathUpNoGoBasicLong() throws Exception {
|
|
|
719156 |
+ final URL u = new URL("https://example.com/applet/../my.jar.q_SlNFU1NJT05JRD02OUY1ODVCNkJBOTM1NThCQjdBMTA5RkQyNDZEQjEwRi5wcm9kX3RwdG9tY2F0MjE1X2p2bTsgRW50cnVzdFRydWVQYXNzUmVkaXJlY3RVcmw9Imh0dHBzOi8vZWZzLnVzcHRvLmdvdi9FRlNXZWJVSVJlZ2lzdGVyZWQvRUZTV2ViUmVnaXN0ZXJlZCI7IFRDUFJPRFBQQUlSc2Vzc2lvbj02MjIxMjk0MTguMjA0ODAuMDAwMA\"");
|
|
|
719156 |
+ final File expected = new File("/tmp/https/example.com/ec97413e3f6eee8215ecc8375478cc1ae5f44f18241b9375361d5dfcd7b0ec");
|
|
|
719156 |
+ File r = CacheUtil.urlToPath(u, "/tmp/");
|
|
|
719156 |
+ Assert.assertEquals(expected, r);
|
|
|
719156 |
+ }
|
|
|
719156 |
+
|
|
|
719156 |
+ @Test
|
|
|
719156 |
+ public void tesPathUpNoGoBasic2() throws Exception {
|
|
|
719156 |
+ final URL u = new URL("https://example.com/../my.jar");
|
|
|
719156 |
+ final File expected = new File("/tmp/https/example.com/eb1a56bed34523dbe7ad84d893ebc31a8bbbba9ce3f370e42741b6a5f067c140.jar");
|
|
|
719156 |
+ File r = CacheUtil.urlToPath(u, "/tmp/");
|
|
|
719156 |
+ Assert.assertEquals(expected, r);
|
|
|
719156 |
+ }
|
|
|
719156 |
+
|
|
|
719156 |
+ @Test
|
|
|
719156 |
+ public void tesPathUpNoGoBasicEvil() throws Exception {
|
|
|
719156 |
+ final URL u = new URL("https://example.com/../../my.jar");
|
|
|
719156 |
+ final File expected = new File("/tmp/https/example.com/db464f11d68af73e37eefaef674517b6be23f0e4a5738aaee774ecf5b58f1bfc.jar");
|
|
|
719156 |
+ File r = CacheUtil.urlToPath(u, "/tmp/");
|
|
|
719156 |
+ Assert.assertEquals(expected, r);
|
|
|
719156 |
+ }
|
|
|
719156 |
+
|
|
|
719156 |
+ @Test
|
|
|
719156 |
+ public void tesPathUpNoGoBasicEvil2() throws Exception {
|
|
|
719156 |
+ final URL u = new URL("https://example.com:99/../../../my.jar");
|
|
|
719156 |
+ final File expected = new File("/tmp/https/example.com/99/95401524c345e0d554d4d77330e86c98a77b9bb58a0f93094204df446b356.jar");
|
|
|
719156 |
+ File r = CacheUtil.urlToPath(u, "/tmp/");
|
|
|
719156 |
+ Assert.assertEquals(expected, r);
|
|
|
719156 |
+ }
|
|
|
719156 |
+ @Test
|
|
|
719156 |
+ public void tesPathUpNoGoBasicEvilest() throws Exception {
|
|
|
719156 |
+ final URL u = new URL("https://example2.com/something/../../../../../../../../../../../my.jar");
|
|
|
719156 |
+ final File expected = new File("/tmp/https/example2.com/a8df64388f5b84d5f635e4d6dea5f4d2f692ae5381f8ec6736825ff8d6ff2c0.jar");
|
|
|
719156 |
+ File r = CacheUtil.urlToPath(u, "/tmp/");
|
|
|
719156 |
+ Assert.assertEquals(expected, r);
|
|
|
719156 |
+ }
|
|
|
719156 |
|
|
|
719156 |
|
|
|
719156 |
@Test
|
|
|
719156 |
diff --git a/tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java b/tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java
|
|
|
719156 |
index 100d9150..7580d23b 100644
|
|
|
719156 |
--- a/tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java
|
|
|
719156 |
+++ b/tests/netx/unit/net/sourceforge/jnlp/runtime/JNLPClassLoaderTest.java
|
|
|
719156 |
@@ -43,6 +43,8 @@
|
|
|
719156 |
import java.io.File;
|
|
|
719156 |
import java.io.FileOutputStream;
|
|
|
719156 |
import java.io.InputStream;
|
|
|
719156 |
+import java.net.URL;
|
|
|
719156 |
+import java.nio.charset.Charset;
|
|
|
719156 |
import java.nio.file.Files;
|
|
|
719156 |
import java.util.Arrays;
|
|
|
719156 |
import java.util.List;
|
|
|
719156 |
@@ -55,6 +57,12 @@
|
|
|
719156 |
import net.sourceforge.jnlp.browsertesting.browsers.firefox.FirefoxProfilesOperator;
|
|
|
719156 |
import net.sourceforge.jnlp.cache.UpdatePolicy;
|
|
|
719156 |
import net.sourceforge.jnlp.config.DeploymentConfiguration;
|
|
|
719156 |
+import net.sourceforge.jnlp.config.PathsAndFiles;
|
|
|
719156 |
+import net.sourceforge.jnlp.JNLPFile;
|
|
|
719156 |
+import net.sourceforge.jnlp.ServerAccess;
|
|
|
719156 |
+import net.sourceforge.jnlp.ServerLauncher;
|
|
|
719156 |
+import net.sourceforge.jnlp.util.StreamUtils;
|
|
|
719156 |
+import net.sourceforge.jnlp.cache.CacheUtil;
|
|
|
719156 |
import net.sourceforge.jnlp.mock.DummyJNLPFileWithJar;
|
|
|
719156 |
import net.sourceforge.jnlp.security.appletextendedsecurity.AppletSecurityLevel;
|
|
|
719156 |
import net.sourceforge.jnlp.security.appletextendedsecurity.AppletStartupSecuritySettings;
|
|
|
719156 |
@@ -65,6 +73,7 @@
|
|
|
719156 |
import org.junit.BeforeClass;
|
|
|
719156 |
|
|
|
719156 |
import org.junit.Test;
|
|
|
719156 |
+import org.junit.Ignore;
|
|
|
719156 |
|
|
|
719156 |
public class JNLPClassLoaderTest extends NoStdOutErrTest {
|
|
|
719156 |
|
|
|
719156 |
@@ -138,7 +147,8 @@
|
|
|
719156 |
File tempDirectory = FileTestUtils.createTempDirectory();
|
|
|
719156 |
File jarLocation = new File(tempDirectory, "test.jar");
|
|
|
719156 |
|
|
|
719156 |
- /* Test with main-class in manifest */ {
|
|
|
719156 |
+ /* Test with main-class in manifest */
|
|
|
719156 |
+ {
|
|
|
719156 |
Manifest manifest = new Manifest();
|
|
|
719156 |
manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, "DummyClass");
|
|
|
719156 |
FileTestUtils.createJarWithContents(jarLocation, manifest);
|
|
|
719156 |
@@ -156,8 +166,10 @@
|
|
|
719156 |
}
|
|
|
719156 |
|
|
|
719156 |
@Test
|
|
|
719156 |
+ @Ignore
|
|
|
719156 |
public void getMainClassNameTestEmpty() throws Exception {
|
|
|
719156 |
- /* Test with-out any main-class specified */ {
|
|
|
719156 |
+ /* Test with-out any main-class specified */
|
|
|
719156 |
+ {
|
|
|
719156 |
File tempDirectory = FileTestUtils.createTempDirectory();
|
|
|
719156 |
File jarLocation = new File(tempDirectory, "test.jar");
|
|
|
719156 |
FileTestUtils.createJarWithContents(jarLocation /* No contents */);
|
|
|
719156 |
@@ -363,4 +375,57 @@
|
|
|
719156 |
}
|
|
|
719156 |
|
|
|
719156 |
}
|
|
|
719156 |
+
|
|
|
719156 |
+ @Test
|
|
|
719156 |
+ public void testRelativePathInUrl() throws Exception {
|
|
|
719156 |
+ CacheUtil.clearCache();
|
|
|
719156 |
+ int port = ServerAccess.findFreePort();
|
|
|
719156 |
+ File dir = FileTestUtils.createTempDirectory();
|
|
|
719156 |
+ dir.deleteOnExit();
|
|
|
719156 |
+ dir = new File(dir,"base");
|
|
|
719156 |
+ dir.mkdir();
|
|
|
719156 |
+ File jar = new File(dir,"j1.jar");
|
|
|
719156 |
+ File jnlp = new File(dir+"/a/b/up.jnlp");
|
|
|
719156 |
+ jnlp.getParentFile().mkdirs();
|
|
|
719156 |
+ InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("net/sourceforge/jnlp/runtime/up.jnlp");
|
|
|
719156 |
+ String jnlpString = StreamUtils.readStreamAsString(is, true, "utf-8");
|
|
|
719156 |
+ is.close();
|
|
|
719156 |
+ jnlpString = jnlpString.replaceAll("8080", ""+port);
|
|
|
719156 |
+ is = ClassLoader.getSystemClassLoader().getResourceAsStream("net/sourceforge/jnlp/runtime/j1.jar");
|
|
|
719156 |
+ StreamUtils.copyStream(is, new FileOutputStream(jar));
|
|
|
719156 |
+ Files.write(jnlp.toPath(),jnlpString.getBytes("utf-8"));
|
|
|
719156 |
+ ServerLauncher as = ServerAccess.getIndependentInstance(jnlp.getParent(), port);
|
|
|
719156 |
+ boolean verifyBackup = JNLPRuntime.isVerifying();
|
|
|
719156 |
+ boolean trustBackup= JNLPRuntime.isTrustAll();
|
|
|
719156 |
+ boolean securityBAckup= JNLPRuntime.isSecurityEnabled();
|
|
|
719156 |
+ boolean verbose= JNLPRuntime.isDebug();
|
|
|
719156 |
+ JNLPRuntime.setVerify(false);
|
|
|
719156 |
+ JNLPRuntime.setTrustAll(true);
|
|
|
719156 |
+ JNLPRuntime.setSecurityEnabled(false);
|
|
|
719156 |
+ JNLPRuntime.setDebug(true);
|
|
|
719156 |
+ try {
|
|
|
719156 |
+ final JNLPFile jnlpFile1 = new JNLPFile(new URL("http://localhost:" + port + "/up.jnlp"));
|
|
|
719156 |
+ final JNLPClassLoader classLoader1 = new JNLPClassLoader(jnlpFile1, UpdatePolicy.ALWAYS) {
|
|
|
719156 |
+ @Override
|
|
|
719156 |
+ protected void activateJars(List<JARDesc> jars) {
|
|
|
719156 |
+ super.activateJars(jars);
|
|
|
719156 |
+ }
|
|
|
719156 |
+
|
|
|
719156 |
+ };
|
|
|
719156 |
+ InputStream is1 = classLoader1.getResourceAsStream("Hello1.class");
|
|
|
719156 |
+ is1.close();
|
|
|
719156 |
+ is1 = classLoader1.getResourceAsStream("META-INF/MANIFEST.MF");
|
|
|
719156 |
+ is1.close();
|
|
|
719156 |
+ Assert.assertTrue(new File(PathsAndFiles.CACHE_DIR.getFullPath()+"/0/http/localhost/"+port+"/up.jnlp").exists());
|
|
|
719156 |
+ Assert.assertTrue(new File(PathsAndFiles.CACHE_DIR.getFullPath()+"/1/http/localhost/"+port+"/f812acb32c857fd916c842e2bf4fb32b9c3837ef63922b167a7e163305058b7.jar").exists());
|
|
|
719156 |
+ } finally {
|
|
|
719156 |
+ JNLPRuntime.setVerify(verifyBackup);
|
|
|
719156 |
+ JNLPRuntime.setTrustAll(trustBackup);
|
|
|
719156 |
+ JNLPRuntime.setSecurityEnabled(securityBAckup);
|
|
|
719156 |
+ JNLPRuntime.setDebug(verbose);
|
|
|
719156 |
+ as.stop();
|
|
|
719156 |
+ }
|
|
|
719156 |
+
|
|
|
719156 |
+ }
|
|
|
719156 |
+
|
|
|
719156 |
}
|
|
|
719156 |
diff --git a/tests/netx/unit/net/sourceforge/jnlp/runtime/up.jnlp b/tests/netx/unit/net/sourceforge/jnlp/runtime/up.jnlp
|
|
|
719156 |
new file mode 100644
|
|
|
719156 |
index 00000000..b22fdfb7
|
|
|
719156 |
--- /dev/null
|
|
|
719156 |
+++ b/tests/netx/unit/net/sourceforge/jnlp/runtime/up.jnlp
|
|
|
719156 |
@@ -0,0 +1,15 @@
|
|
|
719156 |
+
|
|
|
719156 |
+<jnlp spec="6.0+" codebase=".">
|
|
|
719156 |
+
|
|
|
719156 |
+<information><title>1965</title><vendor>Nemzeti Ado- es Vamhivatal</vendor><offline-allowed/></information>
|
|
|
719156 |
+
|
|
|
719156 |
+
|
|
|
719156 |
+<resources>
|
|
|
719156 |
+ <j2se href="http://java.sun.com/products/autodl/j2se" version="1.8+" />
|
|
|
719156 |
+
|
|
|
719156 |
+ <jar href="http://localhost:8080/../../../base/j1.jar" version="2.0"/>
|
|
|
719156 |
+</resources>
|
|
|
719156 |
+
|
|
|
719156 |
+<application-desc main-class="Hello1" />
|
|
|
719156 |
+
|
|
|
719156 |
+</jnlp>
|