Xcheck的 C/C++ 安全检查引擎,能够在不编译源码的情况下对 C 和 C++ 源代码进行安全检查,检测SQL注入、命令注入、SSRF、路径穿越、缓冲区溢出、格式化字符串漏洞等漏洞。
作为最古老的“现代”编程语言,C/C++ 的框架和库极为丰富,当前 Xcheck 优先支持了业界和公司内部的主流开发框架,如 gRPC-C++ 、tRPC-C++、SPP、Svrkit、TARS 等,同时,用户也可以编写自定义规则,快速适配自己的框架。
随着多年的社区发展,C 和 C++ 的版本多、语法杂,各项目使用的编译器、编译参数、编译环境配置等均不统一,导致大部分 C/C++ 源代码不能拿到就直接编译。
C/C++ 的各个版本
传统 SAST 工具针对 C/C++ 代码的安全检查,强依赖于编译的中间产物进行扫描,编译失败就无法正常执行,用户在使用时,需要配置 SAST 工具可用的编译脚本,或者将源代码改造为 SAST 工具可以直接编译的代码,增加了使用成本。
Xcheck 的 C/C++ 安全检查引擎,采用了自研的 Xparser
语法解析器,可以在 不编译代码
的情况下模糊解析出代码的语法信息,再结合 Xcheck 的语义分析算法和污点传播算法,高效快速地发现代码中的安全风险。
Xparser 支持在各类无法编译的场景模糊解析语法,最大程度保留语法信息,如:缺少相关头文件/库、编译时替换字符、特殊编译器语法等。
include 的库不在代码中,会导致代码中存在未知的宏定义
TEST(xxx, xxx){} // 单测代码,像 function 声明 for_each(a, b, c){} // 封装了一层的 for 循环 __END_DECLS // 未知语句块
也可能存在未知的类型
XXClass * a // 未知类型,造成歧义,声明或者相乘
某些项目的代码会在编译时,编译脚本替换源代码中的关键字
namespace ${APP} { // 编译时替换 ${APP} -> xxx_project ... }
一些编译器会有特殊的语法
int $a = 1; // GNU C 支持标识符中包含 $
和 Xcheck 的其他引擎一样,为了能够精准的发现安全漏洞,Xcheck 对 C/C++ 的各类语法特性都做了支持,包括:Template、流式操作、命名空间、指针等。
Template(模板)是 C++ 中比较有代表性的语法之一,主要特点如下:
语法复杂:包括函数模板的声明和调用、类模板的声明和调用,类外声明模板函数、模板类外定义模板函数,模板特化等
编译器特殊处理:编译器在编译时,会根据传入参数的具体类型去实例化对应的函数/类
声明一个名为 Foo 的函数模板,函数中的 T 在这个时候是未知的
template <typename T> T foo(T b){ T a = b; return a; }
调用 Foo,这里因为 Foo 是函数模板,会先用 a 的类型 int,去实例化一个真实的函数,然后调用
int a = 1; Foo(a); // 实例化出 int Foo(int a){}
double b = 1.1; Foo(b); // 实例化出 double Foo(double a){}
string c = “test”; Foo(b); // 实例化出 string Foo(string a){}
声明一个名为 Pair 的类模板,类中的 T 同样是未知的,类中有一个 value 属性和构造函数
template <class T> class Pair { public: T value; Pair(T v): value(v) { } }
显示传入模板中需要的类型,这里传入了一个 int 类型,这时候会实例出一个真实的类,类中的 value 是 int 类型的,接着用这个类实例化一个对象,执行 Pair 的构造函数
Pair<int> a (1);
以真实业务代码为例(已脱敏)
req
参数为外部传入的污点,经过参数提取和字符串拼接后,污点传递给 select_sql
,然后作为参数传给 LogServicesInstance.exec_query
int GetXXMsg::execute(const DbProxy::CommonReq &req, DbProxy::CommonRsp &rsp) { const DbProxy::GetXXReq &real_req = req.getservicelogtopic(); std::string select_sql = "SELECT xx FROM xxxxx " "WHERE uin=" + Util::toString((unsigned long)real_req.uin()) + " AND name= '" + real_req.name() + "'" + " AND tag = '" + SERVICE_LOG_TAG + "'"; std::vector<struct XXX> topic_rows; int ret = LogServicesInstance.exec_query(select_sql, topic_rows,...);
LogServicesInstance
实际上是一个宏class LogServicesDb : public MysqlClient {}; #define LogServicesInstance (CSingleton<LogServicesDb>::instance())
Xparser 在解析 C/C++ 源代码时会调用自研的预处理模块,将该代码宏展开为:
int ret = (CSingleton<LogServicesDb>::instance()).exec_query(select_sql, topic_rows,...)
CSingleton
是一个单例模式的模板类,传入 LogServicesDb
,再调用 instance
方法,就会返回 LogServicesDb
的一个实例template<class T> class CSingleton { public: static T& instance(); }; template<class T> T& CSingleton<T>::instance(){ static T _instance; return _instance; }
LogServicesDb
实际上继承了 MysqlClient
class MysqlClient { public: ... int exec_query(std::string& sql, ...); private: ... xxxxx::MySQLPool _pool; }
具体的方法实现是在类外定义的
int MysqlClient::exec_query(std::string& sql, ...) { if (interface <= 0) return _pool.exec_query(sql, row_vec); else { ... } }
_pool.exec_query(sql, xx)
,而 _pool
是 xxxxx::CSyncMySQLPool
的实例, Xcheck 已将该类的 exec_query
方法标记为漏洞函数,故此处触发 SQL 注入漏洞。涉及到的语义:
类声明/继承/实例化
类模板声明/实例化
函数调用
赋值语句
字符串拼接
...
Xcheck 在 8 月内部灰度上线了 C/C++ 检查引擎,同时也和多个安全团队联合进行了测试,相关数据如下:
灰度数据
项目数
34177
风险项目数
246
问题总数
2573
误报率
10%(抽检)
内部灰度测试漏洞类型分布
Xcheck 的 C/C++ 引擎还在持续迭代中,后续会逐步支持更多主流框架和漏洞,同时也会尝试对开源项目进行静态代码分析,敬请期待。