我们一起来看一段不起眼的条件判断代码,这里可能有什么问题吗
| |
| |
| |
|
复制代码
你可能会纳闷,这是再常见不过的一个条件判断了,能有什么安全隐患?
这里的隐患是数值类型需要防范溢出,否则这不仅仅可能会带来逻辑错误,在特定情况下可能导致严重的安全漏洞。
从语言特性来说,Java 和 JVM 提供了很多基础性的改进,相比于传统的 C、C++ 等语言,对于数组越界等处理要完善的多,原生的避免了缓冲区溢出等攻击方式,提高了软件的安全性。但这并不代表完全杜绝了问题,Java 程序可能调用本地代码,也就是 JNI 技术,错误的数值可能导致 C/C++ 层面的数据越界等问题,这是很危险的。
所以,上面的条件判断,需要判断其数值范围,例如,写成类似下面结构。
|
复制代码
再来看一个例子,请看下面的一段异常处理代码:
| |
| |
| |
| |
|
复制代码
这段代码将敏感信息包含在异常消息中,试想,如果是一个 Web 应用,异常也没有良好的包装起来,很有可能就把内部信息暴露给终端客户。古人曾经告诫我们“言多必失”是很有道理的,虽然其本意不是指软件安全,但尽量少暴露信息,也是保证安全的基本原则之一。即使我们并不认为某个信息有安全风险,我的建议也是如果没有必要,不要暴露出来。
这种暴露还可能通过其他方式发生,比如某著名的编程技术网站,就被曝光过所有用户名和密码。这些信息都是明文存储,传输过程也未必进行加密,类似这种情况,暴露只是个时间早晚的问题。
对于安全标准特别高的系统,甚至可能要求敏感信息被使用后,要立即明确在内存中销毁,以免被探测;或者避免在发生 core dump 时,意外暴露。
第三,Java 提供了序列化等创新的特性,广泛使用在远程调用等方面,但也带来了复杂的安全问题。直到今天,序列化仍然是个安全问题频发的场景。
针对序列化,通常建议:
-
敏感信息不要被序列化!在编码中,建议使用 transient 关键字将其保护起来。
-
反序列化中,建议在 readObject 中实现与对象构件过程相同的安全检查和数据检查。
另外,在 JDK 9 中,Java 引入了过滤器机制,以保证反序列化过程中数据都要经过基本验证才可以使用。其原理是通过黑名单和白名单,限定安全或者不安全的类型,并且你可以进行定制,然后通过环境变量灵活进行配置, 更加具体的使用你可以参考 ObjectInputFilter。
通过前面的介绍,你可能注意到,很多安全问题都是源于非常基本的编程细节,类似 Immutable、封装等设计,都存在着安全性的考虑。从实践的角度,让每个人都了解和掌握这些原则,有必要但并不太现实,有没有什么工程实践手段,可以帮助我们排查安全隐患呢?
开发和测试阶段
在实际开发中,各种功能点五花八门,未必能考虑的全面。我建议没有必要所有都需要自己去从头实现,尽量使用广泛验证过的工具、类库,不管是来自于 JDK 自身,还是 Apache 等第三方组织,都在社区的反馈下持续地完善代码安全。
开发过程中应用代码规约标准,是避免安全问题的有效手段。我特别推荐来自孤尽的《阿里巴巴 Java 开发手册》,以及其配套工具,充分总结了业界在 Java 等领域的实践经验,将规约实践系统性地引入国内的软件开发,可以有效提高代码质量。
当然,凡事都是有代价的,规约会增加一定的开发成本,可能对迭代的节奏产生一定影响,所以对于不同阶段、不同需求的团队,可以根据自己的情况对规约进行适应性的调整。
落实到实际开发流程中,以 OpenJDK 团队为例,我们应用了几个不同角度的实践:
-
在早期设计阶段,就由安全专家组对新特性进行风险评估。
-
开发过程中,尤其是 code review 阶段,应用 OpenJDK 自身定制的代码规范。
-
利用多种静态分析工具如FindBugs、Parfait等,帮助早期发现潜在安全风险,并对相应问题采取零容忍态度,强制要求解决。
-
甚至 OpenJDK 会默认将任何(编译等)警告,都当作错误对待,并体现在 CI 流程中。
-
在代码 check-in 等关键环节,利用 hook 机制去调用规则检查工具,以保证不合规代码不能进入 OpenJDK 代码库。
关于静态分析工具的选择,我们选取的原则是“足够好”。没有什么工具能够发现所有问题,所以在保证功能的前提下,影响更大的是分析效率,换句话说是代码分析的噪音高低。不管分析有多么的完备,如果太多误报,就会导致有用信息被噪音覆盖,也不利于后续其他程序化的处理,反倒不利于排查问题。
以上这些是为了保证 JDK 作为基础平台的苛刻质量要求,在实际产品中,你需要斟酌具体什么程度的要求是合理的。
部署阶段
JDK 自身的也是个软件,难免会存在实现瑕疵,我们平时看到 JDK 更新的安全漏洞补丁,其实就是在修补这些漏洞。我最近还注意到,某大厂后台被曝出了使用的 JDK 版本存在序列化相关的漏洞。类似这种情况,大多数都是因为使用的 JDK 是较低版本,算是可以通过部署解决的问题。
如果是安全敏感型产品,建议关注 JDK 在加解密方面的路线图,同样的标准也应用于其他语言和平台,很多早期认为非常安全的算法,已经被攻破,及时地升级基础软件是安全的必要条件。
攻击和防守是不对称的,只要有一个严重漏洞,对于攻击者就足够了,所以,不能对黑盒形式的部署心存侥幸,这并不能保证系统的安全,攻击者可以利用对软件设计的猜测,结合一系列手段,探测出漏洞。