部分Fortify代码扫描高风险解决方案
一、Category: Access Control: Database
问题描述:
Database access control 错误在以下情况下发生:
1.数据从一个不可信赖的数据源进入程序。
2.这个数据用来指定 SQL 查询中主键的值。
官方案例:
示例 1:
以下代码使用可转义元字符并防止出现 SQL 注入漏洞的参数化语句,以构建和执行用于搜索与指定标识符
相匹配的清单的 SQL 查询。您可以从与当前被授权用户有关的所有清单中选择这些标识符。
id = Integer.decode(request.getParameter("invoiceID")); String query = "SELECT * FROM invoices WHERE id = ?";
PreparedStatement stmt = conn.prepareStatement(query); stmt.setInt(1, id); ResultSet results = stmt.execute();
问题在于开发者没有考虑到所有可能出现的 id 值。虽然接口生成了一个当前用户的标识符清单,但是攻击者可以绕过
这个接口,从而获取所需的任何清单。因为此例中的代码没有执行检查,确保用户有权访问需要的清单,所以代码会
显示所有清单,即使这些清单并不属于当前用户。
有些人认为在移动世界中,典型的 Web 应用程序漏洞(如 Database access control 错误)是无意义的 -- 为什么用户要攻
击自己?但是,谨记移动平台的本质是从各种来源下载并在相同设备上运行的应用程序。恶意软件在银行应用程序附
近运行的可能性很高,它们会强制扩展移动应用程序的攻击面(包括跨进程通信)。
示例 2:
以下代码对示例 1 进行调整,使其适用于 Android 平台。
String id = this.getIntent().getExtras().getString("invoiceID"); String query = "SELECT * FROM invoices WHERE id = ?";
SQLiteDatabase db = this.openOrCreateDatabase("DB", MODE_PRIVATE, null); Cursor c = db.rawQuery(query, newObject[]{id});
许多现代 Web 框架都提供对用户输入执行验证的机制。其中包括 Struts 和 Spring MVC。为了突出显示未经验证的输入源,该规则包会降低 HPE Security Fortify Static Code Analyzer(HPE Security Fortify 静态代码分析器)报告的问题被利用的可能性,并在使用框架验证机制时提供相应的依据,以动态重新调整问题优先级。我们将这种功能称之为上下文敏感排序。为了进一步帮助 HPE Security Fortify 用户执行审计过程,HPE Security Fortify 软件安全研究团队提供了数据验证项目模板,该模板会根据应用于输入源的验证机制,将问题分组到多个文件夹中。
例 3:
以下代码实施了与例 1 相同的功能,但是附加了一个限制,即为当前被授权的用户指定某一特定的获取清单的方
式。
userName = ctx.getAuthenticatedUserName();
id = Integer.decode(request.getParameter("invoiceID"));
String query ="SELECT * FROM invoices WHERE id = ? AND user = ?";
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setInt(1, id);
stmt.setString(2, userName);
ResultSet results = stmt.execute();
Recommendations:
与其靠表示层来限制用户输入的值,还不如在应用程序和数据库层上进行 access control。任何情况下都不允许用户在没有取得相应权限的情况下获取或修改数据库中的记录。每个涉及数据库的查询都必须遵守这个原则,这可以通过把当前被授权的用户名作为查询语句的一部分来实现。
实际解决方案:
这个问题出现的地方大多数是数据返回值上,其实在返回值return的时候判空返回相应的基本类型就OK了
或者是出现在不明所以的地方,简单粗暴直接删除问题代码行。
例如:
return grayanologdao.getGrayAnoLogList(map);
改为:
if(map != null){
return map;
}
二、Category: Null Dereference
问题描述:
Category: Null Dereference:
当违反程序员的一个或多个假设时,通常会出现 null 指针异常。如果程序明确将对象设置为 null,但稍后却间接引用该对象,则将出现 dereference-after-store 错误。此错误通常是因为程序员在声明变量时将变量初始化为 null。
大部分空指针问题只会引起一般的软件可靠性问题,但如果攻击者能够故意触发空指针间接引用,攻击者就有可能利用引发的异常绕过安全逻辑,或致使应用程序泄漏调试信息,这些信息对于规划随后的攻击十分有用。
官方案例:
示例:
在下列代码中,程序员将变量 foo 明确设置为 null。稍后,程序员间接引用 foo,而未检查对象是否为 null 值。
Foo foo = null;
...
foo.setBar(val);
...
}
Recommendations:
在间接引用可能为 null 值的对象之前,请务必仔细检查。如有可能,在处理资源的代码周围的包装器中纳入 null 检查,确保在所有情况下均会执行 null 检查,并最大限度地减少出错的地方。
实际解决方案:
错误多发生在流关闭等地方,程序员没有判空就关闭流,就引起了这个错误,解决起来也蛮方便,就是在流关闭的时候增加判空条件就ok了。
例如:
} finally {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
改为:
} finally {
if(pstmt != null){
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
三、Category: Password Management: Password in Configuration File
问题描述:
在配置文件中存储明文密码会使所有能够访问该文件的人都能访问那些用密码保护的资源。程序员有时候认为,他们不可能阻止应用程序被那些能够访问配置文件的攻击者入侵,但是这种想法会导致攻击者发动攻击变得更加容易。健全的 password management 方针从来不会允许以明文形式存储密码。
官方案例:
Recommendations:
绝不能采用明文的形式存储密码。相反,应在系统启动时,由管理员输入密码。如果这种方法不切实际,一个安全性较差、但通常都比较恰当的解决办法是将密码模糊化,并把这些去模糊化的资源分散到系统各处,因此,要破译密码,攻击者就必须取得并正确合并多个系统资源。
有些第三方产品宣称可以采用更加安全的方式管理密码。例如,WebSphere Application Server 4.x 用简单的异或加密算法加密数值,但是请不要对诸如此类的加密方式给予完全的信任。WebSphere 以及其他一些应用服务器通常都只提供过期的且相对较弱的加密机制,这对于安全性敏感的环境来说是远远不够的。较为安全的解决方法是由用户自己创建一个新机制,而这也是如今唯一可行的方法。
Tips:
1. HPE Security Fortify Static Code Analyzer(HPE Security Fortify 静态代码分析器)会从配置文件中搜索那些用于密码属性的常用名称。当发现密码条目中包含明文时,就会将其标记为问题。
2. 如果配置文件中包含一个默认密码条目,除了需要在配置文件中将其模糊化以外,还需要对其进行修改。
实际解决方案:
尝试过对password进行加密操作,但是还是会被扫描出来,扫描好像不能出现 password=XXXX 不能出现等号情况,因此最简单粗暴的方法就是把这行错误删掉。
四、Category: Unreleased Resource: Database
问题描述:
程序可能无法释放某个数据库连接。
资源泄露至少有两种常见的原因:
- 错误状况及其他异常情况。
- 未明确程序的哪一部份负责释放资源。
大部分 Unreleased Resource 问题只会导致一般的软件可靠性问题,但如果攻击者能够故意触发资源泄漏,该攻击者就有可能通过耗尽资源池的方式发起 denial of service 攻击。
官方案例:
示例:
在正常条件下,以下代码会执行数据库查询指令,处理数据库返回的结果,并关闭已分配的指令对象。但如果
在执行 SQL 或是处理结果时发生异常,指令对象将不会关闭。如果这种情况频繁出现,数据库将用完所有可用的指针,且不能再执行任何 SQL 查询。
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(CXN_SQL);
harvestResults(rs);
stmt.close();
Recommendations:
1. 请不要依赖 finalize() 回收资源。为了使对象的 finalize() 方法能被调用,垃圾收集器必须确认对象符合垃圾回收的条件。但是垃圾收集器只有在 JVM 内存过小时才会使用。因此,无法保证何时能够调用该对象的 finalize() 方法。垃圾收集器最终运行时,可能出现这样的情况,即在短时间内回收大量的资源,这种情况会导致“突发”性能,并降低总体系统通过量。随着系统负载的增加,这种影响会越来越明显。最后,如果某一资源回收操作被挂起(例如该操作需要通过网络访问数据库),那么执行 finalize() 方法的线程也将被挂起。
2. 在 finally 代码段中释放资源。示例中的代码可按以下方式改写:
public void execCxnSql(Connection conn) {
Statement stmt;
try {
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(CXN_SQL);
...
}
finally {
if (stmt != null) {
safeClose(stmt);
}
}
}
public static void safeClose(Statement stmt) {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
log(e);
}
}
}
以上方案使用了一个助手函数,用以记录在尝试关闭指令时可能产生的异常。该助手函数大约会在需要关闭指令时重新使用。同样,execCxnSql 方法不会将 stmt 对象初始化为 null。而是进行检查,以确保调用 safeClose() 之前,stmt 不是 null。如果没有检查 null,Java 编译器会报告 stmt 可能没有进行初始化。编译器做出这一判断源于 Java 可以检测未初始化的变量。如果用一种更加复杂的方法将 stmt 初始化为 null,那么编译器就无法检测 stmt 未经初始化便使用的情况。
Tips:
请注意,关闭数据库连接可能会自动释放与连接对象关联的其他资源,也可能不会自动释放。如果应用程序使用连接池,则最好在关闭连接后,明确关闭其他资源。如果应用程序未使用连接池,则数据库连接关闭后,其他资源也将自动关闭。在这种情况下,此漏洞无效。
实际解决方案:
就是对将数据库连接对象先进行判空再关闭就ok,但是注意可能还需要再进行try/catch一下
例如:
rs = st.executeQuery(sql);
在finally中添加finally{
try{
if(rs != null){
rs.close;
}
}catch(Exception e){
e.printStackTrace();
}
}
五、Category: Unreleased Resource: Streams
问题描述:
程序可能无法成功释放某一项系统资源。
资源泄露至少有两种常见的原因:
- 错误状况及其他异常情况。
- 未明确程序的哪一部份负责释放资源。
大部分 Unreleased Resource 问题只会导致一般的软件可靠性问题,但如果攻击者能够故意触发资源泄漏,该攻击者就有可能通过耗尽资源池的方式发起 denial of service 攻击。
官方案例:
示例:
下面的方法绝不会关闭它所打开的文件句柄。FileInputStream 中的 finalize() 方法最终会调用 close(),但是不能确定何时会调用 finalize() 方法。在繁忙的环境中,这会导致 JVM 用尽它所有的文件句柄。
private void processFile(String fName) throws FileNotFoundException, IOException {
FileInputStream fis = new FileInp