C/C++编程规范

17 篇文章 0 订阅

请添加图片描述
更多资讯、知识,微信公众号搜索:“上官宏竹”。


1、strncpy、strncat

注意:strncpy、strncat等带n版本的字符串操作函数在源字符串长度超出n标识的长度时,会将包括’\0’结束符在内的超长字符串截断,导致’\0’结束符丢失。这时需要手动为目标字符串设置’\0’结束符。

	char dst[11];     // 【注意】最好每次定义时初始化为0: dst[11] = {0};
	char src[] = "0123456789";
	char *tmp = NULL;
	memset(dst, '@', sizeof(dst));
	memcpy(dst, src, strlen(src));
	dst[sizeof(dst) - 1] = ’\0’; 	//【修改】dst以’\0’结尾

2、避免字符串/内存操作函数的源指针和目标指针指向内存重叠区

在使用像memcpy、strcpy、strncpy、sscanf()、sprintf()、snprintf()和wcstombs()这样的函数时,复制重叠对象会存在未定义的行为,这种行为可能破坏数据的完整性。

memcpy与memmove的目的都是将N个字节的源内存地址的内容拷贝到目标内存地址中。
但当源内存和目标内存存在重叠时,memcpy会出现错误,而memmove能正确地实施拷贝,但这也增加了一点点开销。
memmove的处理措施:
当源内存的首地址等于目标内存的首地址时,不进行任何拷贝
当源内存的首地址大于目标内存的首地址时,实行正向拷贝
当源内存的首地址小于目标内存的首地址时,实行反向拷贝

3、使用格式化函数时推荐使用精度说明符

#define BUF_SIZE 128
void Compliant()
{
	char buffer[BUF_SIZE + 1];
	sprintf(buffer, "Usage: %.100s argument\n", argv[0]); /*【修改】字符串加上精度说明符 */
	/* ...do something... */
}
//通过精度限制从argv[0] 中只能拷贝 100 个字节。

4、确保无符号整数运算时不会出现反转

无符号数u1 u2,在计算u1+u2时,需要判断u1+u2是否大于UINT_MAX

	if((UINT_MAX - ui1) < ui2) //【修改】确保无符号整数运算时不会出现反转
	{
		return ERROR;
	}
	else
	{
		*ret = ui1+ ui2;
	}

5、确保有符号整数运算时不会出现溢出

	INT32 si1, INT32 si2;
	INT64 tmp = (INT64)si1 *(INT64)si2; /*【修改】确保有符号整数运算时不会出现溢出 */
	//++ 将32位有符号数转换成64位,并且计算完成后需要比较结果是否有符号数的范围内
	if((INT_MAX < tmp) || (INT_MIN > tmp))
	{
		return ERROR;
	}

6、确保整型转换时不会出现截断错误、符号错误

【截断错误】将一个较大整型转换为较小整型,并且该数的原值超出较小类型的表示范围,就会发生截断错误,原值的低位被保留而高位被丢弃。
【符号错误】从带符号整型转换到无符号整型会发生符号错误,符号错误并不丢失数据,但数据失去了原来的含义。带符号整型转换到无符号整型,最高位(high-order bit)会丧失其作为符号位的功能。如果该带符号整数的值非负,那么转换后值不变;如果该带符号整数的值为负,那么转换后的结果通常是一个非常大的正数。

	//++[符号错误绕过长度检查]
	int	length;       //++ 声明为无符号数
	char buf[BUF_SIZE];
	length = atoi(argv[1]); //【错误】atoi返回值可能为负数
	if (length < BUF_SIZE)  // len为负数,长度检查无效
	{
		memcpy(buf, argv[2], length); /* 带符号的len被转换为size_t类型的无符号整数,负值被解释为一个极大的正整数。memcpy()调用时引发buf缓冲区溢出*/
		printf("Data copied\n");
	}

7、把整型表达式比较或赋值为一种更大类型之前必须用这种更大类型对它进行求值

UINT32 blockNum;
UINT64 alloc = (UINT64)blockNum * 16; /*【修改】确保整型表达式转换时不出现数值错误 */
//++ 先将两个32位的数之积转换为64位再赋值给64位的变量。

8、避免对有符号整数进行位操作符运算

说明:位操作符(~、>>、<<、&、^、|)应该只用于无符号整型操作数,因为有符号整数上的有些位操作的结果是由编译器所决定的,可能会出现出乎意料的行为或编译器定义的行为。

9、内存管理

申请内存后初始化(memset )
禁止内存指针移动后(非malloc分配后的起始值),通过该指针释放内存,会出现未知错误。因为malloc一块内存后,它的前一个字节存放了分配的内存大小,free时会根据该字节所代表的大小去free内存。

10、禁止调用OS命令解析器执行命令或运行程序,防止命令注入

