1 整体思路
1.1 背景描述
某需求测试过程中:新增报价申请界面,点击【暂存】或【提交】,显示系统错误,无法提交,截图如下:
1.2 日志定位
查看日志,发现报错是:4398046512275超过int(-2147483648-2147483647)的边界范围。
1.3 查看数据库
可以看到在数据库对应的表中,product_id被定义为bigint类型,表中的数据长度定义的足够长,所以4398046512275可以存储到数据库中,是没有问题的。
1.4 查看代码
可以看到后端代码是声明的int类型,所以4398046512275是在代码层处理的时候也是有问题的,会将4398046512275转为为一个错误的值,导致代码处理中产生错误。
1.5 思路总结
报错显示的4398046512275为“产品ID”,即产品对应的是产品的编号,在数据库中可以存下,而在后台代码处理中是int,导致代码运行中其他要用到的“产品ID”是个异常值,所以报错。
1.6 解决方案
修改代码中productId的类型为long,这样可以存下4398046512275这样的比较的大的数据。
2 关于整数溢出深入代码层的思考
2.1 举个栗子
#include <stdio.h>
//返回读取的内容长度
int Read(int fd, int start, int end) {
int length = end - start + 1;
//Read 函数中最大只支持一次读取1024个字节,所以增加了一个判断逻辑。
if (length > 1024)
return -1;
return length;
}
int main(void)
{
//调用函数Read
int Length = Read(0, 0, 5);
printf("长度为:%d\n",Length);
return 0;
}
大家看下,以上代码有没有什么问题?
2.2 调用函数
Length = Read(0, 0, 4294967295);
这里的4294967295会传参给end,而4294967295大于1024,正常会返回-1,但是真实情况是这样的吗?
但是请注意Read这个函数的参数中,start
和end
都是int
型变量,把4294967295传递给end的时候,会被当作有符号的整数解析,也就是 -1 !所以在执行以下代码时,
int length = end - start + 1;
length的结果会是0!计算结果就是-1 - 0 + 1 = 0!这里计算的结果逃过了代码中对于长度大于1024的检查。
if (length > 1024)
return -1;
2.3 思考
整数溢出在日常其他方面是否真实存在影响?
3 关于整数溢出的影响
3.1 真实栗子
漏洞编号:CVE-2015-1635
漏洞编号 | CVE-2015-1635 |
漏洞描述 | windows2008+iis7,当向服务器web服务发送特殊header时,会造成IIS服务器蓝屏崩溃,特殊环境下或导致信息泄露 且这个漏洞位于IIS处理HTTP请求的HTTP.sys驱动程序中。 |
验证方式 | wget --header=”Range: bytes=18-18446744073709551615” http://ip/welcome.png |
漏洞危害 | 造成IIS服务器蓝屏崩溃,特殊环境下或导致信息泄露 |
受影响版本 | IIS 7.0以上的Windows 7/8/8.1和Windows Server 2008 R2/Server 2012/Server 2012 R2等操作系统 |
修复建议 | 目前微软官方已经给出修复补丁,用户安装修复补丁即可,选择 KB3042553 安全更新进行系统升级:http://www.catalog.update.microsoft.com/Search.aspx?q=KB3042553 |
漏洞分析 |
这是一个微软的互联网服务器IIS中的一个漏洞,更重要的是,这个漏洞位于IIS处理HTTP请求的HTTP.sys驱动程序中。
而驱动程序是运行在操作系统内核之中,一旦内核驱动执行出现异常,那后果,轻则蓝屏崩溃,重则直接被攻击者远程执行代码,控制服务器。
3.2 HTTP协议中Range字段
HTTP 1.1版本的协议中,可以通过请求头中的Range字段,请求或上传指定资源的部分内容:
GET /bg-upper.png HTTP/1.1
User-Agent: curl/7.35.0
Host: 127.0.0.1:8180
Accept: */*
Range: bytes=0-10
其中的Range字段格式如下:
Range: bytes=start-end
微软的IIS为了提高性能,将HTTP协议的解析放在了内核驱动 HTTP.sys 中实现。
分析:而其中对range字段的处理,就是第2部分中的Read代码的那个逻辑错误,不同的是,我们上面的那个示例是一个32位整数的版本,而IIS这个真实的漏洞是64位整数产生的问题,但原理是一样的。
3.3 整数溢出攻击
通过向存在漏洞的IIS服务器发送对应的HTTP请求,即可将目标服务器打蓝屏,实现DOS——拒绝服务攻击。
这种攻击方式就是——整数溢出攻击。
3.4 思考
其他常见的整数溢出有哪些?
4 其他常见的整数溢出
4.1 有符号数溢出
4.1.1 上溢出
#include <stdio.h>
#include <limits.h>
int main(void)
{
int i;
i = INT_MAX; //i = 2147483647
i++;
printf("i = %d\n",i); //i = -2147483648
return 0;
}
4.1.2 下溢出
#include <stdio.h>
#include <limits.h>
int main(void)
{
int i;
i = INT_MIN; //i = -2147483648
i--;
printf("i = %d\n",i); //i = 2147483647
}
4.2 无符号数回绕
4.2.1 上回绕
#include <stdio.h>
#include <limits.h>
int main(void)
{
unsigned int i;
i = UINT_MAX; //在32位电脑上:i = 4294967295
i++;
printf("i = %u\n",i); //i = 0
}
4.2.2 下回绕
#include <stdio.h>
int main(void)
{
unsigned int i;
i = 0;
i--;
printf("i = %u\n",i); //在32位电脑上:i = 4294967295
}
4.3 截断
4.3.1 加法截断
#include <stdio.h>
int main(void)
{
long a, b, c;
a = 0xffffffff;
b = 0x00000001;
c = a + b; // c = 0x0000001 00000000(long long) = 0x00000000(long)
printf("c = 0x%x\n",c); //c = 0x00000000
}
4.3.2 乘法截断
#include <stdio.h>
int main(void)
{
long a, b, c;
a = 0x00123456;
b = 0x00654321;
c = a * b; // c = 0x00000733 6bf94116(long long) = 0x6bf94116(long)
printf("c = 0x%x\n",c); //c = 0x6bf94116
}
4.4 宽度溢出(不够的用符号位填充)
#include<stdio.h>
int main(){
int a;
short b;
char c;
a = 0xabcddcba;
b = a;
c = a;
printf("a = 0x%x(%d bits)\n", a, sizeof(a) * 8);//a = 0xabcddcba(32 bits)
printf("b = 0x%x(%d bits)\n", b, sizeof(b) * 8);//b = 0xffffdcba(16 bits)
printf("c = 0x%x(%d bits)\n", c, sizeof(c) * 8);//c = 0xffffffba(8 bits)
printf("b + c = 0x%x(%d bits)\n", b+c, sizeof(b+c) * 8);//b + c = 0xffffdc74(32 bits)
}
思考:为什么输出的b和c的高位用ffff填充?
解答:因为变量b和c定义的为有符号的类型,以b为例,当语句执行b = a;此时先看 低位 dcba(1101 1100 1011 1010)2个字节(16位)可以容纳b,而高位需要看b的符号位,dcba(1101 1100 1011 1010)为1,所以高位全部用1填充,即ffff(1111 1111 1111 1111),执行完语句b = a后 b = 0xffffdcba; 以c为例,当语句执行c = a;此时先看 低位 ba(1011 1010)1个字节(8位)可以容纳c,而高位需要看c的符号位, ba(1011 1010)为1,所以高位全部用1填充,即ffffff(1111 1111 1111 1111 1111 1111),执行完语句c = a后 c= 0xffffffba。
5 心得及总结
- 开发 or 测试 中,一定要注意变量的数据类型,特别是涉及到数据类型转换的地方要格外留神。比如符号数与无符号数的互转,32位整数和64位整数的转换等等。
- 涉及联调,在处理上下游系统传参数处理时,要慎之又慎,一个小小的变量类型可能就会给服务器计算机造成毁灭性打击。
- 测试活动中,要注意数据类型的边界值的测试(而非仅仅是PRD中定义的最大值)。
- 数据类型的长度范围汇总:
Type | Size | 数值范围 |
无值型void | 0 byte | 无值域 |
布尔型bool | 1 byte | true、false |
有符号短整型short [int] / signed short [int] | 2 byte | -32768 ~ 32767 |
无符号短整型unsigned short [int] | 2 byte | 0 ~ 65535 |
有符号整型int / signed [int] | 4 byte | -2147483648 ~ 2147483647 |
无符号整型unsigned [int] | 4 byte | 0 ~ 4294967295 |
有符号长整型long int / signed long [int] | 4 byte | -2147483648 ~ 2147483647 |
无符号长整型unsigned long [int] | 4 byte | 0 ~ 4294967295 |
long long | 8 byte | 0 ~ 18446744073709552000 |
有符号字符型char / signed char | 1 byte | -128 ~ 127 |
无符号字符型unsigned char | 1 byte | 0 ~ 255 |
宽字符型wchar_t / unsigned short | 2 byte | 0 ~ 65535 |
单精度浮点型float | 4 byte | -3.4E-38 ~ 3.4E+38 |
双精度浮点型double | 8 byte | 1.7E-308 ~ 1.7E+308 |