C++安全编码-第一章

1、 优先C++特性而不是C特性
std::string/std::string_view char*
std::vector/std::array 原生数组
namespace static
引用/智能指针 普通指针
iostream printf,sacnf
智能指针/new,delete malloc,free
const常量/inline函数/函数模板 宏
2、类型命名大驼峰风格----类、结构体、联合体、类型定义(typedef)、枚举
函数命名大驼峰风格
全局变量、局部变量、函数参数、成员变量小驼峰风格
宏、常量、枚举值、goto 标签 全大写,下划线分割
文件名 大驼峰或者小写加下划线
m_fileName加m_表示成员变量
const int g_connectionIds[10] = {0};数组不是基本类型,这里全局常量加g_即可
static const int MAX_LENGTH = 5;静态常量全大写
3、使用命名空间来限定作用域,防止名字冲突(现代语言,只有C语言没有命名空间这个特性)
函数左大括号放行首,其他左大括号跟随语句放行末
表达式换行操作符放行末
当变量被const修饰,无法跟随变量,也不要跟随类型char * const VERSION = “V100”
声明对应的.h文件用于声明需要对外公开的类与接口
static修饰的变量、常量、函数只能在当前文件中被使用,不会被导出给外部的其他模块使用,C++中推荐namespace来代替,这样里面的成员只能在当前文件中使用(相当于全局变量,直接调用)
4、类的成员变量必须显示初始化:如果类有成员变量,没有定义构造函数,也没有定义默认构造函数,编译器将自动生成一个构造函数(但这个构造函数不会对成员变量初始化,对象处于一种不确定性) 例外:类的成员变量具有默认构造函数,那么可以不需要显示初始化,如:std::string someIdentifier;
推荐构造函数初始化列表初始化
int msgID{0};或者int msgID = 0; C++11
5、如果不需要拷贝、移动函数,必须明确禁止(该类不允许拷贝赋值,移动):
1、如果用户不定义,编译器会默认生成拷贝构造、移动构造函数和拷贝赋值、移动赋值操作符(移动C++11之后才有)
2、拷贝和移动分别成对出现和禁止
禁止的方法:
传统C++:class Foo{
private:
Foo(const Foo&);
Foo& operater=(const Foo&);
};
现代C++:class Foo{
public:
Foo(const Foo&) = delete;
Foo& operater=(const Foo&) = delete;
Foo(Foo&&) = delete; // C++11才有移动语义
Foo& operater=(Foo&&) = delete; // C++11才有移动语义
};
6、禁止虚函数使用缺省参数值
7、函数不超过50行(非空非注释)
内联函数不超过10行(非空非注释)
函数参数不超过5个
函数规模小功能简单
8、函数参数尽量使用引用取代指针,推荐string代替char
,array代替内置数组
9、不允许魔鬼数字:type = 12,status = 0;就是魔鬼数字;month = year12,这里12就不是
const int ZERO = 0是魔鬼常量, const int XX_TIMER_INTERVAL_300MS = 300;常量名字限制了取值
10、变量需要使用时才声明并初始化
11、尽量避免使用宏,因为它是 简单文本替换,没有类型检查,没有作用域,在预处理阶段完成,运行报错时直接报相应的值,跟踪调试也是显示值
优先使用内联函数和模板函数替换宏函数
const char * const STRING = “ABC”;当声明在头文件中时,指针本身也必须是const,避免符号冲突
可以保留使用的场景:#和##进行标志符的拼接;用到了关联代码位置的预定义值,如:FILE, LINE
12、一组相关的整形常量(如星期一到七)应该定义为枚举(当枚举值重复时,应用已定义的枚举来修饰)
13、禁止memcpy_s、memset_s初始化 非POD(Plain Old Data,int,char,指针,聚合类型)对象,如用户定义的构造、赋值、析构函数,基类,虚函数
14、为了更好的阅读要求,常量写在运算符的右边
15、避免类型转换(C++类型的类型转换有四种,),避免void
类型
short samll = 0;
long big = small;小转大,自动转换

	dynamic_cast:用于将父类转化为子类,避免做下行转换,其他转换可以用
	Derived derived;
	Base* base = &derived;上行转换,是自动转换
	Derived* local = dynamic_cast<Derived*>(base);下行转换,反映出设计问题

	static_cast:用于基本类型的强制转换,或者void*到其他指针类型的转换
	char tiny = static_cast<char>(big);大转小,强制转换
	const char* immutable = &tiny;非常量转常量指针,自动转换
	void* nakedPtr = local;一个指针自动转换void*,无需强制
	Unknown* unknown = static_cast<Unknown*>(nakedPtr);void*转换为其他类型,避免
	
	reinterpret_cast:用于转换不相关的类型,不安全,少用
	const_cast:移除对象的const属性,使得对象可修改,会破坏数据的不变性,少用(只应在对接外部接口时使用)
	char* canchange = const_cast<char*>(immutable);
	char* buffer = reinterpret_cast<char*>(base);一个类型解释为另一个不想关的类型,危险操作

