此文章主要是针对生成word相关框架的使用和避雷,这篇文章的产生原因是领导说我漏洞描述修复建议口语化随便写点,没有标准化,于是花了两天写了个生成报告的小工具,这里分享我的排坑记录。
01
界面一览
自用小工具界面直接让AI写的微调,丑的话别喷。
02
核心依赖
Java POI + spire.doc for Java
01
POI
poi-tl(poi template language)是Word模板引擎,使用模板和数据创建很棒的Word文档。
poi-tl是一个基于Apache POI的Word模板引擎,也是一个免费开源的Java类库,可以非常方便的加入到你的项目中,并且拥有着让人喜悦的特性。官方网站:http://deepoove.com/poi-tl/
这个库主要是用于基于模板填充数据生成新word,但是这个库对目录更新功能比较欠缺,所以使用spire.doc去更新目录。
02
Spire.doc
这个库不多说,好用但收费,我们只用他更新目录这一个功能即可。
03
开发过程
01
关键依赖
<!-- Spring Expression Language (SpEL) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</dependency>
<dependency>
<groupId>e-iceblue</groupId>
<artifactId>spire.doc</artifactId>
<version>13.1.3</version>
</dependency>
<!-- poi-tl (基于 Apache POI 的模板引擎) -->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.1</version><!-- Updated to the latest version -->
<exclusions>
<!-- Exclude conflicting dependencies -->
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-transcoder</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Apache POI (核心库) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version>
</dependency>
<!-- Apache POI (处理 .docx 文件的库) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<!-- Lombok (简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<!--这里不进行依赖去除会有冲突执行会报错-》bug1-->
02
POI语法
先准备一分template.docx:
用{}包裹我们要替换的变量。
// 文本替换
private Map<String, Object> rawData = new HashMap<>();
rawData.put("CompanyName", "测试公司");
漏洞统计:
这里核心的表格代码用{{#vulnTable}}进行插入
TableRenderData riskTable = new TableRenderData();
TableStyle riskTableStyle = new TableStyle();
riskTableStyle.setWidth("100%");
riskTable.setTableStyle(riskTableStyle);
//创建表头
RowRenderData row0 = Rows.of("序号", "应用名称","风险说明", "漏洞等级", "修复情况").textColor("FFFFFF")
.bgColor("31322C").center().create();
riskTable.addRow(row0);
Integer highLevelNum = 0;
Integer mediumLevelNum = 0;
Integer lowLevelNum = 0;
if (vulnerabilityEntityList.size() > 0){
for (int i = 0; i < vulnerabilityEntityList.size(); i++) {
String levelName = "";
if (vulnerabilityEntityList.get(i).getLevel() == 1){
levelName = "低危";
lowLevelNum += 1;
} elseif (vulnerabilityEntityList.get(i).getLevel() == 2) {
levelName = "中危";
mediumLevelNum += 1;
} elseif (vulnerabilityEntityList.get(i).getLevel() == 3) {
levelName = "高危";
highLevelNum += 1;
}
//生成每行
RowRenderData row = Rows.create(String.valueOf(i+1), wordInfo.getSystemName(), vulnerabilityEntityList.get(i).getVulnTitle(), levelName, "/");
riskTable.addRow(row);
}
}
wordInfo.setHighNum(highLevelNum);
wordInfo.setMediumNum(mediumLevelNum);
wordInfo.setLowNum(lowLevelNum);
rawData.put("vulnTable", riskTable);
报告详情(用遍历):
List<Map<String, Object>> dataList = new ArrayList<>();
if (vulnerabilityEntityList.size() > 0){
for (int i = 0; i < vulnerabilityEntityList.size(); i++) {
Map<String, Object> data = new HashMap<>();
data.put("title", vulnerabilityEntityList.get(i).getVulnTitle());
data.put("url", vulnerabilityEntityList.get(i).getVulnUrl());
data.put("description", vulnerabilityEntityList.get(i).getDescribe());
String levelName = "";
//这里可以换成枚举类,给自己写的小玩意我就没正常处理,弱智一点
if (vulnerabilityEntityList.get(i).getLevel() == 1){
levelName = "低危";
} elseif (vulnerabilityEntityList.get(i).getLevel() == 2) {
levelName = "中危";
} elseif (vulnerabilityEntityList.get(i).getLevel() == 3) {
levelName = "高危";
}
data.put("risk", levelName);
data.put("content", wordTemplateService.DealVulnsContent(vulnerabilityEntityList.get(i).getVulnContent()));
data.put("recommendation", vulnerabilityEntityList.get(i).getFixAdvice());
dataList.add(data);
}
}
rawData.put("dataList", dataList);
02
spire.doc来更新目录
publicclassWordUtil {
publicstaticvoidupdateTOC(String outputPath) {
Document doc = newDocument();
doc.loadFromFile(outputPath);
// 更新文档中的所有字段(包括目录)
doc.updateTableOfContents();
// 保存更新后的文档
doc.saveToFile(outputPath, FileFormat.Docx);
restWord(outputPath);
}
public static void restWord(String docFilePath) {
try (FileInputStream in = new FileInputStream(docFilePath)) {
XWPFDocument doc = new XWPFDocument(OPCPackage.open(in));
List<XWPFParagraph> paragraphs = doc.getParagraphs();
if (paragraphs.size() < 1) {
return;
}
XWPFParagraph firstParagraph = paragraphs.get(0);
if (firstParagraph.getText().contains("Spire.Doc")) {
doc.removeBodyElement(doc.getPosOfParagraph(firstParagraph));
}
OutputStream out = new FileOutputStream(docFilePath);
doc.write(out);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
为什么要封装两下,因为updateTOC是真正的更新目录,但是咱没有付费,他会在文档里面生成一个水印,我们需要重新对文档进行处理把他的文字水印去掉。=》这个我用docx4j也处理过,效果不理想,最终才选择这个方案,卡我好几个小时。
03
注意
用到模板渲染这个功能就会存在模板注入的情况,在做系统的时候要考虑到,高版本JDK+部分逻辑处理预防一下。
04
小结
好耶!又水一篇,平时有啥技术交流的可以后台找我噻。兄弟们,祝大家新年快乐!
原文始发于微信公众号(阿呆攻防):开发|自动化渗透报告生成系统核心代码
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论