自动修复360代码卫士扫描出来的日志伪造和删除main方法

项目经过360代码卫士扫描以后或多或少有些漏洞,既然公司要求扫描,那就会要求修复,进入正题,如文章标题,本篇文章主要涉及一个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方法,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-lang3StringEscapeUtils.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();
    }


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羽轩GM

您的鼓励是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值