前言
本篇主要思路及方法来自于bit4woo和CC11001100两位巨佬的成果。学习链接https://github.com/bit4woo/burp-api-drops
根据大佬的建议,本次插件使用java进行编写。
而且bp插件的编写对于类名也有严格的限制。
主要的调用逻辑如下所示

根据该图,对于插件编写的流程其实也很清楚了,我们只需要先调用这些之前写好的类,然后只需要编写BurpExtender这一个类就好了。
编写流程
首先因为不同网站对应的Key不同,先创建个字典进行存放。
public BurpExtender() {
this.signMap = new ConcurrentHashMap<>();
signMap.put("class.mosaic.cn", "ac6190d7dfaa77df726f0a82244d3eda68675ccd4e95de802f5042e91d15edc7bae3026d8f0fb2a8287446bb289563970264");
signMap.put("bbsapi.mosaic.cn", "Wj8BI3VUZ6BuojAkqzBM3HWHNHv08xdZEtaksbRg6snnuLsvivwa8IvR6PvQ76H0IQQsqkIsa5OKJtg6QcBMfCblMMywgZaA8co");
}
然后是IBurpExtender必须实现的方法registerExtenderCallbacks
@Override
public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
this.callbacks = callbacks;
callbacks.setExtensionName("xxsign重放辅助渗透");
helpers = callbacks.getHelpers();
stdout = new PrintWriter(callbacks.getStdout(), true);
callbacks.registerHttpListener(this);//如果不注册,processHttpMessage方法不会生效。
}
接下来就是processHttpMessage方法,首先要确定toolFlag,不同的toolFlag代表不同的burp组件,此处我们只处理repeater发出的消息。
if (toolFlag != burp.IBurpExtenderCallbacks.TOOL_REPEATER) {
return;
}
// 只处理请求,响应则忽略
if (!messageIsRequest) {
return;
}
byte[] request = messageInfo.getRequest();
if (request == null || request.length == 0) {
return;
}
// 只有特定的两个域名才会生效
final IHttpService httpService = messageInfo.getHttpService();
final String host = httpService.getHost();
if (!signMap.containsKey(host)) {
return;
}
接下来是获取参数,由于上一节中已经分析出了参数组成,key是固定值,noncestr是随机生成。timestamp是当前时间戳,因此实际上参数的获取所产生交互是比较少的。只需要生成这些参数的拼接格式,以及SHA1算法。
long now = System.currentTimeMillis();
private static String randomNoncestr() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 8; i++) {
char c = (char) (ThreadLocalRandom.current().nextInt(10) + 48);
sb.append(c);
}
return sb.toString();
}
final String sign = DigestUtils.sha1Hex(sb.toString());
最后每次重放就替换掉参数即可。
final IParameter newTimestampParam = helpers.buildParameter("timestamp", now + "", IParameter.PARAM_URL);
final IParameter newSignParam = helpers.buildParameter("sign", sign, IParameter.PARAM_URL);
final IParameter newNoncestrParam = helpers.buildParameter("noncestr", noncestr, IParameter.PARAM_URL);
request = helpers.updateParameter(request, newTimestampParam);
request = helpers.updateParameter(request, newSignParam);
request = helpers.updateParameter(request, newNoncestrParam);
messageInfo.setRequest(request);
stdout.println("更新了sign: " + sign);
stdout.println("更新了timestamp: " + now);
stdout.println("更新了noncestr: " + noncestr);
stdout.println("本次处理完毕");
所有的关键操作就这样完成了。
总结
最终BurpExtender类代码
public class BurpExtender implements IBurpExtender, IHttpListener {
private IBurpExtenderCallbacks callbacks;
private IExtensionHelpers helpers;
private PrintWriter stdout;
private ConcurrentHashMap<String, String> signMap;
public BurpExtender() {
this.signMap = new ConcurrentHashMap<>();
signMap.put("class.mosaic.cn", "ac6190d7dfaa77df726f0a82244d3eda68675ccd4e95de802f5042e91d15edc7bae3026d8f0fb2a8287446bb289563970264");
signMap.put("bbsapi.mosaic.cn", "Wj8BI3VUZ6BuojAkqzBM3HWHNHv08xdZEtaksbRg6snnuLsvivwa8IvR6PvQ76H0IQQsqkIsa5OKJtg6QcBMfCblMMywgZaA8co");
}
@Override
public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
this.callbacks = callbacks;
callbacks.setExtensionName("xx客户端sign重放辅助渗透");
helpers = callbacks.getHelpers();
stdout = new PrintWriter(callbacks.getStdout(), true);
callbacks.registerHttpListener(this);
}
@Override
public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {
// 只处理repeater发出的消息
if (toolFlag != burp.IBurpExtenderCallbacks.TOOL_REPEATER) {
return;
}
// 只处理请求,响应则忽略
if (!messageIsRequest) {
return;
}
byte[] request = messageInfo.getRequest();
if (request == null || request.length == 0) {
return;
}
// 只有特定的两个域名才会生效
final IHttpService httpService = messageInfo.getHttpService();
final String host = httpService.getHost();
if (!signMap.containsKey(host)) {
return;
}
stdout.println("检测到,开始处理...");
final IRequestInfo requestInfo = helpers.analyzeRequest(request);
TreeMap<String, String> signParamsMap = new TreeMap<>();
requestInfo.getParameters().forEach(x -> {
if (IParameter.PARAM_URL != x.getType()) {
return;
}
if ("timestamp".equals(x.getName()) || "sign".equals(x.getName()) || "noncestr".equals(x.getName())) {
return;
}
signParamsMap.put(URLDecoder.decode(x.getName(), UTF_8), URLDecoder.decode(x.getValue(), UTF_8));
});
String appSignKey = signMap.get(host);
signParamsMap.put("appSignKey", appSignKey);
long now = System.currentTimeMillis();
// long now = 1626237221383L;
signParamsMap.put("timestamp", now + "");
String noncestr = randomNoncestr();
// String noncestr = "73699378";
signParamsMap.put("noncestr", noncestr);
StringBuilder sb = new StringBuilder();
signParamsMap.forEach((key, value) -> {
sb.append(key).append("=").append(value).append("&");
});
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
stdout.println("用来SHA1加密的字符串: " + sb.toString());
final String sign = DigestUtils.sha1Hex(sb.toString());
// 替换掉参数
final IParameter newTimestampParam = helpers.buildParameter("timestamp", now + "", IParameter.PARAM_URL);
final IParameter newSignParam = helpers.buildParameter("sign", sign, IParameter.PARAM_URL);
final IParameter newNoncestrParam = helpers.buildParameter("noncestr", noncestr, IParameter.PARAM_URL);
request = helpers.updateParameter(request, newTimestampParam);
request = helpers.updateParameter(request, newSignParam);
request = helpers.updateParameter(request, newNoncestrParam);
messageInfo.setRequest(request);
stdout.println("更新了sign: " + sign);
stdout.println("更新了timestamp: " + now);
stdout.println("更新了noncestr: " + noncestr);
stdout.println("本次处理完毕");
}
private static String randomNoncestr() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 8; i++) {
char c = (char) (ThreadLocalRandom.current().nextInt(10) + 48);
sb.append(c);
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println(randomNoncestr());
}
}