c/c++安全编码 总结
01 Mar 2020
|
|
十分粗糙的读书记录,主要是案例大多不怎么醒目
字符串
-
标准库函数拷贝越界 puts越界 -> 缓冲区溢出 ->栈溢出 ->代码注入
-
字符串数组越界
-
空字符串结尾错误
-
处理字符串漏洞的方案
- 输入验证,避免缓冲区溢出
-
堆栈保护器
几个安全函数替代以及使用(注意,gcc基本没实现,用不上唉)
原函数 | 安全版本 | 变化后的函数原型 | 引入版本 |
---|---|---|---|
vsnprintf | vsnprintf_s | 无变化 | C11 |
memcpy | memcpy_s | 增加长度为第二个参数,限制拷贝长度 errno_t memcpy_s( void *restrict dest, rsize_t destsz, const void *restrict src, rsize_t count ); | C11 |
strncpy | strncpy_s | 同上 errno_t strncpy_s(char *restrict dest, rsize_t destsz, const char *restrict src, rsize_t count); | C11 |
snprintf | snprintf_s | 无变化 | c11 |
sscanf | sscanf_s | 无变化,但涉及到%c %s %[ 要提供长度信息 https://en.cppreference.com/w/c/io/fscanf https://stackoverflow.com/questions/24078746/confusion-with-sscanf-s | c11 |
memset | memset_s | 增加长度为第二个参数,限制拷贝长度errno_t memset_s( void *dest, rsize_t destsz, int ch, rsize_t count ); | c11 注意需要定义 `__STDC_WANT_LIB_EXT1__` |
指针
- 缓冲溢出改写函数指针
- 修改指令指针的指向
- 修改全局偏移表
- 改写.dtor区,调用析构转移权限
- 改写虚函数指针
- atexit注入,转移权限
- longjmp溢出
内存管理
- 不要假定分配的内存被初始化了
- 检查malloc返回值 (不过现在的设备,霉有检查的必要吧,如果用new 遇到bad_alloc挂掉就挂掉吧)
- 指针引用,不要引用已经释放 的指针
- double free
- 内存泄漏问题
- malloc(0) 傻逼行为
- 垃圾回收中的伪装指针 -> std::pointer_safety 看SO这个问题https://stackoverflow.com/questions/27728142/c11-what-is-its-gc-interface-and-how-to-implement
- 在条件分支中没有检查new分配失败,因为new失败抛异常,强制nothrow
- new delete malloc free没有正确配对
- 注意new数组的坑
- 如果是placement new一定要知道自己在做什么
- 容器装指针导致的释放 ->智能指针 ptr_container等等
- 引入智能指针,比如shared_ptr,有可能又引入循环引用。注意
- 析构函数不能抛异常
- gc库原理以及对gc库的缓冲区溢出共计,复写边界标志块
- 一些解决方案,静态检查等等
整数安全
- 无符号数回绕
- ` if (sum +i > UINT_MAX)
->
if (i > UINT_MAX - sum)` if (sum -j < 0)
->if (sum < j)
- ` if (sum +i > UINT_MAX)
-
有符号整数类型溢出,注意值范围
- 除了char默认都是signed char在arm上表现是unsigned
-
整数转换与类型提升。
- 简单说,低到高的转换提升可以,注意符号可能错误解释但是没关系,高到低可能会丢或者读错数据
- 整数操作,注意溢出回绕与截断场景,几个规避方案
- 用更大的类型强制转换,简单粗暴
- 先验 ,类似上面的回绕判定
- 后验
- 状态位,比如jc。这种实践价值不大
- 类似回绕,反向判断运算结果是不是被回绕了
- 重点考虑一下场景
-
数组索引
-
指针计算
-
对象长度大小
-
循环边界
-
内存分配
- 比如传入有符号数但是这个数可能溢出了 or截断了
-
格式化输出
- %s替换导致的缓冲区溢出
- 格式化字符和对应的参数不匹配,导致读取栈上内存,可能读到错误地址segfault
- 通过这个手段查看栈内容
- %n 通过printf改写内存
- 规避方案。避免用户输入字符串,编译检查避免低级错误
并发
- 概念:阿姆达尔定律?竞争条件和临界区。也有翻译成竞态的
- volatile 不保证多个线程间同步,不防止并发内存访问,不保证对对象的原子性访问(经常被滥用)
- 内存模型以及原语。这里没有讲漏洞,只是讲了几个锁,原子量的用法以及避免用错锁
文件IO
- 进程特权
- 文件权限
- 目录 缓冲区溢出
- 等价错误
- 符号链接串改
上述解决方案,目录规范化
- 文件竞争!
推荐实践
- 安全的开发生命周期
- QA
- 设计
- 静态检查,编译检查等等
- 验证,代码审计,静态分析,渗透测试,安全检查,攻击面回顾等等
###
gcc/linux平台安全编译链接选项
- 栈溢出保护 (c_flags)
-fstack-protector-all
/-fstack-protector-strong
- 原理,canary word,在堆栈边界标记,然后检查,变过,则发生了溢出 会打印
__stack_chk_fail
- 原理,canary word,在堆栈边界标记,然后检查,变过,则发生了溢出 会打印
- 重定位只读 (ld_flags)
-Wl, -z,relro
避免缓冲区溢出修改GOT- 立即绑定 (ld_flags)
-Wl, -z,relro, -z,now
- 原理,符号只读不能复写,直接segfault
- 防不了GOT PLT重写
- 立即绑定 (ld_flags)
- 禁止缓存区溢出shellcode (ld_flags)
-Wl, -z, noexecstack
- 原理,代码段标记不可执行,访问直接segfault 通过观察data端可以看到权限是rwp不是rwxp
- 注意,堆区,栈区shellcode无效
- 位置无关代码(c_flags)
-fPIC
这个和安全关系不是特别大感觉 - 位置无关可执行文件(c_flags, ld_flags)
-fPIE
-pie
- 实时路径选择 –rpath,可能会被替换造成hook,不安全
- 动态链接库查找顺序,检查rpath,检查LD_LIBRARY_PATH,检查ld.so.cache,rpath优先级较高导致hook
- 隐藏符号
-fvisibility=hidden
针对共享库隐藏符号 - 堆栈检查
-fstack-check
影响性能- 检查栈空间大小,预留缓冲区被使用会告警
- 删除符号 strip(ld_flags)
-s
影响调试 - 整数溢出
-ftrapv
强制检查一遍所有参数再计算,异常直接abort,严重影响性能和稳定性,应该没人用这个吧 - 缓冲区检查 宏
-D_FORTIFY_SOURCE=2
影响运行时性能- 原理,hook函数, 替换成_chk函数,检查长度
- 缺陷,动态分配的空间无法检查