1、CC+语言安全编程规范V10华为技术有限公司内部技术规范DKBA6914-2013.05C&C+语言安全编程规范2013年05月07日发布2013年05月07日实施华为技术有限公司HuaweiTechnologiesCo.,Ltd.版权所有XX修订声明本规范拟制与解释部门:网络安全技术能力中心本规范的相关系列规范或文件:Java语言安全编程规范Web应用安全开发规范相关国际规范或文件一致性:无替代或作废的其它规范或文件:无相关规范或文件的相互关系:本规范作为C语言编程规范和C+语言编程规范安全性要求的补充和扩展。规范号主要起草部门专家主要评审部门专家修订情况DKBA6914-2013.05网络
2、安全能力中心:罗东67107、于鹏00210657电信软件与核心网:陈辉军00190784无线产品线:肖飞龙 00051938网络产品线:魏建雄00222905IT产品线:熊华梁00106214中央软件院:朱楚毅 00217543、林水平00109837、周强00048368、辛威00176185、鞠章蕾00040951、谢青00101378中央硬件院:刘永合 00222758终端公司:杨棋斌00060469企业网络:黄凯进00040281、企业SecoSpace:王瑾中央软件院:黄茂青00057072、卢峰00210300网络产品线:李强00203020、罗天00062283、廖永强0011
3、1217、任志清00048956、李海蛟00040826、陈璟00222879、勾国凯00048893、范佳甲00109753中央硬件院:刘崇山00159994、施文超00109740企业网络:李有永IT产品线:李显才00044635、何昌军00061280能力中心:郭曙光00121837网络安全实验室:林结斌00206214电信软件与核心网:朱刚00192988无线产品线:李瀛00130531、王爱成00223009、杨彬00065941、于继万00052142、解然00234688V1.0C&C+语言安全编程规范 1C&C+语言安全编程规范 60 规范制定说明 60.1 前言 60.2 使
4、用对象 60.3 适用范围 60.4 术语定义 61 通用原则 7原则1.1:对外部输入进行校验 7原则1.2:禁止在日志中保存口令、密钥 8原则1.3:及时清除存储在可复用资源中的敏感信息 8原则1.4:正确使用经过验证的安全的标准加密算法 8原则1.5:遵循最小权限原则 9原则1.6:删除或修改没有效果的代码 9原则1.7:删除或修改没有使用到的变量或值 92 字符串操作安全 10规则2.1:确保有足够的空间存储字符串的字符数据和0结束符 10规则2.2:字符串操作过程中确保字符串有0结束符 11规则2.3:把数据复制到固定长度的内存前必须检查边界 13规则2.4:避免字符串/内存操作函数
5、的源指针和目标指针指向内存重叠区 133 格式化输出安全 15规则3.1:格式化输出函数的格式化参数和实参类型必须匹配 15规则3.2:格式化输出函数的格式化参数和实参个数必须匹配 17规则3.3:禁止以用户输入来构造格式化字符串 17建议3.1:使用格式化函数时推荐使用精度说明符 184 整数安全 19规则4.1:确保无符号整数运算时不会出现反转 19规则4.2:确保有符号整数运算时不会出现溢出 20规则4.3:确保整型转换时不会出现截断错误 21规则4.4:确保整型转换时不会出现符号错误 22规则4.5:把整型表达式比较或赋值为一种更大类型之前必须用这种更大类型对它进行求值 23建议4.1
6、:避免对有符号整数进行位操作符运算 235 内存管理安全 24规则5.1:禁止引用未初始化的内存 24规则5.2:禁止访问已经释放的内存 25规则5.3:禁止重复释放内存 27规则5.4:必须对指定申请内存大小的整数值进行合法性校验 27规则5.5:禁止释放非动态申请的内存 29建议5.1:避免使用alloca函数申请内存 296 禁用不安全函数或对象 30规则6.1:禁止使用未显式指明目标缓冲区大小的字符串操作函数 30规则6.2:禁止调用OS命令解析器执行命令或运行程序,防止命令注入 32规则6.3:禁止使用std:ostrstream,推荐使用std:ostringstream 33规则
7、6.4:C+中,必须使用C+标准库替代C的字符串操作函数 337 文件输入/输出安全 35规则7.1:必须使用int类型来接收字符输入/输出函数的返回值 35规则7.2:创建文件时必须显式指定合适的文件访问权限 36规则7.3:文件路径验证前,必须对其进行标准化 36建议7.1:访问文件时尽量使用文件描述符代替文件名作为输入,以避免竞争条件问题 378 STL库安全 38规则8.1:引用容器前后元素时要确保容器元素存在 38规则8.2:迭代子使用前必须保证迭代子有效 39规则8.3:必须确保迭代子指向的内容有效 40规则8.4:正确处理容器的erase()方法与迭代子的关系 419 C+类和对
8、象安全 42规则9.1:禁止切分多态的类对象 42规则9.2:禁止定义基类析构函数为非虚函数,所有可能被继承类的析构函数都必须定义为virtual 44规则9.3:避免出现deletethis操作 46规则9.4:禁止在类的公共接口中返回类的私有数据地址 48建议9.1:重载后缀操作符应返回const类型 49建议9.2:显式声明的模板类应进行类型特化 5010 其它 51规则10.1:禁止使用rand()产生用于安全用途的伪随机数 51规则10.2:禁止存储getenv()返回的字符串指针 55规则10.3:多线程环境下,禁止使用可能会导致crash的不安全函数 56建议10.1:编译时应当
9、使用编译器的最高警告等级 60建议10.2:防止处理敏感数据的代码因被编译器优化而失效 6011 参考资料 62C&C+语言安全编程规范1 规范制定说明1.1 前言随着公司业务发展,越来越多的产品被公众、互联网所熟知,并成为安全研究组织的研究对象、黑客的漏洞挖掘目标,容易引起安全问题。安全问题影响的不只是单个产品,甚至有可能影响到公司整体声誉。产品安全涉及需求、设计、实现、部署多个环节,实现的安全是产品安全的重要一环。为了帮助产品开发团队编写安全的代码,减少甚至规避由于编码错误引入安全风险,特制定本规范。C&C+语言安全编程规范参考业界安全编码的研究成果,并结合产品编码实践的经验总结,针对C/
10、C+语言编程中的字符串操作、整数操作、内存管理、文件操作、STL库使用等方面,描述可能导致安全漏洞或潜在风险的常见错误。以期减少缓冲区溢出、整数溢出、格式化字符串攻击、命令注入攻击、目录遍历等典型安全问题。1.2 使用对象本规范的读者及使用对象主要为使用C和C+语言的开发人员、测试人员等。1.3 适用范围本规范适合于公司基于C或C+语言开发的产品。1.4 术语定义原则:编程时必须遵守的指导思想。规则:编程时必须遵守的约定。建议:编程时必须加以考虑的约定。说明:对此原则/规则/建议进行必要的解释。错误示例:对此原则/规则/建议从反面给出例子。推荐做法:对此原则/规则/建议从正面给出例子。延伸阅读
11、材料:建议进一步阅读的参考材料。2 通用原则原则1.1:对外部输入进行校验说明:对于外部输入(包括用户输入、外部接口输入、配置文件、网络数据和环境变量等)可能用于以下场景的情况下,需要检验入参的合法性: 输入会改变系统状态 输入作为循环条件 输入作为数组下标 输入作为内存分配的尺寸参数 输入作为格式化字符串 输入作为业务数据(如作为命令执行参数、拼装sql语句、以特定格式持久化) 输入影响代码逻辑这些情况下如果不对用户数据作合法性验证,很可能导致DoS、内存越界、格式化字符串漏洞、命令注入、SQL注入、缓冲区溢出、数据破坏等问题。对外部输入验证常见有如下几种方式:(1)校验输入数据长度:如果输
12、入数据是字符串,通过校验输入数据的长度可以加大攻击者实施攻击的难度,从而防止缓冲区溢出、恶意代码注入等漏洞。(2)校验输入数据的范围:如果输入数据是数值,必须校验数值的范围是否正确,是否合法、在有效值域内,例如在涉及到内存分配、数组操作、循环条件、计算等安全操作时,若没有进行输入数值有效值域的校验,则可能会造成内存分配失败、数组越界、循环异常、计算错误等问题,这可能会被攻击者利用并进行进一步的攻击。(3)输入验证前,对数据进行归一化处理以防止字符转义绕过校验:通过对输入数据进行归一化处理(规范化,按照常用字符进行编码),彻底去除元字符,可以防止字符转义绕过相应的校验而引起的安全漏洞。(4)输入
13、校验应当采用“白名单”形式:“黑名单”和“白名单”是进行数据净化的两种途径。“黑名单”尝试排斥无效的输入,而“白名单”则通过定义一个可接受的字符列表,并移除任何不接受的字符来仅仅接受有效的输入。有效输入值列表通常是一个可预知的、定义良好的集合,并且其大小易于管理。“白名单”的好处在于,程序员可以确定一个字符串中仅仅包含他认为安全的字符。“白名单”比“黑名单”更受推荐的原因是,程序员不必花力气去捕捉所有不可接受的字符,只需确保识别了可接受的字符就可以了。这样一来,程序员就不用绞尽脑汁去考虑攻击者可能尝试哪些字符来绕过检查。原则1.2:禁止在日志中保存口令、密钥说明:在日志中不能保存口令和密钥,其
14、中的口令包括明文口令和密文口令。对于敏感信息建议采取以下方法, 不打印在日志中; 若因为特殊原因必须要打印日志,则用“*”代替。原则1.3:及时清除存储在可复用资源中的敏感信息说明:存储在可复用资源中的敏感信息如果没有正确的清除则很有可能被低权限用户或者攻击者所获取和利用。因此敏感信息在可复用资源中保存应该遵循存储时间最短原则。可复用资源包括以下几个方面: 堆(heap) 栈(stack) 数据段(datasegment) 数据库的映射缓存存储口令、密钥的变量使用完后必须显式覆盖或清空。原则1.4:正确使用经过验证的安全的标准加密算法说明:禁用私有算法或者弱加密算法(如DES,SHA1等),应
15、该使用经过验证的、安全的、公开的加密算法。加密算法分为对称加密算法和非对称加密算法。推荐使用的常用对称加密算法有: AES推荐使用的常用非对称算法有: RSA 数字签名算法(DSA)此外还有验证消息完整性的安全哈希算法(SHA256)等。基于哈希算法的口令安全存储必须加入盐值(salt)。密钥长度符合最低安全要求: AES:128位 RSA:2048位 DSA:1024位 SHA:256位原则1.5:遵循最小权限原则说明:程序在运行时可能需要不同的权限,但对于某一种权限不需要始终保留。例如,一个网络程序可能需要超级用户权限来捕获原始网络数据包,但是在执行数据报分析等其它任务时,则可能不需要相同
16、的权限。因此程序在运行时只分配能完成其任务的最小权限。过高的权限可能会被攻击者利用并进行进一步的攻击。(1)撤销权限时应遵循正确的撤销顺序:在涉及到set-user-ID和set-group-ID程序中,当有效的用户ID(userID)和组ID(groupID)与真实的用户不同时,不但要撤销用户层面(userlevel)的权限而且要撤销组层面(grouplevel)的权限。在进行这样的操作时,要保证撤销顺序的正确性。权限撤销顺序的不正确操作,可能会被攻击者获得过高的权限而进行进一步的攻击。(2)完成权限撤销操作后,应确保权限撤销成功:不同平台下所谓的“适当的权限”的意义是不相同的。例如在Sol
17、aris中,setuid()的适当的权限指的是PRIV_PROC_SETID权限在进程的有效权限集中。在BSD中意味着有效地用户ID(EUID)为0或者uid=geteuid()。而在Linux中,则是指进程具有CAP_SETUID能力并且当EUID不等于0、真正的用户ID(RUID)或者已保存的set-userID(SSUID)中任何一个时,setuid(geteuid()是失败的。原则1.6:删除或修改没有效果的代码说明:删除或修改一些即使执行后、也不会有任何效果的代码。一些存在的代码(声明或表达式),即使它被执行后,也不会对代码的结果或数据的状态产生任何的影响,或者产生不是所预期的效果,
18、这样的代码在可能是由于编码错误引起的,往往隐藏着逻辑上的错误。原则1.7:删除或修改没有使用到的变量或值说明:删除或修改没有使用到的变量或值。一些变量或值存在于代码里,但并没有被使用到,这可能隐含着逻辑上的错误,需要被识别出来,删除这类语句或做相应的修改。3 字符串操作安全规则2.1:确保有足够的空间存储字符串的字符数据和0结束符说明:在分配内存或者在执行字符串复制操作时,除了要保证足够的空间可以容纳字符数据,还要预留0结束符的空间,否则会造成缓冲区溢出。错误示例1:拷贝字符串时,源字符串长度可能大于目标数组空间。voidmain(intargc,char*argv) chardst128;
19、if(argc1) strcpy(dst,argv1);/源字符串长度可能大于目标数组空间,造成缓冲区溢出 /*/推荐做法:根据源字符串长度来为目标字符串分配空间。voidmain(intargc,char*argv) char*dst=NULL; if(argc1) dst=(char*)malloc(strlen(argv1)+1);/*【修改】确保字符串空间足够容纳argv1*/ if(dst!=NULL) strncpy(dst,argv1,strlen(argv1); dststrlen(argv1)=0;/【修改】dst以0结尾 /*.dst使用后free.*/错误示例2:典型的差
20、一错误,未考虑0结束符写入数组的位置,造成缓冲区溢出和内存改写。voidNoCompliant() chardstARRAY_SIZE+1; charsrcARRAY_SIZE+1; unsignedinti=0; memset(src,sizeof(dst); for(i=0;srci!=0&(isizeof(dst);+i) dsti=srci; dsti=0; /*/推荐做法:voidCompliant() chardstARRAY_SIZE+1; charsrcARRAY_SIZE+1; unsignedinti=0; memset(src,sizeof(dst); for(i=0;s
21、rci!=0&(isizeof(dst)-1);+i)/*【修改】考虑0结束符*/ dsti=srci; dsti=0; /*/规则2.2:字符串操作过程中确保字符串有0结束符说明:字符串结束与否是以0作为标志的。没有正确地使用0结束字符串可能导致字符串操作时发生缓冲区溢出。因此对于字符串或字符数组的定义、设置、复制等操作,要给0预留空间,并保证字符串有0结束符。注意:strncpy、strncat等带n版本的字符串操作函数在源字符串长度超出n标识的长度时,会将包括0结束符在内的超长字符串截断,导致0结束符丢失。这时需要手动为目标字符串设置0结束符。错误示例1:strlen()不会将0结束符算
22、入长度,配合memcpy使用时会丢失0结束符。voidNoncompliant() chardst11; charsrc=; char*tmp=NULL; memset(dst,sizeof(dst); memcpy(dst,src,strlen(src); printf(src:%srn,src); tmp=dst;/到此,dst还没有以0结尾 do putchar(*tmp); while(*tmp+);/访问越界 return;推荐做法:为目标字符串设置0结束符voidCompliant() chardst11; charsrc=; char*tmp=NULL; memset(dst,s
23、izeof(dst); memcpy(dst,src,strlen(src); dstsizeof(dst)-1=0; /【修改】dst以0结尾 printf(src:%srn,src); tmp=dst; do putchar(*tmp); while(*tmp+); return;错误示例2:strncpy()拷贝限长字符串,截断了0结束符。voidNoncompliant() chardst5; charsrc=; strncpy(dst,src,sizeof(dst);printf(dst);/访问越界,dst没有0结束符 return;推荐做法:voidCompliant() cha
24、rdst5; charsrc=; strncpy(dst,src,sizeof(dst); dstsizeof(dst)-1=0;/【修改】最后字节置为0printf(dst); return;规则2.3:把数据复制到固定长度的内存前必须检查边界说明:将未知长度的数据复制到固定长度的内存空间可能会造成缓冲区溢出,因此在进行复制之前应首先获取并检查数据长度。典型的如来自gets()、getenv()、scanf()的字符串。错误示例:输入消息长度不可预测,不加检查的复制会造成缓冲区溢出。voidNoncompliant() chardst16; char*temp=getInputMsg();
25、if(temp!=NULL) strcpy(dst,temp);/temp长度可能超过dst的大小 return;推荐做法:voidCompliant() chardst16; char*temp=getInputMsg(); if(temp!=NULL) strncpy(dst,temp,sizeof(dst);/*【修改】只复制不超过数组dst大小的数据*/ dstsizeof(dst)-1=0;/【修改】copy以0结尾 return;规则2.4:避免字符串/内存操作函数的源指针和目标指针指向内存重叠区说明:内存重叠区是指一段确定大小及地址的内存区,该内存区被多个地址指针指向或引用,这些
26、指针介于首地址和尾地址之间。在使用像memcpy、strcpy、strncpy、sscanf()、sprintf()、snprintf()和wcstombs()这样的函数时,复制重叠对象会存在未定义的行为,这种行为可能破坏数据的完整性。错误示例1:snprintf的参数使用存在问题voidNoncompliant()#defineMAX_LEN1024 charcBufMAX_LEN+1=0; intnPid=0; strncpy(cBuf,”HelloWorld!”,strlen(”HelloWorld!”); snprintf(cBuf,MAX_LEN,%d:%s,nPid,cBuf);/
27、*cBuf既是源又是目标,函数使用不安全*/ return;推荐做法:使用不同源和目标缓冲区来实现复制功能。voidCompliant()#defineMAX_LEN1024 charcBufMAX_LEN+1=0; charcDescMAX_LEN+1=0;/【修改】另起一个缓冲区,防止缓冲区重叠出错 intnPid=0; strncpy(cDesc,”HelloWorld!”,strlen(”HelloWorld!”);/*【修改】防止缓冲区重叠出错*/ snprintf(cBuf,MAX_LEN,%d:%s,nPid,cDesc);/*【修改】防止缓冲区重叠出错*/ return;错误示
28、例2:#defineMSG_OFFSET3#defineMSG_SIZE6voidNoCompliant() charstr=teststring; char*ptr1=str; char*ptr2; ptr2=ptr1+MSG_OFFSET; memcpy(ptr2,ptr1,MSG_SIZE); return;推荐做法:使用memmove函数,源字符串和目标字符串所指内存区域可以重叠,但复制后目标字符串内容会被更改,该函数将返回指向目标字符串的指针。#defineMSG_OFFSET3#defineMSG_SIZE6voidCompliant() charstr=teststring; c
29、har*ptr1=str; char*ptr2; ptr2=ptr1+MSG_OFFSET; memmove(ptr2,ptr1,MSG_SIZE);/*【修改】使用memmove代替memcpy,防止缓冲区重叠出错*/ return;memcpy与memmove的目的都是将N个字节的源内存地址的内容拷贝到目标内存地址中。但当源内存和目标内存存在重叠时,memcpy会出现错误,而memmove能正确地实施拷贝,但这也增加了一点点开销。memmove的处理措施: 当源内存的首地址等于目标内存的首地址时,不进行任何拷贝 当源内存的首地址大于目标内存的首地址时,实行正向拷贝 当源内存的首地址小于目标内存的首地址时,实行反向拷贝4 格式化输出安全规则3.1:格式化输出函数的格式化参数和实参类型必须匹配说明:使用格式化字符串应该小心,确保格式字符和参数在数据类型上的匹配。格式字符和参数之间的不匹配会导致未定义的行为。大多数情况下,不正确的格式化字符串会可能会导致格式化漏洞,使程序异常终止。错误示例1:格式字符和参数的类
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1