收集的一些漏洞原理
1. 缓冲区溢出漏洞
1.1 get函数溢出漏洞
gets()函数不检查缓冲长度,调用gets(buffer),会把用户输入的内容放在buffer中,但是对内容没有检查长度,如果用户输入的内容过长,就会覆盖在buffer之后定义的变量,这也使一些用户可以随便更改 一些程序中的变量。
可以使用fgets()函数,首先要用malloc为buffer分配一部分固定的空间。然后调用fgets()的时候传递进 去预计长度的值,这就不会造成溢出了。
1.2 strcpy函数溢出漏洞
strcpy函数是复制字符串的,这个家族有三个函数:strcpy、strcat、strcmp都有溢出问题。都是没有检查 长度。
因此在使用三个函数时检查其长度语句;使用strncpy、strncat、strncmp这三个会进行长度的检查,但是 存在的问题是,如果截取了长度,不能保证字符串以’\0’结尾,因此可以添加一些代码进行判断。
1.3 sprintf函数溢出漏洞
sprintf()函数打印到字符串中,对长度不做检查,造成溢出。
可以使用snprintf()函数,这个函数不仅能够避免缓冲区溢出,还能返回传递字符串的长度,以供判断是否需要处理截取后的’\0’结尾问题
1.4 printf函数溢出漏洞
这种漏洞和字符串格式化攻击相关,也就是我们常说的利用格式化字符串漏洞进行攻击。这种攻击通常 会导致信息泄露,覆盖内存(%n)等等。这个漏洞可以被以下函数触发:printf、fprintf、sprintf以及 snprintf。这些函数的共同特点:都是以格式化的字符串作为参数,即百分号的格式约定。
这时就需要用到硬编码格式化字符串了。
1.5 scanf函数溢出漏洞
scanf是输入函数,由于对输入长度没有控制导致缓冲区溢出问题,同类型的有:scanf、fscanf、sscanf。
这时就要精度说明符或自己进行解析。
1.6 栈溢出漏洞
栈溢出漏洞属于缓冲区漏洞的一种,实例如下:
1 |
|
该实例运行时直接运行到了地址0x41414141,这个就是字符串AAAA,就是变量str里面的字符串通过strcpy拷贝到栈空间时,没有对字符串长度做限制,导致了栈溢出,最后覆盖到了返回地址,造成程序崩溃。
溢出后的栈空间布局如下:
1.7 整数溢出漏洞
整数分为有符号和无符号两类,有符号数以最高位作为符号位,正整数符号位为0,负整数符号位为1。不同类型的整数在内存中有不同的取值范围,unsigned int = 4字节,int = 4字节,当存储的数值超过该类型整数的最大值就会发生溢出。
基于栈的整数溢出的实例:
1 |
|
代码中size变量是无符号短整型,取值范围是0~65535,输入的值大于65535就会发生溢出,最后得到size为4,这样会通过边界检查,但是使用memcpy复制数据的时候,使用的是int类型的参数i,这个值是输入的65540,就会发生栈溢出。
1.8 格式化字符串漏洞
格式化字符串漏洞产生的原因主要是对用户输入的内容没有做过滤,有些输入数据都是作为参数传递给某些执行格式化操作的函数的,比如:printf、fprintf、vprintf、sprintf。
恶意用户可以使用%s和%x等格式符,从堆栈和其他内存位置输出数据,也可以使用格式符%n向任意地址写入数据,配合printf()函数就可以向任意地址写入被格式化的字节数,可能导致任意代码执行,或者读取敏感数据。
格式化字符串漏洞实例:
1 |
|
当输入数据包含%s和%x格式符的时候,会意外输出其他数据:
1.9 Double Free漏洞
Double Free漏洞是由于对同一块内存进行二次释放导致的,利用漏洞可以执行任意代码,编译成release示例代码如下:
1 |
|
在二次释放p2的时候就会发生程序崩溃,但不是每次出现Double Free都会发生崩溃,要有堆块合并的动作发生才会发生崩溃
1 |
|
1.10 UAF漏洞
代码实例:
1 |
|
buf2”占坑”了buf1的内存位置,经过UAF后,buf2被成功篡改了
程序通过分配和buf1大小相同的堆块buf2实现占坑,使得buf2分配到已经释放的buf1内存位置,但由于buf1指针依然有效,并且指向的内存数据是不可预测的,可能被堆管理器回收,也可能被其他数据占用填充,buf1指针称为悬挂指针,借助悬挂指针buf1将内存赋值为hack,导致buf2也被篡改为hack。
如果原有的漏洞程序引用到悬挂指针指向的数据用于执行指令,就会导致任意代码执行。
在通常的浏览器UAF漏洞中,都是某个C++对象被释放后重引用,假设程序存在UAF的漏洞,有个悬挂指针指向test对象,要实现漏洞利用,通过占坑方式覆盖test对象的虚表指针,虚表指针指向虚函数存放地址,现在让其指向恶意构造的shllcode,当程序再次引用到test对象就会导致任意代码执行。
1.11 数组越界访问漏洞
1 | 数组越界漏洞:数组越界访问包含读写类型 |
数组越界访问漏洞代码实例:
1 |
|
执行生成的程序,然后分别输入12345,输出结果如上。当输入的数字下表分别为1、2的时候,会得到正常数值。但是从索引3开始就超出了原来的数组array的范围,比如输入5,就会数组越界访问array数组,导致读取不在程序控制范围内的数值。
使用ollydbg调试发现array[5]就是从array开始的第六个数据0x4012A9,已经读取到了array之外的数据。如果越界访问距离过大,就会访问到不可访问的内存空间,导致程序崩溃。
1.12 类型混淆漏洞
类型混淆漏洞(Type Confusion)一般是将数据类型A当作数据类型B来解析引用,这就可能导致非法访问数据从而执行任意代码,比如将Unit转成了String,将类对象转成数据结构。
类型混淆漏洞是现在浏览器漏洞挖掘的主流漏洞,这类漏洞在Java、js等弱类型语言中非常常见。
下面的代码,A类被混淆成B类,就可能导致私有域被外部访问到:
1 | class A { |
1.13 竞争条件漏洞
竞争条件(Race Condition)是由于多个线程/对象/进程同时操作同一资源,导致系统执行违背原有逻辑设定的行为。这类漏洞在Linux、内核层面非常多见,在windows和web层面也存在。
互斥锁的出现就是为了解决此类漏洞问题,保证某一对象在特定资源访问时,其他对象不能操作此资源。
1 | 竞争条件发生在多个线程同时访问同一个共享代码、变量、文件等没有进行锁操作或者同步操作的场景中。 |
实例代码如下:
1 | #-*-coding:utf-8-*- |
按照我们的预想,结果应该都是10,但是发现结果可能存在非预期解。原因就在于我们没有对变量COUNT做同步制约,导致可能Thread-7在读COUNT,还没来得及更改COUNT。Thread-8抢夺资源,也来读COUNT,并且将COUNT修改为它读的结果+1,由此出现非预期。
2. 指针覆写
指针覆写漏洞是指由于对之后指针操作没有进行很好的处理和保护,造成指针的值被修改和利用的漏洞。其中利用的方式有代码注入攻击、返回函数攻击、返回导向编程。
2.1 命令注入
执行命令时,命令将未验证的用户输入作为命令参数的一部分,导致应用可能遭受攻击。分为三个阶段:
1)不可信的数据通过用户进入程序
2)数据被程序作为运行的命令的一部分执行
3)通过执行命令,程序就会给攻击者本身不具有的特权或者能力
漏洞代码实例:
1 | int main(char* argc,char**argv){ |
3. 存储越界
存储越界指一个变量读或写超出变量分配的内存空间,C语言编译器没有提供数组和缓冲区的边界检查。它对字符串的操作只是通过识别’\0’来判断是否达到该字符串的结尾,这就很容易造成访问越界问题。如果越界读取数据,就会得到一些无用信息同时也可能遭到黑客的攻击,导致程序运行出错或者以非法内存而终止程序。
3.1 数组元素边界检查
数组元素没有进行边界检查,这类漏洞主要针对显示的使用数组元组,包括基本类型和用户自定义类型的数组操作。
漏洞代码实例:
1 | vector<type>phone_book(5) |
这就需要我们进行边界检查,包括数组、字符串和数值类型等。
3.2 指针元素边界检查
指针加下标的元素没有进行边界检查,指针可以指向数组也可以指向单个元素,它属于隐式的使用数组元素。
漏洞代码实例:
1 | int array[]={1,2,3}; |
这时就需要对加下标的元素进行边界检查。
3.3 数值变量范围越界
数值型变量范围表示范围越界,数值型变量都有一个表示范围,如果对变量不加限制的进行相互操作就很容易导致范围越界。
漏洞代码实例:
1 | short int var =30000+30000; |
3.4 动态存储分布
静态存储分配对变量进行定义声明是编译器就能获取它们所需存储空间大小,并为其分配相应的内存空间,而且编译器生存期内是固定不变的,生存期结束后系统对其自动释放。
动态存储分配相对于静态存储分配,它是在程序执行期间,通过申请分配指定的内存空间或释放指定的内存空间。C和C++提供各自的动态分配算法,C语言提供了库函数有:malloc、relealloe、free等。而C++语言提供的是运算符:new、new[]、delete、deleteQ等等。这些库函数和运算符都用于动态申请或释放内存,并且C++编译器对函数和运算符都能识别,所以对程序员来说很容易混淆使用,最终造成程序运行错误。
此外,由于是动态分配,所以也会出现对空间释放多次或使用已释放空间等这样的漏洞。
1)内存分配和释放函数均未正确配对
从内存分配的机制来说,malloc和free配对,new和delete配对。因为malloc/free是库函数而不是运算符,它们只负责分配或释放一块连续的内存空间,不执行构造/析构函数,它主要用于非基本数据类型对象的操作。new[]/delete[]是用来同时分配/释放对象数组,这些运算符或函数不能混合使用,否则将出现安全漏洞。
漏洞代码实例:
1 | S1: int*P=(int*)malloc(size(int)); |
2)重复释放同一内存空间
由于编译器没有分析别名引起的漏洞
1 | char *p,*q; |
3)释放连续内存空间的顺序错误
malloc或new函数都是用来动态申请一块连续的空间,包括基本类型和对象类型,释放时必须对空间整体释放,而不能单独释放某空间,即释放时指向连续空间的指针必须分配在分配空间的首部,否则对连续申请的内存空间不能正确释放。
漏洞代码实例:
1 | int*p=(int)malloc(sizeof(int)*10); |
4. 内存泄漏
常说的内存泄漏是指堆内存的泄露。堆内存是指从堆中分配,大小任意,使用完后必须显示释放的内存。应用程序一般使用malloc、realloc、new等操作从堆中分配一块内存,使用完后程序必须负责调用相应的free或delete释放该内存块,否则这块内存就不能被再次使用,即出现内存泄露。
1)未释放动态申请的内存地址空间
但是malloc、realloc、new、new[]等动态申请对象(包括基本类型和自定义类型的对象)空间,但最后没有对其进行相应的空间释放。
漏洞代码实例:
1 | while(i<MAX){ |
2)动态申请的内存地址空间释放顺序错误
这类漏洞主要针对多维数组的释放顺序或指针结构体中含有指针成员以及循环体释放内存空间的不正确引起的。
漏洞代码实例:
1 | typedef struct forest{ |
5. 参考链接
1 | https://bbs.pediy.com/thread-252569.htm |
发布时间: 2021-07-15
最后更新: 2023-07-21
本文标题: 常见漏洞原理
本文链接: https://foxcookie.github.io/2021/07/15/常见漏洞原理/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!