diff --git a/README-EN.md b/README-EN.md
index 3b66ea7f5..20d702170 100755
--- a/README-EN.md
+++ b/README-EN.md
@@ -40,8 +40,8 @@
-
-
+
+
-------------------------------------------------------------------------------
diff --git a/README.md b/README.md
index dbfc75c43..622c5490f 100755
--- a/README.md
+++ b/README.md
@@ -40,8 +40,8 @@
-
-
+
+
-------------------------------------------------------------------------------
diff --git a/hutool-core/src/main/java/cn/hutool/core/compress/ZipReader.java b/hutool-core/src/main/java/cn/hutool/core/compress/ZipReader.java
index 9a3d840b0..3055d8e90 100755
--- a/hutool-core/src/main/java/cn/hutool/core/compress/ZipReader.java
+++ b/hutool-core/src/main/java/cn/hutool/core/compress/ZipReader.java
@@ -1,5 +1,6 @@
package cn.hutool.core.compress;
+import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
@@ -25,6 +26,9 @@ import java.util.zip.ZipInputStream;
*/
public class ZipReader implements Closeable {
+ // size of uncompressed zip entry shouldn't be bigger of compressed in MAX_SIZE_DIFF times
+ private static final int MAX_SIZE_DIFF = 100;
+
private ZipFile zipFile;
private ZipInputStream in;
@@ -202,7 +206,7 @@ public class ZipReader implements Closeable {
private void readFromZipFile(final Consumer consumer) {
final Enumeration extends ZipEntry> em = zipFile.entries();
while (em.hasMoreElements()) {
- consumer.accept(em.nextElement());
+ consumer.accept(checkZipBomb(em.nextElement()));
}
}
@@ -215,11 +219,32 @@ public class ZipReader implements Closeable {
private void readFromStream(final Consumer consumer) throws IORuntimeException {
try {
ZipEntry zipEntry;
- while (null != (zipEntry = in.getNextEntry())) {
+ while (null != (zipEntry = checkZipBomb(in.getNextEntry()))) {
consumer.accept(zipEntry);
}
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}
+
+ /**
+ * 检查Zip bomb漏洞
+ *
+ * @param entry {@link ZipEntry}
+ * @return 检查后的{@link ZipEntry}
+ */
+ private static ZipEntry checkZipBomb(final ZipEntry entry) {
+ if (null == entry) {
+ return null;
+ }
+ final long compressedSize = entry.getCompressedSize();
+ final long uncompressedSize = entry.getSize();
+ if (compressedSize < 0 || uncompressedSize < 0 ||
+ // 默认压缩比例是100倍,一旦发现压缩率超过这个阈值,被认为是Zip bomb
+ compressedSize * MAX_SIZE_DIFF < uncompressedSize) {
+ throw new ValidateException("Zip bomb attack detected, invalid sizes: compressed {}, uncompressed {}, name {}",
+ compressedSize, uncompressedSize, entry.getName());
+ }
+ return entry;
+ }
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/compress/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/compress/ZipUtil.java
index 8eaa4222d..46999dd52 100644
--- a/hutool-core/src/main/java/cn/hutool/core/compress/ZipUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/compress/ZipUtil.java
@@ -2,13 +2,14 @@ package cn.hutool.core.compress;
import cn.hutool.core.collection.iter.EnumerationIter;
import cn.hutool.core.exceptions.UtilException;
-import cn.hutool.core.io.stream.FastByteArrayOutputStream;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileSystemUtil;
import cn.hutool.core.io.file.PathUtil;
import cn.hutool.core.io.resource.Resource;
+import cn.hutool.core.io.stream.FastByteArrayOutputStream;
+import cn.hutool.core.io.stream.LimitedInputStream;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.CharsetUtil;
@@ -77,7 +78,7 @@ public class ZipUtil {
*/
public static InputStream getStream(final ZipFile zipFile, final ZipEntry zipEntry) {
try {
- return zipFile.getInputStream(zipEntry);
+ return new LimitedInputStream(zipFile.getInputStream(zipEntry), zipEntry.getSize());
} catch (final IOException e) {
throw new IORuntimeException(e);
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/stream/LimitedInputStream.java b/hutool-core/src/main/java/cn/hutool/core/io/stream/LimitedInputStream.java
new file mode 100755
index 000000000..d2149358f
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/io/stream/LimitedInputStream.java
@@ -0,0 +1,65 @@
+package cn.hutool.core.io.stream;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 限制读取最大长度的{@link FilterInputStream} 实现
+ * 来自:https://github.com/skylot/jadx/blob/master/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/utils/LimitedInputStream.java
+ *
+ * @author jadx
+ */
+public class LimitedInputStream extends FilterInputStream {
+
+ private final long maxSize;
+ private long currentPos;
+
+ /**
+ * 构造
+ *
+ * @param in {@link InputStream}
+ * @param maxSize 限制最大读取量,单位byte
+ */
+ public LimitedInputStream(final InputStream in, final long maxSize) {
+ super(in);
+ this.maxSize = maxSize;
+ }
+
+ @Override
+ public int read() throws IOException {
+ final int data = super.read();
+ if (data != -1) {
+ currentPos++;
+ checkPos();
+ }
+ return data;
+ }
+
+ @SuppressWarnings("NullableProblems")
+ @Override
+ public int read(final byte[] b, final int off, final int len) throws IOException {
+ final int count = super.read(b, off, len);
+ if (count > 0) {
+ currentPos += count;
+ checkPos();
+ }
+ return count;
+ }
+
+ @Override
+ public long skip(final long n) throws IOException {
+ final long skipped = super.skip(n);
+ if (skipped != 0) {
+ currentPos += skipped;
+ checkPos();
+ }
+ return skipped;
+ }
+
+ private void checkPos() {
+ if (currentPos > maxSize) {
+ throw new IllegalStateException("Read limit exceeded");
+ }
+ }
+}