当前位置:   article > 正文

应用安全系列之三十四:数值溢出_数据溢出

数据溢出

应用程序中难免会遇到数字的处理,针对数字的处理如果不当也会造成严重的问题,著名的Heartbleed漏洞也是没有验证数字的有效性导致的。

数值处理不好的,轻则产生异常,重则影响整个程序的正常运行,因此,针对由数值导致的问题也不能掉以轻心。主要问题总结为以下几点:

1) 异常

当从请求中获取参数的值需要转成数字时,这是就可能产生异常,产生异常的原因:一、内容符合数值的要求,含有异常字符;二、数值超出了数值类型的范围。如一下代码:

  1. public static void main(String[] args) {
  2. int value = Integer.parseInt("9147483647");
  3. System.out.println("the int value is"+value);
  4. }

当数值超过类型的最大值时,就会抛出异常信息如下:

 同样,当数值含有不合法的字符时,抛出的异常也是一样的:

 因此再接受数值的数据时,需要注意捕捉NumberFormatException这个异常,以防,程序在出异常后,直接跳出程序。

2) DOS

当数值被用于创建或者申请资源时,这时就需要注意验证数值的有效范围,例如:当一个数值是从客户端的搜索页面传递而来的,这个数值是用于指示搜索的一个页面显示多少条记录,通常这个数值的是几个常用的数字【10,50,100】;,但是当这个数值被修改为10000或者更大的数字而且符合条件的数据又恰好很多时,服务器如果直接根据这个数值去数据库查询所有记录,并组装成响应消息返回,就会消耗很多资源,特别是内存资源。曾经在渗透的测试过程中,遇到过一个页面通过查询返回了近7000条数据,而且花了很长时间页面才显示正常。

在使用C或者C++时,如果使用攻击者可以控制的数字来分配内存,就会直接导致更严重的问题,例如:内存溢出、分配内存失败或者读内存失败导致的程序运行异常,直接可以导致程序中断运行直接退出。

3) 信息泄露

Heartbleed漏洞就是典型的信息泄露的最好的例子,由于在读取内存时,没有判断数值的有效范围,导致在复制内存时,复制了超越实际范围的内存,使得攻击者可以通过此漏洞读取服务器端内存的内容,如果内存中含有用户登录相关的信息,攻击者就可以直接获取登录的会话信息劫持会话。HeartBleed的实例代码如下:

  1. int dtls1_process_heartbeat(SSL *s)
  2. {
  3. unsigned char *p = &s->s3->rrec.data[0], *pl;
  4. unsigned short hbtype;
  5. unsigned int payload;
  6. unsigned int padding = 16; /* Use minimum padding */
  7. /* Read type and payload length first */
  8. hbtype = *p++;
  9. n2s(p, payload);
  10. pl = p;
  11. //do something with the payload
  12. if (s->msg_callback)
  13. s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT,
  14. &s->s3->rrec.data[0], s->s3->rrec.length,
  15. s, s->msg_callback_arg);
  16. if (hbtype == TLS1_HB_REQUEST)
  17. {
  18. unsigned char *buffer, *bp;
  19. int r;
  20. /* Allocate memory for the response, size is 1 byte
  21. * message type, plus 2 bytes payload length, plus
  22. * payload, plus padding
  23. */
  24. buffer = OPENSSL_malloc(1 + 2 + payload + padding);
  25. //allocate all that memory without any checks
  26. bp = buffer;
  27. /* Enter response type, length and copy payload */
  28. *bp++ = TLS1_HB_RESPONSE;
  29. s2n(payload, bp);
  30. memcpy(bp, pl, payload);
  31. bp += payload;
  32. /* Random padding */
  33. RAND_pseudo_bytes(bp, padding);
  34. r = dtls1_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding);
  35. //send the response back, even the stuff the attacker wasn't supposed to see
  36. if (r >= 0 && s->msg_callback)
  37. s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT,
  38. buffer, 3 + payload + padding,
  39. s, s->msg_callback_arg);
  40. OPENSSL_free(buffer);
  41. if (r tlsext_hb_seq)
  42. {
  43. dtls1_stop_timer(s);
  44. s->tlsext_hb_seq++;
  45. s->tlsext_hb_pending = 0;
  46. }
  47. }
  48. return 0;
  49. }