禁止使用system()和popen()。替代方案是POSIX的exec系列函数或Win32 API CreateProcess()等与命令解释器无关的进程创建函数来替代。
错误示例:

system(sprintf("any_exe %s", input)); //【错误】参数不是硬编码,禁止使用system

这行代码是需要执行一个名为any_exe的程序,程序参数来自用户的输入input。这种情况下,恶意用户输入参数:
happy; useradd attacker
最终shell将字符串”any_exe happy; useradd attacker”解释为两条独立的命令连续执行:
any_exe happy
useradd attacker
这样攻击者通过注入了一条命令”useradd attacker”创建了一个新用户。这明显不是程序所希望的。
改用:

if (execve("/usr/bin/any_exe", args, envs) == -1) /*【修改】使用execve代替system */

11、禁止使用std::ostrstream,推荐使用std::ostringstream

说明: std::ostrstream的使用上需要特别注意几点:
(1)str() 会调用成员函数freeze(),它会冻结字符序列,当缓冲区不够大以至于需要分配新缓冲区时,这么做可以避免事情变得复杂。
(2)str()不会附加字符串终止符号(’\0’)。
(3)data()返回所有字符串,没有附带’\0’结尾字符(目前有些编译器自动调用c_str方法了)。
上面如果不注意,就可能会导致内存访问越界、缓冲区溢出等问题,所以建议不要使用ostrstream。[C++03]标准将std::strstream标明为deprecated,替代方案是std::stringstream。ostringstream没有上述问题。
错误示例:下列代码使用了std::ostrstream,可能会导致内存访问越界等问题。

void NoCompliant()
{
	std::ostrstream mystr; //【错误】不要使用std::ostrstream
	mystr << "hello world";
	// ostream.str方法返回的指针,没有空结束符,容易造成问题
	char *p = mystr.str();
	std::cout << mystr.str() << std::endl;
}

12、C++中,必须使用C++标准库替代C的字符串操作函数

C标准的系列字符串处理函数strcpy/strcat/sprintf/scanf/gets,不检查目标缓冲区的大小,容易引入缓冲区溢出的安全漏洞。
C++标准库提供了字符串类抽象的一个公共实现std::string,支持字符串的常规操作

13、必须使用int类型来接收字符输入/输出函数的返回值,不要使用char型

说明:字符输入/输出函数fgetc()、getc()和getchar()都从一个流读取一个字符,并把它以int值的形式返回。如果这个流到达了文件尾或者发生读取错误,函数返回EOF。fputc()、putc()、putchar()和ungetc()也返回一个字符或EOF。

如果这些I/O函数的返回值需要与EOF进行比较,不要将返回值转换为char类型
因为char是有符号8位的值,int是32位的值。如果getchar()返回的字符的ASCII值为0xFF,转换为char类型后将被解释为EOF。0xFF这个值被有符号扩展后是0xFFFFFFFF,刚好等于EOF的值。
注意:对于sizeof(int) == sizeof(char)的平台,用int接收返回值也可能无法与EOF区分,这时要用feof()和ferror()检测文件尾和文件错误。

14、文件路径验证前,必须对其进行标准化

说明:当文件路径来自非信任域时,需要先将文件路径规范化再做校验。路径在验证时会有很多干扰因素,如相对路径与绝对路径,如文件的符号链接、硬链接、快捷路径、别名等。
所以在验证路径时需要对路径进行标准化,使得路径表达唯一化、无歧义。
如果没有作标准化处理,攻击者就有机会:
推荐做法:
Linux下对文件进行标准化,可以防止黑客通过构造指向系统关键文件的链接文件。realpath() 函数返回绝对路径,删除了所有符号链接:

void  Compliant(char *lpInputPath)
{
	char realpath[MAX_PATH];
	if ( realpath(inputPath, realpath) == NULL)
		/* handle error */;
	/*... do something ...*/
}

Windows下可以使用PathCanonicalize函数对文件路径进行标准化:

void  Compliant(char *lpInputPath)
{
	char realpath[MAX_PATH];
	char *lpRealPath = realpath;
	if ( PathCanonicalize(lpRealPath,lpInputPath) == NULL)
		/* handle error */;
	/*... do something ...*/
}

15、访问文件时尽量使用文件描述符代替文件名作为输入,以避免竞争条件问题

说明:该建议应用场景如下,当对文件的元信息进行操作时(比如修改它的所有者、对文件进行统计,或者修改它的权限位),首先要打开该文件,然后对打开的文件进行操作。只要有可能,应尽量避免使用获取文件名的操作,而是使用获取文件描述符的操作。这样做将避免文件在程序运行时被替换(一种可能的竞争条件)。
例如,当access()和open()两者都利用一个字符串参数而不是一个文件句柄来进行相关操作时,攻击者就可以通过在access()和open()之间的间隙替换掉原来的文件。
错误示例:下列代码使用access()函数,可能引发竞争条件问题。

