原代码审计笔记-质量缺陷
原代码审计笔记-质量缺陷
目录
可能返回值、变量空指针引用:
问题示例:
Reader getReader(String configurationPath) throws IOException {
File file = null;
if (file.exists()) {
return new BufferedReader(new FileReader(file));
}
return null;
}
static void printPoint(Point p) {
if (p == null) {
System.err.println("p is null");
}
}
原因分析:
如果尝试取消引用 null 值,将引发 NullPointerException。取消引用可以是函数调用、字段读取或写入,或阵列访问。当在某个路径上来自某个方法调用的值返回空值时,取消引用方法调用将导致报告此缺陷。
如果尝试取消引用 null 值,将引发 NullPointerException。取消引用可以是函数调用、字段读取或写入,或阵列访问。如果之前对某个本地变量进行了空值检查,并且经检查确认其值可能为空,则对其取消引用就会报缺陷。
解决方案:
建议:使用对象应对对象进行滤空判断,使用其中属性时应对其属性进行滤空处理,防止空指针异常。
密码管理(配置文件存密码):
问题示例:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="oracle.jdbc.driver.OracleDriver"
p:url="jdbc:oracle:thin:@127.0.0.1:1523:orcl"
p:username="cyb"
p:password="[email protected]" />
原因分析:
在配置文件中存储明文密码会使所有能够访问该文件的人都能访问那些用密码保护的资源。程序员有时候认为,他们不可能阻止应用程序被那些能够访问配置文件的攻击者入侵,但是这种想法会导致攻击者发动攻击变得更加容易。健全的 password management 方针从来不会允许以明文形式存储密码。
原理、风险及预防
在配置文件中存储明文密码,可能会危及系统安全。
解决方案:
建议:在配置文件中存储明文密码,很容易会被攻击者利用导致造成系统敏感信息泄露。配置文件中的密码加密存储。
System.out或System.err打印调试信息:
问题示例:
public int internalCalculateSum(int x, int y) {
if (x ﹤ 0 || y ﹤ 0) {
System.out.println("XXX: got strange arguments!");
}
return x + y;
}
原因分析:
调试打印(比如 System.out.println() 和 System.err.println())也可能是多余的。当有调用 System.out 流的打印方法时,会显示警告。 在成熟的应用程序中,这种调用应限制在一个记录模块和功能控制台输出中;否则它将导致滥用诊断,这一问题应当进行修复。
原理、风险及预防
解决方案:
建议:应该用日志模块调用来替换打印到 sysout 的调用。用日志调用或特定方法(比如 printRawToUser())的调用来替换打印到 stdout 的调用。
忽略返回值:
问题示例:
public String bad() {
String filePath = "C:" + File.separator + "test" ;
File f = new File(filePath);
f.mkdir();
return "ok";
}
原因分析:
Java 程序员常常会误解包含在许多 java.io 类中的 read() 及相关方法。在 Java 结果中,将大部分错误和异常事件都作为异常抛出。(这是 Java 相对于 C 语言等编程语言的优势:各种异常更加便于程序员考虑是哪里出现了问题。)但是,如果只有少量的数据可用,stream 和 reader 类并不认为这是异常的情况。这些类只是将这些少量的数据添加到返回值缓冲区,并且将返回值设置为读取的字节或字符数。所以,并不能保证返回的数据量一定等于请求的数据量。
这样,程序员就需要检查 read() 和其他 IO 方法的返回值,以确保接收到期望的数据量。
解决方案:
建议:忽略方法的返回值会导致程序无法发现意外状况和情况。
新建对象被忽略:
问题示例:
public void runMyProcessing() {
Runnable myRunnable = new Runnable() {
public void run() {
// do actual processing...
}
};
new Thread(myRunnable); // ignored!
}
原因分析:
当调用构造函数方法,而该方法调用的结果被忽略时,将出现此警告。在某些情况下,这可能导致错误(例如,线程类,因为用户应在初始化后启动线程)。 对方法行为的误解将导致应用程序逻辑中存在缺陷。也是JVM存储资源的浪费。
解决方案:
建议:应该存储方法所返回的值并加以运用。
冗余的IF表达式-空代码:
问题示例:
if (url == null) {
// return DiaowenProperty.DEFAULT_HTTP_BASEURL_UPLOAD;
}
原因分析:
当 if 语句仅含有空 then 分支时,给出警告。可能是未完成的代码。
程序员可能遗忘了这项检查,其原本想返回并在代码中添加一些内容,但却忘记了。什么也没做的 if 会影响性能,特别是涉及调用方法时。
解决方案:
建议:更改代码,使 if 包含非空分支或全部移除 if。
不良的异常处理(在finally块中抛弃异常):
问题示例:
if (url == null) {
// return DiaowenProperty.DEFAULT_HTTP_BASEURL_UPLOAD;
}
原因分析:
在 Java 中, finally 块通常在其相应的 try-catch 块之后执行,该块通常用于*分配的资源,例如文件句柄或数据库指针。由于破坏了正常的程序执行,在 finally 块中抛出异常会绕过关键的清除代码。
使用 finally 块中的 throw 语句会通过 try-catch-finally 破坏逻辑进度。
解决方案:
建议:请勿从 finally 块中主动或被动抛出异常。如果您必须重新抛出异常,请在 catch 块中执行该操作,这样不会影响到 finally 块的正常执行。
不良的异常处理(多个Catch块):
问题示例:
catch (Exception e) {
e.printStackTrace();
}
catch(Exception e){
log.info("Exception");
}
原因分析:
多个 catch 块看上去既难看又繁琐,但使用一个“简约”的 catch 块捕获高级别的异常类(如 Exception),可能会混淆那些需要特殊处理的异常,或是捕获了不应在程序中这一点捕获的异常。本质上,捕获范围过大的异常与“Java 分类定义异常”这一目的是相违背的。随着程序的增加而抛出新异常时,这种做法会十分危险。而新发生的异常类型也不会被注意到。
原理、风险及预防
catch 块可以处理的异常种类很多,但往往会由于过多的考虑不应该在此位置处理的各种问题或故障而困扰不已。
解决方案:
建议:对自己当前捕获的异常要定位精准,处理得当。不要捕获范围过大的异常类,比如 Exception、Throwable、 Error 或 ,除非是级别非常高的程序或线程。
不良的异常处理(多个throws块):
问题示例:
public ModelAndView answerSurveyEdit(HttpServletRequest request,String answerId) throws Exception {
}
原因分析:
声明一种可以抛出 Exception 或 Throwable 异常的方法,从而使调用者很难处理和修复发生的错误。Java 异常机制的设置是:调用者可以方便地预计有可能发生的各种错误,并为每种异常情况编写处理代码。同时声明:一个方法抛出一个过于笼统的异常违反该系统。
原理、风险及预防
该方法抛出了一个过于笼统异常,从而使调用者很难处理和修复发生的错误。
解决方案:
建议:不要声明抛出 Exception 或 Throwable 异常的方法。
如果方法抛出的异常无法恢复,或者通常不能被调用者捕获,
那么可以考虑抛出未检查的异常,而不是已检查的异常。
这可以通过实现一个继承自 RuntimeException 或 Error 的类来代替 Exception,
或者还可以在方法中加入 try/catch 块将已检查的异常转换为未检查异常。
不良的异常处理(空Catch块):
问题示例:
public ModelAndView answerSurveyEdit(HttpServletRequest request,String answerId) throws Exception {
}
原因分析:
几乎每一个对软件系统的严重攻击都是从违反程序员的假设开始的。攻击后,程序员的假设看起来既脆弱又拙劣,但攻击前,许多程序员会在午休时间为自己的种种假设做很好的辩护。
在代码中,很容易发现两个令人怀疑的假设:“一是这个方法调用不可能出错;二是即使出错了,也不会对系统造成什么重要影响。”因此当程序员忽略异常时,这其实就表明了他们是基于上述假设进行的操作。
解决方案:
建议:应在catch模块中对异常处理,否则程序出现异常将很难定位异常所在。
低效字符串比较:
问题示例:
if ("".equals(path) || path == null)
continue;
原因分析:
不需要调用 equals() 将字符串与空字符串进行比较。s.length() 的速度快两倍。以下表达式:s.equals("") 或 "".equals(s) 可由 (s.length() == 0) 和 (s != null && s.length() == 0) 轻松替代。性能测量值(使用 Java 2 Runtime Environment、Standard Edition,内部版本 1.4.1_02-b06 完成)显示,含“equals”的代码以 147 个时间单位执行,而含“length”的相同代码以 71 个时间单位执行。
解决方案:
建议:
if ( null == path || path.length)
continue;
遗留的调试代码-main方法:
问题示例:
public static void main(String[] args) {
System.out.println(randomWord(5, 12));;
System.out.println(randomStr(5,12));;
for (int i = 0; i < 1000; i++) {
chineseName.getName();
String names=chineseName.getNames();
String pids=chineseName.getPid();
System.out.println(names+":"+pids);
System.out.println(randomNum(2,4));
}
原因分析:
当在 Web 应用程序、J2EE 应用程序以及小型应用程序中检测到主方法时,将发生该错误。
在 Web 应用程序后面留下 main() 方法使得很容易通过后门来访问应用程序。Web 应用程序中的安全性设计趋向于不考虑主方法访问,因此这存在风险。
解决方案:
建议:在产品代码中移除所有非项目入口的main方法。
上一篇: kubernetes资源配额管理
下一篇: DSA数字签名理论分析及代码实现
推荐阅读