避免公式注入,保持单元格值(HSSF / * .xls中的引号前缀)

时间:2016-11-03 10:08:43

标签: java apache-poi

我正在处理的应用程序使用Apache POI创建Excel导出。通过安全审计,我们注意到,如果用户不够谨慎,那么包含恶意值的单元格可能会产生任意进程。

要重现,请运行以下命令:

import java.io.FileOutputStream;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

public class BadWorkbookCreator {
    public static void main(String[] args) throws Exception {
        try(
            Workbook wb = new HSSFWorkbook();
            FileOutputStream fos = new FileOutputStream("C:/workbook-bad.xls")
        ) {
            Sheet sheet = wb.createSheet("Sheet");
            Row row = sheet.createRow(0);
            row.createCell(0).setCellValue("Aaaaaaaaaa");
            row.createCell(1).setCellValue("-2+3 +cmd|'/C calc'!G20");
            wb.write(fos);
        }
    }
}

然后打开生成的文件:

Screenshot to reproduce problem

然后按照以下步骤操作:

  1. 单击(A)以选择包含恶意内容的单元格
  2. 单击(B)以使光标位于公式编辑器中
  3. 按ENTER键
  4. 系统将询问您是否允许Excel运行外部应用程序;如果您回答是,则启动Calc(或任何恶意代码)
  5. 可以说用户负责让Excel运行任意内容并且警告用户。但是,Excel仍然是从受信任的来源下载的,有人可能会陷入陷阱。

    使用Excel,您可以在公式编辑器中的文本前放置单引号以对其进行转义。以编程方式将单引号放在单元格内容中(例如下面的代码)使单引号可见!

    String cellValue = cell.getStringCellValue();
    if( cellValue != null && "=-+@".indexOf(cellValue.charAt(0)) >= 0 ) {
        cell.setCellValue("'" + cellValue);
    }
    

    问题:有没有办法在公式编辑器中保留值转义,但在单元格中显示正确的值,而没有前导单引号?

    Desired outcome, when hand-edited with Excel

2 个答案:

答案 0 :(得分:4)

感谢Axel Richter hereNikos Paraskevopoulos here ....

的辛勤工作

从Apache POI 3.16 beta 1起(或者对于生活危险的人,2016-1105之后的任何夜间构建),CellStyle getQuotePrefixedsetQuotePrefixed(boolean)上有方便的方法

您的代码可能会变为:

// Do this once for the workbook
CellStyle safeFormulaStyle = workbook.createCellStyle();
safeFormulaStyle.setQuotePrefixed(true);


// Per cell
String cellValue = cell.getStringCellValue();
if( cellValue != null && "=-+@".indexOf(cellValue.charAt(0)) >= 0 ) {
    cell.setCellStyle(safeFormulaStyle);
}

答案 1 :(得分:0)

感谢来自POI团队的即时(kudos)响应(请参阅已接受的答案),此解决方案应该已过时。保留它作为参考,在无法升级到POI> = 3.16的情况下非常有用。

感谢Axel Richter的评论(我非常感谢),我设法找到了解决方案。它绝对不像XLSX文件(XSSFWorkbook)那样简单,因为它涉及手工创建org.apache.poi.hssf.model.InternalWorkbook; POI项目将此类标记为@Internal,但就Java而言,它是public。此外,设置为纠正问题的字段,即ExtendedFormatRecord.set123Prefix(true)未记录!

以下是解决方案,了解它的价值 - 将其与问题中的代码进行比较:

import java.io.FileOutputStream;

import org.apache.poi.hssf.model.InternalWorkbook;
import org.apache.poi.hssf.record.ExtendedFormatRecord;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;

public class GoodWorkbookCreator {
    public static void main(String[] args) throws Exception {
        InternalWorkbook internalWorkbook = InternalWorkbook.createWorkbook();
        try(
            HSSFWorkbook wb = HSSFWorkbook.create(internalWorkbook);
            FileOutputStream fos = new FileOutputStream("C:/workbook-good.xls")
        ) {
            HSSFCellStyle style = (HSSFCellStyle) wb.createCellStyle();
            ExtendedFormatRecord xfr = internalWorkbook.getExFormatAt(internalWorkbook.getNumExFormats() - 1);
            xfr.set123Prefix(true); // THIS IS WHAT IT IS ALL ABOUT

            Sheet sheet = wb.createSheet("Sheet");
            Row row = sheet.createRow(0);
            row.createCell(0).setCellValue("Aaaaaaaaaa");
            row.createCell(1).setCellValue("-2+3 +cmd|'/C calc'!G20");
            Cell cell = row.createCell(2);
            cell.setCellValue("-2+3 +cmd|'/C calc'!G20");
            cell.setCellStyle(style);
            wb.write(fos);
        }
    }
}