diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharPool.java b/hutool-core/src/main/java/cn/hutool/core/text/CharPool.java index 9314665a0..2a4fa28fe 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/CharPool.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/CharPool.java @@ -95,9 +95,4 @@ public interface CharPool { * 字符常量:等于 {@code '='} */ char EQUAL = '='; - /** - * 字符常量:减号 {@code '-'} - */ - char MINUS = '-'; - } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/csv/CsvWriteConfig.java b/hutool-poi/src/main/java/cn/hutool/poi/csv/CsvWriteConfig.java index 9548cbc21..f75b9c6ed 100755 --- a/hutool-poi/src/main/java/cn/hutool/poi/csv/CsvWriteConfig.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/csv/CsvWriteConfig.java @@ -57,7 +57,8 @@ public class CsvWriteConfig extends CsvConfig implements Seriali } /** - * 设置是否动态数据交换安全,使用文本包装符包裹可能存在DDE攻击的内容 + * 设置是否动态数据交换安全,使用文本包装符包裹可能存在DDE攻击的内容
+ * 见:https://blog.csdn.net/weixin_41924764/article/details/108665746 * * @param ddeSafe dde安全 * @return this diff --git a/hutool-poi/src/main/java/cn/hutool/poi/csv/CsvWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/csv/CsvWriter.java index 838923463..160fb1bc2 100755 --- a/hutool-poi/src/main/java/cn/hutool/poi/csv/CsvWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/csv/CsvWriter.java @@ -1,12 +1,12 @@ package cn.hutool.poi.csv; import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.collection.iter.ArrayIter; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.iter.ArrayIter; import cn.hutool.core.convert.Convert; -import cn.hutool.core.io.file.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.file.FileUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; @@ -14,13 +14,7 @@ import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjUtil; -import java.io.BufferedWriter; -import java.io.Closeable; -import java.io.File; -import java.io.Flushable; -import java.io.IOException; -import java.io.Serializable; -import java.io.Writer; +import java.io.*; import java.nio.charset.Charset; import java.util.List; import java.util.Map; @@ -181,8 +175,9 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { } /** - * 设置是否启用dde安全模式,默认false,按需修改 - * 防止使用Excel打开csv文件时存在dde攻击风险 + * 设置是否启用dde安全模式,默认false,按需修改
+ * 防止使用Excel打开csv文件时存在dde攻击风险
+ * 注意此方法会在字段第一个字符包含{@code = + - @}时添加{@code '}作为前缀,防止公式执行 * * @param ddeSafe 是否启用 dde 安全模式 * @return this @@ -247,6 +242,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { * @param beans Bean集合 * @return this */ + @SuppressWarnings("resource") public CsvWriter writeBeans(final Iterable beans) { if (CollUtil.isNotEmpty(beans)) { boolean isFirst = true; @@ -331,10 +327,10 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { public CsvWriter writeComment(final String comment) { Assert.notNull(this.config.commentCharacter, "Comment is disable!"); try { - if(isFirstLine){ + if (isFirstLine) { // 首行不补充换行符 isFirstLine = false; - }else { + } else { writer.write(config.lineDelimiter); } writer.write(this.config.commentCharacter); @@ -384,10 +380,10 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { */ private void doAppendLine(final String... fields) throws IOException { if (null != fields) { - if(isFirstLine){ + if (isFirstLine) { // 首行不补换行符 isFirstLine = false; - }else { + } else { writer.write(config.lineDelimiter); } for (final String field : fields) { @@ -425,11 +421,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { boolean needsTextDelimiter = alwaysDelimitText; boolean containsTextDelimiter = false; - for (int i = 0; i < valueChars.length; i++) { - char c = valueChars[i]; - if(i==0 && (c == CharUtil.AT || c == CharUtil.PLUS || c == CharUtil.MINUS || c == CharUtil.EQUAL)){ - needsTextDelimiter = true; - } + for (final char c : valueChars) { if (c == textDelimiter) { // 字段值中存在包装符 containsTextDelimiter = needsTextDelimiter = true; @@ -445,10 +437,15 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { writer.write(textDelimiter); } + // DDE防护,打开不执行公式 + if (config.ddeSafe && isDDEUnsafeChar(valueChars[0])) { + writer.write('\''); + } + // 正文 if (containsTextDelimiter) { for (final char c : valueChars) { - // 转义文本包装符 + // 转义文本包装符,如"转义为"" if (c == textDelimiter) { writer.write(textDelimiter); } @@ -463,5 +460,24 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { writer.write(textDelimiter); } } + + /** + * 给定字符是否为DDE攻击不安全的字符,包括: + * + * + * @param c 被检查的字符 + * @return 是否不安全的字符 + */ + private static boolean isDDEUnsafeChar(final char c) { + return c == CharUtil.AT || + c == CharUtil.PLUS || + c == CharUtil.DASHED || + c == CharUtil.EQUAL; + } // --------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-poi/src/test/java/cn/hutool/poi/csv/CsvWriterTest.java b/hutool-poi/src/test/java/cn/hutool/poi/csv/CsvWriterTest.java index 793c5063b..7af2f302f 100755 --- a/hutool-poi/src/test/java/cn/hutool/poi/csv/CsvWriterTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/csv/CsvWriterTest.java @@ -3,11 +3,10 @@ package cn.hutool.poi.csv; import cn.hutool.core.io.file.FileUtil; import cn.hutool.core.lang.Console; import cn.hutool.core.util.CharsetUtil; -import java.io.File; -import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -48,21 +47,20 @@ public class CsvWriterTest { } @Test + @Ignore public void issue3014Test(){ - File tmp = new File("/Users/test/Desktop/test.csv"); - CsvWriter writer = CsvUtil.getWriter(tmp, CharsetUtil.UTF_8); + // https://blog.csdn.net/weixin_42522167/article/details/112241143 + final File tmp = new File("d:/test/dde_safe.csv"); + final CsvWriter writer = CsvUtil.getWriter(tmp, CharsetUtil.UTF_8); //设置 dde 安全模式 writer.setDdeSafe(true); writer.write( new String[] {"=12+23"}, + new String[] {"%0A=12+23"}, + new String[] {"=;=12+23"}, new String[] {"-3+2+cmd |' /C calc' !A0"}, new String[] {"@SUM(cmd|'/c calc'!A0)"} ); writer.close(); - - List lines = FileUtil.readLines(tmp, CharsetUtil.UTF_8); - Assert.assertEquals("\"=12+23\"",lines.get(0)); - Assert.assertEquals("\"-3+2+cmd |' /C calc' !A0\"",lines.get(1)); - Assert.assertEquals("\"@SUM(cmd|'/c calc'!A0)\"",lines.get(2)); } }