赞
踩
应用程序中难免会遇到数字的处理,针对数字的处理如果不当也会造成严重的问题,著名的Heartbleed漏洞也是没有验证数字的有效性导致的。
数值处理不好的,轻则产生异常,重则影响整个程序的正常运行,因此,针对由数值导致的问题也不能掉以轻心。主要问题总结为以下几点:
1) 异常
当从请求中获取参数的值需要转成数字时,这是就可能产生异常,产生异常的原因:一、内容符合数值的要求,含有异常字符;二、数值超出了数值类型的范围。如一下代码:
- public static void main(String[] args) {
-
- int value = Integer.parseInt("9147483647");
- System.out.println("the int value is"+value);
-
- }
当数值超过类型的最大值时,就会抛出异常信息如下:
同样,当数值含有不合法的字符时,抛出的异常也是一样的:
因此再接受数值的数据时,需要注意捕捉NumberFormatException这个异常,以防,程序在出异常后,直接跳出程序。
2) DOS
当数值被用于创建或者申请资源时,这时就需要注意验证数值的有效范围,例如:当一个数值是从客户端的搜索页面传递而来的,这个数值是用于指示搜索的一个页面显示多少条记录,通常这个数值的是几个常用的数字【10,50,100】;,但是当这个数值被修改为10000或者更大的数字而且符合条件的数据又恰好很多时,服务器如果直接根据这个数值去数据库查询所有记录,并组装成响应消息返回,就会消耗很多资源,特别是内存资源。曾经在渗透的测试过程中,遇到过一个页面通过查询返回了近7000条数据,而且花了很长时间页面才显示正常。
在使用C或者C++时,如果使用攻击者可以控制的数字来分配内存,就会直接导致更严重的问题,例如:内存溢出、分配内存失败或者读内存失败导致的程序运行异常,直接可以导致程序中断运行直接退出。
3) 信息泄露
Heartbleed漏洞就是典型的信息泄露的最好的例子,由于在读取内存时,没有判断数值的有效范围,导致在复制内存时,复制了超越实际范围的内存,使得攻击者可以通过此漏洞读取服务器端内存的内容,如果内存中含有用户登录相关的信息,攻击者就可以直接获取登录的会话信息劫持会话。HeartBleed的实例代码如下:
- int dtls1_process_heartbeat(SSL *s)
- {
- unsigned char *p = &s->s3->rrec.data[0], *pl;
- unsigned short hbtype;
- unsigned int payload;
- unsigned int padding = 16; /* Use minimum padding */
-
- /* Read type and payload length first */
- hbtype = *p++;
- n2s(p, payload);
- pl = p;
-
- //do something with the payload
-
- if (s->msg_callback)
- s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT,
- &s->s3->rrec.data[0], s->s3->rrec.length,
- s, s->msg_callback_arg);
-
- if (hbtype == TLS1_HB_REQUEST)
- {
- unsigned char *buffer, *bp;
- int r;
-
- /* Allocate memory for the response, size is 1 byte
- * message type, plus 2 bytes payload length, plus
- * payload, plus padding
- */
-
-
-
- buffer = OPENSSL_malloc(1 + 2 + payload + padding);
-
-
- //allocate all that memory without any checks
-
- bp = buffer;
-
- /* Enter response type, length and copy payload */
- *bp++ = TLS1_HB_RESPONSE;
- s2n(payload, bp);
- memcpy(bp, pl, payload);
- bp += payload;
- /* Random padding */
- RAND_pseudo_bytes(bp, padding);
-
- r = dtls1_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding);
-
- //send the response back, even the stuff the attacker wasn't supposed to see
-
- if (r >= 0 && s->msg_callback)
- s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT,
- buffer, 3 + payload + padding,
- s, s->msg_callback_arg);
-
- OPENSSL_free(buffer);
-
- if (r tlsext_hb_seq)
- {
- dtls1_stop_timer(s);
- s->tlsext_hb_seq++;
- s->tlsext_hb_pending = 0;
- }
- }
-
- return 0;
- }
从代码中可以看到从SSL中获取payload之后,没有做任何验证,直接用于:buffer = OPENSSL_malloc(1 + 2 + payload + padding)分配内存,并且用于复制内存。修改之后的代码如下:
- int dtls1_process_heartbeat(SSL *s)
- {
- unsigned char *p = &s->s3->rrec.data[0], *pl;
- unsigned short hbtype;
- unsigned int payload;
- unsigned int padding = 16; /* Use minimum padding */
-
- if (s->msg_callback)
- s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT,
- &s->s3->rrec.data[0], s->s3->rrec.length,
- s, s->msg_callback_arg);
-
- /* Read type and payload length first */
- if (1 + 2 + 16 > s->s3->rrec.length)
- return 0; /* silently discard */
- hbtype = *p++;
- n2s(p, payload);
- if (1 + 2 + payload + 16 > s->s3->rrec.length)
- return 0; /* silently discard per RFC 6520 sec. 4 */
- pl = p;
- if (hbtype == TLS1_HB_REQUEST)
- {
- unsigned char *buffer, *bp;
- unsigned int write_length = 1 /* heartbeat type */ +
- 2 /* heartbeat length */ +
- payload + padding;
- int r;
-
- if (write_length > SSL3_RT_MAX_PLAIN_LENGTH)
- return 0;
-
- /* Allocate memory for the response, size is 1 byte
- * message type, plus 2 bytes payload length, plus
- * payload, plus padding
- */
- buffer = OPENSSL_malloc(write_length);
- bp = buffer;
-
- /* Enter response type, length and copy payload */
- *bp++ = TLS1_HB_RESPONSE;
- s2n(payload, bp);
- memcpy(bp, pl, payload);
- bp += payload;
- /* Random padding */
- RAND_pseudo_bytes(bp, padding);
-
- r = dtls1_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, write_length);
-
- if (r >= 0 && s->msg_callback)
- s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT,
- buffer, write_length,
- s, s->msg_callback_arg);
-
- OPENSSL_free(buffer);
-
- if (r tlsext_hb_seq)
- {
- dtls1_stop_timer(s);
- s->tlsext_hb_seq++;
- s->tlsext_hb_pending = 0;
- }
- }
-
- return 0;
- }
在if
(1 + 2 + payload + 16 > s->s3->rrec.length)队payload增加了检查。
当应用程序因为数字处理不当时导致异常被是直接回显到页面时,也会导致信息泄露【可以参考应用安全系列之三十一:信息泄露_jimmyleeee的博客-CSDN博客】。
4) 除零操作
如果进行除法或者取余操作时,没有判断数值的有效性,会导致除零异常。在Java如果有除零发生时,抛出的异常如下:
有的语言也可能会导致程序崩溃,进而也导致DOS攻击的发生。
5) 溢出
数值操作最容易犯的错误还是溢出的问题,无论使用任何语言,都会有堆数值类型的加减乘除操作,在操作时,如果没有类型使用不当可能会导致数值溢出的问题发生。如下Java代码:
- public static void main(String[] args) {
- int overflow = Integer.MAX_VALUE + 10;
- System.out.println("the overflow value is"+overflow);
-
- }
此程序运行的结果是:
可以看出显示的结果是一个负的值。在C、C++语言中,因为有unsigned int无符号整型,结果值就会是一个很大的值,针对无符号整型,还会产生环绕问题,就是溢出之后,变成0或者一个很小的数值,这个值一旦用于用于分配资源操作或者作为业务逻辑的参数,就会导致结果严重偏离实际的操作。
提别是进行减法运算时,如果没有检查减数的合法性,更容易导致溢出问题的发生,可以根据需求转成加法操作,例如,需要判断a-b是否大于c,可以改成a是否大于b+c等。
对于Java语言而言每种类型的定义里都有一个最大值和最小值,可以使用此最大值来判断数值是否可能会产生溢出:
在数值的实际操作时,也需要根据类型操作的结果的可能范围,将结果的类型设置为一个更大的范围的类型,就会避免溢出的发生。例如:两个int相加的结果,类型设置为long型,操作的结果可能超出int型的最大值,但是,绝对不可能超出long型的范围。
6) 截断
当一个高精度的数值存入到低精度的数值时可能会导致数值被截断,截断之后的值可能会变小,也可能会变成负值,这需要根据被赋值变量的类型而定。示例如下:
- public static void main(String[] args) {
-
- long value = Long.MAX_VALUE;
- System.out.println("before cut off value is"+value);
- int cutoff = (int) value;
- System.out.println("After cut off value is"+cutoff);
-
- }
运行结果显示如下:
由上可见,数值如果处理不好对系统影响也非常大,因此,在数值处理时需要记住以下几点,以防数值操作导致结果不符合预期:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。