代码漏洞扫描常见漏洞

1.日志注入(Log Forging漏洞)

漏洞描述

将未经验证的用户输入写入日志文件可致使攻击者伪造日志条目或将恶意信息内容注入日志。

在以下情况下会发生日志伪造的漏洞:

  1. 数据从一个不可信赖的数据源进入应用程序。
  2. 数据写入到应用程序或系统日志文件中。

影响:

  1. 误导日志文件的解读;
  2. 如果日志文件是自动处理的,可能影响日志处理应用程序。
    日志注入一般不会引起服务功能性的损害,而主要是作为一种辅助攻击手段
漏洞案例

例1:登录模块,会在日志里记录所有登录成果或者失败的用户

[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=zhangsan
[2021-04-17 16:50:35][WARN][main] [Login:308] username is wrong,userName=zhangsan
    
// 如果请求中的username参数如下:
username=zhangsan
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=zhangsan
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=zhangsan
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=zhangsan
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=zhangsan
修复建议

使用间接方法防止日志伪造攻击:创建一组与不同事件一一对应的合法日志条目,这些条目必须记录在日志中,并且仅记录该组条目。要捕获动态内容(如用户注销系统),请务必使用由服务器控制的数值,而非由用户提供的数据。这就确保了日志条目中绝不会直接使用由用户提供的输入。

过滤引起Log Forging漏洞的敏感字符的公共方法

/**
	 * Log Forging漏洞校验
	 * @param logs
	 * @return
	 */
	public static String vaildLog(String logs) {
		List<String> list=new ArrayList<String>();
		list.add("%0d");
		list.add("%0a");
        list.add("%0A");
		list.add("%0D");
		list.add("\r");
		list.add("\n");
		String normalize = Normalizer.normalize(logs, Normalizer.Form.NFKC);
		for (String str : list) {
			normalize=normalize.replace(str, "");
		}
		return normalize;
	}

2.空指针引用

漏洞描述

函数如果间接引用null 指针,将导致程序崩溃。

如果程序明确将对象设置为 null,但稍后却间接引用该对象,则将出现 dereference-after-store 错误。此错误通常是因为程序员在声明变量时将变量初始化为 null。在这种情况下,间接引用该变量时,变量有可能为 null,从而引起 null 指针异常。 大部分空指针问题只会引起一般的软件可靠性问题,但如果攻击者能够故意触发空指针间接引用,攻击者就有可能利用引发的异常绕过安全逻辑,或致使应用程序泄漏调试信息,这些信息对于规划随后的攻击十分有用。

漏洞案例
// 01 操作一般对象
String str = null;
for (String item : list) {
    str = item;
}
// 若list为空数组,则循环结束后str依旧为null。此时程序崩溃。
return str.length(); 

// 02 操作资源
InputStream is = null;
OutputStream os = null;
try {
	//...
} catch (IOException e) {
	//...
}finally{	
	os.close();
	is.close(); //若os、is为null,则程序崩溃
}
修复建议

在间接引用可能为 null 值的对象之前,请务必仔细检查。如有可能,在处理资源的代码周围的包装器中纳入 null 检查,确保在所有情况下均会执行 null 检查,并最大限度地减少出错的地方。

// 01 => 建议 
String str = "";//初始化值
----------------------------
if(str != null){
    //调用对象方法时增加判断
    str.length();
}
=============================

// 02 => 建议写法1 增加 != null 的判断,try catch捕获异常
InputStream is = null;
OutputStream os = null;
try {
	//...
} catch (IOException e) {
	//...
}finally{
	try {
		if(os!=null){
			os.close();
		}
		if(is!=null){
			is.close();
		}
	} catch (IOException e2) {
		//...
	}
}

// 02 => 建议写法2 java7中一个新的异常处理机制  
// try()的括号中可以写多行声明,每个声明的变量类型都必须是Closeable的子类,用分号隔开
// try(Resource res = xxx)的方式,try块退出时,会自动调用res.close()方法,关闭资源
try(
	InputStream is = new FileInputStream("...");
	OutputStream os = new FileOutputStream("...");
){
	//...
}catch (IOException e) {
	//...
}

3.路径篡改

漏洞描述

允许用户输入控制文件系统操作所用的路径会导致攻击者能够访问或修改其他受保护的系统资源。

当满足以下两个条件时,就会产生路径篡改错误:

  1. 攻击者可以指定某一文件系统操作中所使用的路径。
  2. 攻击者可以通过指定特定资源来获取某种权限,而这种权限在一般情况下是不可能获得的。
    例如,在某一程序中,攻击者可以获得特定的权限,以重写指定的文件或是在其控制的配置环境下运行程序
漏洞案例
// 下面的代码使用来自于 HTTP 请求的输入来创建一个文件名。
// 像“../../tomcat/conf/server.xml”一样的文件名,从而导致应用程序删除它自己的配置文件。

String rName = request.getParameter("reportName");

File rFile = new File("/usr/local/apfr/reports/" + rName);
...
rFile.delete()
修复建议

防止路径篡改的最佳方法是采用一些间接手段:例如创建一份合法资源名的列表,并且规定用户只能选择其中的文件名。通过这种方法,用户就不能直接由自己来指定资源的名称了。 但在某些情况下,这种方法并不可行,因为这样一份合法资源名的列表过于庞大、难以跟踪。因此,程序员通常在这种情况下采用黑名单的办法。在输入之前,黑名单会有选择地拒绝或避免潜在的危险字符。但是,任何这样一份黑名单都不可能是完整的,而且将随着时间的推移而过时。**更好的方法是创建一份白名单,允许其中的字符出现在资源名称**中,且只接受完全由这些被认可的字符组成的输入。

//白名单
private final static Map<String, String> pathCharWhiteList = new HashMap<String, String>();

    static {
        //路径字符白名单
        String pathCharWhiteListResources = "abcdefghijklmnopqrstuvwxyz_123457890ABCDEFGHIJKLMNOPQRSTUVWXYZ./\\";

        for (int i = 0; i < pathCharWhiteListResources.length(); i++) {
            String c = String.valueOf(pathCharWhiteListResources.charAt(i));
            pathCharWhiteList.put(c, c);

        }
    }

//过滤
public static String validFilePath2(String filePath) {
        String temp = "";
        for (int i = 0; i < filePath.length() - 1; i++) {

            Character curChar = null;
            Character nextChar = null;

            try {
                curChar = filePath.charAt(i);
                nextChar = filePath.charAt(i + 1);
            } catch (Exception e) {
            }
            if (pathCharWhiteList.get(filePath.charAt(i) + "") != null && curChar == '\\') {
                String sysFileSeparator = File.separator;
                if (null != sysFileSeparator && sysFileSeparator.equals(curChar + "")) {
                    temp += pathCharWhiteList.get(filePath.charAt(i) + "");
                }
            } else if (pathCharWhiteList.get(filePath.charAt(i) + "") != null && curChar != '.') {
                temp += pathCharWhiteList.get(filePath.charAt(i) + "");
            } else if (pathCharWhiteList.get(filePath.charAt(i) + "") != null && curChar == '.' && nextChar != '.') {
                temp += pathCharWhiteList.get(filePath.charAt(i) + "");
            }
        }

        filePath = temp;
        return filePath;
    }

4.任意文件上传

1.漏洞描述

恶意用户或者攻击者能利用该漏洞上传不符合要求的文件或者木马病毒。

2.漏洞建议

1.服务端采用白名单方式校验文件后缀,不建议采用黑名单方式校验后缀,黑名单方式校验可能导致攻击者利用文件特性、系统特性、黑名单不全等方式进行绕过攻击;

2.服务端对上传文件进行重命名,防止利用目录跳转等方式控制上传目录;

3.服务端使用系统函数来判断文件类型及文件内容是否合法; md5,关键词

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加拦截器
        registry.addInterceptor(new FileTypeInterceptor())
                .addPathPatterns("/**");
    }
}

/**
 * 全局文件类型拦截器
 */
public class FileTypeInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
        boolean flag = true;
        // 判断是否为文件上传请求
        if (request instanceof MultipartHttpServletRequest) {
            MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
            Map<String, MultipartFile> files = multipartRequest.getFileMap();

            for (Map.Entry<String, MultipartFile> entry : files.entrySet()) {
                MultipartFile multipartFile = entry.getValue();
                String filename = multipartFile.getOriginalFilename();

                if (filename == null || checkFile(filename)) {
                    continue;
                }

                //限制文件类型
                request.setAttribute("errormessage", "不支持的文件类型!");
                response.sendError(500, "不支持的文件类型!");
                flag = false;
            }

        }
        return flag;
    }

    /**
     * 判断是否为允许的上传文件类型,true表示允许
     */
    private boolean checkFile(String fileName) {
        //设置允许上传文件类型
        String suffixList = "xls,xlsx,docx,doc,pdf";
        // 获取文件后缀
        String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
        return suffixList.contains(suffix.trim().toLowerCase());
    }
}