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