目录
前言:
(一)整数溢出漏洞
0x01 整数溢出漏洞介绍
1.1 上界溢出
1.2 下界溢出
0x02 整数溢出漏洞修复
(二)硬编码密码漏洞
修复案例:
(三)不安全的随机数生成器
前言:
本次总结的漏洞在实战情况下,可能只是一个环节,包括我们经常遇到的WAF绕过,技术偏逆向破解,但是在web渗透测试中有一定的利用价值。
(一)整数溢出漏洞
计算机程序中的整数都是有范围的,不同的数据类型范围也不同,这是由编译器决定的,超过这个范围就有可能会出现整数溢出的情况。
比如说Java 的
int
是
32
位有符号整数类型,其最大值是
0x7fffffff
,最小值是0x80000000,即
int
表示的数的范围是在-
2147483648
~
2147483647
之间。当
int
类型的运算结果超出了这个范围时则发生了溢出,而且不会有任何异常抛出。整数溢出一般可以分为上界溢出和下界溢出两种。
0x01 整数溢出漏洞介绍
1.1 上界溢出
int 类型二进制存储的第一位为符号位,0 表示正数, 1 表示负数, 2147483647 这个数字的二进制表达为 01111111 11111111 11111111 11111111 ,加 1 以后的值为 1000000000000000 00000000 00000000 ,发生上界溢出,而 10000000 00000000 00000000 00000000 表示的是-2147483648 这个数字
1.2 下界溢出
int 类型二进制存储的第一位为符号位, 0 表示正数, 1 表示负数, -2147483648 这个数字的二进制表达为 10000000 00000000 00000000 00000000 ,减 1 以后的值为 0111111111111111 11111111 11111111 ,发生下界溢出,而 01111111 11111111 11111111 11111111 表示的是 2147483647 这个数字。 Java 程序溢出运行的案例如图 1-1 所示:1
0x02 整数溢出漏洞修复
在 Java 8 版本中,可以使用新的“ Math#addExact() and Math#subtractExact()”方法,该方法将监测“ ArithmeticException ”溢出
public static boolean willAdditionOverflow(int left, int right) {
try {
Math.addExact(left, right);
return false;
} catch (ArithmeticException e) {
return true;
}
}
public static boolean willSubtractionOverflow(int left, int right) {
try {
Math.subtractExact(left, right);
return false;
} catch (ArithmeticException e) {
return true;
}
}
(二)硬编码密码漏洞
硬编码密码是指在系统中采用明文的形式存储密码,通常会导致严重的身份验证失败,这对于系统管理员而言可能很难检测到,一旦检测到,也很难修复。硬编码密码会造成密码泄露,其主要出现在以下几个方面
- 开发人员的安全意识不强:将代码托管到 github 等互联网平台,可能会造成源代码泄露,任何有该代码权限的人都能读取此密码。
- 程序员可以简单地将后端凭证硬编码到前端软件中:该程序的任何用户都可以提取密码。因为从二进制文件中提取密码非常简单,所以会对带有硬编码密码的客户端系统构成很大的威胁。
以下代码是使用硬编码的密码连接到数据库的示例:
DriverManager.getConnection(url,"scott","tiger");
攻击者可以通过 javap –c
命令来访问反汇编的代码,反汇编的代码将包含所使用的密码值。
javap -c ConnMngr.class
22: ldc #36; //String jdbc:mysql://ixne.com/rxsql
24: ldc #38; //String scott
26: ldc #17; //String tiger
程序员在编写程序时应尽量避免对密码进行硬编码,而采用对密码加以模糊化或先经过 hash
处理再存储,或在外部资源文件中进行处理的方法。
修复案例:
在上述修复代码中,首先将数据库连接的用户名密码加密放入 db.properties
文件中,如图 1-2
所示
然后通过代码读取数据库配置文件,最后进行数据库的连接,这样就避免了硬编码产生的漏洞。
示例:读取 properties
属性文件到输入流中,从输入流中加载属性列表,获取数据库连接属性值,然后进行数据库连接,这样就不会出现硬编码漏洞了。
// 读取 properties 属性文件到输入流中
InputStream is = PropertiesTest.class.getResourceAsStream("/db.properties");
// 从输入流中加载属性列表
properties.load(is);
// 获取数据库连接属性值
DRIVER_CLASS = properties.getProperty("DRIVER_CLASS");
DB_URL = properties.getProperty("DB_URL");
DB_USER = properties.getProperty("DB_USER");
DB_PASSWORD = properties.getProperty("DB_PASSWORD");
// 加载数据库驱动类
Class.forName(DRIVER_CLASS);
(三)不安全的随机数生成器
随机数是专门的随机试验的结果。产生随机数有多种不同的方法,这些方法被称为随机数发生器。随机数最重要的特性是:它所产生的后面的那个数与前面的那个数毫无关系。根据密码学原理,随机数生成器分为以下三类。
- 统计学伪随机数生成器(PRNG):伪随机数生成器从一个初始化的种子值开始计算得到序列,从种子开始,然后从种子中计算出后续值,当种子确定后生成的随机数也是确定的,但其输出结果很容易预测,因此容易复制数值流。
- 密码学安全随机数生成器(CSPRNG):密码学安全伪随机性是统计学伪随机数生成器的一个特例,给定随机样本的一部分和随机算法,不能有效地演算出随机样本的剩余部分。
- 真随机数生成器:其定义为随机样本不可重现。实际上只要给定边界条件,真随机数并不存在。可是如果产生一个真随机数样本的边界条件十分复杂且难以捕捉(比如计算机当地的本底辐射波动值),则可以认为用这个方法演算出来了真随机数。
使用计算机产生真随机数的方法是获取 CPU 频率与温度的不确定性,以及统计一段时间内的运算次数每次都会产生不同的值,系统时间的误差以及声卡的底噪等。在实际应用中往往使用伪随机数就足够了。计算机或计算器产生的随机数有很长的周期性。实际上它们不真正地随机,因为它们是可以计算出来的,但是它们具有类似于随机数的统计特征。这样的发生器称为伪随机数发器。
Java 中的“
java.util.Random
”工具类
LCG
线性同余法伪随机数生成器,可以根据种子和算法生成随机数。此算法的缺陷就是可预测性,攻击者可能会猜测将要生成的下一个值,并使用此猜测来模拟其他用户或访问敏感信息。“java.util.Random
”工具类没带参数构造函数生成的 Random
对象的种子默认是当前系统时间的毫秒数,故进入到
Random
类中查看其种子默认是当前的系统时间,如图 3-1
所示。
Random 函数的使用方式:
Random random = new Random();
int r = random.nextInt(); // 生成一个随机数
只要种子一样,其输出的随机序列也是一样的,如设置两个随机数列种子都是 1,则输出的随机数列也是完全相同的,如图 3-2 所示。
“java.util.Random ”不是加密安全的。可以使用 SecureRandom 来获取密码安全的伪随机数生成器,以供对安全敏感的应用程序使用。“java.Security.SecureRandom ”工具类提供加密的强随机数生成器(RNG) ,要求种子必须是不可预知的,产生非确定性输出。操作系统收集了一些随机事件,比如鼠标点击、键盘点击,等等。SecureRandom 使用这些随机事件作为种子。SecureRandom 函数的使用方式:
SecureRandom secureRandom2 = SecureRandom.getInstance("SHA1PRNG");
int r = secureRandom2.nextInt(); // 生成一个随机数
使用 SecureRandom 生成伪随机,因为 SecureRandom 使用鼠标点击、键盘点击等等这些随机事件作为种子,故其生成的随机数列完全不同,安全性要高,所以建议使用“ java.Security.SecureRandom ”工具类来代替“ java.util.Random ”工具类,如图 3 -3 所示。