16、指针和引用类型的形参,不需要修改的,应该使用const;
对于不会修改成员变量的成员函数,也应该使用const修饰;
对于初始化不会再修改的成员变量也应定义为const;

17、安全编码目标:完整性(确保数据是完整的、未被篡改的),机密性(确保数据不被非法访问与窃取),可用性(要求保护资源在需要时可访问)
攻击者意识:程序所处理的所有外部数据都是不可信的攻击数据;攻击者视图监听、篡改、破坏程序运行环境、外部数据
基于攻击者意识前提得到安全编码基本思想:程序在处理外部数据时必须经过严格合法性校验、尽量减少代码的攻击面、通过防御性编码策略来弥补潜在的编码人员的疏忽
17、指针、资源描述符(int fd = open())、布尔变量操作:引用未初始化变量、内存释放后使用、重复释放内存、对指针类型使用sizeof运算
18、断言操作:断言必须使用宏定义、不能直接使用系统的assert(),断言只能用于调试版本,断言不能检查运行时错误,断言不能改变运行环境
19、传递数组时,必须传递其长度;
不对内容进行修改的指针型参数,定义为const
谨慎使用不可重入函数
检查入参的空指针

20、仅使用C++语言本身抛出的异常、代码中不要主动抛出异常
21、构造函数和析构函数必须同时存在、构造函数内不能做任何有可能失败的操作、如果类的公共接口中返回类的私有数据地址,必须加const类型
22、字符串操作常见问题:字符串复制(没有考虑到dest缓冲区的大小是否能容纳source缓冲区,导致复制的时候将dest以外的的内容进行非法覆盖)、字符串连接(没有考虑到连接后的字符串是否超出缓冲区的限制,导致缓冲区被非法覆盖)、字符指针的重叠操作(若两个指针指向的字符串内存空间存在重叠,会导致未定义的行为,且这种行为可能破坏数据的完整性)
带来的风险:由于字符串缓冲区被破坏,导致程序崩溃,若攻击者恶意输入,造成“拒绝服务让正常用户无法使用”;
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
23、格式化函数的常见问题:参数类型的不匹配(使程序异常终止,拒绝服务),参数数目不匹配,格式化字符串的长度没有限制并且格式化字符串部分由用户输入(sprintf(szBuffer,“%s”,userinput))–可以由攻击者控制,输入的恶意数据会被当做指令执行
在这里插入图片描述
在这里插入图片描述
24、正确使用安全函数:正确设置安全函数中的destMax值,恰当处理安全函数的返回值
不能重定义安全函数(以宏的方式修改安全函数在代码中的名字,降低了代码的可读性,引发误用操作)
不能以函数封装的方式调用安全函数,降低了代码可读性,引发误用风险
不能对destMax参数使用立即数(在代码中没有准确意义,无法关联到变量的定义,无法关联到缓冲区的长度,也不容易维护)
在这里插入图片描述
在这里插入图片描述
不能对destMax使用源的长度,安全函数对目的缓冲区的保护功能失效,
在这里插入图片描述
在这里插入图片描述
destMax为struct结构体的局部变量时,必须设置为sizeof(变量名)或者sizeof(结构体名)
在这里插入图片描述
destbuff为变长struct结构中的变长成员变量时,destMax必须设置为该成员变量的实际大小,结构体中必须有描述变长部分长度的成员变量
在这里插入图片描述
对dest的局部范围进行操作,dest必须设为destbuff操作范围的内存大小
在这里插入图片描述
对固定长度的全局数组变量,结构体变量进行初始化时,可以不用检查安全函数的返回值
在这里插入图片描述
对函数入参中的内存缓冲区进行初始化,使用了内存中传递的长度进行初始化,如果指针不为空,可以不检查初始化安全函数返回值
在这里插入图片描述
对同一函数内堆上分配的内存缓冲区初始化,使用了分配内存的大小进行初始化,如果指针不为空,可以不检查初始化安全函数的返回值;
根据源内存的大小进行同等大小的内存复制,同一个长度拷贝,不需要检查返回值
在这里插入图片描述
对字符串常量的拷贝与拼接,编码时仔细检查目标内存是否有足够的存储空间,可以不检查安全函数返回值
在这里插入图片描述
25、整数操作不当的几种场景:
有符号整数运算操作出现溢出(溢出攻击(符号位常常在整数表示的最高位上,会算入数值内):执行任意代码):若算数运算的计算结果太大而无法在系统位宽度范围内存储时导致整数溢出;当操作数都是有符号时,溢出就有可能发生,而且溢出的结果未定义,当一个运算的结果发生溢出时,所有关于结果如何的假设均不可靠;当整数太大,进位的信息覆盖掉了符号位的信息,就会产生溢出
在这里插入图片描述

