上一篇得到发送验证码请求的sign签名算法后,这篇主要介绍4位纯数字验证码burp插件的编写。介绍验证码插件之前,我们先介绍一下验证码插件会用到的三个burp接口IIntruderPayloadGeneratorFactory、IIntruderPayloadGenerator、IIntruderPayloadProcessor,再介绍验证码插件编写及使用。
一、burp接口介绍
-
IIntruderPayloadGeneratorFactory
在插件中implements此接口,并且在registerExtenderCallbacks方法中注册它后,即实现了一个IntruderPayloadGenerator工厂。我们可以重写该接口的createNewInstance的方法来调用我们自定义的Payload生成器(比如本篇文章的4位纯数字验证码生成器),如下所示
-
IIntruderPayloadGenerator
此接口用于实现我们自定义的生成器,implements该接口后,可以使用该接口提供的3个方法,返回我们生成的payload,如下所示
-
IIntruderPayloadProcessor
在插件中implements此接口,并且在registerExtenderCallbacks方法中注册它后,即我们实现了一个payload处理器。我们可以重写该接口中的processPayload方法,来实现对payload的处理,再返回处理后的结果(比如本篇文章将payload中的验证码替换为新的验证码,再重新计算sign后替换之前的sign),如下所示
processPayload方法包含三个byte[]类型的参数currentPayload、originalPayload、baseValue。
currentPayload 即我们传进来的生成的payload,本篇文章即我们在payload生成器中生成的4位纯数字验证码。
originalPayload表示在应用了任何Processor处理器之前的最原始的payload值 测试发现与currentPayload是一样的
baseValue 我们在burp Positions中选定的值,如下面所示
二、验证码插件实现与使用
- 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;
}
}
- 根据新的验证码参数及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);
}
}
- 在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;
}
-
intruder中设置插件
首先在Extender中将插件加载进来,如下所示
在Intruder中设置
-
测试插件功能
综上所述,该篇文章主要介绍了burp intruder模块插件编写。安全测试时在处理请求中带有sign请求校验的,可以尝试使用插件。如果需要本篇文章中测试的burp插件代码,可以在公众号回复"VerifyCode BurpExnteder",通过百度云链接下载。