c语言代码审计规范


 

 

目录

1. 缓冲区溢出漏洞... 2

1.1 get函数溢出漏洞... 2

1.2 strcpy函数溢出漏洞... 2

1.3 sprintf函数溢出漏洞... 3

1.4 printf字符串格式攻击漏洞... 3

1.5 scanf函数溢出漏洞... 3

2 指针覆写... 4

2.1命令注入... 4

3 存储越界... 5

3.1 数组元素没有进行边界检查... 5

3.2 指针加下标的元素没有进行边界检查.... 5

3.3 数值型变量范围表示范围越界... 6

4 动态存储分配... 6

4.1 内存分配和释放函数没有正确配对... 7

4.2 重复释放同一内存空间... 7

4.3 释放连续内存空间的顺序错误... 8

5 内存泄露... 8

5.1 未释放动态申请的内存地址空间... 8

5.2 动态申请的内存地址空间释放顺序错误... 9

 

 

 

 

 

 

 

 

 

 

 

1. 缓冲区溢出漏洞

介绍:

程序向缓冲区写入了超过缓冲区最大能保存的数据。当黑客用精心构造的数据覆盖函数返回值,等到函数返回时,就会去黑客覆盖的返回值地址去执行事先安排好的攻击代码。

1.1 get函数溢出漏洞

介绍:

gets()函数不检查缓冲长度,调用gets(buffer),会把用户输入的内容放在buffer中,但是对这个内容没有检查长度,如果用户输入的内容过长,就会覆盖在buffer之后定义的变量,也就是用户可以随意更改一些程序中的变量。

审计策略:

全局搜索gets函数

修复建议:

使用fgets()函数,首先要用malloc为buffer分配一部分固定的空间。然后调用fgets()的时候传递进去预计长度的值,这样就不会造成溢出了

1.2 strcpy函数溢出漏洞

介绍:

strcpy函数是复制字符串的,接受两个参数一个是被复制字符串,一个是新字符串。这个家族有三个函数:strcpy,strcat,strcmp,都有溢出问题。因为不检查长度

审计策略:

全局搜索strcpy,strcat,strcmp这三个函数

修复建议:

建议一:在使用这三个函数函数前加入检查长度的语句。

建议二:使用strncpy,strncat,strncmp,这三个是会检查长度的,但是存在的问题是,如果截取了长度,不能保证字符串以''结尾,所以可能需要添加一些代码进行判断。

1.3 sprintf函数溢出漏洞

介绍:

sprintf函数打印到字符串中,对长度不做检查,造成溢出。

审计策略:

全局搜索sprintf()函数

修复建议:

使用snprintf()函数,这个函数不仅能够避免缓冲区溢出(检查长度),还能返回传递字符串的长度,以供判断是否需要处理截取后的''结尾问题。

1.4 printf字符串格式攻击漏洞

介绍:

这一类的漏洞与字符串格式攻击相关,也就是我们常说的利用格式化字符串漏洞进行攻击。这种攻击通常会导致信息泄露、覆盖内存(%n)等等。这个漏洞可以被以下函数触发:printf, fprintf, sprintf以及snprintf。这些函数的共同特点就是,都以格式化的字符串作为参数,即百分号之后的格式约定。

审计策略:

全局搜索printf, fprintf, sprintf以及snprintf这些函数。

修复建议:

硬编码格式化字符串

1.5 scanf函数溢出漏洞

介绍:

scanf是输入函数,由于对输入长度没有控制导致缓冲区溢出问题,通类型有scanf,fscanf,sscanf

审计策略:

全局搜索scanf,fscanf,sscanf

修复建议

使用精度说明符,或自己进行解析

2 指针覆写

介绍:

指针覆写漏洞是指由于对指针没有进行很好的处理和保护,造成指针的值被修改和利用的漏洞。其中利用的方式有代码注入攻击,返回函数攻击,返回导向编程。

利用指针覆写实施攻击需要满足两个条件:
1)程序中存在缓冲区溢出漏洞。
2)发生溢出错误的缓冲区要和被覆写的指针在同一内存段内,且位置最好相邻。

2.1命令注入

介绍:

