diff --git a/src/main/java/com/nccgroup/loggerplusplus/LoggerPlusPlus.java b/src/main/java/com/nccgroup/loggerplusplus/LoggerPlusPlus.java index cc16301..2633805 100644 --- a/src/main/java/com/nccgroup/loggerplusplus/LoggerPlusPlus.java +++ b/src/main/java/com/nccgroup/loggerplusplus/LoggerPlusPlus.java @@ -14,6 +14,7 @@ import com.nccgroup.loggerplusplus.logview.processor.LogProcessor; import com.nccgroup.loggerplusplus.preferences.PreferencesController; import com.nccgroup.loggerplusplus.reflection.ReflectionController; +import com.nccgroup.loggerplusplus.util.DateFormattedGsonProvider; import com.nccgroup.loggerplusplus.util.Globals; import com.nccgroup.loggerplusplus.util.userinterface.LoggerMenu; import lombok.Getter; @@ -39,7 +40,7 @@ public class LoggerPlusPlus implements BurpExtension { public static LoggingController loggingController; public static LoggerPlusPlus instance; public static MontoyaApi montoya; - public static IGsonProvider gsonProvider = new DefaultGsonProvider(); + public static IGsonProvider gsonProvider = new DateFormattedGsonProvider(); private Registration menuBarRegistration; private LogProcessor logProcessor; diff --git a/src/main/java/com/nccgroup/loggerplusplus/imports/LoggerImport.java b/src/main/java/com/nccgroup/loggerplusplus/imports/LoggerImport.java index 1792fb1..8c9e184 100644 --- a/src/main/java/com/nccgroup/loggerplusplus/imports/LoggerImport.java +++ b/src/main/java/com/nccgroup/loggerplusplus/imports/LoggerImport.java @@ -24,9 +24,9 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.nccgroup.loggerplusplus.LoggerPlusPlus; +import com.nccgroup.loggerplusplus.logentry.ImportingLogEntryHttpRequestResponse; import com.nccgroup.loggerplusplus.logview.processor.EntryImportWorker; import lombok.extern.log4j.Log4j2; -import org.apache.logging.log4j.util.Base64Util; import com.google.gson.Gson; import com.google.gson.JsonElement; @@ -43,6 +43,8 @@ @Log4j2 public class LoggerImport { + private static final String COMMENT_IMPORTED_MARKER = "[Imported from JSON]"; + public static String getLoadFile() { JFileChooser chooser = null; chooser = new JFileChooser(); @@ -79,9 +81,9 @@ public static ArrayList readFile(String filename) { return lines; } - public static ArrayList importWStalker() { + public static ArrayList importWStalker() { ArrayList lines; - ArrayList requests = new ArrayList<>(); + ArrayList requests = new ArrayList<>(); String filename = getLoadFile(); if ( filename.length() == 0 ) { // exit if no file selected @@ -101,9 +103,10 @@ public static ArrayList importWStalker() { HttpService httpService = HttpService.httpService(url); HttpRequest httpRequest = HttpRequest.httpRequest(httpService, b64Decoder.decode(v[0], Base64DecodingOptions.URL)); HttpResponse httpResponse = HttpResponse.httpResponse(b64Decoder.decode(v[1], Base64DecodingOptions.URL)); - HttpRequestResponse requestResponse = HttpRequestResponse.httpRequestResponse(httpRequest, httpResponse); - requests.add(requestResponse); + requests.add(new ImportingLogEntryHttpRequestResponse( + HttpRequestResponse.httpRequestResponse(httpRequest, httpResponse) + )); } catch (Exception e) { log.error("LoggerImport-importWStalker: Error Parsing Content"); @@ -114,13 +117,13 @@ public static ArrayList importWStalker() { return requests; } - public static ArrayList importZAP() { + public static ArrayList importZAP() { ArrayList lines = new ArrayList(); - ArrayList requests = new ArrayList(); + ArrayList requests = new ArrayList(); String filename = getLoadFile(); if ( filename.length() == 0 ) { // exit if no file selected - return new ArrayList(); + return new ArrayList(); } lines = readFile(filename); @@ -155,9 +158,10 @@ public static ArrayList importZAP() { HttpService httpService = HttpService.httpService(url); HttpRequest httpRequest = HttpRequest.httpRequest(httpService, requestBuffer); HttpResponse httpResponse = HttpResponse.httpResponse(responseBuffer); - HttpRequestResponse requestResponse = HttpRequestResponse.httpRequestResponse(httpRequest, httpResponse); - requests.add(requestResponse); + requests.add(new ImportingLogEntryHttpRequestResponse( + HttpRequestResponse.httpRequestResponse(httpRequest, httpResponse) + )); // Reset content isRequest = true; @@ -203,8 +207,8 @@ public static ArrayList importZAP() { return requests; } - public static ArrayList importFromExportedJson() { - ArrayList requests = new ArrayList<>(); + public static ArrayList importFromExportedJson() { + ArrayList requests = new ArrayList<>(); String filename = getLoadFile(); if ( filename.length() == 0 ) { // exit if no file selected @@ -223,17 +227,19 @@ public static ArrayList importFromExportedJson() { Gson gson = LoggerPlusPlus.gsonProvider.getGson(); JsonArray arr = gson.fromJson(reader, JsonElement.class).getAsJsonArray(); Base64Utils b64Decoder = LoggerPlusPlus.montoya.utilities().base64Utils(); - JsonObject obj, req, res; + JsonObject obj, req, res, jsonEntry; HttpService httpService; HttpRequest httpRequest; HttpResponse httpResponse; - HttpRequestResponse requestResponse; + HttpRequestResponse requestResponse = null; String url; String[] v = new String[2]; + ImportingLogEntryHttpRequestResponse logEntry; - Iterator i = arr.iterator(); - while (i.hasNext()) { - obj = i.next().getAsJsonObject(); + Iterator iter = arr.iterator(); + StringBuilder comment = new StringBuilder(); + while (iter.hasNext()) { + obj = iter.next().getAsJsonObject(); req = obj.getAsJsonObject("Request"); res = obj.getAsJsonObject("Response"); @@ -246,19 +252,53 @@ public static ArrayList importFromExportedJson() { httpRequest = HttpRequest.httpRequest(httpService, b64Decoder.decode(v[0])); httpResponse = HttpResponse.httpResponse(b64Decoder.decode(v[1])); requestResponse = HttpRequestResponse.httpRequestResponse(httpRequest, httpResponse); - - requests.add(requestResponse); } catch (Exception e) { log.error("LoggerImport-importFromExportedJson: Error Parsing Content", e); + } + + logEntry = new ImportingLogEntryHttpRequestResponse(requestResponse); + logEntry.setRequestTime(req.get("Time").getAsString()); + logEntry.setResponseTime(res.get("Time").getAsString()); + + // might not exist + if (req.has("Tool")) { + logEntry.setTool(req.get("Tool").getAsString()); + } + + if (res.has("RTT")) { + logEntry.setRTT(res.get("RTT").getAsInt()); + } + + jsonEntry = obj.getAsJsonObject("Entry"); + if (jsonEntry.has("ListenInterface")) { + logEntry.setListenInterface(jsonEntry.get("ListenInterface").getAsString()); + } + + comment.setLength(0); // empty the string + if (req.has("Comment")) { + comment.append(req.get("Comment").getAsString()); + // prevent duplicated 'imported' marker + if (comment.indexOf(COMMENT_IMPORTED_MARKER) == -1) + { + comment.insert(0, " "); + comment.insert(0, COMMENT_IMPORTED_MARKER); + } + } + else { + comment.insert(0, COMMENT_IMPORTED_MARKER); } + + logEntry.setComment(comment.toString()); + + requests.add(logEntry); } return requests; } //TODO Integrate progress bar with SwingWorkerWithProgressDialog - public static boolean loadImported(ArrayList requests, Boolean sendToAutoExporters) { + public static boolean loadImported(ArrayList requests, Boolean sendToAutoExporters) { EntryImportWorker importWorker = LoggerPlusPlus.instance.getLogProcessor().createEntryImportBuilder() .setOriginatingTool(ToolType.EXTENSIONS) .setHttpEntries(requests) diff --git a/src/main/java/com/nccgroup/loggerplusplus/logentry/ImportingLogEntryHttpRequestResponse.java b/src/main/java/com/nccgroup/loggerplusplus/logentry/ImportingLogEntryHttpRequestResponse.java new file mode 100644 index 0000000..153c26c --- /dev/null +++ b/src/main/java/com/nccgroup/loggerplusplus/logentry/ImportingLogEntryHttpRequestResponse.java @@ -0,0 +1,86 @@ +package com.nccgroup.loggerplusplus.logentry; + +import burp.api.montoya.core.ToolType; +import burp.api.montoya.http.message.HttpRequestResponse; +import burp.api.montoya.http.message.requests.HttpRequest; +import burp.api.montoya.http.message.responses.HttpResponse; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.log4j.Log4j2; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +@Getter +@Log4j2 +public class ImportingLogEntryHttpRequestResponse { + // example: Nov 15, 2023, 6:30:46 PM + // ...btw wt is this date time format, it is not common at all! + // ...ffs, there is also a '0x202f' (Narrow no-break space) char in the date sometime.. + private static final SimpleDateFormat dtFormatter = new SimpleDateFormat("MMM d, y, K:m:s a"); + private static final SimpleDateFormat longDtFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + private final HttpRequestResponse httpReqRes; + + private Date requestTime = null; + + private Date responseTime = null; + + @Setter + private Integer RTT = null; + + @Setter + private String comment = null; + + private ToolType tool = null; + + @Setter + private String listenInterface = null; + + public ImportingLogEntryHttpRequestResponse(HttpRequestResponse hrr) { + this.httpReqRes = hrr; + } + + public HttpRequest request() { + return httpReqRes.request(); + } + + public HttpResponse response() { + return httpReqRes.response(); + } + + private Date formattedTimeParser(String dateTimeString) throws ParseException { + if (dateTimeString.indexOf(',') > -1) + { + return dtFormatter.parse(dateTimeString.replace('\u202F', ' ')); + } + return longDtFormatter.parse(dateTimeString); + } + + public void setRequestTime(String dateTimeString) { + try { + this.requestTime = formattedTimeParser(dateTimeString); + } catch (ParseException e) { + log.error("Failed to parse requestTime: " + dateTimeString); + throw new RuntimeException(e); + } + } + + public void setResponseTime(String dateTimeString) { + try { + this.responseTime = formattedTimeParser(dateTimeString); + } catch (ParseException e) { + log.error("Failed to parse responseTime: " + dateTimeString); + throw new RuntimeException(e); + } + } + + public void setTool(String toolName) { + try { + this.tool = ToolType.valueOf(toolName.toUpperCase()); + } catch (Exception e) { + log.error("Error at setTool: " + toolName); + } + } +} diff --git a/src/main/java/com/nccgroup/loggerplusplus/logentry/LogEntry.java b/src/main/java/com/nccgroup/loggerplusplus/logentry/LogEntry.java index 92a619a..75fee1e 100644 --- a/src/main/java/com/nccgroup/loggerplusplus/logentry/LogEntry.java +++ b/src/main/java/com/nccgroup/loggerplusplus/logentry/LogEntry.java @@ -132,7 +132,7 @@ public LogEntry(ToolType tool, HttpRequest request, HttpResponse response){ */ public LogEntry(ToolType tool, HttpRequest request, Date formattedRequestTime) { this(tool, request); - this.setReqestTime(formattedRequestTime); + this.setRequestTime(formattedRequestTime); } public boolean process() { @@ -388,7 +388,7 @@ public byte[] getResponseBytes() { return response.toByteArray().getBytes(); } - public void setReqestTime(Date requestTime) { + public void setRequestTime(Date requestTime) { this.requestDateTime = requestTime; this.formattedRequestTime = LogProcessor.LOGGER_DATE_FORMAT.format(this.requestDateTime); } diff --git a/src/main/java/com/nccgroup/loggerplusplus/logview/processor/EntryImportWorker.java b/src/main/java/com/nccgroup/loggerplusplus/logview/processor/EntryImportWorker.java index c1f29bc..6876706 100644 --- a/src/main/java/com/nccgroup/loggerplusplus/logview/processor/EntryImportWorker.java +++ b/src/main/java/com/nccgroup/loggerplusplus/logview/processor/EntryImportWorker.java @@ -1,11 +1,12 @@ package com.nccgroup.loggerplusplus.logview.processor; import burp.api.montoya.core.ToolType; -import burp.api.montoya.http.message.HttpRequestResponse; import burp.api.montoya.http.message.requests.HttpRequest; import burp.api.montoya.http.message.responses.HttpResponse; import burp.api.montoya.proxy.ProxyHttpRequestResponse; +import com.nccgroup.loggerplusplus.logentry.ImportingLogEntryHttpRequestResponse; import com.nccgroup.loggerplusplus.logentry.LogEntry; +import lombok.extern.log4j.Log4j2; import javax.swing.*; import java.util.ArrayList; @@ -14,12 +15,13 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.function.Consumer; +@Log4j2 public class EntryImportWorker extends SwingWorker { private final LogProcessor logProcessor; - private final ToolType originatingTool; + private ToolType originatingTool; private final List proxyEntries; - private final List httpEntries; + private final List httpEntries; private final Consumer> interimConsumer; private final Runnable callback; private final boolean sendToAutoExporters; @@ -40,26 +42,59 @@ protected Void doInBackground() throws Exception { boolean isProxyEntries = proxyEntries.size() > 0; int count = isProxyEntries ? proxyEntries.size() : httpEntries.size(); - CountDownLatch countDownLatch = new CountDownLatch(count); ThreadPoolExecutor entryImportExecutor = logProcessor.getEntryImportExecutor(); + ImportingLogEntryHttpRequestResponse entry = null; for (int index = 0; index < count; index++) { if(entryImportExecutor.isShutdown() || this.isCancelled()) return null; HttpRequest request; HttpResponse response; + if(isProxyEntries){ request = proxyEntries.get(index).finalRequest(); response = proxyEntries.get(index).originalResponse(); }else{ - request = httpEntries.get(index).request(); - response = httpEntries.get(index).response(); + entry = httpEntries.get(index); + request = entry.request(); + response = entry.response(); + + //TODO review: do we want to keep the original tool of the entry? + if (entry.getTool() != null) { + this.originatingTool = entry.getTool(); + } } final LogEntry logEntry = new LogEntry(originatingTool, request, response); + + if (!isProxyEntries) { + // add extra log entry data back to the entry + // might / not exist when not import from JSON + if (entry.getRequestTime() != null) { + logEntry.setRequestDateTime(entry.getRequestTime()); + } + if (entry.getResponseTime() != null) { + logEntry.setResponseDateTime(entry.getResponseTime()); + } + } + int finalIndex = index; + ImportingLogEntryHttpRequestResponse finalEntry = entry; entryImportExecutor.submit(() -> { if(this.isCancelled()) return; LogEntry result = logProcessor.processEntry(logEntry); if(result != null) { + if (!isProxyEntries) { + // must be called after processing: + if (finalEntry.getComment() != null) { + result.setComment(finalEntry.getComment()); + } + if (finalEntry.getListenInterface() != null) { + result.setListenerInterface(finalEntry.getListenInterface()); + } + if (finalEntry.getRTT() != null) { + result.setRequestResponseDelay(finalEntry.getRTT()); + } + } + logProcessor.addNewEntry(logEntry, sendToAutoExporters); } publish(finalIndex); @@ -88,7 +123,7 @@ public static class Builder { private final LogProcessor logProcessor; private ToolType originatingTool = ToolType.EXTENSIONS; private List proxyEntries = new ArrayList<>(); - private List httpEntries = new ArrayList<>(); + private List httpEntries = new ArrayList<>(); private Consumer> interimConsumer; private Runnable callback; private boolean sendToAutoExporters = false; @@ -108,7 +143,7 @@ public Builder setProxyEntries(List entries) { return this; } - public Builder setHttpEntries(List entries) { + public Builder setHttpEntries(List entries) { this.httpEntries.addAll(entries); this.proxyEntries.clear(); return this; diff --git a/src/main/java/com/nccgroup/loggerplusplus/preferences/PreferencesPanel.java b/src/main/java/com/nccgroup/loggerplusplus/preferences/PreferencesPanel.java index dfdf2dc..1250e8a 100644 --- a/src/main/java/com/nccgroup/loggerplusplus/preferences/PreferencesPanel.java +++ b/src/main/java/com/nccgroup/loggerplusplus/preferences/PreferencesPanel.java @@ -27,6 +27,7 @@ import com.nccgroup.loggerplusplus.filter.savedfilter.SavedFilter; import com.nccgroup.loggerplusplus.filter.tag.Tag; import com.nccgroup.loggerplusplus.imports.LoggerImport; +import com.nccgroup.loggerplusplus.logentry.ImportingLogEntryHttpRequestResponse; import com.nccgroup.loggerplusplus.logentry.LogEntryField; import com.nccgroup.loggerplusplus.logview.logtable.LogTableColumn; import com.nccgroup.loggerplusplus.logview.logtable.LogTableColumnModel; @@ -186,7 +187,7 @@ public void actionPerformed(ActionEvent e) { importGroup.add(new JButton(new AbstractAction("Import From WStalker CSV") { @Override public void actionPerformed(ActionEvent e) { - ArrayList requests = LoggerImport.importWStalker(); + ArrayList requests = LoggerImport.importWStalker(); if (LoggerPlusPlus.instance.getExportController().getEnabledExporters().size() > 0) { int res = JOptionPane.showConfirmDialog(LoggerPlusPlus.instance.getLoggerFrame(), "One or more auto-exporters are currently enabled. " + @@ -202,7 +203,7 @@ public void actionPerformed(ActionEvent e) { importGroup.add(new JButton(new AbstractAction("Import From OWASP ZAP") { @Override public void actionPerformed(ActionEvent e) { - ArrayList requests; + ArrayList requests; try{ requests = LoggerImport.importZAP(); } catch (Exception ex){ @@ -227,7 +228,7 @@ public void actionPerformed(ActionEvent e) { importGroup.add(new JButton(new AbstractAction("Import From Exported JSON") { @Override public void actionPerformed(ActionEvent e) { - ArrayList requests = LoggerImport.importFromExportedJson(); + ArrayList requests = LoggerImport.importFromExportedJson(); if (LoggerPlusPlus.instance.getExportController().getEnabledExporters().size() > 0) { int res = JOptionPane.showConfirmDialog(LoggerPlusPlus.instance.getLoggerFrame(), "One or more auto-exporters are currently enabled. " + diff --git a/src/main/java/com/nccgroup/loggerplusplus/util/Globals.java b/src/main/java/com/nccgroup/loggerplusplus/util/Globals.java index 7c37a0f..30d374f 100644 --- a/src/main/java/com/nccgroup/loggerplusplus/util/Globals.java +++ b/src/main/java/com/nccgroup/loggerplusplus/util/Globals.java @@ -8,7 +8,7 @@ public class Globals { public static final String APP_NAME = "Logger++"; - public static final String VERSION = "3.20.1"; + public static final String VERSION = "3.21.1"; public static final String AUTHOR = "Corey Arthur (@CoreyD97), Soroush Dalili (@irsdl) from NCC Group"; public static final String TWITTER_URL = "https://twitter.com/CoreyD97"; public static final String IRSDL_TWITTER_URL = "https://twitter.com/irsdl";