无符号整数反转(也叫回绕):无符号操作数的计算不会溢出,因为结果不能被无符号类型表示的时候,就会对比结果类型能表示的最大值加一再执行求模操作。

在这里插入图片描述

整型转换出现截断错误
有符号整数使用位操作符运算
带来的风险:
内存分配出错:用户输入在与有符号的值和无符号的值之间的隐式转换进行交互时会产生一些错误,而这些错误会导致内存分配函数出现问题;
执行任意代码:整数溢出错误,可导致内存破坏,并可能被用于执行任意代码
我们知道short和char在进行运算的时候会自动提升到整型int

整型表达式赋值给较大类型前未转换为较大类型,造成数据溢出

在这里插入图片描述
对有符号位进行位操作导致数据反转
在这里插入图片描述
正确使用指针的整数类型
在这里插入图片描述
26、危险的堆操作:
内存拷贝时未判断目标内存长度的有效性(目标内存过小)
内存申请完毕后未判断空指针(空指针引用)
使用已经释放的内存
调用不匹配的内存管理操作(new,delete与malloc,free混用)
重复释放内存(double free)
堆管理不当带来的风险:
拒绝服务攻击:由于堆被破坏,可导致程序崩溃;如果被攻击者通过恶意输入控制这种情况,攻击者可以使程序崩溃从而让合法用户无法继续使用。
执行任意代码:攻击者输入的恶意数据被当做指令执行

	堆溢出原理:malloc(10)--此时内存并未初始化,只是分配了内存,申请10字节内存,先申请内存头(包含前向指针和后向指针,共8个字节),其次申请内存体(按照4字节对齐)需申请12字节,一共20字节
	不能引用未初始化的内存

在这里插入图片描述
不能访问已经释放的内存
在这里插入图片描述
堆内存:在这里插入图片描述

栈内存:在这里插入图片描述
需要校验申请内存大小的整数值
在这里插入图片描述
总结:使用0字节长度去申请内存的行为是没有定义的;
大于内存长度最大值,可能导致申请失败,造成拒绝服务;
负数申请内存,会被当成一个很大的无符号数,从而申请失败,造成拒绝服务
小心不要引用空指针:
在这里插入图片描述
禁止alloca函数申请内存,POSIX和C99均未定义alloca的行为,在不支持的平台上运行会有未定义的后果,且该函数在栈帧申请内存,申请的大小可能越过栈的边界而无法预知
在这里插入图片描述
指针释放后必须置为NULL,否则悬挂指针可能会导致双重释放以及访问已释放内存的危险,消除悬挂指针的方法就是使用完后将其置为NULL或者指向另一个合法对象