从代码中可以看到从SSL中获取payload之后,没有做任何验证,直接用于:buffer = OPENSSL_malloc(1 + 2 + payload + padding)分配内存,并且用于复制内存。修改之后的代码如下:

  1. int dtls1_process_heartbeat(SSL *s)
  2. {
  3. unsigned char *p = &s->s3->rrec.data[0], *pl;
  4. unsigned short hbtype;
  5. unsigned int payload;
  6. unsigned int padding = 16; /* Use minimum padding */
  7. if (s->msg_callback)
  8. s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT,
  9. &s->s3->rrec.data[0], s->s3->rrec.length,
  10. s, s->msg_callback_arg);
  11. /* Read type and payload length first */
  12. if (1 + 2 + 16 > s->s3->rrec.length)
  13. return 0; /* silently discard */
  14. hbtype = *p++;
  15. n2s(p, payload);
  16. if (1 + 2 + payload + 16 > s->s3->rrec.length)
  17. return 0; /* silently discard per RFC 6520 sec. 4 */
  18. pl = p;
  19. if (hbtype == TLS1_HB_REQUEST)
  20. {
  21. unsigned char *buffer, *bp;
  22. unsigned int write_length = 1 /* heartbeat type */ +
  23. 2 /* heartbeat length */ +
  24. payload + padding;
  25. int r;
  26. if (write_length > SSL3_RT_MAX_PLAIN_LENGTH)
  27. return 0;
  28. /* Allocate memory for the response, size is 1 byte
  29. * message type, plus 2 bytes payload length, plus
  30. * payload, plus padding
  31. */
  32. buffer = OPENSSL_malloc(write_length);
  33. bp = buffer;
  34. /* Enter response type, length and copy payload */
  35. *bp++ = TLS1_HB_RESPONSE;
  36. s2n(payload, bp);
  37. memcpy(bp, pl, payload);
  38. bp += payload;
  39. /* Random padding */
  40. RAND_pseudo_bytes(bp, padding);
  41. r = dtls1_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, write_length);
  42. if (r >= 0 && s->msg_callback)
  43. s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT,
  44. buffer, write_length,
  45. s, s->msg_callback_arg);
  46. OPENSSL_free(buffer);
  47. if (r tlsext_hb_seq)
  48. {
  49. dtls1_stop_timer(s);
  50. s->tlsext_hb_seq++;
  51. s->tlsext_hb_pending = 0;
  52. }
  53. }
  54. return 0;
  55. }

if (1 + 2 + payload + 16 > s->s3->rrec.length)队payload增加了检查。

当应用程序因为数字处理不当时导致异常被是直接回显到页面时,也会导致信息泄露【可以参考应用安全系列之三十一:信息泄露_jimmyleeee的博客-CSDN博客】。

4) 除零操作

如果进行除法或者取余操作时,没有判断数值的有效性,会导致除零异常。在Java如果有除零发生时,抛出的异常如下:

有的语言也可能会导致程序崩溃,进而也导致DOS攻击的发生。

5) 溢出

数值操作最容易犯的错误还是溢出的问题,无论使用任何语言,都会有堆数值类型的加减乘除操作,在操作时,如果没有类型使用不当可能会导致数值溢出的问题发生。如下Java代码:

  1. public static void main(String[] args) {
  2. int overflow = Integer.MAX_VALUE + 10;
  3. System.out.println("the overflow value is"+overflow);
  4. }

此程序运行的结果是:

可以看出显示的结果是一个负的值。在C、C++语言中,因为有unsigned int无符号整型,结果值就会是一个很大的值,针对无符号整型,还会产生环绕问题,就是溢出之后,变成0或者一个很小的数值,这个值一旦用于用于分配资源操作或者作为业务逻辑的参数,就会导致结果严重偏离实际的操作。

提别是进行减法运算时,如果没有检查减数的合法性,更容易导致溢出问题的发生,可以根据需求转成加法操作,例如,需要判断a-b是否大于c,可以改成a是否大于b+c等。

对于Java语言而言每种类型的定义里都有一个最大值和最小值,可以使用此最大值来判断数值是否可能会产生溢出:

 在数值的实际操作时,也需要根据类型操作的结果的可能范围,将结果的类型设置为一个更大的范围的类型,就会避免溢出的发生。例如:两个int相加的结果,类型设置为long型,操作的结果可能超出int型的最大值,但是,绝对不可能超出long型的范围。

6) 截断

当一个高精度的数值存入到低精度的数值时可能会导致数值被截断,截断之后的值可能会变小,也可能会变成负值,这需要根据被赋值变量的类型而定。示例如下:

  1. public static void main(String[] args) {
  2. long value = Long.MAX_VALUE;
  3. System.out.println("before cut off value is"+value);
  4. int cutoff = (int) value;
  5. System.out.println("After cut off value is"+cutoff);
  6. }

运行结果显示如下:

 由上可见,数值如果处理不好对系统影响也非常大,因此,在数值处理时需要记住以下几点,以防数值操作导致结果不符合预期:

  • 在使用数值之前判断其是否在预期的合法范围之内;
  • 避免将高精度类型转换成低精度类型,大范围的数值类型转成小范围的数值类型;
  • 定义变量时,根据可能的取值范围选择合适的类型;【也不是越大越好,越大占用的内存就大,计算时消耗的CPU资源也大】;
  • 在进行计算时,尝试使用不容易出错的运算符,例如:将减法转换成加法;
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号