From 6cd0318dd2bdb9c6c488f537bcbe77d47a7cf5a5 Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Thu, 13 Feb 2025 14:50:12 -0500 Subject: [PATCH 1/9] Add variable support for pre and post execution steps --- .../coreyd97/stepper/MessageProcessor.java | 86 +++++++++++++++++-- 1 file changed, 77 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/coreyd97/stepper/MessageProcessor.java b/src/main/java/com/coreyd97/stepper/MessageProcessor.java index d039891..11dafc8 100644 --- a/src/main/java/com/coreyd97/stepper/MessageProcessor.java +++ b/src/main/java/com/coreyd97/stepper/MessageProcessor.java @@ -1,10 +1,12 @@ package com.coreyd97.stepper; import burp.*; + import com.coreyd97.BurpExtenderUtilities.Preferences; import com.coreyd97.stepper.sequence.StepSequence; import com.coreyd97.stepper.sequencemanager.SequenceManager; import com.coreyd97.stepper.util.ReplacingInputStream; +import com.coreyd97.stepper.variable.PreExecutionStepVariable; import com.coreyd97.stepper.variable.StepVariable; import javax.swing.*; @@ -225,6 +227,34 @@ public static byte[] updateContentLength(byte[] request){ return Stepper.callbacks.getHelpers().buildHttpMessage(newRequestHeaders, newBody); } + public Map extractVariablesFromStepperHeader(String headerValue) { + //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Extracting variable from: " + headerValue); + HashMap map = new HashMap<>(); + + Pattern variableListPattern = Pattern.compile("\\w+(:\\s*(?.+))?", Pattern.CASE_INSENSITIVE); + Pattern variablePattern = Pattern.compile("(?\\w+)=(?[^;]+);?", Pattern.CASE_INSENSITIVE); + + Matcher variableListMatcher = variableListPattern.matcher(headerValue); + + if (variableListMatcher.find()) { + String variableList = variableListMatcher.group("variables"); + //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Variable list: " + variableList); + + Matcher variablesMatcher = variablePattern.matcher(variableList); + + while (variablesMatcher.find()) { + String variableKey = variablesMatcher.group("key"); + String variableValue = variablesMatcher.group("value"); + + //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Found variable key=" + variableKey + ", value=" + variableValue + ")"); + + map.put(variableKey, variableValue); + } + } + + return map; + } + /** * Locates the X-Stepper-Execute-Before or X-Stepper-Execute-After headers and returns the matching sequences. * @param requestInfo @@ -236,18 +266,56 @@ public List extractExecSequencesFromRequest(IRequestInfo requestIn List requestHeaders = requestInfo.getHeaders(); ArrayList execSequences = new ArrayList<>(); + Pattern namePattern = Pattern.compile("^([^:]+)(?::?)", Pattern.CASE_INSENSITIVE); + for (Iterator iterator = requestHeaders.iterator(); iterator.hasNext(); ) { String header = iterator.next(); + Matcher m = pattern.matcher(header); - if (m.matches()) { - Optional execSequence = sequenceManager.getSequences().stream() - .filter(sequence -> sequence.getTitle().equalsIgnoreCase(m.group(1).trim())) - .findFirst(); - - if(execSequence.isPresent()) - execSequences.add(execSequence.get()); - else - JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Could not find execution sequence named: \"" + m.group(1).trim() + "\"."); + if (!m.matches()) { + continue; + } + + ArrayList currentSequences = new ArrayList<>(); + String stepperHeader = m.group(1).trim(); + + //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Stepper header:" + stepperHeader); + + Matcher nameMatcher = namePattern.matcher(stepperHeader); + if (!nameMatcher.find()) { + //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Could not extract name"); + continue; + } + + String sequenceName = nameMatcher.group(1); + //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Name is: " + sequenceName); + + + Optional execSequence = sequenceManager.getSequences().stream() + .filter(sequence -> sequence.getTitle().equalsIgnoreCase(sequenceName)) + .findFirst(); + + if(execSequence.isPresent()) + currentSequences.add(execSequence.get()); + else + JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Could not find execution sequence named: \"" + sequenceName + "\"."); + + // Extract variables and store them in the sequences + Map arguments = extractVariablesFromStepperHeader(stepperHeader); + //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Extracted " + arguments.size() + " variables"); + for (StepSequence sequence : currentSequences) { + //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Checked sequence " + sequence.getTitle() + ", has " + sequence.getGlobalVariableManager().getVariables().size() + " variables"); + for (StepVariable variable : sequence.getGlobalVariableManager().getVariables()) { + //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Test global variable " + variable.getIdentifier()); + if (arguments.containsKey(variable.getIdentifier())) { + String value = arguments.get(variable.getIdentifier()); + variable.setValue(value); + } + } + } + + for (StepSequence sequence : currentSequences) { + execSequences.add(sequence); } } return execSequences; From c0a5515f02f609ace6a6ee4961bb184f7e4ba41c Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Fri, 14 Feb 2025 14:31:54 -0500 Subject: [PATCH 2/9] Support out-of-order execution of sequences with working arguments --- .../coreyd97/stepper/MessageProcessor.java | 138 +++++++++++------- .../stepper/RequestSequenceInformation.java | 43 ++++++ .../stepper/sequence/StepSequence.java | 25 +++- 3 files changed, 153 insertions(+), 53 deletions(-) create mode 100644 src/main/java/com/coreyd97/stepper/RequestSequenceInformation.java diff --git a/src/main/java/com/coreyd97/stepper/MessageProcessor.java b/src/main/java/com/coreyd97/stepper/MessageProcessor.java index 11dafc8..19207da 100644 --- a/src/main/java/com/coreyd97/stepper/MessageProcessor.java +++ b/src/main/java/com/coreyd97/stepper/MessageProcessor.java @@ -31,6 +31,9 @@ public class MessageProcessor implements IHttpListener { public static final Pattern EXECUTE_AFTER_HEADER_PATTERN = Pattern.compile("^" + EXECUTE_AFTER_REGEX + "$", Pattern.CASE_INSENSITIVE); public static final String STEPPER_IGNORE_HEADER = "X-Stepper-Ignore"; public static final Pattern STEPPER_IGNORE_PATTERN = Pattern.compile("^"+STEPPER_IGNORE_HEADER, Pattern.CASE_INSENSITIVE); + public static final Pattern STEPPER_SEQUENCE_NAME_PATTERN = Pattern.compile("^([^:]+)(?::?)", Pattern.CASE_INSENSITIVE); + public static final Pattern VARIABLE_LIST_PATTERN = Pattern.compile("\\w+(:\\s*(?.+))?", Pattern.CASE_INSENSITIVE); + public static final Pattern VARIABLE_PATTERN = Pattern.compile("(?\\w+)=(?[^;]+);?", Pattern.CASE_INSENSITIVE); public MessageProcessor(SequenceManager sequenceManager, Preferences preferences){ this.sequenceManager = sequenceManager; @@ -63,18 +66,21 @@ public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequ if(messageIsRequest){ byte[] request = messageInfo.getRequest(); System.out.println("Request: " + messageInfo.getRequest()); - List preExecSequences = extractExecSequencesFromRequest(requestInfo, EXECUTE_BEFORE_HEADER_PATTERN); + List preExecSequences = extractExecSequencesFromRequest(requestInfo, EXECUTE_BEFORE_HEADER_PATTERN); if(preExecSequences.size() > 0){ //Remove the headers from the request request = removeHeaderMatchingPattern(request, EXECUTE_BEFORE_HEADER_PATTERN); //Execute the sequences - for (StepSequence sequence : preExecSequences) { - sequence.executeBlocking(); + for (RequestSequenceInformation requestSequenceInformation : preExecSequences) { + StepSequence sequence = requestSequenceInformation.sequence; + Map variables = requestSequenceInformation.variables; + JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Execute sequence: " + sequence.getTitle() + ", with variables: " + variables.toString()); + sequence.executeBlocking(variables); } } - List postExecSequences = extractExecSequencesFromRequest(requestInfo, EXECUTE_AFTER_HEADER_PATTERN); + List postExecSequences = extractExecSequencesFromRequest(requestInfo, EXECUTE_AFTER_HEADER_PATTERN); if(postExecSequences.size() > 0){ //Remove the headers from the request @@ -82,8 +88,19 @@ public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequ //Joining the sequences as string to set them as a comment to catch them in the response String postExecSequencesJoined = EXECUTE_AFTER_HEADER + ":"; - for (StepSequence sequence : postExecSequences) { - postExecSequencesJoined += sequence.getTitle() + EXECUTE_AFTER_COMMENT_DELIMITER; + for (RequestSequenceInformation requestSequenceInformation : postExecSequences) { + StepSequence sequence = requestSequenceInformation.sequence; + Map variables = requestSequenceInformation.variables; + String variableString = ""; + if (variables.size() > 0) { + variableString += ":"; + for (Map.Entry entry : variables.entrySet()) { + variableString += entry.getKey() + "="; + variableString += entry.getValue() + ";"; + } + } + + postExecSequencesJoined += sequence.getTitle() + variableString + EXECUTE_AFTER_COMMENT_DELIMITER; } if(!postExecSequencesJoined.isEmpty()){ @@ -118,11 +135,14 @@ public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequ messageInfo.setRequest(request); }else{ // this is a response so we check for the comments - List postExecSequences = extractExecSequencesFromComment(messageInfo.getComment(), EXECUTE_AFTER_HEADER_PATTERN); + List postExecSequences = extractExecSequencesFromComment(messageInfo.getComment(), EXECUTE_AFTER_HEADER_PATTERN); if(postExecSequences.size() > 0){ //Execute the sequences - for (StepSequence sequence : postExecSequences) { - sequence.executeBlocking(); + for (RequestSequenceInformation requestSequenceInformation : postExecSequences) { + StepSequence sequence = requestSequenceInformation.sequence; + Map variables = requestSequenceInformation.variables; + JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Execute sequence: " + sequence.getTitle() + ", with variables: " + variables.toString()); + sequence.executeBlocking(variables); } // remove the added comment from the request messageInfo.setComment(messageInfo.getComment().replaceAll(EXECUTE_AFTER_REGEX+EXECUTE_AFTER_COMMENT_DELIMITER,"")); @@ -227,27 +247,34 @@ public static byte[] updateContentLength(byte[] request){ return Stepper.callbacks.getHelpers().buildHttpMessage(newRequestHeaders, newBody); } - public Map extractVariablesFromStepperHeader(String headerValue) { - //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Extracting variable from: " + headerValue); + /** + * Extract all defined variables from the request sequence information string. + * + * If a variable has more than one definition, the later one is used. + * It expects the string to have a format that matches the definition in RequesteSequenceInformation. + * + * For example, the string: `test: firstVariable=old; secondVariable=someValue; firstVariable=new` + * would be converted to a map with the following content: + * - firstVariable: new + * - secondVariable: someValue + * + * @see RequestSequenceInformation + * @param sequenceInfoString The string that contains sequence information + * @return All defined variables + */ + public Map extractVariablesFromSequenceInfoString(String sequenceInfoString) { HashMap map = new HashMap<>(); - Pattern variableListPattern = Pattern.compile("\\w+(:\\s*(?.+))?", Pattern.CASE_INSENSITIVE); - Pattern variablePattern = Pattern.compile("(?\\w+)=(?[^;]+);?", Pattern.CASE_INSENSITIVE); - - Matcher variableListMatcher = variableListPattern.matcher(headerValue); + Matcher variableListMatcher = VARIABLE_LIST_PATTERN.matcher(sequenceInfoString); - if (variableListMatcher.find()) { + if (variableListMatcher.find() && variableListMatcher.group("variables") != null) { String variableList = variableListMatcher.group("variables"); - //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Variable list: " + variableList); - - Matcher variablesMatcher = variablePattern.matcher(variableList); + Matcher variablesMatcher = VARIABLE_PATTERN.matcher(variableList); while (variablesMatcher.find()) { String variableKey = variablesMatcher.group("key"); String variableValue = variablesMatcher.group("value"); - //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Found variable key=" + variableKey + ", value=" + variableValue + ")"); - map.put(variableKey, variableValue); } } @@ -255,18 +282,30 @@ public Map extractVariablesFromStepperHeader(String headerValue) return map; } + /** + * Extract a sequence's name from the request info string. + * @param sequenceInfoString String that contains the name and variables of a step sequence + * @return The sequence name + */ + private Optional extractSequenceNameFromSequenceInfoString(String sequenceInfoString) { + Matcher nameMatcher = STEPPER_SEQUENCE_NAME_PATTERN.matcher(sequenceInfoString); + if (!nameMatcher.find()) { + return Optional.empty(); + } + + return Optional.of(nameMatcher.group(1).trim()); + } + /** * Locates the X-Stepper-Execute-Before or X-Stepper-Execute-After headers and returns the matching sequences. * @param requestInfo * @param pattern * @return Optional value of step sequence to execute before or after the request. */ - public List extractExecSequencesFromRequest(IRequestInfo requestInfo, Pattern pattern){ + public List extractExecSequencesFromRequest(IRequestInfo requestInfo, Pattern pattern){ //Check if headers ask us to execute a request before the request. List requestHeaders = requestInfo.getHeaders(); - ArrayList execSequences = new ArrayList<>(); - - Pattern namePattern = Pattern.compile("^([^:]+)(?::?)", Pattern.CASE_INSENSITIVE); + ArrayList execSequences = new ArrayList<>(); for (Iterator iterator = requestHeaders.iterator(); iterator.hasNext(); ) { String header = iterator.next(); @@ -279,17 +318,13 @@ public List extractExecSequencesFromRequest(IRequestInfo requestIn ArrayList currentSequences = new ArrayList<>(); String stepperHeader = m.group(1).trim(); - //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Stepper header:" + stepperHeader); + Optional optionalName = this.extractSequenceNameFromSequenceInfoString(stepperHeader); - Matcher nameMatcher = namePattern.matcher(stepperHeader); - if (!nameMatcher.find()) { - //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Could not extract name"); + if (optionalName.isEmpty()) { continue; } - String sequenceName = nameMatcher.group(1); - //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Name is: " + sequenceName); - + String sequenceName = optionalName.get(); Optional execSequence = sequenceManager.getSequences().stream() .filter(sequence -> sequence.getTitle().equalsIgnoreCase(sequenceName)) @@ -301,21 +336,10 @@ public List extractExecSequencesFromRequest(IRequestInfo requestIn JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Could not find execution sequence named: \"" + sequenceName + "\"."); // Extract variables and store them in the sequences - Map arguments = extractVariablesFromStepperHeader(stepperHeader); - //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Extracted " + arguments.size() + " variables"); - for (StepSequence sequence : currentSequences) { - //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Checked sequence " + sequence.getTitle() + ", has " + sequence.getGlobalVariableManager().getVariables().size() + " variables"); - for (StepVariable variable : sequence.getGlobalVariableManager().getVariables()) { - //JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Test global variable " + variable.getIdentifier()); - if (arguments.containsKey(variable.getIdentifier())) { - String value = arguments.get(variable.getIdentifier()); - variable.setValue(value); - } - } - } + Map arguments = extractVariablesFromSequenceInfoString(stepperHeader); for (StepSequence sequence : currentSequences) { - execSequences.add(sequence); + execSequences.add(new RequestSequenceInformation(sequence, arguments)); } } return execSequences; @@ -327,21 +351,31 @@ public List extractExecSequencesFromRequest(IRequestInfo requestIn * @param pattern * @return Optional value of step sequence to execute after the request. */ - public List extractExecSequencesFromComment(String comment, Pattern pattern){ - ArrayList execSequences = new ArrayList<>(); + public List extractExecSequencesFromComment(String comment, Pattern pattern){ + ArrayList execSequences = new ArrayList<>(); Matcher m = pattern.matcher(comment); if (m.find()) { String[] allSequences = m.group(1).split(EXECUTE_AFTER_COMMENT_DELIMITER); - for(String sequenceName : allSequences){ - if(sequenceName != null && !sequenceName.isEmpty()){ + for(String sequenceInfo : allSequences){ + if(sequenceInfo != null && !sequenceInfo.isEmpty()){ + Optional optionalName = this.extractSequenceNameFromSequenceInfoString(sequenceInfo); + + if (optionalName.isEmpty()) { + continue; + } + + String sequenceName = optionalName.get(); + Optional execSequence = sequenceManager.getSequences().stream() - .filter(sequence -> sequence.getTitle().equalsIgnoreCase(sequenceName.trim())) + .filter(sequence -> sequence.getTitle().equalsIgnoreCase(sequenceName)) .findFirst(); + Map variables = this.extractVariablesFromSequenceInfoString(sequenceInfo); + if(execSequence.isPresent()) - execSequences.add(execSequence.get()); + execSequences.add(new RequestSequenceInformation(execSequence.get(), variables)); else - JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Could not find execution sequence named: \"" + m.group(1).trim() + "\"."); + JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Could not find execution sequence named: \"" + sequenceInfo + "\"."); } } } diff --git a/src/main/java/com/coreyd97/stepper/RequestSequenceInformation.java b/src/main/java/com/coreyd97/stepper/RequestSequenceInformation.java new file mode 100644 index 0000000..d4eb8f4 --- /dev/null +++ b/src/main/java/com/coreyd97/stepper/RequestSequenceInformation.java @@ -0,0 +1,43 @@ +package com.coreyd97.stepper; + +import java.util.Map; + +import com.coreyd97.stepper.sequence.StepSequence; + +/** + * Holds information about a step sequence as defined in a request with the stepper headers. + * Includes information about all defined variables. + * + * This information is stored either in the request's X-Stepper-Execute-Before and X-Steper-Execute-After header + * as well in the user comments in the following format: + * : (=;)* + * + * Here some examples to demonstrate how the string representation of this looks like: + *
+ * - `simpleExample: first=value`
+ *  - Name: simpleExample
+ *  - Variables:
+ *      first: value
+ * - `advancedExample: firstKey=firstValue;secondKey=secondValue; firstKey=overwrittenValue;`
+ *  - Name: advancedExample
+ *  - Variables:
+ *      firstKey: overwrittenValue
+ *      secondKey: secondValue
+ * 
+ */ +class RequestSequenceInformation { + /** + * Defines step sequence. + */ + public StepSequence sequence; + + /** + * All variables for the sequence as defined in the stepper header. + */ + public Map variables; + + public RequestSequenceInformation(StepSequence sequence, Map variables) { + this.sequence = sequence; + this.variables = variables; + } +} \ No newline at end of file diff --git a/src/main/java/com/coreyd97/stepper/sequence/StepSequence.java b/src/main/java/com/coreyd97/stepper/sequence/StepSequence.java index 8895532..06ed5fb 100644 --- a/src/main/java/com/coreyd97/stepper/sequence/StepSequence.java +++ b/src/main/java/com/coreyd97/stepper/sequence/StepSequence.java @@ -17,8 +17,10 @@ import javax.swing.*; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Vector; public class StepSequence @@ -42,11 +44,28 @@ public StepSequence(){ this("Step Sequence"); } - public void executeBlocking(){ + /** + * Execute the step sequence with a predefined list of global variables. + * + * Variables are set in the synchronization block which should ensure that if the sequence + * has already been executed with another set of variable in between the extraction of the + * variables from the header or comment, it is still executed with this set of variables. + * @param newGlobalVariables Map of global variables + */ + public void executeBlocking(Map newGlobalVariables) { if(this.isExecuting) return; //Sequence already being executed. this.isExecuting = true; try { synchronized (StepSequence.this) { + // Assign variables to global variables + for (StepVariable variable : this.globalVariablesManager.getVariables()) { + if (newGlobalVariables.containsKey(variable.getIdentifier())) { + String newValue = newGlobalVariables.get(variable.getIdentifier()); + + variable.setValue(newValue); + } + } + StepSequenceTab tabUI = Stepper.getUI().getTabForStepManager(this); SequenceContainer sequenceContainer = tabUI.getStepsContainer(); @@ -99,6 +118,10 @@ public void executeBlocking(){ } } + public void executeBlocking(){ + this.executeBlocking(new HashMap<>()); + } + public void executeAsync(){ new Thread(() -> executeBlocking()).start(); } From 3a25e3e0d25a3bd65a7fdbda8312f07a9154815e Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Fri, 14 Feb 2025 14:32:02 -0500 Subject: [PATCH 3/9] Add bin directory to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 520879b..8d70ccf 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ releases/ *.iml gradle-app.setting .gradletasknamecache -.git \ No newline at end of file +.git +bin \ No newline at end of file From 1ca8d69ddf6a55ef73ed00ca8631cb3a86120fd5 Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Tue, 18 Feb 2025 04:53:55 -0500 Subject: [PATCH 4/9] Removed debugging dialog --- src/main/java/com/coreyd97/stepper/MessageProcessor.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/coreyd97/stepper/MessageProcessor.java b/src/main/java/com/coreyd97/stepper/MessageProcessor.java index 19207da..fa2e219 100644 --- a/src/main/java/com/coreyd97/stepper/MessageProcessor.java +++ b/src/main/java/com/coreyd97/stepper/MessageProcessor.java @@ -75,7 +75,6 @@ public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequ for (RequestSequenceInformation requestSequenceInformation : preExecSequences) { StepSequence sequence = requestSequenceInformation.sequence; Map variables = requestSequenceInformation.variables; - JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Execute sequence: " + sequence.getTitle() + ", with variables: " + variables.toString()); sequence.executeBlocking(variables); } } @@ -141,7 +140,6 @@ public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequ for (RequestSequenceInformation requestSequenceInformation : postExecSequences) { StepSequence sequence = requestSequenceInformation.sequence; Map variables = requestSequenceInformation.variables; - JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Execute sequence: " + sequence.getTitle() + ", with variables: " + variables.toString()); sequence.executeBlocking(variables); } // remove the added comment from the request From dd6d8769933797e4f1ed1775a49953cce7f28ae1 Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Sun, 23 Mar 2025 08:46:02 -0400 Subject: [PATCH 5/9] Add separate argument header --- .../coreyd97/stepper/MessageProcessor.java | 63 +++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/coreyd97/stepper/MessageProcessor.java b/src/main/java/com/coreyd97/stepper/MessageProcessor.java index 19207da..9ee2ab5 100644 --- a/src/main/java/com/coreyd97/stepper/MessageProcessor.java +++ b/src/main/java/com/coreyd97/stepper/MessageProcessor.java @@ -24,16 +24,20 @@ public class MessageProcessor implements IHttpListener { private final Preferences preferences; public static final String EXECUTE_BEFORE_HEADER = "X-Stepper-Execute-Before"; public static final String EXECUTE_AFTER_HEADER = "X-Stepper-Execute-After"; + public static final String EXECUTE_VAR_HEADER = "X-Stepper-Var"; public static final String EXECUTE_BEFORE_REGEX = EXECUTE_BEFORE_HEADER + ":(.*)"; public static final String EXECUTE_AFTER_REGEX = EXECUTE_AFTER_HEADER+":(.*)"; + public static final String EXECUTE_VAR_REGEX = EXECUTE_VAR_HEADER + ":(.*)"; public static final String EXECUTE_AFTER_COMMENT_DELIMITER = "#%~%#"; public static final Pattern EXECUTE_BEFORE_HEADER_PATTERN = Pattern.compile("^" + EXECUTE_BEFORE_REGEX + "$", Pattern.CASE_INSENSITIVE); public static final Pattern EXECUTE_AFTER_HEADER_PATTERN = Pattern.compile("^" + EXECUTE_AFTER_REGEX + "$", Pattern.CASE_INSENSITIVE); + public static final Pattern EXECUTE_VAR_HEADER_PATTERN = Pattern.compile("^" + EXECUTE_VAR_REGEX + "$", Pattern.CASE_INSENSITIVE); public static final String STEPPER_IGNORE_HEADER = "X-Stepper-Ignore"; public static final Pattern STEPPER_IGNORE_PATTERN = Pattern.compile("^"+STEPPER_IGNORE_HEADER, Pattern.CASE_INSENSITIVE); public static final Pattern STEPPER_SEQUENCE_NAME_PATTERN = Pattern.compile("^([^:]+)(?::?)", Pattern.CASE_INSENSITIVE); - public static final Pattern VARIABLE_LIST_PATTERN = Pattern.compile("\\w+(:\\s*(?.+))?", Pattern.CASE_INSENSITIVE); - public static final Pattern VARIABLE_PATTERN = Pattern.compile("(?\\w+)=(?[^;]+);?", Pattern.CASE_INSENSITIVE); + public static final Pattern VARIABLE_LIST_PATTERN = Pattern.compile("[^:]+(:\\s*(?.+))?", Pattern.CASE_INSENSITIVE); + public static final Pattern VARIABLE_PATTERN = Pattern.compile("(?[^=]+)=(?[^;]+);?", Pattern.CASE_INSENSITIVE); + public static final Pattern SINGLE_VARIABLE_PATTERN = Pattern.compile("(?[^=]+)=(?.*)", Pattern.CASE_INSENSITIVE); public MessageProcessor(SequenceManager sequenceManager, Preferences preferences){ this.sequenceManager = sequenceManager; @@ -65,7 +69,10 @@ public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequ if(messageIsRequest){ byte[] request = messageInfo.getRequest(); - System.out.println("Request: " + messageInfo.getRequest()); + + // Extract variable headers from request + Map sequenceArguments = extractSequenceArgumentsFromRequest(requestInfo, EXECUTE_VAR_HEADER_PATTERN); + List preExecSequences = extractExecSequencesFromRequest(requestInfo, EXECUTE_BEFORE_HEADER_PATTERN); if(preExecSequences.size() > 0){ //Remove the headers from the request @@ -75,7 +82,13 @@ public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequ for (RequestSequenceInformation requestSequenceInformation : preExecSequences) { StepSequence sequence = requestSequenceInformation.sequence; Map variables = requestSequenceInformation.variables; - JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Execute sequence: " + sequence.getTitle() + ", with variables: " + variables.toString()); + + // Merge arguments from variable headers into arguments for this sequence + // Prioritize arguments specifically set for this sequence + for (Map.Entry entry : sequenceArguments.entrySet()) { + variables.putIfAbsent(entry.getKey(), entry.getValue()); + } + sequence.executeBlocking(variables); } } @@ -296,6 +309,48 @@ private Optional extractSequenceNameFromSequenceInfoString(String sequen return Optional.of(nameMatcher.group(1).trim()); } + /** + * Extract arguments from variable headers from the given request. + * @param requestInfo The request + * @param pattern Pattern used to identify variable header + * @return Map of all sequence arguments + */ + private Map extractSequenceArgumentsFromRequest(IRequestInfo requestInfo, Pattern pattern) { + Stepper.callbacks.printOutput("[MessageProcessor] extract arguments"); + //Check if headers ask us to execute a request before the request. + List requestHeaders = requestInfo.getHeaders(); + Map arguments = new HashMap<>(); + + for (Iterator iterator = requestHeaders.iterator(); iterator.hasNext(); ) { + String header = iterator.next(); + Stepper.callbacks.printOutput("[MessageProcessor] processing header: " + header); + + Matcher m = pattern.matcher(header); + if (!m.matches()) { + continue; + } + + String variableInfo = m.group(1).trim(); + + Stepper.callbacks.printOutput("[MessageProcessor] processing variable info: " + variableInfo); + + Matcher argumentMatcher = SINGLE_VARIABLE_PATTERN.matcher(variableInfo); + if (!argumentMatcher.matches()) { + Stepper.callbacks.printOutput("[MessageProcessor] found variable header without value"); + continue; + } + + String variableKey = argumentMatcher.group("key"); + String variableValue = argumentMatcher.group("value"); + + Stepper.callbacks.printOutput("[MessageProcessor] got argument: " + variableKey + "=" + variableValue); + + arguments.put(variableKey, variableValue); + } + + return arguments; + } + /** * Locates the X-Stepper-Execute-Before or X-Stepper-Execute-After headers and returns the matching sequences. * @param requestInfo From c3e79954f375babca1ea4a7bd8404f44c9ea6446 Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Fri, 18 Apr 2025 10:44:17 -0400 Subject: [PATCH 6/9] Remove debugging output --- src/main/java/com/coreyd97/stepper/MessageProcessor.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/com/coreyd97/stepper/MessageProcessor.java b/src/main/java/com/coreyd97/stepper/MessageProcessor.java index 9cdf09f..f835443 100644 --- a/src/main/java/com/coreyd97/stepper/MessageProcessor.java +++ b/src/main/java/com/coreyd97/stepper/MessageProcessor.java @@ -315,14 +315,12 @@ private Optional extractSequenceNameFromSequenceInfoString(String sequen * @return Map of all sequence arguments */ private Map extractSequenceArgumentsFromRequest(IRequestInfo requestInfo, Pattern pattern) { - Stepper.callbacks.printOutput("[MessageProcessor] extract arguments"); //Check if headers ask us to execute a request before the request. List requestHeaders = requestInfo.getHeaders(); Map arguments = new HashMap<>(); for (Iterator iterator = requestHeaders.iterator(); iterator.hasNext(); ) { String header = iterator.next(); - Stepper.callbacks.printOutput("[MessageProcessor] processing header: " + header); Matcher m = pattern.matcher(header); if (!m.matches()) { @@ -331,19 +329,15 @@ private Map extractSequenceArgumentsFromRequest(IRequestInfo req String variableInfo = m.group(1).trim(); - Stepper.callbacks.printOutput("[MessageProcessor] processing variable info: " + variableInfo); Matcher argumentMatcher = SINGLE_VARIABLE_PATTERN.matcher(variableInfo); if (!argumentMatcher.matches()) { - Stepper.callbacks.printOutput("[MessageProcessor] found variable header without value"); continue; } String variableKey = argumentMatcher.group("key"); String variableValue = argumentMatcher.group("value"); - Stepper.callbacks.printOutput("[MessageProcessor] got argument: " + variableKey + "=" + variableValue); - arguments.put(variableKey, variableValue); } From c083e138542c4dc280149d0959aa17f54442d1ee Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Thu, 27 Mar 2025 15:27:59 -0400 Subject: [PATCH 7/9] Serialize information about post execution sequences for comment --- .../coreyd97/stepper/MessageProcessor.java | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/coreyd97/stepper/MessageProcessor.java b/src/main/java/com/coreyd97/stepper/MessageProcessor.java index f835443..16d7231 100644 --- a/src/main/java/com/coreyd97/stepper/MessageProcessor.java +++ b/src/main/java/com/coreyd97/stepper/MessageProcessor.java @@ -8,6 +8,8 @@ import com.coreyd97.stepper.util.ReplacingInputStream; import com.coreyd97.stepper.variable.PreExecutionStepVariable; import com.coreyd97.stepper.variable.StepVariable; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import javax.swing.*; import java.io.ByteArrayInputStream; @@ -99,25 +101,28 @@ public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequ //Remove the headers from the request request = removeHeaderMatchingPattern(request, EXECUTE_AFTER_HEADER_PATTERN); + HashMap> allSequences = new HashMap<>(); + //Joining the sequences as string to set them as a comment to catch them in the response String postExecSequencesJoined = EXECUTE_AFTER_HEADER + ":"; for (RequestSequenceInformation requestSequenceInformation : postExecSequences) { StepSequence sequence = requestSequenceInformation.sequence; Map variables = requestSequenceInformation.variables; - String variableString = ""; - if (variables.size() > 0) { - variableString += ":"; - for (Map.Entry entry : variables.entrySet()) { - variableString += entry.getKey() + "="; - variableString += entry.getValue() + ";"; - } + + // Merge arguments from variable headers into arguments for this sequence + // Prioritize arguments specifically set for this sequence + for (Map.Entry entry : sequenceArguments.entrySet()) { + variables.putIfAbsent(entry.getKey(), entry.getValue()); } - postExecSequencesJoined += sequence.getTitle() + variableString + EXECUTE_AFTER_COMMENT_DELIMITER; + allSequences.put(sequence.getTitle(), variables); } + Gson gson = new Gson(); + String serializedSequences = gson.toJson(allSequences); + if(!postExecSequencesJoined.isEmpty()){ - messageInfo.setComment(messageInfo.getComment() + postExecSequencesJoined); + messageInfo.setComment(messageInfo.getComment() + EXECUTE_AFTER_HEADER + ":" + serializedSequences); } } @@ -403,28 +408,24 @@ public List extractExecSequencesFromComment(String c ArrayList execSequences = new ArrayList<>(); Matcher m = pattern.matcher(comment); if (m.find()) { - String[] allSequences = m.group(1).split(EXECUTE_AFTER_COMMENT_DELIMITER); - for(String sequenceInfo : allSequences){ - if(sequenceInfo != null && !sequenceInfo.isEmpty()){ - Optional optionalName = this.extractSequenceNameFromSequenceInfoString(sequenceInfo); + String allSequencesSerialized = m.group(1); + Gson gson = new Gson(); + TypeToken>> mapType = new TypeToken>>(){}; + HashMap> allSequences = gson.fromJson(allSequencesSerialized, mapType.getType()); - if (optionalName.isEmpty()) { - continue; - } + for(Map.Entry> entry : allSequences.entrySet()){ + String sequenceName = entry.getKey(); - String sequenceName = optionalName.get(); + Optional execSequence = sequenceManager.getSequences().stream() + .filter(sequence -> sequence.getTitle().equalsIgnoreCase(sequenceName)) + .findFirst(); - Optional execSequence = sequenceManager.getSequences().stream() - .filter(sequence -> sequence.getTitle().equalsIgnoreCase(sequenceName)) - .findFirst(); + Map variables = entry.getValue(); - Map variables = this.extractVariablesFromSequenceInfoString(sequenceInfo); - - if(execSequence.isPresent()) - execSequences.add(new RequestSequenceInformation(execSequence.get(), variables)); - else - JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Could not find execution sequence named: \"" + sequenceInfo + "\"."); - } + if(execSequence.isPresent()) + execSequences.add(new RequestSequenceInformation(execSequence.get(), variables)); + else + JOptionPane.showMessageDialog(Stepper.getUI().getUiComponent(), "Could not find execution sequence named: \"" + sequenceName + "\"."); } } From 2c37cea7c6fb5492cfd6108fa9f9fba32f05ac84 Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Fri, 18 Apr 2025 11:45:19 -0400 Subject: [PATCH 8/9] Change argument header to mention argument --- src/main/java/com/coreyd97/stepper/MessageProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/coreyd97/stepper/MessageProcessor.java b/src/main/java/com/coreyd97/stepper/MessageProcessor.java index 16d7231..08894a3 100644 --- a/src/main/java/com/coreyd97/stepper/MessageProcessor.java +++ b/src/main/java/com/coreyd97/stepper/MessageProcessor.java @@ -26,7 +26,7 @@ public class MessageProcessor implements IHttpListener { private final Preferences preferences; public static final String EXECUTE_BEFORE_HEADER = "X-Stepper-Execute-Before"; public static final String EXECUTE_AFTER_HEADER = "X-Stepper-Execute-After"; - public static final String EXECUTE_VAR_HEADER = "X-Stepper-Var"; + public static final String EXECUTE_VAR_HEADER = "X-Stepper-Argument"; public static final String EXECUTE_BEFORE_REGEX = EXECUTE_BEFORE_HEADER + ":(.*)"; public static final String EXECUTE_AFTER_REGEX = EXECUTE_AFTER_HEADER+":(.*)"; public static final String EXECUTE_VAR_REGEX = EXECUTE_VAR_HEADER + ":(.*)"; From 58989bf0c51411c8ae39778ff896d5932305144a Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Fri, 18 Apr 2025 11:45:43 -0400 Subject: [PATCH 9/9] Add explaination for passing arguments to about tab --- .../stepper/about/view/AboutPanel.java | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/coreyd97/stepper/about/view/AboutPanel.java b/src/main/java/com/coreyd97/stepper/about/view/AboutPanel.java index 952e0fb..bf2386c 100644 --- a/src/main/java/com/coreyd97/stepper/about/view/AboutPanel.java +++ b/src/main/java/com/coreyd97/stepper/about/view/AboutPanel.java @@ -162,7 +162,23 @@ public AboutPanel(){ "as usual, and add the headers "; String stepExecutionUsageItalics = "\"X-Stepper-Execute-Before: SEQUENCENAME\", \"X-Stepper-Execute-After: SEQUENCENAME\""; String stepExecutionUsageB = " to the request respectively. This will cause the sequence to be executed and variables " + - "to be updated every time the request is sent."; + "to be updated every time the request is sent.\n\n"; + + String sequenceInputHeader = "Passing Arguments to Sequences:\n"; + String sequenceInputUsageA = "It is possible to pass arguments to sequences. " + + "This is helpful if the sequences before or after the execution of a request must contain some dynamic value that must match with the request itself. "+ + "In combination with other tools such as intruder or active scanner, this also allows testing more complex sequences and pass the tool's payload into the sequences.\n" + + "Before you can pass arguments, you must first define a global variable for a sequence with your desired name. " + + "The arguments are based on the global variables of a sequence and use the same names. If an argument is passed to the sequence, the value will override the global's value in the sequence. "+ + "There are two ways you can pass an argument.\n\n" + + "1. Option: Execute before and after header:\n" + + "You can pass the arguments in the stepper headers as shown below. You can pass a single argument or multiple arguments at once. The only limitation of this approach is that you can't pass the character \";\" as a value as it is used as the delimiter.\n"; + String sequenceInputUsageBItalics = "\"X-Stepper-Execute-Before: SEQUENCENAME: arg1=value1\" or \"X-Stepper-Execute-After: SEQUENCENAME: arg1=value1; arg2=value2\"\n\n"; + String sequenceInputUsageC = "2. Option: Argument header:\n" + + "If there is the need to pass a semicolon or you are using payloads from another tool where the semicolon might be used, you can use a dedicated header to pass the argument." + + "This header allows you to use any value as an argument but you can only set one argument per header. If you want to pass multiple arguments you have to use multiple headers." + + "You can use this option as shown below:\n"; + String sequenceInputUsageDItalics = "\"X-Stepper-Argument: var=value\""; // aboutContent.getDocument().insertString(aboutContent.getText().length(), intro, italics); // aboutContent.getDocument().insertString(aboutContent.getText().length(), instructionsHeader, bold); @@ -178,13 +194,24 @@ public AboutPanel(){ //Doing this an odd way since insertString seems to cause errors on windows! int offset = 0; - String[] sections = new String[]{intro, instructionsHeader, instructions, variableHelpHeader, variableHelp - , regularExpressionHeader, regularExpressionHelp, regularExpressionExampleHeader - , regularExpressionExample, variableUsageHeader, variableInsertion, variableExampleSequenceTitle, variableExampleSequenceUsage - , variableExampleToolTitle, variableExampleToolUsage, stepExecutionHeader, stepExecutionUsageA, - stepExecutionUsageItalics, stepExecutionUsageB}; - Style[] styles = new Style[]{italics, bold, null, bold, null, bold, null, bold, - null, bold, null, null, italics, null, italics, bold, null, italics, null}; + String[] sections = new String[]{ + intro, + instructionsHeader, instructions, + variableHelpHeader, variableHelp, + regularExpressionHeader, regularExpressionHelp, + regularExpressionExampleHeader, regularExpressionExample, + variableUsageHeader, variableInsertion, variableExampleSequenceTitle, variableExampleSequenceUsage, variableExampleToolTitle, variableExampleToolUsage, + stepExecutionHeader, stepExecutionUsageA, stepExecutionUsageItalics, stepExecutionUsageB, + sequenceInputHeader, sequenceInputUsageA, sequenceInputUsageBItalics, sequenceInputUsageC, sequenceInputUsageDItalics}; + Style[] styles = new Style[]{ + italics, + bold, null, + bold, null, + bold, null, + bold, null, + bold, null, null, italics, null, italics, + bold, null, italics, null, + bold, null, italics, null, italics}; String content = String.join("", sections); aboutContent.setText(content); for (int i = 0; i < sections.length; i++) {