void  Noncompliant(char * file)
{
	if(!access(file, W_OK))     //【不推荐】不要使用函数access(),易引发条件竞争
	{
		f = fopen(file, "w+");
		/*...*/
		/* close f after operate(f)*/
	}
	else 
	{
		fprintf(stderr, "Unable to open file %s.\n", file);
	}
}

16、正确处理容器的erase()方法与迭代子的关系

说明:调用容器的erase(iter)方法后,迭代子指向的对象被析构,迭代子已经失效,如果再对迭代子执行递增递减或者引用操作会导致程序崩溃。

//++ 错误用法:
	m_mapID2NE.erase(iter); 
	iter++;  //【错误】erase后,iter指向的对象可能已失效
//++ 正确用法:
	m_mapID2NE.erase(iter++); //【修改】将迭代子后置递增作为erase参数	

也可以使用earse方法的返回值来保存迭代子,因为返回的是被删除元素迭代子指向的下一个元素位置:iter = erase(iter)
注意这种用法可以用于list和vector的erase(),但不适用于map。因为std::map::erase()的返回值在不同STL实现版本是有差异的,有的有返回值,有的没有返回值,所以对map只能使用推荐做法。


更多资讯、知识,微信公众号搜索:“上官宏竹”。
请添加图片描述

  • 5
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 背景 4 2. 编码安全 4 2.1. 输入验证 4 2.1.1. 概述 5 2.1.2. 白名单 5 2.1.3. 黑名单 5 2.1.4. 规范化 5 2.1.5. 净化 5 2.1.6. 合法性校验 6 2.1.7. 防范SQL注入 6 2.1.8. 文件校验 6 2.1.9. 访问控制 6 2.2. 输出验证 6 2.2.1. 概述 6 2.2.2. 编码场景 6 2.2.3. 净化场景 7 2.3. SQL注入 7 2.3.1. 概述 7 2.3.2. 参数化处理 7 2.3.3. 最小化授权 7 2.3.4. 敏感数据加密 7 2.3.5. 禁止错误回显 8 2.4. XSS跨站 8 2.4.1. 输入校验 8 2.4.2. 输出编码 8 2.5. XML注入 8 2.5.1. 输入校验 8 2.5.2. 输出编码 8 2.6. CSRF跨站请求伪造 8 2.6.1. Token使用 9 2.6.2. 二次验证 9 2.6.3. Referer验证 9 3. 逻辑安全 9 3.1. 身份验证 9 3.1.1. 概述 9 3.1.2. 提交凭证 9 3.1.3. 错误提示 9 3.1.4. 异常处理 10 3.1.5. 二次验证 10 3.1.6. 多因子验证 10 3.2. 短信验证 10 3.2.1. 验证码生成 10 3.2.2. 验证码限制 10 3.2.3. 安全提示 11 3.2.4. 凭证校验 11 3.3. 图灵测试 11 3.3.1. 验证码生成 11 3.3.2. 验证码使用 11 3.3.3. 验证码校验 11 3.4. 密码管理 12 3.4.1. 密码设置 12 3.4.2. 密码存储 12 3.4.3. 密码修改 12 3.4.4. 密码找回 12 3.4.5. 密码使用 12 3.5. 会话安全 13 3.5.1. 防止会话劫持 13 3.5.2. 会话标识符安全 13 3.5.3. Cookie安全设置 13 3.5.4. 防止CSRF攻击 13 3.5.5. 会话有效期 14 3.5.6. 会话注销 14 3.6. 访问控制 14 3.6.1. 跨权访问 14 3.6.2. 控制方法 14 3.6.3. 控制管理 14 3.6.4. 接口管理 15 3.6.5. 权限变更 15 3.7. 文件上传安全 15 3.7.1. 身份校验 15 3.7.2. 合法性校验 15 3.7.3. 存储环境设置 15 3.7.4. 隐藏文件路径 16 3.7.5. 文件访问设置 16 3.8. 接口安全 16 3.8.1. 网络限制 16 3.8.2. 身份认证 16 3.8.3. 完整性校验 16 3.8.4. 合法性校验 16 3.8.5. 可用性要求 17 3.8.6. 异常处理 17 4. 数据安全 17 4.1. 敏感信息 17 4.1.1. 敏感信息传输 17 4.1.2. 客户端保存 17 4.1.3. 服务端保存 17 4.1.4. 敏感信息维护 18 4.1.5. 敏感信息展示 18 4.2. 日志规范 18 4.2.1. 记录原则 18 4.2.2. 事件类型 18 4.2.3. 事件要求 18 4.2.4. 日志保护 19 4.3. 异常处理 19 4.3.1. 容错机制 19 4.3.2. 自定义错误信息 19 4.3.3. 隐藏用户信息 19 4.3.4. 隐藏系统信息 19 4.3.5. 异常状态恢复 20 4.3.6. 通信安全 20

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值