diff --git a/build.gradle b/build.gradle index 82f49c5..050efa1 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { } group 'com.synfron.reshaper.burp' -version '1.6.1' +version '1.6.2' targetCompatibility = '15' sourceCompatibility = '15' diff --git a/src/main/java/burp/BurpExtender.java b/src/main/java/burp/BurpExtender.java index fd80cc6..bb75bcb 100644 --- a/src/main/java/burp/BurpExtender.java +++ b/src/main/java/burp/BurpExtender.java @@ -6,6 +6,7 @@ import synfron.reshaper.burp.core.settings.GeneralSettings; import synfron.reshaper.burp.core.utils.Log; import synfron.reshaper.burp.ui.components.ReshaperComponent; +import synfron.reshaper.burp.ui.utils.ContextMenuHandler; import synfron.reshaper.burp.ui.utils.UiMessageHandler; public class BurpExtender implements IBurpExtender { @@ -20,7 +21,8 @@ public class BurpExtender implements IBurpExtender { private static final GeneralSettings generalSettings = new GeneralSettings(); @Getter private static final MessageEvent messageEvent = new MessageEvent(); - private static UiMessageHandler uiMessageHandler = new UiMessageHandler(messageEvent); + private static final UiMessageHandler uiMessageHandler = new UiMessageHandler(messageEvent); + private static final ContextMenuHandler contextMenuHandler = new ContextMenuHandler(); @Override public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { @@ -34,6 +36,8 @@ public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { callbacks.registerProxyListener(connector); callbacks.registerHttpListener(connector); callbacks.registerExtensionStateListener(connector); + callbacks.registerContextMenuFactory(contextMenuHandler); + Log.get().withMessage("Reshaper started").log(); } } diff --git a/src/main/java/synfron/reshaper/burp/core/events/Event.java b/src/main/java/synfron/reshaper/burp/core/events/Event.java index 9a02ce1..eb88b92 100644 --- a/src/main/java/synfron/reshaper/burp/core/events/Event.java +++ b/src/main/java/synfron/reshaper/burp/core/events/Event.java @@ -8,9 +8,15 @@ public class Event { private List>> listeners; + public synchronized void clearListeners() { + if (listeners != null) { + listeners.clear(); + } + } + public synchronized void remove(IEventListener listener) { if (listeners != null) { - listeners.remove(listener); + listeners.removeIf(listenerReference -> listenerReference.get() == null || listenerReference.get().equals(listener)); if (listeners.size() == 0) { listeners = null; } @@ -18,7 +24,7 @@ public synchronized void remove(IEventListener listener) { } public synchronized void add(IEventListener listener) { - getListeners().add(new WeakReference(listener)); + getListeners().add(new WeakReference<>(listener)); } public synchronized void invoke(A args) { diff --git a/src/main/java/synfron/reshaper/burp/core/messages/MessageValueHandler.java b/src/main/java/synfron/reshaper/burp/core/messages/MessageValueHandler.java index 599aaa3..2f54153 100644 --- a/src/main/java/synfron/reshaper/burp/core/messages/MessageValueHandler.java +++ b/src/main/java/synfron/reshaper/burp/core/messages/MessageValueHandler.java @@ -8,6 +8,9 @@ import synfron.reshaper.burp.core.utils.TextUtils; import synfron.reshaper.burp.core.vars.VariableString; +import java.util.Collections; +import java.util.List; + public class MessageValueHandler { public static String getValue(IEventInfo eventInfo, MessageValue messageValue, VariableString identifier, GetItemPlacement itemPlacement) @@ -109,4 +112,15 @@ public static void setResponseValue(IEventInfo eventInfo, HttpResponseMessage re case HttpResponseStatusMessage -> responseMessage.getStatusLine().setMessage(StringUtils.defaultString(replacementText)); } } + + public static List getIdentifier(IEventInfo eventInfo, MessageValue messageValue) { + return switch (messageValue) { + case HttpRequestHeader -> eventInfo.getHttpRequestMessage().getHeaders().getHeaderNames(); + case HttpResponseHeader -> eventInfo.getHttpResponseMessage().getHeaders().getHeaderNames(); + case HttpRequestCookie -> eventInfo.getHttpRequestMessage().getHeaders().getCookies().getCookiesNames(); + case HttpResponseCookie -> eventInfo.getHttpResponseMessage().getHeaders().getCookies().getCookiesNames(); + case HttpRequestUriQueryParameter -> eventInfo.getHttpRequestMessage().getStatusLine().getUrl().getQueryParams().getParamNames(); + default -> Collections.emptyList(); + }; + } } diff --git a/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpCookies.java b/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpCookies.java index c7eddd1..2bb8ad0 100644 --- a/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpCookies.java +++ b/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpCookies.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import java.util.stream.Collectors; public class HttpCookies extends HttpEntity { private ListMap cookies; @@ -44,6 +45,10 @@ public String getCookie(String name, GetItemPlacement itemPlacement) { return getCookies().get(new CaseInsensitiveString(name), itemPlacement); } + public List getCookiesNames() { + return getCookies().keys().stream().map(CaseInsensitiveString::toString).sorted().collect(Collectors.toList()); + } + public boolean contains(String name) { return getCount() > 0 && getCookies().containsKey(new CaseInsensitiveString(name)); diff --git a/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpHeaders.java b/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpHeaders.java index 01aaf9c..75f27c9 100644 --- a/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpHeaders.java +++ b/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpHeaders.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; public abstract class HttpHeaders extends HttpEntity { protected final List headerLines; @@ -62,6 +63,10 @@ public HttpCookies getCookies() { }); } + public List getHeaderNames() { + return getHeaders().keys().stream().map(CaseInsensitiveString::toString).sorted().collect(Collectors.toList()); + } + private ListMap> getHeaders() { if (headers == null) { headers = new ListMap<>(); diff --git a/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpRequestQueryParams.java b/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpRequestQueryParams.java index a563aea..9327a90 100644 --- a/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpRequestQueryParams.java +++ b/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpRequestQueryParams.java @@ -6,6 +6,7 @@ import synfron.reshaper.burp.core.utils.*; import java.util.List; +import java.util.stream.Collectors; public class HttpRequestQueryParams extends HttpEntity { @Getter @@ -26,6 +27,11 @@ public void prepare() { } } + public List getParamNames() { + prepare(); + return params.keys().stream().sorted().collect(Collectors.toList()); + } + public boolean hasQueryParameter(String name) { prepare(); return params.containsKey(name); diff --git a/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpRequestStatusLine.java b/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpRequestStatusLine.java index e1450fb..10bd13e 100644 --- a/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpRequestStatusLine.java +++ b/src/main/java/synfron/reshaper/burp/core/messages/entities/HttpRequestStatusLine.java @@ -23,7 +23,7 @@ private void prepare() { String[] lineParts = statusLine.split(" ", 3); method = CollectionUtils.elementAtOrDefault(lineParts, 0, ""); url = new HttpRequestUri(CollectionUtils.elementAtOrDefault(lineParts, 1, "")); - version = CollectionUtils.elementAtOrDefault(lineParts, 2, "");; + version = CollectionUtils.elementAtOrDefault(lineParts, 2, ""); parsed = true; } } diff --git a/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSaveFile.java b/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSaveFile.java index a1486ea..11a9264 100644 --- a/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSaveFile.java +++ b/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSaveFile.java @@ -47,7 +47,7 @@ public RuleResponse perform(IEventInfo eventInfo) { encodingValue = VariableString.getTextOrDefault(eventInfo, encoding, Charset.defaultCharset().name()); Encoder encoder = new Encoder(encodingValue); if (encoder.isUseDefault()) { - byte[] fileBytes = encoder.encode(encodingValue); + byte[] fileBytes = encoder.encode(textValue); FileUtils.writeByteArrayToFile(path.toFile(), fileBytes, fileExistsAction == FileExistsAction.Append); } else { FileUtils.write(path.toFile(), textValue, encoder.isUseAutoDetect() ? StandardCharsets.UTF_8.toString() : encodingValue, fileExistsAction == FileExistsAction.Append); diff --git a/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/XmlHttpRequestObj.java b/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/XmlHttpRequestObj.java index 13d3708..264d80f 100644 --- a/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/XmlHttpRequestObj.java +++ b/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/XmlHttpRequestObj.java @@ -76,7 +76,7 @@ public void open(String method, String url, boolean async) throws URISyntaxExcep uriBuilder.setPath(StringUtils.defaultIfBlank(inputUriBuilder.getPath(), "/")); uriBuilder.setParameters(inputUriBuilder.getQueryParams()); uriBuilder.setFragment(inputUriBuilder.getFragment()); - String request = String.format(requestTemplate, method, uriBuilder.toString(), requestUrl.getAuthority()); + String request = String.format(requestTemplate, method, uriBuilder, requestUrl.getAuthority()); Encoder encoder = ((IEventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getEncoder(); requestMessage = new HttpRequestMessage(encoder.encode(request), encoder); setReadyState(OPENED); diff --git a/src/main/java/synfron/reshaper/burp/core/utils/ListMap.java b/src/main/java/synfron/reshaper/burp/core/utils/ListMap.java index ddf04fe..c2be265 100644 --- a/src/main/java/synfron/reshaper/burp/core/utils/ListMap.java +++ b/src/main/java/synfron/reshaper/burp/core/utils/ListMap.java @@ -154,6 +154,10 @@ public boolean containsKey(K key) { return backingMap.containsKey(key); } + public Set keys() { + return backingMap.keySet(); + } + public int size() { return nodeCount; } diff --git a/src/main/java/synfron/reshaper/burp/core/utils/Select.java b/src/main/java/synfron/reshaper/burp/core/utils/Select.java new file mode 100644 index 0000000..8627bf7 --- /dev/null +++ b/src/main/java/synfron/reshaper/burp/core/utils/Select.java @@ -0,0 +1,21 @@ +package synfron.reshaper.burp.core.utils; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Collections; +import java.util.List; + +public class Select { + + @Getter + private final List options; + @Getter @Setter + private T selectedOption; + + public Select(List options, T selectedOption) { + this.options = Collections.unmodifiableList(options); + this.selectedOption = selectedOption != null ? selectedOption : options.stream().findFirst().orElse(null); + } + +} diff --git a/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleComponent.java b/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleComponent.java index 2130854..0379728 100644 --- a/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleComponent.java +++ b/src/main/java/synfron/reshaper/burp/ui/components/rules/RuleComponent.java @@ -69,7 +69,7 @@ private Component getRuleNameBox() { ruleName.getDocument().addDocumentListener(new DocumentActionListener(this::onRuleNameChanged)); - container.add(new JLabel("Rule Name")); + container.add(new JLabel("Rule Name *")); container.add(ruleName); return container; } @@ -83,7 +83,7 @@ private Component getGitHubLink() { githubLink.setFont(font.deriveFont(attributes)); githubLink.addMouseListener(new MouseListener() { - private Color originalColor = githubLink.getForeground(); + private final Color originalColor = githubLink.getForeground(); @SneakyThrows @Override diff --git a/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/WhenWizardItemComponent.java b/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/WhenWizardItemComponent.java new file mode 100644 index 0000000..0ad0034 --- /dev/null +++ b/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/WhenWizardItemComponent.java @@ -0,0 +1,124 @@ +package synfron.reshaper.burp.ui.components.rules.wizard; + +import net.miginfocom.swing.MigLayout; +import synfron.reshaper.burp.core.messages.MessageValue; +import synfron.reshaper.burp.ui.components.IFormComponent; +import synfron.reshaper.burp.ui.models.rules.wizard.WhenWizardItemModel; +import synfron.reshaper.burp.ui.models.rules.wizard.WizardMatchType; +import synfron.reshaper.burp.ui.utils.ComponentVisibilityManager; +import synfron.reshaper.burp.ui.utils.DocumentActionListener; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.util.List; + +public class WhenWizardItemComponent extends JPanel implements IFormComponent { + private final WhenWizardItemModel model; + private final boolean deletable; + private JComboBox messageValue; + private JComboBox identifier; + private JComboBox matchType; + private JTextField text; + + public WhenWizardItemComponent(WhenWizardItemModel model, boolean deletable) { + this.model = model; + this.deletable = deletable; + initComponent(); + } + + private void initComponent() { + setLayout(new BorderLayout()); + JPanel container = new JPanel(new MigLayout()); + + messageValue = new JComboBox<>(MessageValue.values()); + identifier = new JComboBox<>(model.getIdentifiers().getOptions().toArray(new String[0])); + matchType = new JComboBox<>(WizardMatchType.values()); + text = createTextField(); + text.setColumns(20); + text.setMaximumSize(new Dimension(text.getPreferredSize().width, text.getPreferredSize().height)); + text.setAlignmentX(Component.LEFT_ALIGNMENT); + + messageValue.setSelectedItem(model.getMessageValue()); + identifier.setSelectedItem(model.getIdentifiers().getSelectedOption()); + matchType.setSelectedItem(model.getMatchType()); + text.setText(model.getText()); + + messageValue.addActionListener(this::onMessageValueChanged); + identifier.addActionListener(this::onIdentifierChanged); + matchType.addActionListener(this::onMatchTypeChanged); + text.getDocument().addDocumentListener(new DocumentActionListener(this::onTextChanged)); + + container.add(text, "wrap"); + container.add(messageValue, "wrap"); + container.add(ComponentVisibilityManager.withVisibilityFieldChangeDependency( + identifier, + List.of(messageValue, identifier), + () -> ((MessageValue) messageValue.getSelectedItem()).isIdentifierRequired() && identifier.getItemCount() != 0 + ), "wrap"); + container.add(ComponentVisibilityManager.withVisibilityFieldChangeDependency( + new JLabel("Not Applicable"), + List.of(messageValue, identifier), + () -> ((MessageValue) messageValue.getSelectedItem()).isIdentifierRequired() && identifier.getItemCount() == 0 + ), "wrap"); + container.add(ComponentVisibilityManager.withVisibilityFieldChangeDependency( + matchType, + List.of(messageValue, identifier), + () -> !((MessageValue) messageValue.getSelectedItem()).isIdentifierRequired() || identifier.getItemCount() != 0 + ), "wrap"); + container.add(ComponentVisibilityManager.withVisibilityFieldChangeDependency( + text, + List.of(matchType, messageValue, identifier), + () -> (!((MessageValue) messageValue.getSelectedItem()).isIdentifierRequired() || identifier.getItemCount() != 0) && ((WizardMatchType) matchType.getSelectedItem()).isMatcher() + ), "wrap"); + + add(new JSeparator(), BorderLayout.PAGE_START); + add(container, BorderLayout.CENTER); + if (deletable) { + JButton delete = new JButton("Delete"); + delete.addActionListener(this::onDelete); + add(getRightJustifiedButton(delete), BorderLayout.PAGE_END); + } + } + + private void resetIdentifiers() { + identifier.removeAllItems(); + model.getIdentifiers().getOptions().forEach(option -> identifier.addItem(option)); + identifier.setSelectedItem(model.getIdentifiers().getSelectedOption()); + } + + private void resetText() { + text.setText(model.getText()); + } + + private void onMessageValueChanged(ActionEvent actionEvent) { + model.setMessageValue((MessageValue) messageValue.getSelectedItem()); + resetIdentifiers(); + resetText(); + } + + private void onIdentifierChanged(ActionEvent actionEvent) { + model.setIdentifier((String) identifier.getSelectedItem()); + resetText(); + } + + private void onMatchTypeChanged(ActionEvent actionEvent) { + model.setMatchType((WizardMatchType) matchType.getSelectedItem()); + } + + private void onTextChanged(ActionEvent actionEvent) { + model.setText(text.getText()); + } + + private void onDelete(ActionEvent actionEvent) { + model.setDeleted(true); + } + + protected Component getRightJustifiedButton(JButton button) { + JPanel outerContainer = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + outerContainer.setAlignmentX(LEFT_ALIGNMENT); + outerContainer.setAlignmentY(TOP_ALIGNMENT); + outerContainer.add(button); + return outerContainer; + } +} diff --git a/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/WhenWizardOptionPane.java b/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/WhenWizardOptionPane.java new file mode 100644 index 0000000..d342631 --- /dev/null +++ b/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/WhenWizardOptionPane.java @@ -0,0 +1,141 @@ +package synfron.reshaper.burp.ui.components.rules.wizard; + +import net.miginfocom.swing.MigLayout; +import synfron.reshaper.burp.core.events.IEventListener; +import synfron.reshaper.burp.core.events.PropertyChangedArgs; +import synfron.reshaper.burp.ui.components.IFormComponent; +import synfron.reshaper.burp.ui.models.rules.wizard.WhenWizardItemModel; +import synfron.reshaper.burp.ui.models.rules.wizard.WhenWizardModel; +import synfron.reshaper.burp.ui.utils.DocumentActionListener; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; + +public class WhenWizardOptionPane extends JOptionPane implements IFormComponent { + + private final JPanel container; + private final WhenWizardModel model; + private JTextField ruleName; + private JPanel whenWizardItemsComponent; + private final IEventListener whenWizardItemChangedListener = this::onWhenWizardItemChanged; + + private WhenWizardOptionPane(WhenWizardModel model) { + super(new JPanel(new BorderLayout()), JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION); + container = (JPanel)message; + this.model = model; + addPropertyChangeListener(JOptionPane.VALUE_PROPERTY, this::onPropertyChanged); + initComponent(); + } + + private void onPropertyChanged(PropertyChangeEvent event) { + if (getValue() != null && (int)getValue() == JOptionPane.OK_OPTION) { + if (model.createRule()) { + JOptionPane.showMessageDialog(this, + "Rule created. Navigate to Reshaper to finish the rule.", + "Rule Created", + JOptionPane.INFORMATION_MESSAGE); + } else { + JOptionPane.showMessageDialog(this, + String.join("\n", model.validate()), + "Validation Error", + JOptionPane.ERROR_MESSAGE); + } + } + } + + public static void showDialog(WhenWizardModel model) { + WhenWizardOptionPane optionPane = new WhenWizardOptionPane(model); + JDialog dialog = optionPane.createDialog("When"); + dialog.setResizable(true); + + dialog.setModal(false); + dialog.setVisible(true); + } + + private void initComponent() { + container.add(getBody(), BorderLayout.CENTER); + } + + private Component getBody() { + JPanel container = new JPanel(new MigLayout()); + + ruleName = createTextField(); + JScrollPane scrollPane = new JScrollPane(); + scrollPane.setBorder(new EmptyBorder(0,0,0,0)); + scrollPane.setViewportView(container); + scrollPane.setPreferredSize(new Dimension(300, 300)); + + JButton addItem = new JButton("Add"); + + ruleName.getDocument().addDocumentListener(new DocumentActionListener(this::onRuleNameChanged)); + addItem.addActionListener(this::onAddItem); + + container.add(getLabeledField("Rule Name *", ruleName), "wrap"); + container.add(getWhenWizardItemList(), "wrap"); + container.add(getPaddedButton(addItem), "wrap"); + + return scrollPane; + } + + private void onRuleNameChanged(ActionEvent actionEvent) { + model.setRuleName(ruleName.getText()); + } + + protected Component getPaddedButton(JButton button) { + JPanel outerContainer = new JPanel(new FlowLayout(FlowLayout.LEFT)); + outerContainer.setAlignmentX(LEFT_ALIGNMENT); + outerContainer.setAlignmentY(TOP_ALIGNMENT); + outerContainer.add(button); + return outerContainer; + } + + private void onAddItem(ActionEvent actionEvent) { + onAddItem(); + } + + private void onAddItem() { + boolean deletable = !model.getItems().isEmpty(); + WhenWizardItemModel itemModel = model.addItem() + .withListener(whenWizardItemChangedListener); + whenWizardItemsComponent.add(new WhenWizardItemComponent( + itemModel, + deletable + ), "wrap"); + whenWizardItemsComponent.revalidate(); + whenWizardItemsComponent.repaint(); + } + + private JPanel getWhenWizardItemList() { + whenWizardItemsComponent = new JPanel(new MigLayout()); + whenWizardItemsComponent.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); + + if (model.getItems().isEmpty()) { + model.addItem(); + } + + boolean deletableItem = false; + for (WhenWizardItemModel item : model.getItems()) { + item.withListener(whenWizardItemChangedListener); + whenWizardItemsComponent.add(new WhenWizardItemComponent(item, deletableItem), "wrap"); + deletableItem = true; + } + return whenWizardItemsComponent; + } + + private void onWhenWizardItemChanged(PropertyChangedArgs propertyChangedArgs) { + if (propertyChangedArgs.getName().equals("deleted") && (boolean)propertyChangedArgs.getValue()) { + WhenWizardItemModel whenWizardItemModel = (WhenWizardItemModel)propertyChangedArgs.getSource(); + removeWhenWizardItem(whenWizardItemModel); + } + } + + private void removeWhenWizardItem(WhenWizardItemModel whenWizardItemModel) { + int index = model.removeItem(whenWizardItemModel); + whenWizardItemsComponent.remove(index); + whenWizardItemsComponent.revalidate(); + whenWizardItemsComponent.repaint(); + } +} diff --git a/src/main/java/synfron/reshaper/burp/ui/components/vars/VariableComponent.java b/src/main/java/synfron/reshaper/burp/ui/components/vars/VariableComponent.java index e4c747d..c51da1d 100644 --- a/src/main/java/synfron/reshaper/burp/ui/components/vars/VariableComponent.java +++ b/src/main/java/synfron/reshaper/burp/ui/components/vars/VariableComponent.java @@ -71,7 +71,7 @@ private Component getVariableNameBox() { variableName.getDocument().addDocumentListener(new DocumentActionListener(this::onVariableNameChanged)); - container.add(new JLabel("Variable Name")); + container.add(new JLabel("Variable Name *")); container.add(variableName); return container; } @@ -129,7 +129,7 @@ private Component getGitHubLink() { githubLink.setFont(font.deriveFont(attributes)); githubLink.addMouseListener(new MouseListener() { - private Color originalColor = githubLink.getForeground(); + private final Color originalColor = githubLink.getForeground(); @SneakyThrows @Override diff --git a/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/WhenWizardItemModel.java b/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/WhenWizardItemModel.java new file mode 100644 index 0000000..f9f6005 --- /dev/null +++ b/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/WhenWizardItemModel.java @@ -0,0 +1,121 @@ +package synfron.reshaper.burp.ui.models.rules.wizard; + +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; +import synfron.reshaper.burp.core.events.IEventListener; +import synfron.reshaper.burp.core.events.PropertyChangedArgs; +import synfron.reshaper.burp.core.events.PropertyChangedEvent; +import synfron.reshaper.burp.core.messages.DataDirection; +import synfron.reshaper.burp.core.messages.IEventInfo; +import synfron.reshaper.burp.core.messages.MessageValue; +import synfron.reshaper.burp.core.messages.MessageValueHandler; +import synfron.reshaper.burp.core.utils.GetItemPlacement; +import synfron.reshaper.burp.core.utils.Select; +import synfron.reshaper.burp.core.vars.VariableString; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class WhenWizardItemModel { + + @Getter + private MessageValue messageValue; + @Getter + private final IEventInfo eventInfo; + @Getter + private Select identifiers = new Select<>(Collections.emptyList(), null); + @Getter + private WizardMatchType matchType = WizardMatchType.Equals; + @Getter + private String text; + @Getter + protected boolean deleted; + + @Getter + private final PropertyChangedEvent propertyChangedEvent = new PropertyChangedEvent(); + + public WhenWizardItemModel(MessageValue messageValue, IEventInfo eventInfo) { + this.eventInfo = eventInfo; + setMessageValue(messageValue); + } + + private void propertyChanged(String name, Object value) { + propertyChangedEvent.invoke(new PropertyChangedArgs(this, name, value)); + } + + public WhenWizardItemModel withListener(IEventListener listener) { + propertyChangedEvent.add(listener); + return this; + } + + public void setMessageValue(MessageValue messageValue) { + this.messageValue = messageValue; + propertyChanged("messageValue", messageValue); + resetText(); + resetIdentifiers(); + } + + private void resetText() { + try { + text = !hasLongValue(messageValue) ? MessageValueHandler.getValue( + eventInfo, + messageValue, + identifiers.getSelectedOption() == null ? null : VariableString.getAsVariableString(identifiers.getSelectedOption(), false), + GetItemPlacement.Last + ) : ""; + } catch (Exception e) { + text = ""; + } + propertyChanged("text", text); + } + + private boolean hasLongValue(MessageValue messageValue) { + return switch (messageValue) { + case HttpRequestBody, HttpRequestHeaders, HttpRequestMessage, HttpResponseBody, HttpResponseHeaders, HttpResponseMessage -> true; + default -> false; + }; + } + + private void resetIdentifiers() { + try { + identifiers = new Select<>(MessageValueHandler.getIdentifier(eventInfo, messageValue), null); + } catch (Exception e) { + identifiers = new Select<>(Collections.emptyList(), null); + } + propertyChanged("identifiers", identifiers); + } + + public void setIdentifier(String identifier) { + identifiers.setSelectedOption(identifier); + propertyChanged("identifiers", identifiers); + resetText(); + } + + public void setMatchType(WizardMatchType matchType) { + this.matchType = matchType; + propertyChanged("matchType", matchType); + } + + public void setText(String text) { + this.text = text; + propertyChanged("text", text); + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + propertyChanged("deleted", deleted); + } + + public boolean requiresResponse() { + return messageValue.getDataDirection() == DataDirection.Response; + } + + public List validate() { + List errors = new ArrayList<>(); + if (messageValue.isIdentifierRequired() && (identifiers.getOptions().isEmpty() || StringUtils.isEmpty(identifiers.getSelectedOption()))) { + errors.add("Has non-applicable constraint"); + } + return errors; + } +} diff --git a/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/WhenWizardModel.java b/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/WhenWizardModel.java new file mode 100644 index 0000000..a41580e --- /dev/null +++ b/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/WhenWizardModel.java @@ -0,0 +1,137 @@ +package synfron.reshaper.burp.ui.models.rules.wizard; + +import burp.BurpExtender; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; +import synfron.reshaper.burp.core.events.IEventListener; +import synfron.reshaper.burp.core.events.PropertyChangedArgs; +import synfron.reshaper.burp.core.events.PropertyChangedEvent; +import synfron.reshaper.burp.core.messages.DataDirection; +import synfron.reshaper.burp.core.messages.IEventInfo; +import synfron.reshaper.burp.core.messages.MessageValue; +import synfron.reshaper.burp.core.rules.MatchType; +import synfron.reshaper.burp.core.rules.Rule; +import synfron.reshaper.burp.core.rules.whens.When; +import synfron.reshaper.burp.core.rules.whens.WhenEventDirection; +import synfron.reshaper.burp.core.rules.whens.WhenHasEntity; +import synfron.reshaper.burp.core.rules.whens.WhenMatchesText; +import synfron.reshaper.burp.core.utils.GetItemPlacement; +import synfron.reshaper.burp.core.vars.VariableString; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +public class WhenWizardModel { + @Getter + private final IEventInfo eventInfo; + @Getter + private String ruleName; + @Getter + private final List items = new ArrayList<>(); + + @Getter + private final PropertyChangedEvent propertyChangedEvent = new PropertyChangedEvent(); + @Getter + private boolean invalidated; + + public WhenWizardModel(IEventInfo eventInfo) { + this.eventInfo = eventInfo; + } + + public void resetPropertyChangedListener() { + propertyChangedEvent.clearListeners(); + items.forEach(item -> item.getPropertyChangedEvent().clearListeners()); + } + + public void setRuleName(String ruleName) { + this.ruleName = ruleName; + propertyChanged("ruleName", ruleName); + } + + private void propertyChanged(String name, Object value) { + propertyChangedEvent.invoke(new PropertyChangedArgs(this, name, value)); + } + + public WhenWizardModel withListener(IEventListener listener) { + propertyChangedEvent.add(listener); + return this; + } + + public List validate() { + List errors = new ArrayList<>(); + if (StringUtils.isEmpty(ruleName)) { + errors.add("Rule Name is required"); + } + errors.addAll(items.stream().flatMap(item -> item.validate().stream()).collect(Collectors.toList())); + return errors; + } + + public WhenWizardItemModel addItem() { + WhenWizardItemModel item = new WhenWizardItemModel(MessageValue.Url, eventInfo); + items.add(item); + propertyChanged("items", items); + return item; + } + + private WhenHasEntity toHasEntity(WhenWizardItemModel item) { + WhenHasEntity when = new WhenHasEntity(); + when.setMessageValue(item.getMessageValue()); + when.setIdentifier(VariableString.getAsVariableString(item.getIdentifiers().getSelectedOption(), false)); + return when; + } + + private WhenMatchesText toMatchesText(WhenWizardItemModel item) { + WhenMatchesText when = new WhenMatchesText(); + when.setMatchType(MatchType.valueOf(item.getMatchType().name())); + when.setMessageValue(item.getMessageValue()); + when.setIdentifier(VariableString.getAsVariableString(item.getIdentifiers().getSelectedOption(), false)); + when.setIdentifierPlacement(GetItemPlacement.Last); + when.setUseMessageValue(true); + when.setMatchText(VariableString.getAsVariableString(item.getText())); + return when; + } + + public int removeItem(WhenWizardItemModel whenWizardItemModel) { + int index = items.indexOf(whenWizardItemModel); + items.remove(index); + propertyChanged("items", items); + return index; + } + + public void setInvalidated(boolean invalidated) { + this.invalidated = invalidated; + propertyChanged("invalidated", invalidated); + } + + public boolean createRule() { + if (validate().isEmpty()) { + Rule rule = new Rule(); + LinkedList> whens = new LinkedList<>(); + boolean requiresResponse = false; + for (WhenWizardItemModel item : items) { + requiresResponse |= item.requiresResponse(); + if (item.getMatchType().isMatcher()) { + WhenMatchesText when = toMatchesText(item); + whens.addLast(when); + } else { + WhenHasEntity when = toHasEntity(item); + whens.addLast(when); + } + } + WhenEventDirection direction = new WhenEventDirection(); + direction.setDataDirection(requiresResponse ? DataDirection.Response : DataDirection.Request); + whens.addFirst(direction); + rule.setName(ruleName); + rule.setEnabled(false); + rule.setAutoRun(true); + rule.setWhens(new ArrayList<>(whens)); + BurpExtender.getConnector().getRulesEngine().getRulesRegistry().addRule(rule); + setInvalidated(false); + return true; + } + setInvalidated(true); + return false; + } +} diff --git a/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/WizardMatchType.java b/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/WizardMatchType.java new file mode 100644 index 0000000..cbd5896 --- /dev/null +++ b/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/WizardMatchType.java @@ -0,0 +1,27 @@ +package synfron.reshaper.burp.ui.models.rules.wizard; + +import lombok.Getter; + +public enum WizardMatchType { + Exists("Exists", false), + Equals("Equals", true), + Contains("Contains", true), + BeginsWith("Begins With", true), + EndsWith("Ends With", true), + Regex("Regex", true); + + @Getter + private final String name; + @Getter + private final boolean matcher; + + WizardMatchType(String name, boolean matcher) { + this.name = name; + this.matcher = matcher; + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/synfron/reshaper/burp/ui/models/vars/VariableModel.java b/src/main/java/synfron/reshaper/burp/ui/models/vars/VariableModel.java index 998c524..c1c7fc5 100644 --- a/src/main/java/synfron/reshaper/burp/ui/models/vars/VariableModel.java +++ b/src/main/java/synfron/reshaper/burp/ui/models/vars/VariableModel.java @@ -28,7 +28,7 @@ public class VariableModel { private boolean saved = true; @Getter private final PropertyChangedEvent propertyChangedEvent = new PropertyChangedEvent(); - private IEventListener variableChanged = this::onVariableChanged; + private final IEventListener variableChanged = this::onVariableChanged; public VariableModel() { saved = false; diff --git a/src/main/java/synfron/reshaper/burp/ui/utils/ContextMenuHandler.java b/src/main/java/synfron/reshaper/burp/ui/utils/ContextMenuHandler.java new file mode 100644 index 0000000..07acb59 --- /dev/null +++ b/src/main/java/synfron/reshaper/burp/ui/utils/ContextMenuHandler.java @@ -0,0 +1,53 @@ +package synfron.reshaper.burp.ui.utils; + +import burp.IContextMenuFactory; +import burp.IContextMenuInvocation; +import burp.IHttpRequestResponse; +import synfron.reshaper.burp.core.events.IEventListener; +import synfron.reshaper.burp.core.events.PropertyChangedArgs; +import synfron.reshaper.burp.core.messages.EventInfo; +import synfron.reshaper.burp.core.messages.IEventInfo; +import synfron.reshaper.burp.core.utils.Log; +import synfron.reshaper.burp.ui.components.rules.wizard.WhenWizardOptionPane; +import synfron.reshaper.burp.ui.models.rules.wizard.WhenWizardModel; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.util.Collections; +import java.util.List; + +public class ContextMenuHandler implements IContextMenuFactory { + + private final IEventListener whenWizardChangedListener = this::onWhenWizardChanged; + + private void onWhenWizardChanged(PropertyChangedArgs propertyChangedArgs) { + if (propertyChangedArgs.getName().equals("invalidated") && (boolean)propertyChangedArgs.getValue()) { + WhenWizardModel whenWizardModel = (WhenWizardModel)propertyChangedArgs.getSource(); + openWhenWizard(whenWizardModel); + } + } + + @Override + public List createMenuItems(IContextMenuInvocation invocation) { + JMenuItem menuItem = new JMenuItem("Create Rule"); + menuItem.addActionListener(actionEvent -> onCreateRule(invocation, actionEvent)); + return invocation.getSelectedMessages().length == 1 ? Collections.singletonList(menuItem) : Collections.emptyList(); + } + + private void onCreateRule(IContextMenuInvocation invocation, ActionEvent actionEvent) { + IHttpRequestResponse requestResponse = invocation.getSelectedMessages()[0]; + IEventInfo eventInfo = new EventInfo(null, null, requestResponse); + WhenWizardModel model = new WhenWizardModel(eventInfo); + openWhenWizard(model); + } + + private void openWhenWizard(WhenWizardModel model) { + try { + model.resetPropertyChangedListener(); + model.withListener(whenWizardChangedListener); + WhenWizardOptionPane.showDialog(model); + } catch (Exception e) { + Log.get().withMessage("Failed to create rule from content menu").withException(e).logErr(); + } + } +}