项目经过360代码卫士扫描以后或多或少有些漏洞,既然公司要求扫描,那就会要求修复,进入正题,如文章标题,本篇文章主要涉及一个jar包工具,功能主要实现如下功能:
-
解决项目中360扫描出来的【输入验证》日志伪造】漏洞
解决前:
logger.debug(“xxxxx”+message);
解决后:
String logDebugStr = StringEscapeUtils.escapeEcmaScript(“xxxxx”+message);
logger.debug(logDebugStr);解决代码包括:logger.info logger.error logger.debug logger.trace ,日志输出内容包括字符串,对象,堆栈信息
-
删除项目中java类中的main方法,main方法被360扫描为低级》遗留调试代码
小项目或者比较规范的项目可能这种问题会比较少,可以手工修改,但如果老项目或者开发人员比较多,规范性不够好,这种漏洞可能就非常的多,这种漏洞可分布在360代码卫士的高级、中级、低级漏洞中。下面贴上jar包源码
readme.txt
该文件为jar使用说明
-------------------------------------------------------------------------------------------------------------------
功能:
1.解决项目中360扫描出来的【输入验证》日志伪造】漏洞
解决前: logger.debug("xxxxx"+message);
解决前: String logDebugStr = StringEscapeUtils.escapeEcmaScript("xxxxx"+message);
logger.debug(logDebugStr);
解决代码包括:logger.info logger.error logger.debug logger.trace ,日志输出内容包括字符串,对象,堆栈信息
2.删除项目中java类中的main方法,改方法被360扫描为低级》遗留调试代码
操作步骤:
1.修改security.properties配置文件,根据提示配置
2.点击run.bat运行
3.查看log.txt查看处理结果
-------------------------------------------------------------------------------------------------------------------
security.properties
该配置文件是jar使用的核心配置文件,配置项都有说明
#项目绝对路径,例如E:/workspace/BDC
project.dir=E:\\workspace\\BDC\\rt_plt\\java\\com\\zhuxl\\sims\\rtplt\\component\\alarm\\service
#1:日志伪造处理,2:删除main方法
operation=1
#源文件编码格式【gbk|utf-8】
file.encoding=gbk
#全局日志变量名称,例如logger或LOGGER
logger.variable.name=LOGGER
#要处理的日志输出级别,【info|debug|error|trace|warn】
logger.level=debug
#不处理的日志输出行所包含的关键字符串,为了避免重复处理以及有些不需要处理
logger.skip.content=logEscapedStr
Main.java
jar包入口方法,读取配置security.properties
,按照配置来执行日志伪造或者删除main的动作
package com.zhuxl.bdc.security;
import com.zhuxl.bdc.security.result.HandleResult;
import com.zhuxl.bdc.security.tool.DeleteMainTool;
import com.zhuxl.bdc.security.tool.LoggerEscapeTool;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.util.Properties;
/**
* @author zhuxiaolong
*/
public class Main {
public static void main(String[] args) throws FileNotFoundException {
Properties prop = new Properties();
InputStream is = new FileInputStream("security.properties");
try {
prop.load(is);
} catch (IOException e) {
e.printStackTrace();
}
String projectPath = prop.getProperty("project.dir");
File project = new File(projectPath);
if (!project.exists()) {
throw new RuntimeException("配置的项目路径不存在" + projectPath);
} else {
System.out.println("配置项目路径:" + projectPath);
}
String operation = prop.getProperty("operation");
if (!StringUtils.equalsAny(operation, "1", "2")) {
throw new RuntimeException("配置的操作类型有误,正确的为1或2,实际配置" + operation);
} else {
System.out.println("配置的操作类型:" + operation);
}
String encoding = prop.getProperty("file.encoding");
if (!StringUtils.equalsAny(encoding, "gbk", "utf-8")) {
throw new RuntimeException("配置的操作类型有误,正确的为gbk或utf-8,实际配置" + encoding);
} else {
System.out.println("配置的文件编码格式:" + encoding);
}
HandleResult handleResult = null;
switch (operation) {
case "1":
String loggerName = prop.getProperty("logger.variable.name");
System.out.println("配置的日志变量名" + loggerName);
String level = prop.getProperty("logger.level");
if (!StringUtils.equalsAny(level, "info", "debug", "error", "trace")) {
throw new RuntimeException("配置的日志输出级别有误,正确的配置项info/debug/error/trace,实际配置" + level);
} else {
System.out.println("配置的日志输出级别" + level);
}
String skipContent = prop.getProperty("logger.skip.content");
handleResult = Executor.exe(project, LoggerEscapeTool.class, loggerName, level, encoding, skipContent);
break;
case "2":
handleResult = Executor.exe(project, DeleteMainTool.class, encoding);
break;
default:
System.out.println("错误的指令");
}
System.out.println("------------------------------------------------------------------------");
System.out.println(" 处理成功,处理java文件个数" + handleResult.getFileCount());
System.out.println(" 处理日志伪造记录" + handleResult.getRecordCount());
System.out.println("------------------------------------------------------------------------");
}
}
HandleResult.java
该类为处理结果统计信息
package com.zhuxl.bdc.security.result;
import lombok.Data;
/**
* @author zhuxiaolong
*/
@Data
public class HandleResult {
private Integer fileCount;
private Integer recordCount;
public HandleResult() {
this.fileCount = 0;
this.recordCount = 0;
}
}
CallbackFileHandler.java
抽象类回调函数,提供一个处理单个文件的抽象方法,用于日志伪造和删除main的具体实现类扩展
package com.zhuxl.bdc.security.func;
import com.zhuxl.bdc.security.result.HandleResult;
import lombok.Getter;
import java.io.File;
/**
* @author zhuxiaolong
*/
@Getter
public abstract class CallbackFileHandler {
protected HandleResult handleResult = new HandleResult();
public void handleFile(File file, String... cmd) {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
handleFile(f, cmd);
}
} else {
String fileName = file.getName();
if (fileName.contains(".")) {
String extendName = fileName.substring(fileName.lastIndexOf(".") + 1);
if ("java".equals(extendName)) {
// java文件
// 处理文件,处理单个java文件内容中logger.debug
handleSingleFile(file, cmd);
}
}
}
}
/**
* 处理单个文件
*
* @param file 处理文件
* @param cmd 处理文件时的额外参数
*/
protected abstract void handleSingleFile(File file, String... cmd);
}
Executor.java
执行器,创建一个处理文件的实例,执行处理文件的具体逻辑并返回处理统计信息
package com.zhuxl.bdc.security;
import com.zhuxl.bdc.security.func.CallbackFileHandler;
import com.zhuxl.bdc.security.result.HandleResult;
import java.io.File;
/**
* @author zhuxiaolong
*/
public class Executor {
public static HandleResult exe(File file, Class<? extends CallbackFileHandler> clazz, String... cmd) {
try {
CallbackFileHandler handler = clazz.newInstance();
handler.handleFile(file, cmd);
return handler.getHandleResult();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
throw new RuntimeException("处理失败");
}
}
LoggerEscapeTool.java
日志伪造处理实现类,主要是通过common-lang3
的StringEscapeUtils.escapeEcmaScript()
方法进行转义,解决360扫描出的日志伪造漏洞
package com.zhuxl.bdc.security.tool;
import com.zhuxl.bdc.security.util.StreamUtils;
import com.zhuxl.bdc.security.func.CallbackFileHandler;
import java.io.*;
/**
* @author zhuxiaolong
*/
public class LoggerEscapeTool extends CallbackFileHandler {
@Override
public void handleSingleFile(File file, String... cmd) {
if (cmd.length != 4) {
throw new RuntimeException("输入的指令有误");
}
handleResult.setFileCount(handleResult.getFileCount()+1);
final String prefix = cmd[0] + "." + cmd[1] + "(";
BufferedReader reader = null;
BufferedWriter writer = null;
InputStreamReader inputStreamReader = null;
FileInputStream fileInputStream = null;
OutputStreamWriter outputStreamWriter = null;
FileOutputStream fileOutputStream = null;
try {
fileInputStream = new FileInputStream(file);
inputStreamReader = new InputStreamReader(fileInputStream, cmd[2]);
reader = new BufferedReader(inputStreamReader);
StringBuilder content = new StringBuilder();
String line;
int lineNumber = 0;
while ((line = reader.readLine()) != null) {
lineNumber++;
String templine = line.trim();
final String suffix = ");";
final String suffixE = ", e);";
final String logSuffix;
if (templine.startsWith(prefix) && templine.endsWith(suffix) && !templine.contains(cmd[3])) {
// 去掉左右空白,如果以logger.debug(开头,以);结尾,视为打印日志的语句
String logContent = templine.substring(prefix.length(), templine.lastIndexOf(suffix));
String leftEmpty = line.substring(0, line.indexOf(prefix));
String logNewContent;
if (templine.endsWith(suffixE)) {
// 打印的是包含堆栈信息的日志
logContent = templine.substring(prefix.length(), templine.lastIndexOf(suffixE));
logSuffix = suffixE;
} else {
// 打印的是普通日志信息
logSuffix = suffix;
}
logNewContent = new StringBuilder()
.append(leftEmpty).append("String logEscapedStr").append(lineNumber)
.append(" = ")
.append("StringEscapeUtils.escapeEcmaScript(\"\"+").append(logContent).append(");\n")
.append(leftEmpty).append(cmd[0]).append(".").append(cmd[1]).append("(logEscapedStr").append(lineNumber).append(logSuffix)
.toString();
lineNumber++;
content.append(logNewContent).append("\n");
handleResult.setRecordCount(handleResult.getRecordCount()+1);
} else {
if (line.trim().startsWith("package com")) {
content.append(line).append("\n")
.append("import org.apache.commons.lang3.StringEscapeUtils;").append("\n");
lineNumber++;
} else {
content.append(line).append("\n");
}
}
}
fileOutputStream = new FileOutputStream(file);
outputStreamWriter = new OutputStreamWriter(fileOutputStream, cmd[2]);
writer = new BufferedWriter(outputStreamWriter);
// 写入
writer.write(content.toString());
writer.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
StreamUtils.close(reader);
StreamUtils.close(writer);
StreamUtils.close(fileInputStream);
StreamUtils.close(inputStreamReader);
StreamUtils.close(fileOutputStream);
StreamUtils.close(outputStreamWriter);
}
}
}
DeleteMainTool.java
删除main方法的具体实现类,注意:本方法有弊端,比如main方法内有注释,注释内有不成对的{}出现,可能会造成删除混乱,不过这种情况一般很少。
package com.zhuxl.bdc.security.tool;
import com.zhuxl.bdc.security.util.StreamUtils;
import com.zhuxl.bdc.security.func.CallbackFileHandler;
import java.io.*;
/**
* @author zhuxiaolong
*/
public class DeleteMainTool extends CallbackFileHandler {
@Override
public void handleSingleFile(File file, String... cmd) {
handleResult.setFileCount(handleResult.getFileCount() + 1);
if (cmd.length != 1) {
throw new RuntimeException("配置的参数有误");
}
BufferedReader reader = null;
BufferedWriter writer = null;
InputStreamReader inputStreamReader = null;
FileInputStream fileInputStream = null;
OutputStreamWriter outputStreamWriter = null;
FileOutputStream fileOutputStream = null;
try {
fileInputStream = new FileInputStream(file);
inputStreamReader = new InputStreamReader(fileInputStream, cmd[0]);
reader = new BufferedReader(inputStreamReader);
StringBuilder content = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
String con = delMainMethod(content.toString());
handleResult.setRecordCount(handleResult.getRecordCount() + 1);
fileOutputStream = new FileOutputStream(file);
outputStreamWriter = new OutputStreamWriter(fileOutputStream, cmd[0]);
writer = new BufferedWriter(outputStreamWriter);
// 写入
writer.write(con);
writer.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
StreamUtils.close(reader);
StreamUtils.close(writer);
StreamUtils.close(fileInputStream);
StreamUtils.close(inputStreamReader);
StreamUtils.close(fileOutputStream);
StreamUtils.close(outputStreamWriter);
}
}
public String delMainMethod(String content) {
int publicStart = -1, mainStart = -1;
int checkStart = -1;
String tmp = null, tmp2 = null;
mainStart = content.lastIndexOf("main");
String tmpContent = new String(content);
// 得到真正的main方法
while ((mainStart = tmpContent.lastIndexOf("main")) != -1) {
tmp2 = new String(tmpContent.substring(mainStart));
checkStart = tmp2.indexOf(")");
tmp2 = new String(tmpContent.substring(mainStart - 1, mainStart
+ checkStart + 1));
tmp2 = tmp2.replaceAll("#_end_#", "\n");
tmp2 = tmp2.replaceAll("\\n", "");
tmp2 = tmp2.replaceAll("\\s+", "");
if (tmp2.startsWith("main(String") && (tmp2.indexOf(",") == -1)) {
break;
}
tmpContent = new String(tmpContent.substring(0, mainStart));
}
if (mainStart != -1) {
tmp = new String(content.substring(0, mainStart));
// 得到main方法的开始位置
publicStart = tmp.lastIndexOf("public");
}
if (publicStart != -1) {
tmp = new String(content.substring(mainStart));
}
if (tmp == null) {
// 去掉多余空白行
content = content.replaceAll("#_end_#", "\r\n");
content = content.replaceAll("\\s{2,}\\r\\n", "\r\n");
return content;
}
// 得到main方法的结束位置
char[] tmpChar = tmp.toCharArray();
int bracketStart = 0, bracketEnd = 0;
for (int i = 0, len = tmpChar.length; i < len; i++) {
if (tmpChar[i] == '{') {
bracketStart++;
} else if (tmpChar[i] == '}') {
bracketEnd = i;
bracketStart--;
}
if (i != 0 && bracketStart == 0 && bracketEnd != 0) {
break;
}
}
StringBuffer contentBuffer = new StringBuffer(5120);
// 截取字符串
contentBuffer.append(content.substring(0, publicStart)).append(
content.substring(mainStart + bracketEnd + 1));
content = null;
content = contentBuffer.toString();
// 去掉多余空白行
content = content.replaceAll("#_end_#", "\r\n");
content = content.replaceAll("\\s{2,}\\r\\n", "\r\n");
return content;
}
public String getFileContent(String fileName, String chartSet)
throws Exception {
if (chartSet == null) {
chartSet = "utf-8";
}
StringBuffer buffer = new StringBuffer(5120);
String line = null;
InputStream is = new FileInputStream(fileName);
BufferedReader reader = new BufferedReader(new InputStreamReader(is,
chartSet));
while ((line = reader.readLine()) != null) {
buffer.append(line).append("#_end_#");
}
return buffer.toString();
}
public void writeStrToFile(String str, String filePath, String charsetName)
throws Exception {
if (charsetName == null) {
charsetName = "utf-8";
}
OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(
filePath), charsetName);
out.write(str);
out.close();
}
}