软件源码安全攻防之道(下)
上次咱们讲完了源码安全漏洞利用常见的几个姿势,本文接着来对对应的防御技术进行说明。改一个漏洞一般会比较简单,但是如何通过安全编程来避免一类某种类型的漏洞出现就会比较麻烦。其实,防御的原则相对比较好总结,主要就一个字“控”:控输入、控过程、控输出。“控”字做好了,防御也就可以随之构建起来。
●源码安全漏洞的防守之道●
这里的输入是相对的,取决于你要关注的系统,取决于“安全防线”的位置(也称之为“安全边界”)。防线内部视为可信的系统,当做自己人;防线外部自然就是不可信的,当做外人。那么对于咱们要保护程序的来说,往往会有哪些类型的外部输入呢,下图给出了常见的一些外部输入类型:
对于外部的输入,你是否有预期逻辑限制,输入的全集是怎样的,合理输入的区间位于何处,需要妥当思考。举个例子,对于一个函数接口,如果需要处理一个外部输入的数据,那么往往会考虑到输入数据的长度、数据的数值范围和数据的字符集(如果是当成字符来处理的话)范围等等。
如果输入数据还需要做进一步处理(比如URL解码后,仍有危险字符构造注入),或者与预置数据拼接(比如拼接路径,使用“../”拼接跨越父级目录),那么需要进行再次确认,确保处理后的数据送到函数入参前是预期符合要求的参数。
另外前面也说到,“安全防线”的位置不是一成不变的,如果我们某台机器已证实被入侵,则来自它的输入也变为了不可信输入,其他机器也需要与之“划清界限”,对其输入再做校验。当前流行的“零信任安全”就假定了任何输入都不安全,接收输入前必须进行认证校验。输入校验是需要成本的,完全的“零信任”具有挑战,可以选择将力气着重花在主要矛盾的主要方面。
前文也有提到,解析器往往是安全攻击的重灾区,解析器本身是为了提升固定格式或协议文件的解析效率,越复杂的解析器越可能出现较多的安全缺陷。为了保护解析器,需要知道我们用到了哪些解析器,是否是安全版本的解析器,使用解析器处理了哪种数据等等。
有些解析器是需要按照说明书进行合理配置的,而不是一上来就直接使用。安全配置解析器不意味着一定要限制解析器的功能,而是要避免开启开发者未预料到的功能。如果开发者对于要处理的数据协议本身都不了解,那很有可能会被攻击者钻了空子,比如xml协议的使用,有开发者可能对xml实体扩展的功能都不了解,而xml解析器如果开了实体扩展解析的功能,这样就极有可能导致xml解析接收到过量的实体扩展解析,损耗计算资源,导致拒绝服务。
常见的解析器安全配置和入参限制如下所示:
简而言之,三个要点:安全的版本,安全的配置,安全的入参。
权限管控是“控过程”中一块很重要的关注点,不同权限处理问题带来的影响也往往是不同的。现在用的比较普遍的权限访问控制是RBAC(即基于角色的访问控制),常见的越权攻击也分为水平越权(角色不变)和垂直越权(角色转变),如下图所示:
鉴权的过程需要由服务器端完成,而非客户端,完成鉴权后,给对应用户一个“票据”,也就是会话Id,用户通过会话Id来进行对应权限的任务执行。针对单体的权限管控,也是从时间(权限的有效时间)与空间(权限的影响范围)角度来进行约束。
权限的使用原则应遵循最小化原则(空间+时间最小化),即“该用多少才用多少,该用多久采用多久”,而非“能用多少就用多少,能用多久就用多久”。
根用户权限和超级用户权限,需要进行约束,非系统级或弱系统级的服务尽量不用或少用。
数据安全生命周期包括:安全采集,安全存储,安全传输,安全处理,安全交换和安全销毁。开发者需要对用到的数据,尤其是用户敏感数据和系统机密数据,保持相当高的敏感度,各阶段要进行对应的保护措施。常见的数据安全流转注意要点如下图所示:
数据的使用,也需要由“面向解决问题的数据获取”转为“面向数据保护的问题解决”,优先保护用户隐私和系统机密,然后再基于此给出问题解决之道,而非粗暴的拉取数据,图一时之快。数据加密技术的使用也是一个关注点,需要注意加密算法的安全性,密钥的安全保管,随机性因子的随机安全。
前文有提到过攻击中“猜”的思想,其实从防御角度来说也就是解决确定与随机的问题。
我们程序中的异常处理,其实是一种确定性处理,确定的异常给出确定的处理。
对于异常,尽量不要宽泛捕获,这样容易导致忽略事先没有考虑到的情况,导致确定性缺失。确定性主要是对抗“天灾”,增强了开发者对于代码的把控,以便减少不确定性带来的未知风险。
而随机性的使用,主要是对抗“人祸”,干扰攻击者,增加攻击难度,像之前提到的ASLR技术就是通过随机干扰内存地址预测。终端隐私保护技术中,也有使用随机MAC地址摆脱网络探针跟踪的技术。
伪随机数生成器PRNG一般分为统计学PRNG和密码学PRNG。统计学PRNG通过统计来输出随机数,相对而言容易预测出来;而密码学PRNG一般会内置复杂的熵源,使生成的随机数相对来说更安全,不易预测。对于开发者来说,安全随机数API一般是可以查阅得到的,比如Java,使用SecureRandom替代Random就可获得安全性良好的随机数生成器。
实施有效的安全编码,可以减少很多安全缺陷,通过源码的层层安全防御,将风险也层层消减。及时修复代码中的最弱一环,提升整体软件的安全水位,避免攻击者从最弱一环层层突破开来。
如果初期的开发团队还没有安全编码规范制定,可以先参考业界的优秀实践,整理定制一个适合自己团队的规范,引入已有安全库进行分析研究,落地到自己的软件产品之中。
通过规范+流程+技术,增强开发人员整体的源码质量。规范的制定可能会引起部分编码高手的不满,认为降低了开发效率,觉得没有必要,缺乏安全意识。因此规范的培训和意识宣贯是必要的,这和部分老司机不经常打转向灯一样,不是因为不会,而是因为缺乏意识。
当开发人员认为做好了以上方面和安全编码以后,接下来就是需要进行“测验”了。先面临工具的考验,可以通过静态代码检查工具,模拟数据流(污染传递技术)和控制流把逻辑分支跑一遍,发现有哪些安全缺陷,如下图所示:
第一次的整改往往伴随阵痛,也是对开发人员安全编码落地的一次检验。静态检查中的误报是难免的,部分人员甚至可以通过分析误报了解静态检查工具是如何运作的。排除掉误报,将真实缺陷问题逐一整改,再进行一轮检查确保整改没有引入新问题。
面临完工具的考验,接下来就是安全评估专家的考验了。内部安全评估专家一般通过两种方式进行安全评估。
第一种是“硬核派”,正向而行,直接走读代码和不断调试发现缺陷,需要对目标软件系统有足够了解,且对安全编码规范了如指掌;第二种是“讨巧派”,逆向溯源,通过检查或监控关键部位,分析系统输出看问题是否可利用,有时也会通过Fuzz等技术帮他们找出异常点。
内部的安全评估可以发挥安全评估人员(白帽子)的专长,发掘更深层次的安全问题,同时也可以对开发进行赋能,将部分对安全产生兴趣的同学也纳入进来作为种子成员。不断的内部攻防可以有效提升产品安全质量,是技术,更是艺术。
●攻防之道总结●
攻防技术互促,知点防面是很重要的。开发者对代码开发是最了解的,但是也不能忽视安全意识。有的技巧点并不是不知道,而是容易忽略掉,遵守安全编码,是可以有效降低软件的缺陷密度的。
知己知彼,百战不殆。笔者能力有限,如文章中有讹误之处,欢迎大家指正。
最后给大家推荐2个链接,前者为攻,后者为防,供大家进一步了解。
ATT&CK框架:https://attack.mitre.org
CWE:https://cwe.mitre.org