不可以使用realloc函数调整内存的大小:
void* realloc(void* ptr, size_t size)
1、当ptr与size均不为NULL时,函数会重新调整内存大小,返回新的内存指针,并保证最小的size内容不变
2、ptr为NULL,但是size不为0,等同于malloc(size);
3、size为0,等同于free(ptr);

system函数使用不当导致命令注入
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
system函数可以由win32 API中的CreatProcess()函数或POSIX的exec系列函数代替,或者使用硬编码的函数入参或对外部输入中的命令分隔符进行过滤转义

多线程下不能使用线程不安全函数(strtok–使用了静态分配的空间存储被分割的字符串的位置,但是strtok_r是线程安全的,r的意思是reentrant,可重入的;

printf、cout混用,信号处理函数中使用异步不安全函数:IO函数、自定义的异步不安全函数),
两者的缓冲机制不同(printf无缓冲区,cout有缓冲区),而且对于标准输出的加锁时机也略有不同;
printf:在对标准输出做 任何处理 前先加锁;
cout:在实际向标准输出 打印 时才加锁;
二者存在微弱的时序差别,混用会带来不可预知的错误(如打印输出结果不符合预期,严重导致缓冲区溢出,导致crash)
在这里插入图片描述
在这里插入图片描述

危害:拒绝服务(当程序运行在多线程环境,往往会导致变量被多处修改,从而导致程序运行不正常,甚至crash),执行任意代码(变量被其他线程修改,若被攻击者写入恶意数据。可能导致任意代码执行)

信号处理程序中只能调用异步安全函数,只有C标准库中的abort()、_Exit()、quick_exit()和signal()函数可以在信号处理程序中安全的调用;
信号处理程序中不要调用IO函数

不安全函数:strcpy、strcat等缺乏严格的入参校验和错误处理机制,很容易导致缓冲区溢出等安全问题,这些就是危险函数
27、文件操作问题:
文件操作返回值判断有误;
文件创建时没有指定合适的文件权限;
文件的路径校验不完全
操作不当带来的风险:
重要文件丢失:如文件指针偏移错误,出现相对文件头的负偏移
关键文件被不预期的访问者查看甚至执行:
TOCTOU(time of check time of use)条件竞争漏洞:检查文件到使用文件的这段间隙,可以发动一次扩大权限的攻击,以文件句柄(不能用文件名)作为依据,可以保证文件不被替换
在这里插入图片描述
因为文件名和真实文件本身实际上是一个松绑定的关系,文件名并不包含真实文件的具体信息,多次使用相同文件名试图对同一文件进行操作是不可靠的,存在文件本身被外部手段替换掉而程序并不感知的可能

输入的文件路径必须进行标准化(即转换成绝对路径),确保打开文件是符合预期的,若必须使用相对路径(对应用安装目录不确定),需要对文件名中的特殊字符进行过滤,如“.”和“/”
在这里插入图片描述在这里插入图片描述
内部文件名最好能硬编码参数到应用程序中,如果出于更高的安全要求,需要对文件的完整性进行校验

28、常见随机数安全用途(随机数的随机性不够好,易于预测,容易导致安全机制失效)
SessionID的生成
挑战认证算法中随机数的生成
验证码的随机数生成
生成重要随机文件(存有系统信息的文件)的随机文件名
用于密码算法用途(生成IV、密钥、盐值)的随机数生成
伪随机数:rand(产生的随机序列存在较短的循环周期,产生的随机数是可预测的)
在这里插入图片描述
真随机数推荐方法:
在这里插入图片描述
敏感信息(SessionID、明文口令、密钥)使用完毕后不及时清理。可能通过读取缓存,内存映像等方式被非法获取并利用,造成资产损失

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值