安全性在软件开发过程中是一个极其重要和深刻的话题。当安全性受到损害时,会发生非常糟糕的事情。我们在软件开发生命周期的各个阶段都必须记住这一点。
不同于一些其他非功能性要求,一般不能在之后才在系统中考虑到安全性。开发一个相对安全的项目离不开一个安全规则的指导,一个好用的开发工具,一套安全的编码规范。
下面就从开发安全规则、开发工具的安全利用,安全编码这三方面进行分析。降低软件中的漏洞,包括但不限于缓冲区溢出、边界外的数组访问、未初始化的内存使用、类型混淆等安全漏洞。
一个好的安全开发指导规则,能够在开发软件过程中挖掘出漏洞的。这个好的安全规则首选推荐微软的SDL(安全开发生命周期)。下面就梳理下这个SDL的一些相对核心的理论基础。
它主要侧重于软件开发的安全保证过程。SDL致力于减少软件中漏洞的数量和严重性。
SDL的核心理念是将安全考虑集成在软件开发的每一个阶段:需求分析、设计、编码、测试和维护。从需求、设计到发布产品的每一个阶段每都增加了相应的安全活动,以减少软件中漏洞的数量并将安全缺陷降低到最小程度。
SDL基于三个核心概率:培训教育、持续过程改善和责任。
SDL的一个主要目标:安全和隐私。
SDL在开发过程的所有阶段进行安全和隐私保护
安全开发生命周期 (SDL)由一组支持安全保证和合规性要求的实践组成。SDL 通过减少软件中漏洞的数量和严重性,同时降低开发成本,帮助开发人员构建更安全的软件。
SDL详细步骤
SDL安全设计核心原则主要包括:攻击面最小化、基本隐私、权限最小化、默认安全、纵深防御、威胁建模。下面就这对这些原则展开做个简单解析。
攻击面最小化(Attack Surface Reduction):
指尽量减少暴露恶意用户可能发现并试图利用的攻击面数量。软件产品的受攻击面是一个混合体,不仅包括代码、接口、服务,也包括对所有用户提供服务的协议。尤其是那些未被验证或者远程的用户都可以访问到的协议,安全人员在攻击面最小化时首先要对攻击面进行分析,攻击面分析就是枚举所有访问入库、接口、协议一剂可执行代码的过程,从更高层次来说,攻击面分析着重于如下4点:
1、降低默认执行的代码量
2、限制可访问到代码的人员范围
3、限定可访问到代码的人员身份
4、降低代码执行所需权限
基本隐私(Basic Privacy):
指用户在使用软件时无可避免个人信息被收集、使用甚至分发,企业则有责任和义务建立保护个人信息的保护措施,抵御敌对攻击行为,确保用户基本隐私的安全性。
权限最小化(Least Privilege):
指如果一个应用程序或网站被攻击、破坏,权限最小化机制能够有效的将潜在损害最小化。常见的权限最小化实践如:
1、普通管理员/系统管理员等角色管理
2、文件只读权限/文件访问权限等访问控制
3、进程/服务以所需最小用户权限运行
默认安全(Secure Defaults):
默认安全配置在客户熟悉安全配置选项之前不仅有利于更好的帮助用户掌握安全配置经验,同时也可以确保应用程序初始状态下处于较安全状态。
纵深防御(Defense in Depth):
纵深防御包含两层含义:首先,要在各个不同层面、不同方面实施安全方案,避免出现疏漏,不同安全方案之间需要相互配合,构成一个整体;其次,要在正确的地方做正确的事情,即:在解决根本问题的地方实施针对性的安全方案。
威胁建模(Threat Modeling):
威胁建模是一种分析应用程序威胁的过程和方法。这里的威胁是指恶意用户可能会试图利用以破坏系统。
下面就以visual studio工具进行展开,利用工具上的几个配置进行提高软件安全性。使用这些工具和做法并不会使应用程序免受攻击,但能降低攻击成功的可能性。
1、代码分析功能
此编译器选项将激活报告潜在安全问题(比如缓冲区溢出、未初始化的内存、null指针取消引用和内存泄漏)的代码分析。此选项默认已关闭。建议开启这个开关。
2、/GS(缓冲区安全检查)
这个的安全检查主要处理:函数调用的返回地址;函数的异常处理程序的地址;易受攻击的函数参数。导致缓冲区溢出是黑客用来利用不强制实施缓冲区大小限制的代码的技术。
指示编译器将溢出检测代码插入到面临被利用风险的函数中。检测到溢出时,则停止执行。默认情况下,此选项处于启用状态。
传递到函数中的易受攻击的参数。易受攻击的参数是指针、C++ 引用、C 结构 (C++ POD 类型) 包含指针或 GS 缓冲区。
3、/DYNAMICBASE(使用地址空间布局随机化)
使用 Windows 的地址空间布局随机化 (ASLR) 功能,指定是否生成可在加载时随机重新设定基址的可执行文件映像。
通过使用此链接器选项,可以生成一个在执行开始时可在内存的不同位置加载的可执行映像。此选项还使内存中的堆栈位置更加不可预测。
当前软件中都可能存在相同类别的内存安全漏洞,也可能存在于推理且无序的执行路径中,包括但不限于缓冲区溢出、边界外的数组访问、未初始化的内存使用、类型混淆等漏洞。
一个套规范的安全开发可以大大降低软件漏洞的风险,安全开发通常需要我们在编码过程中做到
1、不要使用那些易受攻击的API函数;
2、要做好对输入参数做校验;
3、慎重使用强制类型转换;
4、防止算术溢出和下溢;
5、异常捕获是定时炸弹;
6、多用安全工具进行检查代码;
7、文件操作过程中要做好路径和权限管控。
1、系统函数
系统函数的使用可以大大降低代码的开发工作量,但使用不安全的系统函数那就得不偿失了。
在开过过程中许多旧CRT函数具有持续更新、更安全的版本。如果存在安全函数,则较旧的、安全性更低的版本将标记为已弃用,并且新版本具有 _s(“安全”)后缀。
安全函数不会阻止或更正安全错误。相反,它们会在发生错误时捕获错误。它们对错误情况执行其他检查。如果出现错误,则调用错误处理程序。
上图中函数strcpy 无法判断正在复制的字符串对于目标缓冲区而言是否太大。其安全对应项 strcpy_s 会将缓冲区大小作为参数。因此,可以确定是否会发生缓冲区溢出。如果你使用 strcpy_s 将 11 个字符复制到 10 个字符缓冲区中,则这是你方造成的错误;strcpy_s 无法更正错误。
2、SafeInt库
SafeInt它是可以与 MSVC、GCC或 Clang 结合使用的可移植库,有助于防止在应用程序执行数学运算时可能会出现的整数溢出而被利用。SafeInt库包括 SafeInt 类、SafeIntException 类和几个SafeInt 函数。
详细的链接地址:
https://github.com/dcleblanc/SafeInt
SafeInt 类可防止整数溢出和被零除攻击。可以通过使用它处理不同类型的值之间的比较。它提供了两种错误处理策略。默认策略是针对引发 SafeInt 类异常的 SafeIntException 类,以报告无法完成数学运算的原因。第二个策略针对 SafeInt 类,用以停止程序的执行。还可以定义自定义策略。
每个 SafeInt 函数各保护一个数学运算免于出现可被利用的错误。使用两种不同的参数,而不必将它们转换为相同类型。若要保护多个数学运算,请使用 SafeInt 类。
3、信任边界
信任边界存在于应用程序可能与信任度较低的上下文提供的数据进行交互的位置,例如系统上的另一个进程,或者内核模式设备驱动程序中的非管理用户模式进程。
4、类型转换
类型强制转换使用尽可能用C++的风格static_cast<>,dynamic_cast<>,它允许允许更多编译器检查,并且更为显式,相对更安全。
5、接口应用
无论是C还是C++的编程范式,从实用的角度,最终对面向接口编程,好的代码接口具备下述特性:
1、Self-describing,即自描述性,设计清晰简洁的API接口名称,一眼就能知道是什么功能。
2、Clear hierarchy,即清晰的层级性,API的接口大类不能与小类相混淆。
3、Granularity sex,即粒度性,掌控好粒度性的API接口,不能太过于粗糙,尽量细分化,容易后续的扩展。
6、外部可控函数
尽量减少使用外部可控数据作为启动函数的参数例如:system、WinExec、ShellExecute、CreateProcess、execv、ececvp ,popen;如果外部可控作为这些函数的参数,就会有导致被注入的风险。如果确实需要使用这些函数,可以使用白名单机制验证其参数,确保这些函数的参数不受到外来数据的命令注入影响。
7、文件操作
对文件操作的时候可以几个降低安全风险
1、当文件路径来自外部数据时候,需要先将文件路径规范化,这个没处理攻击者就会有机会通过恶意构造文件路径进行文件的越权访问。
2、创建文件时候必须做好指定文件的访问权限
int open( const char * pathname, int flags);
int open( const char * pathname, int flags, mode_t mode);
尽可能使用第二个模式。
以上知识的梳理更多从基础理论出发,并且很多详细细节还有待在后续实践进行进一步的完善。
软件安全和二进制漏洞是一个永恒的对抗话题,基于一套安全的开发规范,指导在开发安全生命周期内进行推进软件开发。并且加强开发中的安全意识的培养,又助于降低减少软件的漏洞的出现。
参考借鉴
https://learn.microsoft.com/zh-cn/cpp/security/security-best-practices-for-cpp?view=msvc-170
https://www.microsoft.com/en-us/securityengineering/sdl/practices
https://learn.microsoft.com/en-us/previous-versions/bb288454(v=msdn.10)?redirectedfrom=MSDN
https://google.github.io/styleguide/cppguide.html