diff --git "a/openKylin-C&C++\350\257\255\350\250\200Fortify\344\273\243\347\240\201\345\256\211\345\205\250\346\243\200\346\237\245.md" "b/openKylin-C&C++\350\257\255\350\250\200Fortify\344\273\243\347\240\201\345\256\211\345\205\250\346\243\200\346\237\245.md" new file mode 100644 index 0000000000000000000000000000000000000000..ed5702e4fb2866549ba08d982fd7bcfc2f08b97e --- /dev/null +++ "b/openKylin-C&C++\350\257\255\350\250\200Fortify\344\273\243\347\240\201\345\256\211\345\205\250\346\243\200\346\237\245.md" @@ -0,0 +1,300 @@ +# Fortify代码安全检查 + +| 审核人: | 编写日期: | +| --- | --- | +| 批准人: | 审核日期: | + +openKylin + +## 版本说明 + +| 日期 | 版本号 | 发布说明 | 编写人 | 审核人 | +| --- | --- | --- | --- | --- | +| 2022.09.19 | v1.0 | 添加命令注入、路径操纵、缓冲区溢出类型的规则 | 苏鑫 | + | + | + | + | + | + | + | + | + +# 目录 + +[0.](#_Toc1474278514)前言...............................................................3 + +[](#_Toc886152291)目的...............................................................3 + +[](#_Toc304473081)适用范围...............................................................3 + +[1.](#_Toc189507830)命令注入(Command Injection)类型...............................................................3 + +[1.1.](#_Toc1077273378)描述...............................................................3 + +[1.2.](#_Toc1128555694)整改建议...............................................................4 + +[2.](#_Toc1156090797)路径操纵(Path Manipulation)类型...............................................................5 + +[2.1.](#_Toc673585654)描述...............................................................5 + +[2.2.](#_Toc1348179914)整改建议...............................................................5 + +[3.](#_Toc892763222)缓冲区溢出(Buffer overflow)类型...............................................................6 + +[3.1.](#_Toc1540032177)描述...............................................................6 + +[3.2.](#_Toc178227594)整改建议...............................................................7 + +[4.](#_Toc168365859)代码规范工具...............................................................8 + +[4.1. Fortify](#_Toc763838464)工具...............................................................8 + +# 0.前言 + +## 目的 + +本规范作为Fortify代码安全检查规范指南,引导编程人员建立安全编程思维,强化安全编程意识,养成良好的编程习惯,从而交付高质量,高可靠的代码 + +## 适用范围 + +本规范适用但不限于C/C++语言编程人员及审核人员,本规范通过实践,不断迭代和完善。 + + +# 1.命令注入(Command Injection)类型 + + +## 1.1.描述 + +命令注入漏洞主要表现为以下两种形式: + +- 攻击者能够篡改程序执行的命令:攻击者直接控制了所执行的命令。 + +- 攻击者能够篡改命令的执行环境:攻击者间接地控制了所执行的命令。 + +在这种情况下,我们着重关注第一种情况,既攻击者显式地控制了所执行的命令。这种类型的Command Injection漏洞会在以下情况下出现: + +1. 数据从不可信赖的数据源进入应用程序。 + +2. 数据是字符串的一部分,应用程序将该字符串作为命令加以执行。 + +3. 通过命令的执行,应用程序会授予攻击者一种原本不该拥有的特权或能力。 + +示例 1:以下这个简单的程序将文件名作为命令行参数加以接受,并将文件的内容回显给用户。该程序是按照setuid root安装的,因为其最初的用途是一种学习工具,以便让那些仍在正在接受培训的系统管理员查看特权系统文件,而不授予其篡改权限或损坏系统的权力。 + +``` +int main(char\* argc, char\*\* argv) { + + char cmd[CMD\_MAX] = "/usr/bin/cat "; + + strcat(cmd, argv[1]); + + system(cmd); + +} +``` + +因为程序是利用root权限运行的,所以也会以root权限来调用system()。如果用户指定了标准的文件名,那么调用就可按照您期望的方式进行。然而,如果攻击者传递了一个";rm -rf /"形式的字符串,由于缺少参数,对system()的调用无法成功地执行cat,然后程序会逐层删除根分区中的内容。 + +注意:如果用户控制的数据可以修改进程执行的环境,则Fortify Static Code Analyzer(Fortify静态代码分析器)也将报告Command Injection结果。报告的结果基于以下假设:由于正在运行的进程所在的环境受到破坏,该进程可以修改其行为。当该进程通过易受ShellShock攻击的Bash shell版本运行时尤其如此,在这种情况下,攻击者可通过注入恶意环境变量运行任意命令。 + + +## 1.2.整改建议 + +应当禁止用户直接控制由程序执行的命令。如果用户输入影响到命令的运行,那么请仅从一个预先决定的、安全的命令的集合中进行选择。如果输入中出现了恶意的内容,那么程序应当传递给函数一个默认的安全参数去执行,或者拒绝执行任何命令。 + +在需要将用户的输入用作程序命令中的参数时,由于合法的参数集合实在很大,或是难以跟踪,使得这个方法通常都不切实际。在这种情况下,程序员往往又会退而采用执行拒绝列表方法,以便在使用输入之前,有选择性地拒绝或避免潜在的危险字符。但是,任何这样一个定义不安全字符的列表都很可能是不完整的,并且会严重地依赖于执行命令的系统。更好的方法是创建一个字符列表,允许其中的字符出现在输入中,且只接受完全由这些被认可的字符组成的输入。 + +另外一个抵御恶意输入的防线是避免使用执行shell解析的函数。例如,不要使用system(),该函数会执行它自己的命令shell。留意外部环境,看环境会对您所执行命令的行为产生何种影响。特别要注意$PATH、$LD\_LIBRARY\_PATH和$IFS变量在Unix和Linux机器中的使用方式。 + +尽管可能无法完全阻止强大的攻击者为了控制程序执行的命令而对系统进行的攻击,但只要程序执行外部命令,就务必使用最小授权原则:不给予超过执行该命令所必需的权限。 + +例1.合规代码: + +``` +#include +#include +#include +#include +#include +void func(char *input) { + pid_t pid; + int status; + pid_t ret; + char *const args[3] = {"any_cmd", input, NULL}; + char **env; + extern char **environ; + /* ... Sanitize arguments ... */ + pid = fork(); + if (pid == -1) { + /* Handle error */ + } else if (pid != 0) { + while ((ret = waitpid(pid, &status, 0)) == -1) { + if (errno != EINTR) { + /* Handle error */ + break; + } + } + if ((ret != -1) && (!WIFEXITED(status) || !WEXITSTATUS(status)) ) { + /* Report unexpected child status */ + } + } else { + /* ... Initialize env as a sanitized copy of environ ... */ + if (execve("/usr/bin/any_cmd", args, env) == -1) { + /* Handle error */ + _Exit(127); + } + } +} +``` + +参考《openKylin-C&C++安全编程规范》规则6.1 不调用system()或等价的函数 + + +# 2.路径操纵(Path Manipulation)类型 + + +## 2.1.描述 + +命当满足以下两个条件时,就会产生path manipulation错误: + +1.攻击者能够指定某一文件系统操作中所使用的路径。 + +2.攻击者可以通过指定特定资源来获取某种权限,而这种权限在一般情况下是不可能获得的。 + +例如,在某一程序中,攻击者可以获得特定的权限,以重写指定的文件或是在其控制的配置环境下运行程序。 + +示例 1:以下代码利用来自CGI请求的输入生成一个文件名。程序员没有考虑到攻击者可能使用像"../../apache/conf/httpd.conf"一样的文件名,从而导致应用程序删除特定的配置文件。 + +``` +char* rName = getenv("reportName"); +... +unlink(rName); +``` + +## 2.2.整改建议 + +防止Path Manipulation的最佳方法是采用一些间接手段:创建一个必须由用户选择的合法值的列表。通过这种方法,就不能直接使用用户提供的输入来指定资源名称。 + +但在某些情况下,这种方法并不可行,因为这样一份合法资源名的列表过于庞大,维护难度过大。因此,在这种情况下,程序员通常会采用执行拒绝列表的办法。在输入之前,拒绝列表会有选择地拒绝或避免潜在的危险字符。但是,任何这样一个列表都不可能是完整的,而且将随着时间的推移而过时。更好的方法是创建一个字符列表,允许其中的字符出现在资源名称中,且只接受完全由这些被认可的字符组成的输入。 + +例1.合规代码: + +``` +#include + +char *realpath_res = NULL; +char *canonical_filename = NULL; +size_t path_size = 0; +path_size = (size_t)PATH_MAX; +if (path_size > 0) { + canonical_filename = malloc(path_size); + if (canonical_filename == NULL) { + /* Handle error */ + } + realpath_res = realpath(argv[1], canonical_filename); +} +if ( canonical_filename == NULL) { + /* Handle error */ +} +if (!verify_file( canonical_filename) { + /* Handle error */ +} +if (fopen( canonical_filename, "w") == NULL ) { + /* Handle error */ +} +free(canonical_filename); +canonical_filename = NULL; +``` + + +参考《openKylin-C&C++安全编程规范》规则5.1 规范化源自受污染源的路径名称。 + +Tips: + +1. 如果程序执行的自定义输入验证令您满意,请使用Fortify Custom Rules Editor(Fortify自定义规则编辑器)为该验证例程创建清理规则。 + +2. 执行有效的拒绝列表非常困难。如果验证逻辑依赖于执行拒绝列表,那么有必要对这种逻辑进行质疑。在不同的操作系统、数据库或其他资源中,不同类型的输入编码及各种元字符集合可能会解析成不同的含义。确定随着需求的不断变化,拒绝列表能否方便、正确、完整地进行更新。 + + +# 3.缓冲区溢出(Buffer overflow)类型 + + +## 3.1.描述 + +在一个典型的缓冲区溢出攻击中,攻击者将数据传送到某个程序,程序会将这些数据储存到一个较小的堆栈缓冲区内。结果,调用堆栈上的信息会被覆盖,其中包括函数的返回指针。数据会被用来设置返回指针的值,这样,当该函数返回时,函数的控制权便会转移给包含在攻击者数据中的恶意代码。 + +虽然这种类型的堆栈缓冲区溢出在某些平台和开发组织中十分常见,但仍不乏存在其他各种类型的缓冲区溢出,其中包括堆缓冲区溢出和off-by-one错误等。 + +在代码层上,缓冲区溢出漏洞通常会违反程序员的各种假设。C和C++中的很多内存处理函数都没有执行边界检查,因而可以轻易地覆盖缓冲区所操作的、已分配的边界。即使是边界函数(如strncpy()),使用方式不正确也会引发漏洞。对内存的处理加之有关数据段大小和结构方面所存在种种错误假设,是导致大多数缓冲区溢出漏洞产生的根源。 + +缓冲区溢出漏洞通常出现在以下代码中: + +— 依靠外部的数据来控制行为的代码。 + +— 受数据属性影响的代码,该数据在代码的临接范围之外执行。 + +— 代码过于复杂,以致于程序员无法准确预测它的行为。 + +以下例子分别演示了上面三种情况。 + +例 1:这是一个关于第二种情况的例子,代码受未在本地校验的数据属性的影响。在本例中,名为lccopy()的函数将一个字符串作为其变量,然后返回一个堆分配字符串副本,并将该字符串的所有大写字母转化成了小写字母。因为该函数认为str总是比BUFSIZE小,所以它不会对输入执行任何边界检查。如果攻击者避开对调用lccopy()代码的检查,或者如果更改代码,使得程序员对str长度的原有假设与实际不符,那么lccopy()就会通过无边界调用strcpy()溢出buf。 + +``` +char *lccopy(const char *str) { + char buf[BUFSIZE]; + char *p; + strcpy(buf, str); + for (p = buf; *p; p++) { + if (isupper(*p)) { + *p = tolower(*p); + } + } + return strdup(buf); +} +``` + + +## 3.2.整改建议 + +这绝不要使用自身安全性较差的函数,如gets(),避免使用那些难以安全使用的函数,如strcpy()。使用相应的边界函数(如strncpy()或在strsafe.h中定义的WinAPI函数)来取代无边界函数(如strcpy())。 + +虽然谨慎使用边界函数能够大大降低缓冲区溢出的风险,但这种移植也不能盲目地进行,而且依靠它自己来确保安全是远远不够的。当您利用内存时(特别是字符串),切记缓冲区溢出漏洞通常会出现在以下代码中: + +— 依靠外部的数据来控制行为的代码 + +— 受数据属性影响的代码,该数据在代码的直接(临界)范围之外执行 + +— 代码过于复杂,以致于程序员无法准确预测它的行为。 + +另外,还要考虑到以下原则: + +— 永远不要相信外部资源会为内存操作提供正确的控制信息。 + +— 永远不要相信程序会在执行过程中维护正在处理中的数据所带有的属性。进行操作之前,对这些数据执行健全性检查。 + +— 限制内存处理和边界检查代码的复杂度。保持这些代码的简单性,并清楚地记录已执行的检查、已测试的假设以及在输入验证失败后,希望程序执行的操作。 + +— 如果输入的数据太大,截短数据要十分小心,然后再对这些数据继续处理。截短数据会改变输入的含义。 + +— 不要依赖诸如StackGuard之类的工具,或非可执行堆栈来阻止缓冲区溢出 漏洞。这些方法不能应对堆缓冲区溢出以及更加敏感的堆栈溢出,该堆栈溢出可以改变控制程序的变量内容。另外,许多这样的方法都很容易破解,即使它们运行比较到位,也只能够找出问题的征兆,而不能发现问题的原因。 + + +# 4.代码规范工具 + + +## 4.1.Fortify工具 + +Fortify工具是一款静态代码质量分析工具,支持Java、Python、C/C++、PHP、JavaScript等25种以上的语言。Fortify工具主要用于代码安全性分析,对8个威胁方向进行分析,每个威胁方向又接受不同的规则:缓冲区、配置、内容、控制流、数据流、空指针、语义和结构。 + +Fortify工具分析过程包括4个阶段: + +1. 整合到构建工具阶段-将Fortify静态代码分析器整合到软件构建工具 + +2. 转换阶段-收集源代码并转换成一个统一的中间格式 + +3. 分析阶段-用指定的语法规则集,扫描阶段2中的中间格式文件并生成分析结果 + +4. 验证阶段-验证上述阶段的正确性 + +Fortify工具可以对源码包和PR按照漏洞类型进行扫描。