diff --git a/CHANGELOG.md b/CHANGELOG.md index 30a0b9b..5c9e35f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.3.2] - 2024-07-16 + +### Added + +* Add operation JSON Beautifier +* Add Sequencer filter option +* Add GZIP operation option to set the compression level +* Add comment function to operations and recipe lanes +* Add editable lane names + +### Changed + +* Fix the emergence of null bytes when using variables +* Fix GUI issues with using the operation Drag-and-Drop +* Change the saved recipe structure and add CSTC version, operation comments, lane comments and lane names +* Refactor operation button icons + + ## [1.3.1] - 2024-05-22 ### Added diff --git a/README.md b/README.md index 2f39285..9d7b663 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,15 @@ Take a look at our basic tutorial on [YouTube](https://www.youtube.com/watch?v=B **UPDATE:** Due to some incompatibility issues when installing *CSTC* via *BApp Store*, we had to switch to a new variable prefix. Variables from other *lanes* have now to be prefixed by ``$`` e.g. like ``$Outgoing_step1``. +## FAQ + +### How does the CSTC interact with other Extensions? + +Requests and responses pass through the extensions in the order that they are listed, from top to bottom (as described [here](https://portswigger.net/burp/documentation/desktop/extensions/managing-extensions)). +Depending on the extensions in use, it may make sense to adjust the position of the CSTC. If you want to process a request manipulated by the CSTC in another extension, +the CSTC should be positioned above this extension. Conversely, the CSTC should be positioned below an extension if the CSTC is to work with the response processed by the extension in question. +Currently the Burp Montoya API doesn't offer a way to change this order automatically, therefore the CSTC cannot influence the interaction with other extensions itself. + ## Feedback We gladly appreciate all feedback, bug reports and feature requests. diff --git a/media/CSTC_Workflow.gif b/media/CSTC_Workflow.gif index 1112123..d918b9f 100644 Binary files a/media/CSTC_Workflow.gif and b/media/CSTC_Workflow.gif differ diff --git a/pom.xml b/pom.xml index 5d3e429..ed56f26 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 de.usd.CSTC CSTC - 1.3.1 + 1.3.2 CSTC CSTC @@ -35,13 +35,13 @@ com.fasterxml.jackson.core jackson-core - 2.17.1 + 2.17.2 com.fasterxml.jackson.core jackson-databind - 2.17.1 + 2.17.2 @@ -85,6 +85,7 @@ res + true @@ -102,7 +103,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.5 + 3.3.1 false true diff --git a/res/comment.png b/res/comment.png new file mode 100755 index 0000000..8880c9e Binary files /dev/null and b/res/comment.png differ diff --git a/res/disable.png b/res/disable.png old mode 100644 new mode 100755 index 60149af..0e80c76 Binary files a/res/disable.png and b/res/disable.png differ diff --git a/res/help.png b/res/help.png old mode 100644 new mode 100755 index e66abc6..28c16d3 Binary files a/res/help.png and b/res/help.png differ diff --git a/res/no_comment.png b/res/no_comment.png new file mode 100755 index 0000000..7ba13c9 Binary files /dev/null and b/res/no_comment.png differ diff --git a/res/operation.png b/res/operation.png old mode 100644 new mode 100755 index c1103f7..84c2f02 Binary files a/res/operation.png and b/res/operation.png differ diff --git a/res/remove.png b/res/remove.png old mode 100644 new mode 100755 index 406976c..fc42e25 Binary files a/res/remove.png and b/res/remove.png differ diff --git a/res/stop.png b/res/stop.png old mode 100644 new mode 100755 index e450c91..29ea6d9 Binary files a/res/stop.png and b/res/stop.png differ diff --git a/res/stop_active.png b/res/stop_active.png old mode 100644 new mode 100755 index 9ca2fee..a94eb52 Binary files a/res/stop_active.png and b/res/stop_active.png differ diff --git a/res/version.properties b/res/version.properties new file mode 100644 index 0000000..713c915 --- /dev/null +++ b/res/version.properties @@ -0,0 +1 @@ +version = ${project.version} \ No newline at end of file diff --git a/src/main/java/burp/BurpExtender.java b/src/main/java/burp/BurpExtender.java index 86c306f..659e6df 100644 --- a/src/main/java/burp/BurpExtender.java +++ b/src/main/java/burp/BurpExtender.java @@ -26,7 +26,6 @@ public void initialize(MontoyaApi api) { api.http().registerHttpHandler(new CstcHttpHandler(view)); api.userInterface().registerSuiteTab(extensionName, view); api.userInterface().registerHttpRequestEditorProvider(new MyHttpRequestEditorProvider(view)); - api.userInterface().registerHttpResponseEditorProvider(new MyHttpResponseEditorProvider(view)); if (!api.burpSuite().version().edition().equals(BurpSuiteEdition.COMMUNITY_EDITION)) { PersistedObject persistence = api.persistence().extensionData(); diff --git a/src/main/java/burp/CstcHttpHandler.java b/src/main/java/burp/CstcHttpHandler.java index 3b45924..4ac4901 100644 --- a/src/main/java/burp/CstcHttpHandler.java +++ b/src/main/java/burp/CstcHttpHandler.java @@ -28,7 +28,6 @@ public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent reque if (BurpUtils.getInstance().getFilterState().shouldProcess(FilterState.BurpOperation.OUTGOING, requestToBeSent.toolSource())) { ByteArray request = requestToBeSent.toByteArray(); ByteArray modifiedRequest = view.getOutgoingRecipePanel().bake(request, MessageType.REQUEST); - Logger.getInstance().log("modified request: \n" + new String(modifiedRequest.getBytes())); return continueWith(HttpRequest.httpRequest(modifiedRequest).withService(requestToBeSent.httpService())); } else{ @@ -41,7 +40,6 @@ public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived re if (BurpUtils.getInstance().getFilterState().shouldProcess(FilterState.BurpOperation.INCOMING, responseReceived.toolSource())) { ByteArray response = responseReceived.toByteArray(); ByteArray modifiedResponse = view.getIncomingRecipePanel().bake(response, MessageType.RESPONSE); - Logger.getInstance().log("modified response: \n" + new String(modifiedResponse.getBytes())); return continueWith(HttpResponse.httpResponse(modifiedResponse)); } else{ diff --git a/src/main/java/burp/MyExtensionProvidedHttpRequestEditor.java b/src/main/java/burp/MyExtensionProvidedHttpRequestEditor.java index 94191e2..76ba9de 100644 --- a/src/main/java/burp/MyExtensionProvidedHttpRequestEditor.java +++ b/src/main/java/burp/MyExtensionProvidedHttpRequestEditor.java @@ -2,8 +2,10 @@ import burp.api.montoya.MontoyaApi; import burp.api.montoya.core.ByteArray; +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.repeater.Repeater; import burp.api.montoya.ui.Selection; import burp.api.montoya.ui.editor.EditorOptions; import burp.api.montoya.ui.editor.RawEditor; @@ -23,9 +25,16 @@ public class MyExtensionProvidedHttpRequestEditor implements ExtensionProvidedHt MyExtensionProvidedHttpRequestEditor(EditorCreationContext creationContext, View view) { - this.api = BurpUtils.getInstance().getApi(); - this.view = view; - requestEditor = api.userInterface().createRawEditor(EditorOptions.READ_ONLY); + if(creationContext.toolSource().isFromTool(ToolType.REPEATER)) { + this.api = BurpUtils.getInstance().getApi(); + this.view = view; + requestEditor = api.userInterface().createRawEditor(EditorOptions.READ_ONLY); + } + else { + this.api = null; + this.view = null; + this.requestEditor = null; + } } @Override diff --git a/src/main/java/burp/MyExtensionProvidedHttpResponseEditor.java b/src/main/java/burp/MyExtensionProvidedHttpResponseEditor.java deleted file mode 100644 index 2ccb508..0000000 --- a/src/main/java/burp/MyExtensionProvidedHttpResponseEditor.java +++ /dev/null @@ -1,73 +0,0 @@ -package burp; - -import burp.api.montoya.MontoyaApi; -import burp.api.montoya.core.ByteArray; -import burp.api.montoya.http.message.HttpRequestResponse; -import burp.api.montoya.http.message.responses.HttpResponse; -import burp.api.montoya.ui.Selection; -import burp.api.montoya.ui.editor.EditorOptions; -import burp.api.montoya.ui.editor.RawEditor; -import burp.api.montoya.ui.editor.extension.EditorCreationContext; -import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpResponseEditor; -import de.usd.cstchef.Utils.MessageType; -import de.usd.cstchef.view.View; - -import java.awt.*; - -public class MyExtensionProvidedHttpResponseEditor implements ExtensionProvidedHttpResponseEditor -{ - private final RawEditor requestEditor; - private HttpRequestResponse requestResponse; - private final MontoyaApi api; - private final View view; - - MyExtensionProvidedHttpResponseEditor(EditorCreationContext creationContext, View view) - { - this.api = BurpUtils.getInstance().getApi(); - this.view = view; - requestEditor = api.userInterface().createRawEditor(EditorOptions.READ_ONLY); - } - - @Override - public HttpResponse getResponse() - { - return requestResponse.response(); - } - - @Override - public void setRequestResponse(HttpRequestResponse requestResponse) - { - ByteArray result = view.getIncomingRecipePanel().bake(requestResponse.response().toByteArray(), MessageType.RESPONSE); - this.requestEditor.setContents(result); - } - - @Override - public boolean isEnabledFor(HttpRequestResponse requestResponse) - { - return requestResponse.response() != null; - } - - @Override - public String caption() - { - return "CSTC"; - } - - @Override - public Component uiComponent() - { - return requestEditor.uiComponent(); - } - - @Override - public Selection selectedData() - { - return requestEditor.selection().isPresent() ? requestEditor.selection().get() : null; - } - - @Override - public boolean isModified() - { - return requestEditor.isModified(); - } -} \ No newline at end of file diff --git a/src/main/java/burp/MyHttpResponseEditorProvider.java b/src/main/java/burp/MyHttpResponseEditorProvider.java deleted file mode 100644 index cc8a9c7..0000000 --- a/src/main/java/burp/MyHttpResponseEditorProvider.java +++ /dev/null @@ -1,23 +0,0 @@ -package burp; - -import burp.api.montoya.MontoyaApi; -import burp.api.montoya.ui.editor.extension.EditorCreationContext; -import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpRequestEditor; -import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpResponseEditor; -import burp.api.montoya.ui.editor.extension.HttpResponseEditorProvider; -import de.usd.cstchef.view.View; - -class MyHttpResponseEditorProvider implements HttpResponseEditorProvider -{ - private final View view; - - MyHttpResponseEditorProvider(View view){ - this.view = view; - } - - - @Override - public ExtensionProvidedHttpResponseEditor provideHttpResponseEditor(EditorCreationContext creationContext) { - return new MyExtensionProvidedHttpResponseEditor(creationContext, view); - } -} \ No newline at end of file diff --git a/src/main/java/de/usd/cstchef/Utils.java b/src/main/java/de/usd/cstchef/Utils.java index 1e99477..b6db945 100644 --- a/src/main/java/de/usd/cstchef/Utils.java +++ b/src/main/java/de/usd/cstchef/Utils.java @@ -59,6 +59,7 @@ import de.usd.cstchef.operations.conditional.StringContains; import de.usd.cstchef.operations.conditional.StringMatch; import de.usd.cstchef.operations.dataformat.FromBase64; +import de.usd.cstchef.operations.dataformat.JsonBeautifier; import de.usd.cstchef.operations.dataformat.FromHex; import de.usd.cstchef.operations.dataformat.HtmlDecode; import de.usd.cstchef.operations.dataformat.HtmlEncode; @@ -217,8 +218,11 @@ public static HttpRequest addCookieToHttpRequest(HttpRequest request, Cookie coo } public static ByteArray insertAtOffset(ByteArray input, int start, int end, ByteArray newValue) { - ByteArray prefix = BurpUtils.subArray(input, 0, start); - ByteArray rest = BurpUtils.subArray(input, end, input.length()); + ByteArray prefix = input.subArray(0, start); + ByteArray rest = input.subArray(0, 0); + if(end < input.length()) { + rest = input.subArray(end, input.length()); + } ByteArray output = prefix.withAppended(newValue).withAppended(rest); return output; @@ -318,7 +322,7 @@ public static Class[] getOperationsDev() { TimestampOffset.class, TimestampToDateTime.class, ToBase64.class, ToHex.class, UnixTimestamp.class, UrlDecode.class, UrlEncode.class, Whirlpool.class, WriteFile.class, XmlFullSignature.class, XmlMultiSignature.class, - Xor.class, SoapMultiSignature.class, Luhn.class, Concatenate.class + Xor.class, SoapMultiSignature.class, Luhn.class, Concatenate.class, JsonBeautifier.class }; } diff --git a/src/main/java/de/usd/cstchef/operations/Operation.java b/src/main/java/de/usd/cstchef/operations/Operation.java index fceb094..efd807b 100644 --- a/src/main/java/de/usd/cstchef/operations/Operation.java +++ b/src/main/java/de/usd/cstchef/operations/Operation.java @@ -9,6 +9,8 @@ import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; import java.io.EOFException; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -19,6 +21,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; @@ -28,6 +31,7 @@ import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JLabel; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.JSpinner; @@ -68,6 +72,8 @@ public abstract class Operation extends JPanel { private static ImageIcon disableIcon = new ImageIcon(Operation.class.getResource("/disable.png")); private static ImageIcon removeIcon = new ImageIcon(Operation.class.getResource("/remove.png")); private static ImageIcon helpIcon = new ImageIcon(Operation.class.getResource("/help.png")); + private static ImageIcon commentIcon = new ImageIcon(Operation.class.getResource("/comment.png")); + private static ImageIcon noCommentIcon = new ImageIcon(Operation.class.getResource("/no_comment.png")); private NotifyChangeListener notifyChangeListener; @@ -80,6 +86,9 @@ public abstract class Operation extends JPanel { private Box contentBox; private Map uiElements; + private String comment; + private JButton commentBtn; + private int operationSkip = 0; private int laneSkip = 0; @@ -122,6 +131,19 @@ public Operation() { removeBtn.setToolTipText("Remove"); JButton helpBtn = createIconButton(Operation.helpIcon); helpBtn.setToolTipText(opInfos.description()); + commentBtn = createIconButton(noCommentIcon); + + commentBtn.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + commentBtn.setToolTipText(getComment()); + String comment = JOptionPane.showInputDialog("Edit comment:", commentBtn.getToolTipText()); + commentBtn.setToolTipText(comment); + setComment(comment); + ImageIcon newIcon = comment.isEmpty() ? Operation.noCommentIcon : Operation.commentIcon; + commentBtn.setIcon(newIcon); + } + }); + disableBtn.addActionListener(new ActionListener() { @Override @@ -162,6 +184,8 @@ public void actionPerformed(ActionEvent e) { header.add(titleLbl); header.add(Box.createHorizontalStrut(6)); header.add(helpBtn); + header.add(Box.createHorizontalStrut(3)); + header.add(commentBtn); header.add(Box.createHorizontalGlue()); header.add(disableBtn); header.add(Box.createHorizontalStrut(3)); @@ -189,6 +213,18 @@ public void actionPerformed(ActionEvent e) { this.refreshColors(); } + public String getComment() { + return this.comment; + } + + public void setComment(String comment) { + if(comment != null) { + this.comment = comment; + commentBtn.setIcon(Operation.commentIcon); + commentBtn.setToolTipText(comment); + } + } + public Map getState() { Map properties = new HashMap<>(); for (String key : this.uiElements.keySet()) { diff --git a/src/main/java/de/usd/cstchef/operations/compression/Gzip.java b/src/main/java/de/usd/cstchef/operations/compression/Gzip.java index 649b4a6..b6f55b3 100644 --- a/src/main/java/de/usd/cstchef/operations/compression/Gzip.java +++ b/src/main/java/de/usd/cstchef/operations/compression/Gzip.java @@ -2,6 +2,8 @@ import java.util.zip.GZIPOutputStream; +import javax.swing.JComboBox; + import burp.api.montoya.core.ByteArray; import java.io.ByteArrayInputStream; @@ -11,14 +13,19 @@ import de.usd.cstchef.operations.Operation; import de.usd.cstchef.operations.OperationCategory; import de.usd.cstchef.operations.Operation.OperationInfos; +import de.usd.cstchef.wrapper.GZIPOutputStreamWrapper; @OperationInfos(name = "GZIP", category = OperationCategory.COMPRESSION, description = "Compresses the input using GZIP.") public class Gzip extends Operation { + private JComboBox compressionLevelBox; + @Override protected ByteArray perform(ByteArray input, MessageType messageType) throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); - GZIPOutputStream gzos = new GZIPOutputStream(out); + //GZIPOutputStream gzos = new GZIPOutputStream(out); + GZIPOutputStreamWrapper gzos = new GZIPOutputStreamWrapper(out); + gzos.setCompressionLevel((int)compressionLevelBox.getSelectedItem()); ByteArrayInputStream in = new ByteArrayInputStream(input.getBytes()); byte[] buffer = new byte[1024]; @@ -33,4 +40,13 @@ protected ByteArray perform(ByteArray input, MessageType messageType) throws Exc return factory.createByteArray(out.toByteArray()); } + @Override + public void createUI() + { + Integer[] compressionLevel = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + this.compressionLevelBox = new JComboBox<>(compressionLevel); + this.compressionLevelBox.setSelectedIndex(5); + this.addUIElement("Compression Level", this.compressionLevelBox); + } + } diff --git a/src/main/java/de/usd/cstchef/operations/dataformat/JsonBeautifier.java b/src/main/java/de/usd/cstchef/operations/dataformat/JsonBeautifier.java new file mode 100644 index 0000000..0b44a30 --- /dev/null +++ b/src/main/java/de/usd/cstchef/operations/dataformat/JsonBeautifier.java @@ -0,0 +1,24 @@ +package de.usd.cstchef.operations.dataformat; + +import burp.api.montoya.core.ByteArray; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import de.usd.cstchef.Utils; +import de.usd.cstchef.operations.Operation; +import de.usd.cstchef.operations.OperationCategory; + +@Operation.OperationInfos(name = "JSON Beautifier", category = OperationCategory.DATAFORMAT, description = "Format JSON Data.") +public class JsonBeautifier extends Operation { + @Override + protected ByteArray perform(ByteArray input, Utils.MessageType messageType) throws Exception { + try { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); + JsonNode jsonNode = objectMapper.readTree(input.toString()); + return factory.createByteArray(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode)); + } catch (Exception e2) { + return input; + } + } +} diff --git a/src/main/java/de/usd/cstchef/operations/setter/HttpHeaderSetter.java b/src/main/java/de/usd/cstchef/operations/setter/HttpHeaderSetter.java index 95f8599..b08524c 100644 --- a/src/main/java/de/usd/cstchef/operations/setter/HttpHeaderSetter.java +++ b/src/main/java/de/usd/cstchef/operations/setter/HttpHeaderSetter.java @@ -40,11 +40,16 @@ protected ByteArray perform(ByteArray input, MessageType messageType) throws Exc } else if(messageType == MessageType.RESPONSE){ HttpResponse response = HttpResponse.httpResponse(input); - if(response.hasHeader(headerName) || addIfNotPresent.isSelected()){ - return response.withAddedHeader(HttpHeader.httpHeader(headerName, newValue)).toByteArray(); + if(response.hasHeader(headerName)) { + return response.withUpdatedHeader(HttpHeader.httpHeader(headerName, newValue)).toByteArray(); } - else{ - return input; + else { + if(addIfNotPresent.isSelected()) { + return response.withAddedHeader(HttpHeader.httpHeader(headerName, newValue)).toByteArray(); + } + else { + return input; + } } } else{ diff --git a/src/main/java/de/usd/cstchef/operations/utils/RandomNumber.java b/src/main/java/de/usd/cstchef/operations/utils/RandomNumber.java index 48b3a32..d8328c7 100644 --- a/src/main/java/de/usd/cstchef/operations/utils/RandomNumber.java +++ b/src/main/java/de/usd/cstchef/operations/utils/RandomNumber.java @@ -89,10 +89,6 @@ public void createUI() { this.textFieldMaximum.setText("9999"); this.addUIElement("Maximum Number", this.textFieldMaximum); - // use a separator - JSeparator separator = new JSeparator(SwingConstants.HORIZONTAL); - this.addUIElement(null, separator); - // fields for formatting: Integer digits this.textFieldFormatMinIntDigits = new JTextField(); this.textFieldFormatMinIntDigits.setText("1"); diff --git a/src/main/java/de/usd/cstchef/view/RecipePanel.java b/src/main/java/de/usd/cstchef/view/RecipePanel.java index 8f4cd84..efeb97e 100644 --- a/src/main/java/de/usd/cstchef/view/RecipePanel.java +++ b/src/main/java/de/usd/cstchef/view/RecipePanel.java @@ -12,11 +12,13 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.Timer; import java.util.TimerTask; @@ -350,10 +352,23 @@ public void setFormatMessage(HttpRequestResponse requestResponse, MessageType me } public void restoreState(String jsonState) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { - // TODO do we want to remove all existing operations before loading here? - this.clear(); // Yes! + this.clear(); ObjectMapper mapper = new ObjectMapper(); - JsonNode stepNodes = mapper.readTree(jsonState); + JsonNode rootNode = mapper.readTree(jsonState); + JsonNode stepNodes; + JsonNode versionNode; + + // check if "version" ObjectNode is there (since 1.3.2) + if(rootNode.get(0) != null && rootNode.get(0).get("version") == null) { + // recipes saved by CSTC <= 1.3.1 + stepNodes = rootNode; + } + else { + // currently 1.3.2 + versionNode = rootNode.get(0); + stepNodes = rootNode.get(1); + } + if (!stepNodes.isArray()) { throw new IOException("wrong data format"); } @@ -364,29 +379,81 @@ public void restoreState(String jsonState) throws IOException, ClassNotFoundExce throw new IOException("wrong data format"); } - for (int i = 0; i < operationNodes.size(); i++) { + RecipeStepPanel panel = (RecipeStepPanel) this.operationLines.getComponent(step); + + /* two types of ObjectNodes for every RecipeStepPanel: + Lane information (always at index 0, if set) and the Operations + + If there's a lane ObjectNode we need to tell the inner loop to begin at index 1. + The inner loop iterates over the Operations + */ + int index = 0; + if(operationNodes.get(0) != null) { + if(operationNodes.get(0).get("lane_title") != null) { + index = 1; + panel.setTitle(operationNodes.get(0).get("lane_title").asText()); + } + if(operationNodes.get(0).get("lane_comment") != null) { + index = 1; + panel.setComment(operationNodes.get(0).get("lane_comment").asText()); + } + } + + for (int i = index; i < operationNodes.size(); i++) { JsonNode operationNode = operationNodes.get(i); String operation = operationNode.get("operation").asText(); Map parameters = mapper.convertValue(operationNode.get("parameters"), Map.class); Class cls = (Class) Class.forName(operation); + // check if it is an operation Operation op = cls.newInstance(); op.load(parameters); op.setDisabled(!operationNode.get("is_enabled").asBoolean()); - RecipeStepPanel panel = (RecipeStepPanel) this.operationLines.getComponent(step); - panel.addComponent(op, i); + + // check if "comment" attribute is set (since 1.3.2) + if(operationNode.get("comment") != null) { + if(operationNode.get("comment").asText() != "null") { + op.setComment(operationNode.get("comment").asText()); + } + } + // depending on if lane name is set we may start the loop at index 1, but want to add the first component at index 0 + panel.addComponent(op, index == 1 ? i-1 : i); } } } private String getStateAsJSON() throws IOException { ObjectMapper mapper = new ObjectMapper(); + ArrayNode rootNode = mapper.createArrayNode(); + ObjectNode versionNode = mapper.createObjectNode(); ArrayNode stepsNode = mapper.createArrayNode(); for (int step = 0; step < this.operationSteps; step++) { ArrayNode operationsNode = mapper.createArrayNode(); RecipeStepPanel stepPanel = (RecipeStepPanel) this.operationLines.getComponent(step); + + // save lane name in case it differs from the default + int laneNumber = step + 1; + boolean laneNodeCreated = false; + if(!stepPanel.getTitle().equals("Lane " + laneNumber)) { + laneNodeCreated = true; + ObjectNode laneNode = mapper.createObjectNode(); + laneNode.put("lane_title", stepPanel.getTitle()); + // save comment in same node in case it is set + if(stepPanel.getComment() != null && !stepPanel.getComment().equals("")) { + laneNode.put("lane_comment", stepPanel.getComment()); + } + operationsNode.add(laneNode); + } + + // save comment in case it's not already + if(!laneNodeCreated && stepPanel.getComment() != null && !stepPanel.getComment().equals("")) { + ObjectNode laneNode = mapper.createObjectNode(); + laneNode.put("lane_comment", stepPanel.getComment()); + operationsNode.add(laneNode); + } + List operations = stepPanel.getOperations(); for (Operation op : operations) { ObjectNode operationNode = mapper.createObjectNode(); @@ -394,10 +461,25 @@ private String getStateAsJSON() throws IOException { operationsNode.add(operationNode); operationNode.putPOJO("parameters", op.getState()); operationNode.putPOJO("is_enabled", !op.isDisabled()); + // "comment":null if empty + operationNode.put("comment", op.getComment()); } stepsNode.add(operationsNode); } - return mapper.writeValueAsString(stepsNode); + + /* maven performs a substitution at compile time in "/res/version.properties" + with the version from pom.xml and here it reads from this file + */ + Properties properties = new Properties(); + properties.load(RecipePanel.class.getResourceAsStream("/version.properties")); + String version = properties.getProperty("version"); + + versionNode.put("version", version); + + rootNode.add(versionNode); + rootNode.add(stepsNode); + + return mapper.writeValueAsString(rootNode); } private void save(File file) throws IOException { @@ -587,6 +669,9 @@ private void saveFilterState() { private void clear() { for (int step = 0; step < this.operationSteps; step++) { RecipeStepPanel stepPanel = (RecipeStepPanel) this.operationLines.getComponent(step); + int laneIndex = step + 1; + stepPanel.setTitle("Lane " + laneIndex); + stepPanel.clearComment(); stepPanel.clearOperations(); } } diff --git a/src/main/java/de/usd/cstchef/view/RecipeStepPanel.java b/src/main/java/de/usd/cstchef/view/RecipeStepPanel.java index 89b79da..f479220 100644 --- a/src/main/java/de/usd/cstchef/view/RecipeStepPanel.java +++ b/src/main/java/de/usd/cstchef/view/RecipeStepPanel.java @@ -3,14 +3,24 @@ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; +import java.awt.Cursor; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.List; import javax.swing.BorderFactory; import javax.swing.Box; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; @@ -27,6 +37,13 @@ public class RecipeStepPanel extends JPanel { private JPanel operationsLine; private GridBagConstraints addContraints; private ChangeListener changeListener; + private JTextField contentTextField; + + private String comment; + private JButton commentBtn; + + private static ImageIcon commentIcon = new ImageIcon(Operation.class.getResource("/comment.png")); + private static ImageIcon noCommentIcon = new ImageIcon(Operation.class.getResource("/no_comment.png")); public RecipeStepPanel(String title, ChangeListener changelistener) { this.changeListener = changelistener; @@ -41,12 +58,39 @@ public RecipeStepPanel(String title, ChangeListener changelistener) { CompoundBorder border = new CompoundBorder(lineBorder, margin); headerBox.setBorder(border); - JTextField contentTextField = new JTextField(); + contentTextField = new JTextField(); contentTextField.setBorder(null); contentTextField.setBackground(new Color(0, 0, 0, 0)); contentTextField.setText(title); + contentTextField.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + String newTitle = JOptionPane.showInputDialog("Edit title:", getTitle()); + contentTextField.setText(newTitle.length() <= 50 ? newTitle : getTitle()); + setTitle(newTitle.length() <= 50 ? newTitle : getTitle()); // lane name should be leq 50 chars + } + }); headerBox.add(contentTextField); + JPanel panel = new JPanel(); + panel.setBackground(new Color(0, 0, 0, 0)); // transparent + + commentBtn = createIconButton(noCommentIcon); + + commentBtn.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + commentBtn.setToolTipText(getComment()); + String comment = JOptionPane.showInputDialog("Edit comment:", commentBtn.getToolTipText()); + commentBtn.setToolTipText(comment); + setComment(comment); + ImageIcon newIcon = comment.isEmpty() ? RecipeStepPanel.noCommentIcon : RecipeStepPanel.commentIcon; + commentBtn.setIcon(newIcon); + } + }); + + panel.add(commentBtn); + headerBox.add(panel); + this.add(headerBox, BorderLayout.NORTH); // body @@ -124,4 +168,41 @@ public void clearOperations() { operationsLine.repaint(); this.changeListener.stateChanged(new ChangeEvent(this)); } + + public String getTitle() { + return contentTextField.getText(); + } + + public void setTitle(String title) { + contentTextField.setText(title); + } + + private JButton createIconButton(ImageIcon icon) { + JButton btn = new JButton(); + btn.setBorder(BorderFactory.createEmptyBorder()); + btn.setIcon(icon); + btn.setContentAreaFilled(false); + btn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + btn.setAlignmentX(Component.RIGHT_ALIGNMENT); + + return btn; + } + + public String getComment() { + return this.comment; + } + + public void setComment(String comment) { + if(comment != null) { + this.comment = comment; + commentBtn.setIcon(RecipeStepPanel.commentIcon); + commentBtn.setToolTipText(comment); + } + } + + public void clearComment() { + this.comment = ""; + commentBtn.setToolTipText(""); + commentBtn.setIcon(RecipeStepPanel.noCommentIcon); + } } diff --git a/src/main/java/de/usd/cstchef/view/RequestFilterDialog.java b/src/main/java/de/usd/cstchef/view/RequestFilterDialog.java index 60caa44..e36c9e4 100644 --- a/src/main/java/de/usd/cstchef/view/RequestFilterDialog.java +++ b/src/main/java/de/usd/cstchef/view/RequestFilterDialog.java @@ -36,7 +36,7 @@ private RequestFilterDialog() { JPanel labelPanel = new JPanel(); labelPanel.setLayout(new GridLayout(7, 0)); labelPanel.add(new JLabel("")); - List labels = Arrays.asList("Proxy", "Repeater", "Scanner", "Intruder", "Extender"); + List labels = Arrays.asList("Proxy", "Repeater", "Scanner", "Intruder", "Extender", "Sequencer"); for (String label : labels) { labelPanel.add(new JLabel(label)); } @@ -51,13 +51,11 @@ private RequestFilterDialog() { private JPanel createPanel(BurpOperation operation) { if (BurpUtils.getInstance().getFilterState().getFilterMask(operation).isEmpty()) { BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.PROXY, ToolType.PROXY.ordinal()), false); - BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.REPEATER, ToolType.REPEATER.ordinal()), - false); + BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.REPEATER, ToolType.REPEATER.ordinal()), false); BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.SCANNER, ToolType.SCANNER.ordinal()), false); - BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.INTRUDER, ToolType.INTRUDER.ordinal()), - false); - BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.EXTENSIONS, ToolType.EXTENSIONS.ordinal()), - false); + BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.INTRUDER, ToolType.INTRUDER.ordinal()), false); + BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.EXTENSIONS, ToolType.EXTENSIONS.ordinal()), false); + BurpUtils.getInstance().getFilterState().getFilterMask(operation).put(new Filter(ToolType.SEQUENCER, ToolType.SEQUENCER.ordinal()), false); } JPanel panel = new JPanel(); diff --git a/src/main/java/de/usd/cstchef/wrapper/GZIPOutputStreamWrapper.java b/src/main/java/de/usd/cstchef/wrapper/GZIPOutputStreamWrapper.java new file mode 100644 index 0000000..de35b95 --- /dev/null +++ b/src/main/java/de/usd/cstchef/wrapper/GZIPOutputStreamWrapper.java @@ -0,0 +1,33 @@ +package de.usd.cstchef.wrapper; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.GZIPOutputStream; + +public class GZIPOutputStreamWrapper extends GZIPOutputStream { + + public GZIPOutputStreamWrapper(OutputStream out) throws IOException { + super(out); + //TODO Auto-generated constructor stub + } + + public GZIPOutputStreamWrapper(OutputStream out, int size) throws IOException { + super(out, size); + //TODO Auto-generated constructor stub + } + + public GZIPOutputStreamWrapper(OutputStream out, boolean syncFlush) throws IOException { + super(out, syncFlush); + //TODO Auto-generated constructor stub + } + + public GZIPOutputStreamWrapper(OutputStream out, int size, boolean syncFlush) throws IOException { + super(out, size, syncFlush); + //TODO Auto-generated constructor stub + } + + public void setCompressionLevel(int level) { + def.setLevel(level); + } + +}