代码的安全检视
1. 熵的故事
熵的概念最早起源于物理学,用于度量一个热力学系统的无序程度。热力学第二定律,又称“熵增定律”,表明了在自然过程中,一个孤立的系统总是从最初的集中、有序的排列状态,趋向于分散、混乱和无序;当熵达到最大时,系统就会处于一种静寂状态。
通俗的讲:系统的熵增过程,就是由原始到死亡的过程。“熵” 是 “活跃” 的反义词,代表负能量。
物质总是向着熵增演化:
屋子不收拾会变乱;
手机会越来越卡;
耳机线会凌乱;
热水会慢慢变凉;
太阳会不断燃烧衰变
… …
直到宇宙的尽头 -- 热寂。
除了自然间除了自然风化的作用,也少不了人为的因素。
1982 年 3 月,詹姆士·威尔逊(James Q. Wilson)及乔治·凯林(George L. Kelling)发表一篇题为《Broken Windows》的文档,提出了犯罪学的一个理论 -- 破窗效应。此理论认为环境中的不良现象如果被放任存在,会诱使人们仿效,甚至变本加厉。
一个房子如果窗户破了,没人去修补,隔不久,其他的窗户也可能莫名奇妙地被人打破。环境可以对一个人产生强烈的暗示性和诱导性。
这个理论从另一个侧面说明了对一个系统需要及时维护,以减少人为的熵增现象。古人云:勿以恶小而为之,勿以善小而不为。
对于我们创造的代码,也是一样,大家制定了编码规范,以期尽量减缓熵增,从而达到延长软件的生命周期。
2. 代码检视的意义
代码检视一直被认为是提高代码质量的重要手段,同时也是发现潜在问题的重要途经。代码检视有以下重要意义。
2.1. 编写易于理解的代码
代码检视是确保代码质量的重要环节,它有助于开发人员编写易于理解的代码。
微软和谷歌等公司在代码检视中积累了丰富的经验,强调了代码的可读性对于团队协作的重要性。当代码清晰、结构良好时,其他开发人员能够更快地理解代码的功能和目的,从而提高团队的整体效率。此外,良好的代码可读性也有助于新成员快速融入团队,因为他们可以更容易地理解现有的代码库。
史蒂夫·迈克康奈尔的《代码大全》,被福布斯技术委员会(Forbes Technology Council)誉为“有史以来最好的软件开发基础书” 。
《代码大全》书中提到了提高代码可读性的多种方法,包括使用有意义的变量名、避免嵌套过深的语句、使用函数和模块、面向对象编程、避免重复代码、使用设计模式、编写注释和文档、使用调试工具、进行代码审查、学习和使用更高级的编程语言和技术等等。
软件工程的核心就是管理复杂度。规模更大的项目,如果不对复杂性做出限制,那么项目的每个步骤都可能崩溃失控。以往,无数大型项目都在砸下巨量资金后失败了,原因就是其过于复杂,已经无人能理解究竟发生了什么,就如同过于庞大的巨兽被自身重量所压垮。
复杂性其实贯穿于软件开发的各个阶段,复杂性在编码过程中,因为如果底层代码的质量不好,超大规模系统也可能就由此崩溃。所以必须立足底层,立足细节抓代码质量,关注每个语句、例程和类,步步为营,以此为基础才能扩大规模,同时继续保持代码质量,即在设计和架构层级控制复杂性,对复杂性控制要广泛而深入地体现在软件开发的各个阶段。
唐纳德·克努特(Donald Knuth),他在编程界被广泛认为是“计算机程序设计艺术”的创始人之一。克努特曾说过:“Programs are meant to be read by humans and only incidentally for computers to execute.” 这句话强调了代码的可读性对于人类的重要性,意味着代码首先应该是为了让人理解而编写的,而机器执行只是其次。
![](https://static001.geekbang.org/infoq/0f/0f1639d22912cd1dce3a6565b1fa3082.png)
这是《A practical guide to object-oriented metrics》中的一个统计图,作者分析了一个数据库系统,黄色柱状体是缺陷的百分比,红色柱状体是代码的比例,横坐标标识了不同圈复杂度。最左边代码圈复杂度在 0-5 的时候,代码量占了总量的 40%,缺陷数占了 10%,而最右边全复杂度为 15+, 代码量占比 10%,但缺陷数量占了缺陷总数的 40%+。从左向右的可以看到代码的圈复杂度越高,缺陷的比例也越高。
我们往往不能一次性编写出完全满足需求的代码;或者需求在不断的变化;维护代码的人往往不是原始开发的人员,因此这也要求我们编写的代码是可被另一个人所理解和修改的。
2.1.1. 维护规范
代码检视是维护编码规范的关键机制。
代码的可读性、可维护性需要具体的规约来保持。我们往往通过编码规范来维护和保持代码的易于理解和避免一些常见的错误(或被称为最佳实践)。因此各大公司都有了自己的编码规范,比如我们熟知的 google 的编码规范。同时行业也为了维护行业的统一性,也给出了行业的编码规范,像 MISRA C 2012、AUTOSAR C++14 等等。更多的关于规范的信息,可参考《一图看懂软件缺陷检查涉及的内容》。
通过代码检视,可以确保所有代码都遵循了既定的编码标准和最佳实践。这有助于保持代码库的一致性,减少由于风格不一致或不遵循规范导致的维护问题。
目前的编码规范通常会包含两部分:
一致的代码风格整个项目中保持一致的编码风格,可以减少开发者在阅读不同部分代码时的认知负担,因为他们不需要适应不同的风格。这包括:
命名规范使用清晰、有意义的变量名和函数名,可以帮助其他开发者快速理解代码的目的和功能。例如,使用 calculateTotalPrice 代替 cTP,使得函数的作用一目了然。
代码格式化统一的缩进、括号使用和空白使用,可以使代码结构更加清晰,便于阅读和理解代码的逻辑结构。
注释和文档适当的注释和文档可以为代码提供上下文信息,解释复杂的逻辑或决策,使得其他开发者能够更快地理解代码的意图和工作方式。
编码实践每个编程语言都有自己的特点和特定的规约,很多地方往往被开发人员忽略从而埋下潜在的风险。这部分通常包括:
声明和初始化
类型转换
表达式
函数或方法
并发
错误和异常处理
面向对象的编程
输入输出
外部输入校验
日志处理
其他
规范的维护还有助于提高代码的可维护性和可扩展性,因为所有开发人员都遵循相同的规则和模式。
目前规范的很多部分都能够被静态分析工具得到检测,甚至自动修复。这些工具的告警也会在代码检视的时候得到确认是否是误报还是确实需要修改。对确认误报的部分会反馈给工具团队,进一步的修正检查工具。起到对工具反馈的作用。
此外,代码检视还可以作为规范更新和改进的反馈机制,团队可以根据实际开发经验,不断调整和完善编码规范,以适应不断变化的开发需求和技术进步。
2.1.2. 学习和教育
代码检视为开发人员提供了学习和教育的机会。
在微软和谷歌等公司,代码检视被视为一种学习和知识共享的平台。通过检视他人的代码,开发人员可以学习到新的编程技巧、设计模式和最佳实践。
同时,代码的作者也可以从其他开发人员的反馈中学习,了解自己的代码在哪些方面可以改进,这对于个人技能的提升非常有帮助。代码检视还可以作为一种教育工具,帮助新员工快速了解公司的代码风格和开发流程。通过参与代码检视,新员工可以更快地融入团队,提高自己的开发能力。
代码检视还可以揭示潜在的设计问题,促进团队成员之间的知识共享,增强团队的凝聚力。通过代码检视,开发人员能够从同事那里得到反馈,学习如何写出更清晰、更高效的代码,这对于个人技术成长和团队协作都是有益的。
2.1.3. 把关和事故预防
代码检视在把关和事故预防方面起着至关重要的作用。
代码检视除了来确保代码的质量,还可以防止潜在的错误和缺陷进入生产环境。代码检视可以帮助开发人员发现代码中的错误,包括逻辑错误、性能问题、安全漏洞等,从而在代码发布前进行修复。这不仅可以减少生产环境中的问题,还可以降低维护成本和风险。
虽然静态分析工具在缺陷检查上承担了越来越重要的防护作用,可毕竟检查能力在开发框架和第三方调用上需要很多的人为配置才能使分析工具在很大程度上识别框架上潜在的调用关系和第三方函数的行为。目前静态分析工具在这方面都还有很大的欠缺,这将导致很多的漏报。所以代码检视在防范代码缺陷上还是不可缺少的检查环节。
随着外部的恶意攻击的增加,缺陷的发现和漏报的矛盾也越发的突出。
另外检查工具毕竟只是辅助,还是需要开发人员建立安全防范意识,写出没有缺陷的代码,这个才是安全预防的治本方法。因此通过代码检视,团队可以建立起一种预防文化,不断提高代码质量,减少事故发生的概率。
3. 代码检视中需要注意的问题
3.1. 代码检视的文化建设
将代码评审用作团队建设活动,培养对发现缺陷的积极态度。为所有团队成员提供纠正坏习惯、学习新技巧和扩展其审查能力的机会。
尊重和鼓励、礼貌和专业精神;
作为审核人员,保持尊重的态度,鼓励团队成员参与代码审查;
作为开发人员,放弃负面情绪,将其视为学习和成长的机会。
3.2. 代码检视提交
一次代码提交不易过大
SmartBear 对思科系统编程团队的一项研究表明,开发人员一次审查的代码行数不应超过 200 到 400 行(LOC)。大脑一次只能有效地处理这么多信息;超过 400 LOC,发现缺陷的能力会减弱。
在实践中,在 60 到 90 分钟内对 200-400 LOC 进行审查应该会产生 70-90% 的缺陷发现。因此,如果代码中存在 10 个缺陷,那么正确进行的审查会发现其中的 7 到 9 个。
![](https://static001.geekbang.org/infoq/f0/f0e3dcd82bd58adf5cd5792fdfa547a4.gif)
SmartBear 研究表明,以每小时 500 LOC 的速度快速下降,缺陷密度显着下降。在有限的时间内以合理的速度进行合理数量的代码审查,从而产生最有效的代码审查。
![](https://static001.geekbang.org/infoq/ac/ac3b3e42eb57a38c1534e3f82a42331f.gif)
在代码提交检视前还需要完成以下工作:
完成本地测试;完成代码的自检,是作为一个合格代码开发人员的基本素质。
每次提交的代码量不易过多,通常在 200 到 400 行代码之间,以保证审查质量;由前面的研究可以看到,太大即不便于快速完成一次提交的审查,也不便于审阅者发现错误。关于如何将较大的改动拆分成较小的变动,google 在代码审核中给出很好的建议。
有清晰的代码变更描述。可以包括:摘要、背景、说明、需求或设计的变更单关联;目前代码审查的平台都提供了很好的辅助,帮助审核人员快速得到审核代码相关的信息,方便审核人员理解被审核代码。
3.3. 代码检视审核的主要内容
代码审核人员在审核过程中主要的审核内容包括:
使用检查清单是消除经常犯的错误和应对遗漏发现挑战的最有效方法。清单为团队成员提供了对每种类型审查的明确期望,并有助于跟踪报告和流程改进目的。
所有内容都使用了明确的名称,代码符合规定的风格指南;
不过度设计,代码并没有比它需要的更复杂;
没有明显的逻辑错误,异常处理完备;
代码具有适当的单元测试,测试是精心设计的。
4. 代码检视中需要注意的安全问题
结合业界的主要安全编码问题,总结了代码检视的检查清单如下。
注:下面问题涉及的 CWE 有的是类别,需要包括下面的子 CWE 编号。
4.1. 外部输入校验
[ ] 是否有外部输入,例如:http request, 表单输入、配置文件、数据库读?
[ ] 是否对输入做了正确的转义?
[ ] 是否对输入做了白名单或范围检查?
[ ] 是否对外部输入的链接执行了跳转或信息获取?链接是否做了白名单检验?
防范的问题:
4.2. SQL 检查
[ ] 是否有 SQL 操作?
[ ] 是否有外部输入作为 SQL 语句的组成部分?
[ ] 是否有拼接 SQL 语句的场景?
[ ] 是否有删库、删表、索引变更、改权限的操作?是否合理,并做了条件限制?
[ ] 获取的 connection 和 statement 是否关闭?
防范的问题:
4.3. 命令执行检查
[ ] 是否有命令操作?
[ ] 是否有外部输入做为命令操作组成部分?
[ ] 执行命令是否做了白名单校验?
防范的问题:
4.4. 文件操作
[ ] 是否有文件操作?
[ ] 文件的路径是否做了归一化和路径校验?
[ ] 是否为 XML 文件, 是否做了 XXE 防护?
[ ] 是否对 JSON, XML 等做了反序列化操作? 反序列化文件是否安全?
[ ] 是否做了写文件操作? 是否写入了敏感信息?敏感信息的存储是否符合规定?
[ ] 是否有上传文件? 文件的类型、大小是否做了限定和检查?
[ ] 是否有解压缩操作? 是否对解压缩的路径和大小做了校验?
[ ] 文件是否采用了 try-with-resource 方式打开?否则是否在 finally 里关闭?
防范的问题:
4.5. 不安全反射
[ ] 是否有反射操作?
[ ] 类是否为外部指定? 是否做了白名单校验?
[ ] 反射的方法是否为外部指定? 是否做了白名单校验?
防范的问题:
4.6. 输出到页面
[ ] 是否有页面输出操作?
[ ] 是否有外部输入直接输出到页面?是否做了转义和校验?
[ ] 是否有敏感信息?
防范的问题:
4.7. 输出到日志
[ ] 是否有日志输出操作?
[ ] 是否有外部输入直接写如日志? 是否做了转义处理?
[ ] 是否有敏感信息输出到日志中? 是否做了匿名化、脱敏处理?
[ ] 是否将异常信息直接输出到日志中?是否做了脱敏和屏蔽处理?
防范的问题:
4.8. 数据传输
[ ] 是否有数据传输操作?
[ ] 传输协议是否符合规定?
[ ] 是否有敏感信息传输?安全防护措施是否符合规定?
防范的问题:
4.9. 安全加密
[ ] 是否有加解密操作?
[ ] 是否采用了规定的加、解密函数和处理顺序?
[ ] 密钥、随机数的获取是否正确?
[ ] 是否存在硬编码密钥的场景(包括密钥写在注释中)?
防范的问题:
4.10. 权限控制
[ ] 是否有权限验证、设置、变更操作?
[ ] 验证步骤是否符合规范?
[ ] 是否存在逻辑绕过的可能?
防范的问题:
5. 参考
Mark Schroeder, "A practical guide to object-oriented metrics", IT Pro, Nov/Dec 1999
版权声明: 本文为 InfoQ 作者【Tom(⊙o⊙)】的原创文章。
原文链接:【http://xie.infoq.cn/article/9dadc59aaa5bbb901b9eb70d3】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论