最近项目的代码使用fortify工具扫描了一下,发现了项目中存在的一些问题,在以后代码编写的过程中要注意,避免出现类似的错误。
以下为本次代码分析工具FORTIFY对代码的分析结果。这些问题虽然古老、简单然而经典,也是需要引起重视。
代码问题主要集中在如下类别:存在安全隐患、存在资源泄漏隐患、序列化问题、字符串比较、异常处理问题,以及其它一些BAD PRACTICE和粗心引起的问题。
把一个不可序列化的对象作为 HttpSession 属性来储存会破坏应用程序的可靠性。
代码示例
SharerInfo sharerInfo = new SharerInfo();
getServletRequest().getSession().setAttribute( “sharerInfo”, shareInfo);
其中SharerInfo类没有实现序列化(implements java.io.Serializable)
分析
对于不需要将对象序列化到同一jvm以外的应用场景,以上代码没有问题。然而考虑到系统的扩展性,以上问题应该予以避免。
以下是一些常见的会导致问题的场景:
1. 将session对象存储到外部系统
在一些大型应用系统的实现里,会考虑将部分SESSION里的对象钝化到数据库、磁盘里。
2. 集群环境
在非session-sticky的集群环境里,应用服务器会在集群里广播、复制session数据
修正
ShareInfo类应该实现序列化:implements java.io.Serializable
程序可能无法成功释放某一项系统资源
代码示例
Try {
Connection con = jdbcTemplate.getDataSource().getConnection();
}
Catch(Exception e) {
// Handle exception
}
Con.close();
分析
系统资源比如数据库资源(Connection ,Statement,etc),IO资源(InputStream,OutputStream,etc)都是有限的,如果没有正确的释放掉,就会出现资源泄漏的问题。常见的后果如出现数据库连接池无可用连接(pool exhausted),Too many open files等
修正
将资源释放代码放到finally块中进行
将业务上不可变的静态值作为非final属性直接暴露给调用者
代码示例
public static String REALM;
static {
REALM= bundle.getMessage("realm",null,DEFAULT_LOCALE);
}
分析
在业务上REALM是常量。但这样直接将REAM作为非FINAL静态属性暴露给调用者,容易导致静态属性值被修外部调用者修改,从而导致系统问题。
修正
将 realm定义为final类型或者通过getRealm方法暴露接口,而隐藏setRealm方法。
异常不做任何处理直接忽略
代码示例
Try {
//handle logic
}
Catch (Exception e) { }//do nothing
分析
将异常捕获后直接忽略是一种BAD PRACTICE.
修正
如果需要处理异常(比如打印异常信息),则进行处理。如果不需要,则继续抛出异常给更外层的调用者
这是一种BAD PRACTICE。严重的情况下还会对系统性能造成冲击。
代码示例
System.out.print
e.printStackTrace.
分析
以上是两种常见的不被推荐的打印信息的代码例子。
修正
虽然可以通过修改系统属性或者修改System.out的输出属性来改变System.out.print等方法的输出目标。但更建议直接禁止这种写法。
通过LOG4J等LOG工具包将信息按信息级别输出到外部系统(磁盘、数据库,甚至其它服务器比如通过JMS将日志发送给日志服务器)。
如果想获取异常堆栈信息(e.printStackTrace),可以利用如下helper代码捕获整个异常堆栈信息:
public class StackTraceUtil {
public static String getStackTrace(Throwable exception) {
if (exception != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
exception.printStackTrace(pw);
return sw.toString();
} else {
return "thrown exception is null.no more exception information can provide..";
}
}
通过==而不是equals比较字符串数据。
代码示例
String a=”abc”
String b=”abc”;
System.out.println(a==b)
分析
“==”是比较指针是否指向同一地址,而equals比较的是数据。事实上除了原子类型的数据外,其它大部分情况下,值比较都应该使用equals。
String是个特殊情况。为了提高效率,JVM 维护了字符串池。以上代码里定义b时,b仍然指向a的指针,而不是重新分配一块内存给b.所以虽然对于a==b来说,比较的是地址指针,但a==b仍然为true.但对于如下场景 a==b将为false:
String a= “abc”;
String b= new String(“abc”);
System.out.println (a==b).
在以上代码里 new String重新为b分配了一块内存,所以结果将是false.
修正
使用equals进行值比较
代码示例
public int hashcode(Object o){
return this.a.hashCode()+this.b.hashCode();
}
分析
以上代码本意应该是想重载Object的hashCode(Object o)方法。但不小心些错了。对于jdk5及以后的jvm,可以通过@override在编译期发现改问题。对于jdk1.4及以前的环境,只能通过小心检查来避免这种情况。不过很多IDE工具都提供对这种常见需覆盖方法(比如hashCode,equals等)的智能化完成功能。
对于XSS漏洞,通常情况下通过过滤转换<,>等敏感字符来防御。但对于http response splitting(报头分离)漏洞,还应该针对回车、换行这两个敏感字符进行过滤。
代码示例
response.addHeader("Content-Disposition", "attachment; filename="
+ fileName);
修正
如果fileName的来源不可靠,则需要过滤\13\10(回车、换行)字符
其它包括一些粗心造成的代码问题。比如对Nuberm类型的数据进行如下比较:Numbera.equals(“”)等。
以上为本次hot、warn级别的代码分析总结,具体问题代码明细见fortify 分析结果文件。