执行命令时,命令将未验证的用户输入作为命令参数的一部分,导致应用可能遭受攻击。分为三个阶段,(1)不可信的数据通过用户进入程序。(2)数据被程序作为运行的命令的一部分执行。(3)通过执行命令,程序就会给攻击者本身不具有的特权或者能力。

漏洞示例一:

 int main(char* argc,char**argv){

     char cmd[CMD_MAX]="/usr/bin/cat";

     strcat(cmd,argv[1]);

     system(cmd);<br>//1,当用户输入标准的文件名,程序能正常工作。

     //2,当用户输入“;rm -rf/"将删除根分区的所有文件和内容。

}

漏洞示例二:

    char* home =getenv("APPHOME");

    char*cmd=(char*)malloc(strlen(home)+strlen(INITCCMD));

    if(cmd){

        strcpy(cmd,home);

        strcat(cmd,INITCMD);

        execl(cmd,NULL);

    //从系统中取出的变量正确的时候,程序正常运行

    //攻击者变化系统变量,变量进入命令,造成命令注入攻击。}

审计策略:

全局搜索cmd,malloc等关键词看是否对接受参数进行限制,对用户是否进行验证。

3 存储越界

介绍:

存储越界指一个变量读或写超出变量分配的内存空间,c语言编译器没有提供数组和缓冲区的边界检查,他对字符串的操作只是通过识别‘’来判断是否到达该字符串的结尾,这样很容易造成访问越界问题,如果越界读数据,就会得到一些无用信息甚至受到黑客的攻击,导致程序运行出错或者以访问非法内存而终止程序。

3.1 数组元素没有进行边界检查

介绍:

这类漏洞主要针对显示的使用数组元素,包括基本类型和用户自定义类型的数组操作。

漏洞示例

    vector<type>phone_book(5);

    for(i=1;i<6;i++)phone_book[i]="abc";

 

    //由于编译器不会对for循环中的phone_book的引用进行边界检查,就会出现安全漏洞

漏洞示例

    int day[]={1,2,3,4,5,6,7};

    day[8]=8;

    //对数组day的引用day[8]将超出数组的声明范围。

审计策略:

搜索关于数组的关键词,定位数组。

修复建议:

进行边界检查,包括数组,字符串和数值类型等。

3.2 指针加下标的元素没有进行边界检查

介绍:

该漏洞和3.1很相似,但指针可以指向数组也可以指向单个元素。它属于隐式的使用数组元素。

漏洞示例:

    int array[] ={1,2,3};

    int*p=&array[0];

    p[-1]=0;

    *(p+3)=4;

    //对数组array和指针p定义,使指针p指向array首地址,数组array的地址范围

    //限制p的指向也有一定的范围,但是p指向的空间超出了原来所指范围,所以会出现

    //安全漏洞。

审计策略:

定位有关指针,数组等关键词

修复建议:

对指针加下标元素进行边界检查

3.3 数值型变量范围表示范围越界

介绍:

数值型变量都有一个表示范围,如果对变量不加限制的进行相互操作就很容易导致范围越界。

漏洞示例:

    short int var =30000+30000;

    //short int表示的范围是-32769~32769,此时var的值超过了short int 的表示范围

    //系统会进行自动转换,其结果和实际语义不符。

4 动态存储分配

介绍:

静态存储分配对变量进行定义声明时编译器就能获取它 们所需存储空间的大小,并为其分配相应的内存空间,而且在 其生存期内是固定不变的,生存期结束后系统对其自动释放。 动态存储分配相对于静态存储分配,它是在程序执行期间,通 过申请分配指定的内存空间或释放指定的内存空间。C语言和 C++语言都提供各自的动态分配方法,C语言提供的是几个库 函数:malloc, relealloe,free等,而C++语言提供的是运算 符:new , new[], delete,deleteQ等等。这些库函数和运算符都用于 动态申请或释放内存,并且C++编译器对函数和运算符都能识 别,所以对程序员来说很容易混淆使用,最终造成程序运行错 误。此外,由于是动态分配,程序员很难控制分配空间的次数是 否与释放空间的次数相等,而编译器对这些问题都不提供检 查,所以也会出现对同- - 空间释放多次或使用已释放空间等这 样的安全漏洞。

4.1 内存分配和释放函数没有正确配对

介绍:

从内存分配的机理上来说,malloc和free配对,new和delete. 配对, new[]和delete[]配对。因为malloc/free是库函数而不是运 算符,它们只负责分配或释放一-块连续的内存空间,不执行构 造/析构函数。而new/delete是C++的运算符,它在完成分配空 间的同时自动执行了构造/析构函数,它主要用于非基本数据 类型对象的操作。new[]/delete[]是用 来同时分配/释放对象数 组。这些运算符或函数不能混合使用,否则将出现安全漏洞。

漏洞示例:

    S1:int*p=(int*)malloc(sizeof(int));

    S2: *p=100;

    S3: delete p;

    //语句S1用malloe函数为p动态申请空间,而语句S3使用运算符delete来释放p,

     // 显然两者没有正确配对,最终出现安全漏洞。

4.2 重复释放同一内存空间

介绍:

由于编译器没有分析别名引起的漏洞。

    char*p, *q;

    S1:p=( char* )malloc( sizeof( char)* 10);

    S2: strcpy(p, "hello");

    S3:q=p;

    S4: strcpy(q,"world");

    S5:free(p);

    S6: free(q);

    //这个例子中语句S1为p动态分配了空间,语句S3使指针q指向p所指的空间,这时指

    // 针p,q已成为指针别名,但是编译器无法检查出,以致于编译到语句S5,S6仍然不能

    //  报错,这为程序运行带来了隐患。同样如果程序中通过对象间赋值而该类对象的构造

    //  函数动态申请空间,析构函数动态释放空间,这样也可能引起安全漏洞:或者两个动态

    //  申请的对象,通过相互赋值导致某一对象指向的空间被释放多次:也可能是由于拷贝构

    //  造函数使指针成员指向的空间被释放多次等等,这些都属于编译器缺少别名检查最终导

    //  致同一内存重复释放。

4.3 释放连续内存空间的顺序错误

介绍:

malloc或new[]都是用来动态申请一块连续的空间,包括 基本类型和对象类型,释放时必须对空间整体释放,而不能单 独释放某个空间,即释放时指向连续空间的指针必须在分配空 间的首部,否则对连续申请的内存空间不能正确释放。

漏洞示例:

    int*p=( int* )malloc( sizeof( int)* 10);

    while(i<l0){*p=i*i;i++:p++;}

    free(p);

    //指针p不能被正确释放,因为释放时指针p没有返回到申请空间的头部。

审计策略:

定位指针,malloc等关键字进行排查

5 内存泄露

介绍:

常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆 中分配、大小任意、使用完后必须显式释放的内存。应用程序一 般使用malloe, realloe,new等操作从堆中分配一块内存,使用 完后程序必须负责调用相应的free或delete释放该内存块,否 则,这块内存就不能被再次使用,即出现内存泄漏

5.1 未释放动态申请的内存地址空间

介绍:

使用malloc, realloc , alloc, new, new[]等动态申请对象(包括 基本类型和自定义类型的对象)空间,但最后没有对其进行相 应的空间释放。

漏洞示例:

    while( i<MAX ){

    p=( double* )malloc( sizeof( double));

    *p=pow(x,i):

    i++;

    free(p):

    //循环体中对MAX-1个对象进行了动态分配空间,但最后只对一个进行释放,前面的MAX-2个对象的空间将被泄漏。

审计策略:

定位malloc, realloc , alloc, new, new[]等进行查看是否对其进行相应的空间释放。

5.2 动态申请的内存地址空间释放顺序错误

介绍:

这类漏洞主要针对多维数组的释放顺序,或指针结构体中 含有指针成员以及循环体释放内存空间的不正确引起的。

漏洞示例:

    typedef struct forest{

    int tnum;

    tree*t://tree也是一个结构体

    }forest:

    forest*f= new forest[2]:

    for(i=0;i<2;i++){

    (f+i)->tnum=i;

    (f+i)->l=new tree[(J+i )- >tnum];

    delete f;

 

    //在没有释放forest结构体内的指针t时就先释放f,这样为t分配的空间将被泄漏。对于多级连续内存空间的分配

    //是先申请大范围空间,再申请小范围空间,释放空间则相反应先释放小范围空间再释放大范围空间,否则如例所示

    //将出现内存泄漏。

原文地址:https://www.cnblogs.com/msblue/p/14278052.html