diff --git a/build.gradle b/build.gradle index 6498a76..447a083 100644 --- a/build.gradle +++ b/build.gradle @@ -3,10 +3,10 @@ plugins { } group 'com.synfron.reshaper.burp' -version '2.0.0' +version '2.1.0' -targetCompatibility = '15' -sourceCompatibility = '15' +targetCompatibility = '17' +sourceCompatibility = '17' repositories { mavenCentral() @@ -29,7 +29,7 @@ dependencies { implementation 'org.jsoup:jsoup:1.15.3' compileOnly 'org.projectlombok:lombok:1.18.24' annotationProcessor 'org.projectlombok:lombok:1.18.24' - implementation 'net.portswigger.burp.extensions:montoya-api:0.10.1' + implementation 'net.portswigger.burp.extensions:montoya-api:2023.3' implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.0' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.0' testImplementation 'org.mockito:mockito-core:4.8.0' diff --git a/docs/Readme.md b/docs/Readme.md index f409d68..415fb24 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -40,7 +40,7 @@ Break - Stop Rules or then action processing Build HTTP Message - Build an HTTP request or response message and store the full text in a variable -Comment - Add a comment to the request/response line in the HTTP history +Comment - Add a comment to the line item in the HTTP/WebSocket history Delay - Delay further processing/sending of the HTTP/WebSocket event @@ -52,7 +52,7 @@ Drop - Have Burp drop the connection Evaluate - Perform operations on values -Highlight - Highlight the request/response line in the HTTP history +Highlight - Highlight the line item in the HTTP/WebSocket history Intercept - Intercept the message in the Proxy interceptor diff --git a/docs/Rules.md b/docs/Rules.md index c92700e..0a94fed 100644 --- a/docs/Rules.md +++ b/docs/Rules.md @@ -161,9 +161,9 @@ Destination Variable Name - The name of the variable to hold the built HTTP mess ### Comment -Add a comment to the request/response line in the HTTP history +Add a comment to the line item in the HTTP/WebSocket history -Availability: HTTP +Availability: HTTP, WebSocket #### Fields @@ -231,13 +231,13 @@ Y - Second value. Only available for certain operations. Supports variable tags. ### Highlight -Highlight the request/response line in the HTTP history +Highlight the line item in the HTTP/WebSocket history -Availability: HTTP +Availability: HTTP, WebSocket #### Fields -Color - The color used to highlight the request/response line. +Color - The color used to highlight the line item. ### Intercept diff --git a/docs/Variables.md b/docs/Variables.md index 4441ca9..3efbf65 100644 --- a/docs/Variables.md +++ b/docs/Variables.md @@ -2,13 +2,13 @@ ## Custom Variables -Custom variables allow the sharing of values between Rules and are scoped at the Global, Session (WebSockets only), or Event level. Global, Session, and Event variables can be set by Thens or in the UI (Global variables only). Custom variables are only accessible within Reshaper and are readable by Whens and Thens. +Custom variables allow the sharing of values between Rules and are scoped at the Global, Session, or Event level. Global, Session, and Event variables can be set by Thens or in the UI (Global variables only). Custom variables are only accessible within Reshaper and are readable by Whens and Thens. Event variables are shared among Rules processing a single HTTP event (either request or response). Global variables are shared among Rules across all events for as long as the extension is loaded or until the variables are deleted. Global variables can be set to be Persistent in the Global Variables tab of Reshaper. Persistent variables will be saved and reloaded between Reshaper sessions. -Session variables are shared among Rules processing all WebSocket events within the same WebSocket connection. +Session variables are shared among Rules processing the same HTTP request and response, or all WebSocket events within the same WebSocket connection. ## Accessor Variables @@ -16,7 +16,7 @@ Accessor variables provide access to utility functionality and data that is not Message variables provide access to event message values (e.g. Request URI). -Annotation variables process access to HTTP message annotation values (i.e. comments and highlight colors). HTTP Rules only. +Annotation variables process access to HTTP or WebSocket message annotation values (i.e. comments and highlight colors). File variables provide access to the contents of a text file. @@ -37,7 +37,7 @@ For example, if Global variable named `firstName` has the value `John` and varia **Message Variable Tag (message, m):** `{{message:messageValueKey}}` or `{{message:messageValueKey:identifier}}` (e.g. `{{message:httprequesturi}}`, `{{message:httprequestheader:Host}}`). See [Message Values](MessageValues.html#) -**Annotation Variable Tag (annotation, a):** `{{annotation:comment}}` to get the current comment or `{{annotation:highlightcolor}}` to get the current highlight color of the line item in HTTP history for this event. +**Annotation Variable Tag (annotation, a):** `{{annotation:comment}}` to get the current comment or `{{annotation:highlightcolor}}` to get the current highlight color of the line item in HTTP or WebSocket history for this event. **File Variable Tag (file, f):** `{{file:encoding:filePath}}`. Example: `{{file:utf-8:~/Documents/file.txt}}` diff --git a/src/main/java/burp/BurpExtender.java b/src/main/java/burp/BurpExtender.java index add2694..45c8ed0 100644 --- a/src/main/java/burp/BurpExtender.java +++ b/src/main/java/burp/BurpExtender.java @@ -55,7 +55,7 @@ public void initialize(MontoyaApi api) { api.http().registerHttpHandler(httpConnector); api.http().registerSessionHandlingAction(httpConnector); api.extension().registerUnloadingHandler(this); - api.websockets().registerWebSocketCreationHandler(webSocketConnector); + api.websockets().registerWebSocketCreatedHandler(webSocketConnector); api.userInterface().registerContextMenuItemsProvider(contextMenuHandler); Log.get().withMessage("Reshaper started").log(); diff --git a/src/main/java/synfron/reshaper/burp/core/HttpConnector.java b/src/main/java/synfron/reshaper/burp/core/HttpConnector.java index 185661d..a075769 100644 --- a/src/main/java/synfron/reshaper/burp/core/HttpConnector.java +++ b/src/main/java/synfron/reshaper/burp/core/HttpConnector.java @@ -3,17 +3,15 @@ import burp.BurpExtender; import burp.api.montoya.core.Annotations; import burp.api.montoya.core.ByteArray; -import burp.api.montoya.core.ToolSource; import burp.api.montoya.core.ToolType; -import burp.api.montoya.http.HttpHandler; import burp.api.montoya.http.HttpService; -import burp.api.montoya.http.RequestResult; -import burp.api.montoya.http.ResponseResult; -import burp.api.montoya.http.message.HttpRequestResponse; +import burp.api.montoya.http.handler.*; import burp.api.montoya.http.message.requests.HttpRequest; import burp.api.montoya.http.message.responses.HttpResponse; +import burp.api.montoya.http.sessions.ActionResult; import burp.api.montoya.http.sessions.SessionHandlingAction; -import burp.api.montoya.proxy.*; +import burp.api.montoya.http.sessions.SessionHandlingActionData; +import burp.api.montoya.proxy.http.*; import lombok.Getter; import lombok.Setter; import net.jodah.expiringmap.ExpiringMap; @@ -23,33 +21,33 @@ import synfron.reshaper.burp.core.messages.entities.http.HttpRequestMessage; import synfron.reshaper.burp.core.rules.RulesEngine; import synfron.reshaper.burp.core.utils.Log; +import synfron.reshaper.burp.core.utils.ObjectUtils; +import synfron.reshaper.burp.core.vars.Variables; import java.io.*; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; -import java.util.ArrayList; import java.util.InputMismatchException; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; public class HttpConnector implements - ProxyHttpRequestHandler, - ProxyHttpResponseHandler, - HttpHandler, - SessionHandlingAction -{ + SessionHandlingAction, + ProxyRequestHandler, + ProxyResponseHandler, + HttpHandler { - private static final AtomicInteger lastMessageId = new AtomicInteger(1); @Getter private final RulesEngine rulesEngine = new RulesEngine(); private ServerSocket serverSocket; - private final Map continuationMap = ExpiringMap.builder() + private final Map continuationMap = ExpiringMap.builder() .expiration(30, TimeUnit.SECONDS).build(); + private final Map sessionVariableMap = ExpiringMap.builder() + .expiration(1, TimeUnit.DAYS).build(); private ExecutorService serverExecutor; private final String dataDirectionWarning = "Sanity Check - Warning: The %s changed but the data direction is set to %s. Your changes may have no impact. Consider using 'When Data Direction' or 'Then Set Data Direction' to restrict or change the data direction."; private final String interceptWarning = "Sanity Check - Warning: Cannot intercept unless the message is captured from the Proxy tool."; @@ -84,10 +82,10 @@ private void processServerConnection(Socket socket) { try { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); bufferedReader.readLine(); - int reshaperId = getReshaperId(bufferedReader.readLine()); - HttpEventInfo eventInfo = continuationMap.get(reshaperId); + String messageId = getReshaperId(bufferedReader.readLine()); + HttpEventInfo eventInfo = continuationMap.get(messageId); if (eventInfo.isShouldDrop()) { - continuationMap.remove(reshaperId); + continuationMap.remove(messageId); close(socket); } BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream()); @@ -136,10 +134,11 @@ private void close(Closeable closeable) { } } - private HttpEventInfo asEventInfo(boolean messageIsRequest, BurpTool burpTool, HttpRequest httpRequest, HttpResponse httpResponse, Annotations annotations) { + private HttpEventInfo asEventInfo(boolean messageIsRequest, BurpTool burpTool, String messageId, HttpRequest httpRequest, HttpResponse httpResponse, Annotations annotations) { return asEventInfo( messageIsRequest, burpTool, + messageId, httpRequest, httpResponse, annotations, @@ -148,40 +147,42 @@ private HttpEventInfo asEventInfo(boolean messageIsRequest, BurpTool burpTool, H ); } - private HttpEventInfo asEventInfo(boolean messageIsRequest, BurpTool burpTool, HttpRequest httpRequest, HttpResponse httpResponse, Annotations annotations, String proxyName, InetAddress sourceIpAddress) { + private HttpEventInfo asEventInfo(boolean messageIsRequest, BurpTool burpTool, String messageId, HttpRequest httpRequest, HttpResponse httpResponse, Annotations annotations, String proxyName, InetAddress sourceIpAddress) { if (!messageIsRequest && httpRequest.httpService().port() == serverSocket.getLocalPort() && httpRequest.httpService().host().equals(serverSocket.getInetAddress().getHostAddress()) ) { - int reshaperId = Integer.parseInt(httpRequest.headers().get(1).value()); - HttpEventInfo requestEventInfo = continuationMap.remove(reshaperId); + HttpEventInfo requestEventInfo = continuationMap.remove(messageId); httpRequest = requestEventInfo.getInitialHttpRequest(); } + Variables sessionVariables = !messageIsRequest ? Variables.defaultVariables(sessionVariableMap.get(messageId)) : new Variables(); HttpEventInfo eventInfo = new HttpEventInfo( messageIsRequest ? HttpDataDirection.Request : HttpDataDirection.Response, burpTool, + messageId, httpRequest, httpResponse, annotations, proxyName, - sourceIpAddress != null ? sourceIpAddress.getHostAddress() : "burp::" + sourceIpAddress != null ? sourceIpAddress.getHostAddress() : "burp::", + sessionVariables ); eventInfo.getDiagnostics().setEventEnabled(BurpExtender.getGeneralSettings().isEnableEventDiagnostics()); return eventInfo; } - private int getReshaperId(String header) { + private String getReshaperId(String header) { if (!header.startsWith("Reshaper-ID:")) { throw new InputMismatchException("No Reshaper-ID found"); } - return Integer.parseInt(header.split(":", 2)[1].trim()); + return header.split(":", 2)[1].trim(); } private EventResult processEvent(boolean isRequest, HttpEventInfo eventInfo, boolean isIntercept) { - int messageId = isRequest ? lastMessageId.getAndIncrement() : -1; EventResult eventResult = new EventResult(eventInfo); try { rulesEngine.run(eventInfo); + storeSessionVariables(eventInfo); if (eventInfo.isChanged()) { sanityCheck(eventInfo); if (eventInfo.getDataDirection() == HttpDataDirection.Request) { @@ -189,13 +190,13 @@ private EventResult processEvent(boolean isRequest, HttpEventInfo eventInfo, boo if (isIntercept) { eventResult.setInterceptResponse(InterceptResponse.Drop); } else { - sendToSelf(messageId, eventInfo); + sendToSelf(eventInfo); } } else { eventResult.setInterceptResponse(eventInfo.getDefaultInterceptResponse()); } } else if (isRequest && eventInfo.getDataDirection() == HttpDataDirection.Response) { - sendToSelf(messageId, eventInfo); + sendToSelf(eventInfo); if (isIntercept) { eventResult.setInterceptResponse(InterceptResponse.Disable); } @@ -213,6 +214,15 @@ private EventResult processEvent(boolean isRequest, HttpEventInfo eventInfo, boo return eventResult; } + private void storeSessionVariables(HttpEventInfo eventInfo) { + Variables sessionVariables = eventInfo.getSessionVariables(); + if (sessionVariables.size() > 0 && eventInfo.getInitialDataDirection() == HttpDataDirection.Request && !eventInfo.isShouldDrop()) { + sessionVariableMap.put(eventInfo.getMessageId(), sessionVariables); + } else if (eventInfo.getInitialDataDirection() == HttpDataDirection.Response) { + sessionVariableMap.remove(eventInfo.getMessageId()); + } + } + private void sanityCheck(HttpEventInfo eventInfo) { if (BurpExtender.getGeneralSettings().isEnableSanityCheckWarnings()) { if (eventInfo.isRequestChanged() && eventInfo.getDataDirection() == HttpDataDirection.Response) { @@ -232,22 +242,24 @@ private void sanityCheck(HttpEventInfo eventInfo) { } } - private void sendToSelf(int messageId, HttpEventInfo eventInfo) { + private void sendToSelf(HttpEventInfo eventInfo) { HttpRequestMessage requestMessage = eventInfo.getHttpRequestMessage(); - List headers = new ArrayList<>(); - headers.add(requestMessage.getStatusLine().getValue()); - headers.add(String.format("%s: %s", "Reshaper-ID", messageId)); - headers.addAll(requestMessage.getHeaders().getValue()); + List headers = requestMessage.getHeaders().getValue(); + headers.add(0, "Reshaper-ID: " + eventInfo.getMessageId()); + byte[] modifiedRequest = ObjectUtils.asHttpMessage( + requestMessage.getStatusLine().getValue(), + headers, + requestMessage.getBody().getValue() + ); HttpRequest httpRequest = HttpRequest.httpRequest( HttpService.httpService( serverSocket.getInetAddress().getHostAddress(), serverSocket.getLocalPort(), false ), - headers, - ByteArray.byteArray(requestMessage.getBody().getValue()) + ByteArray.byteArray(modifiedRequest) ); - continuationMap.put(messageId, eventInfo); + continuationMap.put(eventInfo.getMessageId(), eventInfo); eventInfo.setHttpRequestOverride(httpRequest); } @@ -257,73 +269,77 @@ private BurpTool getBurpToolIfEnabled(ToolType toolType) { } @Override - public RequestResult handleHttpRequest(HttpRequest request, Annotations annotations, ToolSource toolSource) { - if (!toolSource.isFromTool(ToolType.PROXY)) { - BurpTool burpTool = getBurpToolIfEnabled(toolSource.toolType()); - if (burpTool != null && burpTool != BurpTool.Proxy) { - HttpEventInfo eventInfo = asEventInfo(true, burpTool, request, null, annotations); - processEvent(true, eventInfo, false); - return RequestResult.requestResult(eventInfo.asHttpRequest(), eventInfo.getAnnotations()); - } + public ProxyRequestReceivedAction handleRequestReceived(InterceptedRequest interceptedRequest) { + if (BurpExtender.getGeneralSettings().isCapture(BurpTool.Proxy)) { + HttpEventInfo eventInfo = asEventInfo(true, BurpTool.Proxy, getMessageId(BurpTool.Proxy, interceptedRequest.messageId()), interceptedRequest, null, interceptedRequest.annotations(), interceptedRequest.listenerInterface(), interceptedRequest.sourceIpAddress()); + return processEvent(true, eventInfo, true).asProxyRequestAction(); + } + else { + return ProxyRequestReceivedAction.continueWith(interceptedRequest, interceptedRequest.annotations()); } - return RequestResult.requestResult(request, annotations); } @Override - public ResponseResult handleHttpResponse(HttpResponse response, HttpRequest initiatingRequest, Annotations annotations, ToolSource toolSource) { - if (toolSource.toolType() != ToolType.PROXY) { - BurpTool burpTool = getBurpToolIfEnabled(toolSource.toolType()); + public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent requestToBeSent) { + if (!requestToBeSent.toolSource().isFromTool(ToolType.PROXY)) { + BurpTool burpTool = getBurpToolIfEnabled(requestToBeSent.toolSource().toolType()); if (burpTool != null && burpTool != BurpTool.Proxy) { - HttpEventInfo eventInfo = asEventInfo(false, burpTool, initiatingRequest, response, annotations); - processEvent(false, eventInfo, false); - return ResponseResult.responseResult(eventInfo.asHttpResponse(), eventInfo.getAnnotations()); + HttpEventInfo eventInfo = asEventInfo(true, burpTool, getMessageId(burpTool, requestToBeSent.messageId()), requestToBeSent, null, requestToBeSent.annotations()); + processEvent(true, eventInfo, false); + return RequestToBeSentAction.continueWith(eventInfo.asHttpRequest(), eventInfo.getAnnotations()); } } - return ResponseResult.responseResult(response, annotations); + return RequestToBeSentAction.continueWith(requestToBeSent); } @Override - public String name() { - return "Reshaper"; + public ActionResult performAction(SessionHandlingActionData actionData) { + HttpEventInfo eventInfo = asEventInfo(true, BurpTool.Session, null, actionData.request(), null, actionData.annotations()); + processEvent(true, eventInfo, false); + return ActionResult.actionResult(eventInfo.asHttpRequest(), eventInfo.getAnnotations()); } @Override - public RequestResult handle(HttpRequest currentRequest, Annotations annotations, List macroRequestResponses) { - HttpEventInfo eventInfo = asEventInfo(true, BurpTool.Session, currentRequest, null, annotations); - processEvent(true, eventInfo, false); - return RequestResult.requestResult(eventInfo.asHttpRequest(), eventInfo.getAnnotations()); + public ProxyRequestToBeSentAction handleRequestToBeSent(InterceptedRequest interceptedRequest) { + return ProxyRequestToBeSentAction.continueWith(interceptedRequest); } @Override - public RequestInitialInterceptResult handleReceivedRequest(InterceptedHttpRequest interceptedRequest, Annotations annotations) { + public ProxyResponseReceivedAction handleResponseReceived(InterceptedResponse interceptedResponse) { if (BurpExtender.getGeneralSettings().isCapture(BurpTool.Proxy)) { - HttpEventInfo eventInfo = asEventInfo(true, BurpTool.Proxy, interceptedRequest, null, annotations, interceptedRequest.listenerInterface(), interceptedRequest.sourceIpAddress()); - return processEvent(true, eventInfo, true).asRequestInterceptResult(); + HttpEventInfo eventInfo = asEventInfo(false, BurpTool.Proxy, getMessageId(BurpTool.Proxy, interceptedResponse.messageId()), interceptedResponse.initiatingRequest(), interceptedResponse, interceptedResponse.annotations()); + return processEvent(false, eventInfo, true).asProxyResponseAction(); } else { - return RequestInitialInterceptResult.followUserRules(interceptedRequest, annotations); + return ProxyResponseReceivedAction.continueWith(interceptedResponse); } } @Override - public RequestFinalInterceptResult handleRequestToIssue(InterceptedHttpRequest interceptedRequest, Annotations annotations) { - return RequestFinalInterceptResult.continueWith(interceptedRequest, annotations); + public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) { + if (responseReceived.toolSource().toolType() != ToolType.PROXY) { + BurpTool burpTool = getBurpToolIfEnabled(responseReceived.toolSource().toolType()); + if (burpTool != null && burpTool != BurpTool.Proxy) { + HttpEventInfo eventInfo = asEventInfo(false, burpTool, getMessageId(burpTool, responseReceived.messageId()), responseReceived.initiatingRequest(), responseReceived, responseReceived.annotations()); + processEvent(false, eventInfo, false); + return ResponseReceivedAction.continueWith(eventInfo.asHttpResponse(), eventInfo.getAnnotations()); + } + } + return ResponseReceivedAction.continueWith(responseReceived); + } + + private String getMessageId(BurpTool burpTool, int messageId) { + return String.format("%s_%s", burpTool, messageId); } @Override - public ResponseInitialInterceptResult handleReceivedResponse(InterceptedHttpResponse interceptedResponse, HttpRequest initiatingRequest, Annotations annotations) { - if (BurpExtender.getGeneralSettings().isCapture(BurpTool.Proxy)) { - HttpEventInfo eventInfo = asEventInfo(false, BurpTool.Proxy, initiatingRequest, interceptedResponse, annotations); - return processEvent(false, eventInfo, true).asResponseInterceptResult(); - } - else { - return ResponseInitialInterceptResult.followUserRules(interceptedResponse, annotations); - } + public ProxyResponseToBeSentAction handleResponseToBeSent(InterceptedResponse interceptedResponse) { + return ProxyResponseToBeSentAction.continueWith(interceptedResponse); } @Override - public ResponseFinalInterceptResult handleResponseToReturn(InterceptedHttpResponse interceptedResponse, HttpRequest initiatingRequest, Annotations annotations) { - return ResponseFinalInterceptResult.continueWith(interceptedResponse, annotations); + public String name() { + return "Reshaper"; } public static class EventResult { @@ -344,25 +360,21 @@ public HttpResponse getResponse() { return eventInfo.asHttpResponse(); } - public Annotations getAnnotations() { - return eventInfo.getAnnotations(); - } - - public RequestInitialInterceptResult asRequestInterceptResult() { + public ProxyRequestReceivedAction asProxyRequestAction() { return switch (interceptResponse) { - case UserDefined -> RequestInitialInterceptResult.followUserRules(getRequest(), getAnnotations()); - case Drop -> RequestInitialInterceptResult.drop(); - case Intercept -> RequestInitialInterceptResult.intercept(getRequest(), getAnnotations()); - case Disable -> RequestInitialInterceptResult.doNotIntercept(getRequest(), getAnnotations()); + case UserDefined -> ProxyRequestReceivedAction.continueWith(getRequest()); + case Drop -> ProxyRequestReceivedAction.drop(); + case Intercept -> ProxyRequestReceivedAction.intercept(getRequest()); + case Disable -> ProxyRequestReceivedAction.doNotIntercept(getRequest()); }; } - public ResponseInitialInterceptResult asResponseInterceptResult() { + public ProxyResponseReceivedAction asProxyResponseAction() { return switch (interceptResponse) { - case UserDefined -> ResponseInitialInterceptResult.followUserRules(getResponse(), getAnnotations()); - case Drop -> ResponseInitialInterceptResult.drop(); - case Intercept -> ResponseInitialInterceptResult.intercept(getResponse(), getAnnotations()); - case Disable -> ResponseInitialInterceptResult.doNotIntercept(getResponse(), getAnnotations()); + case UserDefined -> ProxyResponseReceivedAction.continueWith(getResponse()); + case Drop -> ProxyResponseReceivedAction.drop(); + case Intercept -> ProxyResponseReceivedAction.intercept(getResponse()); + case Disable -> ProxyResponseReceivedAction.doNotIntercept(getResponse()); }; } } diff --git a/src/main/java/synfron/reshaper/burp/core/WebSocketConnector.java b/src/main/java/synfron/reshaper/burp/core/WebSocketConnector.java index 2d07d60..37e4c84 100644 --- a/src/main/java/synfron/reshaper/burp/core/WebSocketConnector.java +++ b/src/main/java/synfron/reshaper/burp/core/WebSocketConnector.java @@ -1,25 +1,25 @@ package synfron.reshaper.burp.core; import burp.BurpExtender; +import burp.api.montoya.core.Annotations; import burp.api.montoya.core.ByteArray; -import burp.api.montoya.core.ToolSource; import burp.api.montoya.core.ToolType; import burp.api.montoya.http.message.requests.HttpRequest; -import burp.api.montoya.internal.ObjectFactoryLocator; -import burp.api.montoya.proxy.*; +import burp.api.montoya.proxy.websocket.*; import burp.api.montoya.websocket.*; import lombok.Getter; import lombok.Setter; import synfron.reshaper.burp.core.messages.WebSocketDataDirection; -import synfron.reshaper.burp.core.messages.WebSocketMessageType; import synfron.reshaper.burp.core.messages.WebSocketEventInfo; +import synfron.reshaper.burp.core.messages.WebSocketMessageSender; +import synfron.reshaper.burp.core.messages.WebSocketMessageType; import synfron.reshaper.burp.core.rules.RulesEngine; import synfron.reshaper.burp.core.utils.Log; import synfron.reshaper.burp.core.vars.Variables; -import java.util.function.BiConsumer; - -public class WebSocketConnector implements ProxyWebSocketCreationHandler, WebSocketCreationHandler { +public class WebSocketConnector implements + ProxyWebSocketCreationHandler, + WebSocketCreatedHandler { @Getter private final RulesEngine rulesEngine = new RulesEngine(); @@ -29,25 +29,25 @@ private BurpTool getBurpToolIfEnabled(ToolType toolType) { } @Override - public void handleWebSocketCreated(ProxyWebSocket proxyWebSocket, HttpRequest httpRequest) { + public void handleWebSocketCreation(ProxyWebSocketCreation webSocketCreation) { if (BurpExtender.getGeneralSettings().isCapture(BurpTool.Proxy) && BurpExtender.getGeneralSettings().isCapture(BurpTool.WebSockets)) { - proxyWebSocket.registerHandler(new WebSocketMessageConnector(BurpTool.Proxy, proxyWebSocket, httpRequest)); + webSocketCreation.proxyWebSocket().registerProxyMessageHandler(new WebSocketMessageConnector(BurpTool.Proxy, webSocketCreation.proxyWebSocket(), webSocketCreation.upgradeRequest())); } } @Override - public void handleWebSocketCreated(WebSocket webSocket, HttpRequest httpRequest, ToolSource toolSource) { - if (!toolSource.isFromTool(ToolType.PROXY)) { - BurpTool burpTool = getBurpToolIfEnabled(toolSource.toolType()); + public void handleWebSocketCreated(WebSocketCreated webSocketCreated) { + if (!webSocketCreated.toolSource().isFromTool(ToolType.PROXY)) { + BurpTool burpTool = getBurpToolIfEnabled(webSocketCreated.toolSource().toolType()); if (burpTool != null && burpTool != BurpTool.Proxy && BurpExtender.getGeneralSettings().isCapture(BurpTool.WebSockets)) { - webSocket.registerHandler(new WebSocketMessageConnector(BurpTool.Proxy, webSocket, httpRequest)); + webSocketCreated.webSocket().registerMessageHandler(new WebSocketMessageConnector(BurpTool.Proxy, webSocketCreated.webSocket(), webSocketCreated.upgradeRequest())); } } } - private class WebSocketMessageConnector implements ProxyWebSocketHandler, WebSocketHandler { + private class WebSocketMessageConnector implements ProxyMessageHandler, MessageHandler { - private final BiConsumer messageSender; + private final WebSocketMessageSender messageSender; private final HttpRequest httpRequest; private final BurpTool burpTool; @@ -56,66 +56,56 @@ private class WebSocketMessageConnector implements ProxyWebSocketHandler, WebSoc public WebSocketMessageConnector(BurpTool burpTool, ProxyWebSocket webSocket, HttpRequest httpRequest) { this.burpTool = burpTool; - this.messageSender = (WebSocketDataDirection dataDirection, String message) -> webSocket.sendTextMessage(message, dataDirection.toDirection()); + this.messageSender = new WebSocketMessageSender(webSocket); this.httpRequest = httpRequest; } public WebSocketMessageConnector(BurpTool burpTool, WebSocket webSocket, HttpRequest httpRequest) { this.burpTool = burpTool; - this.messageSender = (WebSocketDataDirection dataDirection, String message) -> { - if (dataDirection == WebSocketDataDirection.Client && burpTool != BurpTool.Proxy) { - throw new UnsupportedOperationException("Can only send client messages for Proxy WebSocket connections"); - } - webSocket.sendTextMessage(message); - }; + this.messageSender = new WebSocketMessageSender(webSocket); this.httpRequest = httpRequest; } @Override - public ProxyWebSocketInitialInterceptTextMessage handleTextMessageReceived(String text, Direction direction) { + public TextMessageReceivedAction handleTextMessageReceived(InterceptedTextMessage interceptedTextMessage) { if (BurpExtender.getGeneralSettings().isCapture(BurpTool.WebSockets)) { - WebSocketEventInfo eventInfo = asEventInfo(WebSocketMessageType.Text, text, direction); - return processEvent(eventInfo).asTextProxyInterceptResult(); + WebSocketEventInfo eventInfo = asEventInfo(WebSocketMessageType.Text, interceptedTextMessage.annotations(), interceptedTextMessage.payload(), interceptedTextMessage.direction()); + return processEvent(eventInfo).asProxyTextAction(); } - return ObjectFactoryLocator.FACTORY.proxyWebSocketTextMessage(text, InitialInterceptAction.FOLLOW_USER_RULES); + return TextMessageReceivedAction.continueWith(interceptedTextMessage); } @Override - public WebSocketTextMessage handleTextMessage(String text, Direction direction) { + public TextMessageAction handleTextMessage(TextMessage textMessage) { if (BurpExtender.getGeneralSettings().isCapture(BurpTool.WebSockets)) { - WebSocketEventInfo eventInfo = asEventInfo(WebSocketMessageType.Text, text, direction); - return processEvent(eventInfo).asTextInterceptResult(); + WebSocketEventInfo eventInfo = asEventInfo(WebSocketMessageType.Text, null, textMessage.payload(), textMessage.direction()); + return processEvent(eventInfo).asTextAction(); } - return WebSocketTextMessage.continueWithTextMessage(text); + return TextMessageAction.continueWith(textMessage); } @Override - public ProxyWebSocketInitialInterceptBinaryMessage handleBinaryMessageReceived(ByteArray byteArray, Direction direction) { + public BinaryMessageReceivedAction handleBinaryMessageReceived(InterceptedBinaryMessage interceptedBinaryMessage) { if (BurpExtender.getGeneralSettings().isCapture(BurpTool.WebSockets)) { - WebSocketEventInfo eventInfo = asEventInfo(WebSocketMessageType.Binary, byteArray.getBytes(), direction); - return processEvent(eventInfo).asBinaryProxyInterceptResult(); + WebSocketEventInfo eventInfo = asEventInfo(WebSocketMessageType.Binary, interceptedBinaryMessage.annotations(), interceptedBinaryMessage.payload().getBytes(), interceptedBinaryMessage.direction()); + return processEvent(eventInfo).asProxyBinaryAction(); } - return ObjectFactoryLocator.FACTORY.proxyWebSocketBinaryMessage(byteArray, InitialInterceptAction.FOLLOW_USER_RULES); + return BinaryMessageReceivedAction.continueWith(interceptedBinaryMessage); } @Override - public WebSocketBinaryMessage handleBinaryMessage(ByteArray byteArray, Direction direction) { + public BinaryMessageAction handleBinaryMessage(BinaryMessage binaryMessage) { if (BurpExtender.getGeneralSettings().isCapture(BurpTool.WebSockets)) { - WebSocketEventInfo eventInfo = asEventInfo(WebSocketMessageType.Binary, byteArray.getBytes(), direction); - return processEvent(eventInfo).asBinaryInterceptResult(); + WebSocketEventInfo eventInfo = asEventInfo(WebSocketMessageType.Binary, null, binaryMessage.payload().getBytes(), binaryMessage.direction()); + return processEvent(eventInfo).asBinaryAction(); } - return WebSocketBinaryMessage.continueWithBinaryMessage(byteArray); + return BinaryMessageAction.continueWith(binaryMessage); } - private WebSocketEventInfo asEventInfo(WebSocketMessageType messageType, T data, Direction direction) { + private WebSocketEventInfo asEventInfo(WebSocketMessageType messageType, Annotations annotations, T data, Direction direction) { WebSocketEventInfo eventInfo = new WebSocketEventInfo<>( messageType, - sessionVariables, - WebSocketDataDirection.from(direction), - burpTool, - messageSender, - httpRequest, - data + WebSocketDataDirection.from(direction), burpTool, messageSender, httpRequest, annotations, data, sessionVariables ); eventInfo.getDiagnostics().setEventEnabled(BurpExtender.getGeneralSettings().isEnableEventDiagnostics()); return eventInfo; @@ -143,18 +133,17 @@ private EventResult processEvent(WebSocketEventInfo eventInfo) { } @Override - public ProxyWebSocketFinalInterceptTextMessage handleTextMessageToBeIssued(String text, Direction direction) { - return ProxyWebSocketFinalInterceptTextMessage.continueWithTextMessage(text); + public TextMessageToBeSentAction handleTextMessageToBeSent(InterceptedTextMessage interceptedTextMessage) { + return TextMessageToBeSentAction.continueWith(interceptedTextMessage); } @Override - public ProxyWebSocketFinalInterceptBinaryMessage handleBinaryMessageToBeIssued(ByteArray byteArray, Direction direction) { - return ProxyWebSocketFinalInterceptBinaryMessage.continueWithBinaryMessage(byteArray); + public BinaryMessageToBeSentAction handleBinaryMessageToBeSent(InterceptedBinaryMessage interceptedBinaryMessage) { + return BinaryMessageToBeSentAction.continueWith(interceptedBinaryMessage); } @Override public void onClose() { - ProxyWebSocketHandler.super.onClose(); } } @@ -172,39 +161,39 @@ public T getData() { return eventInfo.getData(); } - public ProxyWebSocketInitialInterceptTextMessage asTextProxyInterceptResult() { + public TextMessageReceivedAction asProxyTextAction() { return switch (interceptResponse) { case UserDefined -> - ObjectFactoryLocator.FACTORY.proxyWebSocketTextMessage((String) eventInfo.getData(), InitialInterceptAction.FOLLOW_USER_RULES); - case Disable -> ProxyWebSocketInitialInterceptTextMessage.doNotInterceptTextMessage((String) eventInfo.getData()); - case Drop -> ProxyWebSocketInitialInterceptTextMessage.dropTextMessage(); - case Intercept -> ProxyWebSocketInitialInterceptTextMessage.interceptTextMessage((String) eventInfo.getData()); + TextMessageReceivedAction.continueWith((String) eventInfo.getData()); + case Disable -> TextMessageReceivedAction.doNotIntercept((String) eventInfo.getData()); + case Drop -> TextMessageReceivedAction.drop(); + case Intercept -> TextMessageReceivedAction.intercept((String) eventInfo.getData()); }; } - public WebSocketTextMessage asTextInterceptResult() { + public TextMessageAction asTextAction() { return switch (interceptResponse) { case UserDefined, Disable, Intercept -> - WebSocketTextMessage.continueWithTextMessage((String)getData()); - case Drop -> WebSocketTextMessage.dropTextMessage(); + TextMessageAction.continueWith((String)getData()); + case Drop -> TextMessageAction.drop(); }; } - public ProxyWebSocketInitialInterceptBinaryMessage asBinaryProxyInterceptResult() { + public BinaryMessageReceivedAction asProxyBinaryAction() { return switch (interceptResponse) { case UserDefined -> - ObjectFactoryLocator.FACTORY.proxyWebSocketBinaryMessage(ByteArray.byteArray((byte[]) eventInfo.getData()), InitialInterceptAction.FOLLOW_USER_RULES); - case Disable -> ProxyWebSocketInitialInterceptBinaryMessage.doNotInterceptBinaryMessage(ByteArray.byteArray((byte[]) eventInfo.getData())); - case Drop -> ProxyWebSocketInitialInterceptBinaryMessage.dropBinaryMessage(); - case Intercept -> ProxyWebSocketInitialInterceptBinaryMessage.interceptBinaryMessage(ByteArray.byteArray((byte[]) eventInfo.getData())); + BinaryMessageReceivedAction.continueWith(ByteArray.byteArray((byte[]) eventInfo.getData())); + case Disable -> BinaryMessageReceivedAction.doNotIntercept(ByteArray.byteArray((byte[]) eventInfo.getData())); + case Drop -> BinaryMessageReceivedAction.drop(); + case Intercept -> BinaryMessageReceivedAction.intercept(ByteArray.byteArray((byte[]) eventInfo.getData())); }; } - public WebSocketBinaryMessage asBinaryInterceptResult() { + public BinaryMessageAction asBinaryAction() { return switch (interceptResponse) { case UserDefined, Disable, Intercept -> - WebSocketBinaryMessage.continueWithBinaryMessage(ByteArray.byteArray((byte[]) getData())); - case Drop -> WebSocketBinaryMessage.dropBinaryMessage(); + BinaryMessageAction.continueWith(ByteArray.byteArray((byte[]) getData())); + case Drop -> BinaryMessageAction.drop(); }; } } diff --git a/src/main/java/synfron/reshaper/burp/core/messages/ContentType.java b/src/main/java/synfron/reshaper/burp/core/messages/ContentType.java index 0b2b01f..9402bbe 100644 --- a/src/main/java/synfron/reshaper/burp/core/messages/ContentType.java +++ b/src/main/java/synfron/reshaper/burp/core/messages/ContentType.java @@ -59,7 +59,7 @@ private int getId() { return ids.length == 1 ? ids[0] : Unknown.getId(); } - public static ContentType get(burp.api.montoya.http.ContentType contentType) { + public static ContentType get(burp.api.montoya.http.message.ContentType contentType) { return switch (contentType) { case AMF -> Amf; case XML -> Xml; diff --git a/src/main/java/synfron/reshaper/burp/core/messages/EventInfo.java b/src/main/java/synfron/reshaper/burp/core/messages/EventInfo.java index a25e64d..213c3e7 100644 --- a/src/main/java/synfron/reshaper/burp/core/messages/EventInfo.java +++ b/src/main/java/synfron/reshaper/burp/core/messages/EventInfo.java @@ -1,8 +1,10 @@ package synfron.reshaper.burp.core.messages; import burp.BurpExtender; +import burp.api.montoya.core.Annotations; import burp.api.montoya.http.message.requests.HttpRequest; import lombok.Getter; +import lombok.Setter; import synfron.reshaper.burp.core.BurpTool; import synfron.reshaper.burp.core.InterceptResponse; import synfron.reshaper.burp.core.ProtocolType; @@ -15,6 +17,8 @@ public abstract class EventInfo { @Getter protected final HttpRequest initialHttpRequest; + @Getter @Setter + private Annotations annotations; @Getter protected final BurpTool burpTool; @Getter @@ -32,22 +36,28 @@ public abstract class EventInfo { @Getter protected final Variables variables = new Variables(); @Getter + private final Variables sessionVariables; + @Getter protected final Encoder encoder = new Encoder(BurpExtender.getGeneralSettings().getDefaultEncoding()); protected boolean changed; @Getter protected final IDiagnostics diagnostics = new Diagnostics(); - public EventInfo(BurpTool burpTool, HttpRequest httpRequest) { + public EventInfo(BurpTool burpTool, HttpRequest httpRequest, Annotations annotations, Variables sessionVariables) { + this.sessionVariables = sessionVariables; this.burpTool = burpTool; this.initialHttpRequest = httpRequest; + this.annotations = annotations; httpRequestMessage = new HttpRequestMessage(httpRequest, encoder); destinationPort = httpRequest.httpService().port(); destinationAddress = httpRequest.httpService().host(); } protected EventInfo(EventInfo sourceEventInfo) { + this.sessionVariables = sourceEventInfo.getSessionVariables(); this.burpTool = sourceEventInfo.getBurpTool(); this.initialHttpRequest = null; + this.annotations = null; this.encoder.setEncoding(sourceEventInfo.getEncoder().getEncoding(), sourceEventInfo.getEncoder().isAutoSet()); httpRequestMessage = new HttpRequestMessage(sourceEventInfo.getHttpRequestMessage().getValue(), encoder); httpProtocol = sourceEventInfo.getHttpProtocol(); diff --git a/src/main/java/synfron/reshaper/burp/core/messages/HttpEventInfo.java b/src/main/java/synfron/reshaper/burp/core/messages/HttpEventInfo.java index 28ff1e7..6643b87 100644 --- a/src/main/java/synfron/reshaper/burp/core/messages/HttpEventInfo.java +++ b/src/main/java/synfron/reshaper/burp/core/messages/HttpEventInfo.java @@ -6,19 +6,17 @@ import burp.api.montoya.http.message.requests.HttpRequest; import burp.api.montoya.http.message.responses.HttpResponse; import lombok.Getter; -import lombok.Setter; import org.apache.commons.lang3.StringUtils; import synfron.reshaper.burp.core.BurpTool; import synfron.reshaper.burp.core.ProtocolType; import synfron.reshaper.burp.core.messages.entities.http.HttpRequestMessage; import synfron.reshaper.burp.core.messages.entities.http.HttpResponseMessage; import synfron.reshaper.burp.core.utils.Url; +import synfron.reshaper.burp.core.vars.Variables; public class HttpEventInfo extends EventInfo { @Getter private final HttpResponse initialHttpResponse; - @Getter @Setter - private Annotations annotations; @Getter private final HttpDataDirection initialDataDirection; @Getter @@ -29,22 +27,24 @@ public class HttpEventInfo extends EventInfo { protected final String proxyName; @Getter protected final String sourceAddress; + @Getter + private String messageId; private HttpRequest httpRequestOverride; - public HttpEventInfo(HttpDataDirection dataDirection, BurpTool burpTool, HttpRequest httpRequest, HttpResponse httpResponse, Annotations annotations, String proxyName, String sourceAddress) { - super(burpTool, httpRequest); + public HttpEventInfo(HttpDataDirection dataDirection, BurpTool burpTool, String messageId, HttpRequest httpRequest, HttpResponse httpResponse, Annotations annotations, String proxyName, String sourceAddress, Variables sessionVariables) { + super(burpTool, httpRequest, annotations, sessionVariables); this.initialDataDirection = dataDirection; this.dataDirection = dataDirection; + this.messageId = messageId; this.httpProtocol = httpRequest.httpService().secure() ? "https" : "http"; this.initialHttpResponse = httpResponse; - this.annotations = annotations; this.httpResponseMessage = new HttpResponseMessage(httpResponse, encoder); this.sourceAddress = sourceAddress; this.proxyName = proxyName; } - public HttpEventInfo(HttpDataDirection dataDirection, BurpTool burpTool, HttpRequest httpRequest, HttpResponse httpResponse, Annotations annotations) { - this(dataDirection, burpTool, httpRequest, httpResponse, annotations, null, "burp::"); + public HttpEventInfo(HttpDataDirection dataDirection, BurpTool burpTool, String messageId, HttpRequest httpRequest, HttpResponse httpResponse, Annotations annotations, Variables sessionVariables) { + this(dataDirection, burpTool, messageId, httpRequest, httpResponse, annotations, null, "burp::", sessionVariables); } public HttpEventInfo(EventInfo sourceRequestEventInfo) { @@ -52,7 +52,6 @@ public HttpEventInfo(EventInfo sourceRequestEventInfo) { this.dataDirection = HttpDataDirection.Request; this.initialDataDirection = HttpDataDirection.Request; this.initialHttpResponse = null; - this.annotations = null; this.sourceAddress = "burp::"; this.proxyName = null; } @@ -121,7 +120,7 @@ public boolean isSecure() { public HttpRequest asHttpRequest() { return httpRequestOverride != null ? httpRequestOverride : (isChanged() || initialHttpRequest == null ? - HttpRequest.httpRequest(ByteArray.byteArray(httpRequestMessage.getValue())) + httpRequestMessage.asAdjustedHttpRequest() .withService(HttpService.httpService( destinationAddress, destinationPort, diff --git a/src/main/java/synfron/reshaper/burp/core/messages/MimeType.java b/src/main/java/synfron/reshaper/burp/core/messages/MimeType.java index 75848e1..ce4e380 100644 --- a/src/main/java/synfron/reshaper/burp/core/messages/MimeType.java +++ b/src/main/java/synfron/reshaper/burp/core/messages/MimeType.java @@ -61,7 +61,7 @@ public String getName() { return String.join(", ", names); } - public static MimeType get(burp.api.montoya.http.MimeType mimeType) { + public static MimeType get(burp.api.montoya.http.message.MimeType mimeType) { return switch (mimeType) { case HTML -> Html; case SCRIPT -> Script; diff --git a/src/main/java/synfron/reshaper/burp/core/messages/WebSocketEventInfo.java b/src/main/java/synfron/reshaper/burp/core/messages/WebSocketEventInfo.java index 4197277..7f141c7 100644 --- a/src/main/java/synfron/reshaper/burp/core/messages/WebSocketEventInfo.java +++ b/src/main/java/synfron/reshaper/burp/core/messages/WebSocketEventInfo.java @@ -1,5 +1,6 @@ package synfron.reshaper.burp.core.messages; +import burp.api.montoya.core.Annotations; import burp.api.montoya.http.message.requests.HttpRequest; import lombok.Getter; import org.apache.commons.lang3.StringUtils; @@ -7,26 +8,21 @@ import synfron.reshaper.burp.core.ProtocolType; import synfron.reshaper.burp.core.vars.Variables; -import java.util.function.BiConsumer; - public class WebSocketEventInfo extends EventInfo { @Getter private final WebSocketMessageType messageType; @Getter - private final Variables sessionVariables; - @Getter private WebSocketDataDirection dataDirection; @Getter private final WebSocketDataDirection initialDataDirection; @Getter - private BiConsumer messageSender; + private WebSocketMessageSender messageSender; @Getter private T data; - public WebSocketEventInfo(WebSocketMessageType messageType, Variables sessionVariables, WebSocketDataDirection dataDirection, BurpTool burpTool, BiConsumer messageSender, HttpRequest httpRequest, T data) { - super(burpTool, httpRequest); + public WebSocketEventInfo(WebSocketMessageType messageType, WebSocketDataDirection dataDirection, BurpTool burpTool, WebSocketMessageSender messageSender, HttpRequest httpRequest, Annotations annotations, T data, Variables sessionVariables) { + super(burpTool, httpRequest, annotations, sessionVariables); this.messageType = messageType; - this.sessionVariables = sessionVariables; this.initialDataDirection = dataDirection; this.dataDirection = dataDirection; this.httpProtocol = httpRequest.httpService().secure() ? "wss" : "ws"; diff --git a/src/main/java/synfron/reshaper/burp/core/messages/WebSocketMessageSender.java b/src/main/java/synfron/reshaper/burp/core/messages/WebSocketMessageSender.java new file mode 100644 index 0000000..16c3700 --- /dev/null +++ b/src/main/java/synfron/reshaper/burp/core/messages/WebSocketMessageSender.java @@ -0,0 +1,45 @@ +package synfron.reshaper.burp.core.messages; + +import burp.api.montoya.core.ByteArray; +import burp.api.montoya.proxy.websocket.ProxyWebSocket; +import burp.api.montoya.websocket.WebSocket; + +import java.util.function.BiConsumer; + +public class WebSocketMessageSender { + + private final BiConsumer binaryMessageSender; + + private final BiConsumer textMessageSender; + + public WebSocketMessageSender(ProxyWebSocket webSocket) { + binaryMessageSender = (direction, data) -> webSocket.sendBinaryMessage(ByteArray.byteArray(data), direction.toDirection()); + textMessageSender = (direction, data) -> webSocket.sendTextMessage(data, direction.toDirection()); + } + + public WebSocketMessageSender(WebSocket webSocket) { + binaryMessageSender = (direction, data) -> { + if (direction == WebSocketDataDirection.Client) { + throw new UnsupportedOperationException("Can only send client messages for Proxy WebSocket connections"); + } + webSocket.sendBinaryMessage(ByteArray.byteArray(data)); + }; + textMessageSender = (direction, data) -> { + if (direction == WebSocketDataDirection.Client) { + throw new UnsupportedOperationException("Can only send client messages for Proxy WebSocket connections"); + } + webSocket.sendTextMessage(data); + }; + } + + public void send(WebSocketEventInfo eventInfo, WebSocketDataDirection dataDirection, WebSocketMessageType messageType, String data) { + switch (messageType) { + case Text: + textMessageSender.accept(dataDirection, data); + break; + case Binary: + binaryMessageSender.accept(dataDirection, eventInfo.getEncoder().encode(data)); + break; + } + } +} diff --git a/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpRequestMessage.java b/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpRequestMessage.java index 0b58b17..12a26f9 100644 --- a/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpRequestMessage.java +++ b/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpRequestMessage.java @@ -1,17 +1,17 @@ package synfron.reshaper.burp.core.messages.entities.http; import burp.api.montoya.core.ByteArray; -import burp.api.montoya.http.message.headers.HttpHeader; +import burp.api.montoya.http.message.HttpHeader; import burp.api.montoya.http.message.requests.HttpRequest; import org.apache.commons.lang3.StringUtils; import synfron.reshaper.burp.core.messages.ContentType; import synfron.reshaper.burp.core.messages.Encoder; +import synfron.reshaper.burp.core.utils.ObjectUtils; import synfron.reshaper.burp.core.utils.SetItemPlacement; import synfron.reshaper.burp.core.utils.Url; import java.util.Arrays; import java.util.stream.Collectors; -import java.util.stream.Stream; public class HttpRequestMessage extends HttpEntity { @@ -26,7 +26,7 @@ public class HttpRequestMessage extends HttpEntity { public HttpRequestMessage(HttpRequest httpRequest, Encoder encoder) { this.httpRequest = httpRequest; - this.request = httpRequest != null ? httpRequest.asBytes().getBytes() : new byte[0]; + this.request = httpRequest != null ? httpRequest.toByteArray().getBytes() : new byte[0]; this.encoder = encoder; } @@ -63,7 +63,7 @@ public ContentType getContentType() { public HttpRequestStatusLine getStatusLine() { if (statusLine == null) { initialize(); - statusLine = new HttpRequestStatusLine(httpRequest.headers().stream().map(HttpHeader::toString).findFirst().orElse("")); + statusLine = new HttpRequestStatusLine(httpRequest.method(), httpRequest.path(), httpRequest.httpVersion()); } return statusLine; } @@ -75,14 +75,18 @@ public void setStatusLine(String statusLine) { public HttpHeaders getHeaders() { if (headers == null) { - headers = new HttpRequestHeaders(httpRequest.headers().stream().map(HttpHeader::toString).skip(1).collect(Collectors.toList())); + headers = new HttpRequestHeaders(httpRequest.headers().stream().map(HttpHeader::toString).collect(Collectors.toList())); } return headers; } public void setHeaders(String headers) { this.headers = new HttpRequestHeaders( - Arrays.stream(headers.split("\n")).map(String::trim).filter(StringUtils::isNotEmpty).collect(Collectors.toList()) + Arrays.stream( + headers.split("\n")) + .map(header -> StringUtils.strip(header, "\r")) + .filter(StringUtils::isNotEmpty).collect(Collectors.toList() + ) ); changed = true; } @@ -110,21 +114,22 @@ public void setUrl(Url url) { } public byte[] getValue() { - return !isChanged() ? - getAdjustedRequest(request) : - asHttpRequest().asBytes().getBytes(); + return asAdjustedHttpRequest().toByteArray().getBytes(); } - public HttpRequest asHttpRequest() { - return HttpRequest.httpRequest( - null, - Stream.concat(Stream.of(getStatusLine().getValue()), getHeaders().getValue().stream()).collect(Collectors.toList()), - ByteArray.byteArray(getBody().getValue()) - ); + public HttpRequest asAdjustedHttpRequest() { + return !isChanged() ? + getAdjustedRequest(request) : + getAdjustedRequest(ObjectUtils.asHttpMessage( + getStatusLine().getValue(), + getHeaders().getValue(), + getBody().getValue() + )); } - private byte[] getAdjustedRequest(byte[] request) { - return HttpRequest.httpRequest(ByteArray.byteArray(request)).asBytes().getBytes(); + private HttpRequest getAdjustedRequest(byte[] request) { + HttpRequest httpRequest = HttpRequest.httpRequest(ByteArray.byteArray(request)); + return httpRequest.withBody(httpRequest.body()); } public String getText() { diff --git a/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpRequestStatusLine.java b/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpRequestStatusLine.java index 951022b..9b274d5 100644 --- a/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpRequestStatusLine.java +++ b/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpRequestStatusLine.java @@ -7,56 +7,47 @@ import java.util.stream.Stream; public class HttpRequestStatusLine extends HttpEntity { - private final String statusLine; - private boolean parsed; private boolean changed; private HttpRequestUri url; private String method; private String version; public HttpRequestStatusLine(String statusLine) { - this.statusLine = statusLine; + String[] lineParts = statusLine.split(" ", 3); + method = CollectionUtils.elementAtOrDefault(lineParts, 0, ""); + url = new HttpRequestUri(CollectionUtils.elementAtOrDefault(lineParts, 1, "")); + version = CollectionUtils.elementAtOrDefault(lineParts, 2, ""); } - private void prepare() { - if (!parsed) { - String[] lineParts = statusLine.split(" ", 3); - method = CollectionUtils.elementAtOrDefault(lineParts, 0, ""); - url = new HttpRequestUri(CollectionUtils.elementAtOrDefault(lineParts, 1, "")); - version = CollectionUtils.elementAtOrDefault(lineParts, 2, ""); - parsed = true; - } + public HttpRequestStatusLine(String method, String url, String version) { + this.method = StringUtils.defaultString(method); + this.url = new HttpRequestUri(StringUtils.defaultString(url)); + this.version = StringUtils.defaultString(version); } public HttpRequestUri getUrl() { - prepare(); return url; } public void setUrl(String url) { - prepare(); this.url = new HttpRequestUri(url); changed = true; } public String getVersion() { - prepare(); return version; } public void setVersion(String version) { - prepare(); this.version = version; changed = true; } public String getMethod() { - prepare(); return method; } public void setMethod(String method) { - prepare(); this.method = method; changed = true; } @@ -66,9 +57,9 @@ public boolean isChanged() { } public String getValue() { - return !isChanged() ? statusLine : Stream.of(getMethod(), getUrl().getValue(), getVersion()) - .filter(StringUtils::isNotEmpty) - .collect(Collectors.joining(" ") + return Stream.of(getMethod(), getUrl().getValue(), getVersion()) + .filter(StringUtils::isNotEmpty) + .collect(Collectors.joining(" ") ); } diff --git a/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpResponseMessage.java b/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpResponseMessage.java index 0ff5be4..edb659c 100644 --- a/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpResponseMessage.java +++ b/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpResponseMessage.java @@ -1,15 +1,16 @@ package synfron.reshaper.burp.core.messages.entities.http; import burp.api.montoya.core.ByteArray; -import burp.api.montoya.http.message.headers.HttpHeader; +import burp.api.montoya.http.message.HttpHeader; import burp.api.montoya.http.message.responses.HttpResponse; import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; import synfron.reshaper.burp.core.messages.Encoder; import synfron.reshaper.burp.core.messages.MimeType; import java.util.Arrays; +import java.util.Objects; import java.util.stream.Collectors; -import java.util.stream.Stream; import static java.util.stream.Collectors.toList; @@ -26,7 +27,7 @@ public class HttpResponseMessage extends HttpEntity { public HttpResponseMessage(HttpResponse httpResponse, Encoder encoder) { this.httpResponse = httpResponse; - this.response = httpResponse != null ? httpResponse.asBytes().getBytes() : new byte[0]; + this.response = httpResponse != null ? httpResponse.toByteArray().getBytes() : new byte[0]; this.encoder = encoder; } @@ -63,7 +64,7 @@ public MimeType getMimeType() { public HttpResponseStatusLine getStatusLine() { if (statusLine == null) { initialize(); - statusLine = new HttpResponseStatusLine(httpResponse.headers().stream().map(HttpHeader::toString).findFirst().orElse("")); + statusLine = new HttpResponseStatusLine(httpResponse.httpVersion(), Objects.toString(httpResponse.statusCode(), ""), httpResponse.reasonPhrase()); } return statusLine; } @@ -76,14 +77,18 @@ public void setStatusLine(String statusLine) { public HttpHeaders getHeaders() { if (headers == null) { initialize(); - headers = new HttpResponseHeaders(httpResponse.headers().stream().skip(1).map(HttpHeader::toString).collect(toList())); + headers = new HttpResponseHeaders(httpResponse.headers().stream().map(HttpHeader::toString).collect(toList())); } return headers; } public void setHeaders(String headers) { this.headers = new HttpResponseHeaders( - Arrays.stream(headers.split("\n")).map(String::trim).collect(toList()) + Arrays.stream( + headers.split("\n")) + .map(header -> StringUtils.strip(header, "\r")) + .filter(StringUtils::isNotEmpty).collect(Collectors.toList() + ) ); changed = true; } @@ -103,20 +108,22 @@ public void setBody(String body) { } public byte[] getValue() { - return !isChanged() ? - getAdjustedResponse(response) : - asHttpResponse().asBytes().getBytes(); + return asAdjustedHttpResponse().toByteArray().getBytes(); } - public HttpResponse asHttpResponse() { - return HttpResponse.httpResponse( - Stream.concat(Stream.of(getStatusLine().getValue()), getHeaders().getValue().stream()).collect(Collectors.toList()), - ByteArray.byteArray(getBody().getValue()) - ); + public HttpResponse asAdjustedHttpResponse() { + return !isChanged() ? + getAdjustedResponse(response) : + getAdjustedResponse(synfron.reshaper.burp.core.utils.ObjectUtils.asHttpMessage( + getStatusLine().getValue(), + getHeaders().getValue(), + getBody().getValue() + )); } - private byte[] getAdjustedResponse(byte[] response) { - return HttpResponse.httpResponse(ByteArray.byteArray(response)).asBytes().getBytes(); + private HttpResponse getAdjustedResponse(byte[] response) { + HttpResponse httpResponse = HttpResponse.httpResponse(ByteArray.byteArray(response)); + return httpResponse.withBody(httpResponse.body()); } public String getText() { diff --git a/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpResponseStatusLine.java b/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpResponseStatusLine.java index 53b1e36..2337817 100644 --- a/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpResponseStatusLine.java +++ b/src/main/java/synfron/reshaper/burp/core/messages/entities/http/HttpResponseStatusLine.java @@ -8,8 +8,6 @@ import java.util.stream.Stream; public class HttpResponseStatusLine extends HttpEntity { - private final String statusLine; - private boolean parsed; @Getter private boolean changed; private String code; @@ -17,54 +15,47 @@ public class HttpResponseStatusLine extends HttpEntity { private String version; public HttpResponseStatusLine(String statusLine) { - this.statusLine = statusLine; + String[] lineParts = statusLine.split(" ", 3); + version = CollectionUtils.elementAtOrDefault(lineParts, 0, ""); + code = CollectionUtils.elementAtOrDefault(lineParts, 1, ""); + message = CollectionUtils.elementAtOrDefault(lineParts, 2, ""); } - private void prepare() { - if (!parsed) { - String[] lineParts = statusLine.split(" ", 3); - version = CollectionUtils.elementAtOrDefault(lineParts, 0, ""); - code = CollectionUtils.elementAtOrDefault(lineParts, 1, ""); - message = CollectionUtils.elementAtOrDefault(lineParts, 2, ""); - parsed = true; - } + public HttpResponseStatusLine(String version, String code, String message) { + this.version = StringUtils.defaultString(version); + this.code = StringUtils.defaultString(code); + this.message = StringUtils.defaultString(message); } public String getVersion() { - prepare(); return version; } public void setVersion(String version) { - prepare(); this.version = version; changed = true; } public String getCode() { - prepare(); return code; } public void setCode(String code) { - prepare(); this.code = code; changed = true; } public String getMessage() { - prepare(); return message; } public void setMessage(String message) { - prepare(); this.message = message; changed = true; } public String getValue() { - return !isChanged() ? statusLine : Stream.of(getVersion(), getCode(), getMessage()) + return Stream.of(getVersion(), getCode(), getMessage()) .filter(StringUtils::isNotEmpty) .collect(Collectors.joining(" ") ); diff --git a/src/main/java/synfron/reshaper/burp/core/rules/IRuleOperation.java b/src/main/java/synfron/reshaper/burp/core/rules/IRuleOperation.java index 0177675..ae911f9 100644 --- a/src/main/java/synfron/reshaper/burp/core/rules/IRuleOperation.java +++ b/src/main/java/synfron/reshaper/burp/core/rules/IRuleOperation.java @@ -1,7 +1,6 @@ package synfron.reshaper.burp.core.rules; import synfron.reshaper.burp.core.messages.EventInfo; -import synfron.reshaper.burp.core.messages.WebSocketEventInfo; import synfron.reshaper.burp.core.vars.GlobalVariables; import synfron.reshaper.burp.core.vars.VariableSource; import synfron.reshaper.burp.core.vars.Variables; @@ -16,7 +15,7 @@ default Variables getVariables(VariableSource variableSource, EventInfo eventInf return switch (variableSource) { case Event -> eventInfo.getVariables(); case Global -> GlobalVariables.get(); - case Session -> eventInfo instanceof WebSocketEventInfo ? ((WebSocketEventInfo)eventInfo).getSessionVariables() : null; + case Session -> eventInfo.getSessionVariables(); default -> null; }; } diff --git a/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenComment.java b/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenComment.java index a4d1634..e15d25e 100644 --- a/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenComment.java +++ b/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenComment.java @@ -3,13 +3,13 @@ import lombok.Getter; import lombok.Setter; import synfron.reshaper.burp.core.messages.EventInfo; -import synfron.reshaper.burp.core.messages.HttpEventInfo; import synfron.reshaper.burp.core.rules.IHttpRuleOperation; +import synfron.reshaper.burp.core.rules.IWebSocketRuleOperation; import synfron.reshaper.burp.core.rules.RuleOperationType; import synfron.reshaper.burp.core.rules.RuleResponse; import synfron.reshaper.burp.core.vars.VariableString; -public class ThenComment extends Then implements IHttpRuleOperation { +public class ThenComment extends Then implements IHttpRuleOperation, IWebSocketRuleOperation { @Getter @Setter private VariableString text; @@ -18,9 +18,10 @@ public class ThenComment extends Then implements IHttpRuleOperation public RuleResponse perform(EventInfo eventInfo) { boolean hasError = true; try { - HttpEventInfo httpEventInfo = (HttpEventInfo)eventInfo; - httpEventInfo.setAnnotations(httpEventInfo.getAnnotations().withComment(text.getText(eventInfo))); - hasError = false; + if (eventInfo.getAnnotations() != null) { + eventInfo.getAnnotations().setNotes(text.getText(eventInfo)); + hasError = false; + } } finally { if (eventInfo.getDiagnostics().isEnabled()) eventInfo.getDiagnostics().logValue(this, hasError, VariableString.getTextOrDefault(eventInfo, text, null)); } diff --git a/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenHighlight.java b/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenHighlight.java index 69d9d2f..dec940b 100644 --- a/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenHighlight.java +++ b/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenHighlight.java @@ -3,12 +3,12 @@ import lombok.Getter; import lombok.Setter; import synfron.reshaper.burp.core.messages.EventInfo; -import synfron.reshaper.burp.core.messages.HttpEventInfo; import synfron.reshaper.burp.core.rules.IHttpRuleOperation; +import synfron.reshaper.burp.core.rules.IWebSocketRuleOperation; import synfron.reshaper.burp.core.rules.RuleOperationType; import synfron.reshaper.burp.core.rules.RuleResponse; -public class ThenHighlight extends Then implements IHttpRuleOperation { +public class ThenHighlight extends Then implements IHttpRuleOperation, IWebSocketRuleOperation { @Getter @Setter private HighlightColor color = HighlightColor.None; @@ -17,9 +17,10 @@ public class ThenHighlight extends Then implements IHttpRuleOpera public RuleResponse perform(EventInfo eventInfo) { boolean hasError = true; try { - HttpEventInfo httpEventInfo = (HttpEventInfo)eventInfo; - httpEventInfo.setAnnotations(httpEventInfo.getAnnotations().withHighlightColor(color.highlightColor)); - hasError = false; + if (eventInfo.getAnnotations() != null) { + eventInfo.getAnnotations().setHighlightColor(color.highlightColor); + hasError = false; + } } finally { if (eventInfo.getDiagnostics().isEnabled()) eventInfo.getDiagnostics().logValue(this, hasError, color.getValue()); } diff --git a/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenRunRules.java b/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenRunRules.java index afe6584..eb0c341 100644 --- a/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenRunRules.java +++ b/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenRunRules.java @@ -8,6 +8,7 @@ import synfron.reshaper.burp.core.messages.HttpEventInfo; import synfron.reshaper.burp.core.rules.*; +import java.util.NoSuchElementException; import java.util.stream.Stream; public class ThenRunRules extends Then implements IHttpRuleOperation, IWebSocketRuleOperation { @@ -52,7 +53,7 @@ private Rule getRule(RulesEngine rulesEngine) { ruleCache = Stream.of(rulesEngine.getRulesRegistry().getRules()) .filter(rule -> StringUtils.isNotEmpty(rule.getName()) && rule.getName().equals(ruleName)) .findFirst() - .get(); + .orElseThrow(() -> new NoSuchElementException(String.format("Rule '%s' not found", ruleName))); cacheVersion = rulesEngine.getRulesRegistry().getVersion(); } return ruleCache; diff --git a/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSendMessage.java b/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSendMessage.java index a7b6695..a9b4905 100644 --- a/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSendMessage.java +++ b/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSendMessage.java @@ -5,6 +5,7 @@ import synfron.reshaper.burp.core.messages.EventInfo; import synfron.reshaper.burp.core.messages.WebSocketDataDirection; import synfron.reshaper.burp.core.messages.WebSocketEventInfo; +import synfron.reshaper.burp.core.messages.WebSocketMessageType; import synfron.reshaper.burp.core.rules.IWebSocketRuleOperation; import synfron.reshaper.burp.core.rules.RuleOperationType; import synfron.reshaper.burp.core.rules.RuleResponse; @@ -15,6 +16,10 @@ public class ThenSendMessage extends Then implements IWebSocket @Getter @Setter private WebSocketDataDirection dataDirection = WebSocketDataDirection.Server; + + @Getter + @Setter + private WebSocketMessageType messageType = WebSocketMessageType.Text; @Getter @Setter private VariableString message; @@ -24,7 +29,8 @@ public RuleResponse perform(EventInfo eventInfo) { boolean hasError = true; String value = null; try { - ((WebSocketEventInfo)eventInfo).getMessageSender().accept(dataDirection, value = message.getText(eventInfo)); + WebSocketEventInfo webSocketEventInfo = (WebSocketEventInfo)eventInfo; + webSocketEventInfo.getMessageSender().send(webSocketEventInfo, dataDirection, messageType, value = message.getText(eventInfo)); hasError = false; } finally { if (eventInfo.getDiagnostics().isEnabled()) eventInfo.getDiagnostics().logValue(this, hasError, value); diff --git a/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSendRequest.java b/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSendRequest.java index e4de302..5f3ca95 100644 --- a/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSendRequest.java +++ b/src/main/java/synfron/reshaper/burp/core/rules/thens/ThenSendRequest.java @@ -68,16 +68,14 @@ public RuleResponse perform(EventInfo eventInfo) { executor.submit(() -> { try { HttpEventInfo newEventInfo = new HttpEventInfo(eventInfo); - boolean useHttps = !StringUtils.equalsIgnoreCase(newEventInfo.getHttpProtocol(), "http"); if (!VariableString.isEmpty(request)) { newEventInfo.setHttpRequestMessage(eventInfo.getEncoder().encode(this.request.getText(eventInfo))); } if (!VariableString.isEmpty(url)) { newEventInfo.setUrl(url.getText(eventInfo)); - useHttps = !StringUtils.equalsIgnoreCase(newEventInfo.getHttpProtocol(), "http"); } if (!VariableString.isEmpty(protocol)) { - useHttps = !StringUtils.equalsIgnoreCase(protocol.getText(eventInfo), "http"); + newEventInfo.setHttpProtocol(protocol.getText(eventInfo)); } if (!VariableString.isEmpty(address)) { newEventInfo.setDestinationAddress(address.getText(eventInfo)); @@ -87,15 +85,15 @@ public RuleResponse perform(EventInfo eventInfo) { eventInfo, this.port, (newEventInfo.getDestinationPort() == null || newEventInfo.getDestinationPort() == 0) ? - (useHttps ? 443 : 80) : + (newEventInfo.isSecure() ? 443 : 80) : newEventInfo.getDestinationPort() )); } - HttpResponse httpResponse = BurpExtender.getApi().http().issueRequest( + HttpResponse httpResponse = BurpExtender.getApi().http().sendRequest( newEventInfo.asHttpRequest(), HttpMode.AUTO - ).httpResponse(); + ).response(); response.set(httpResponse); } catch (Exception e) { Log.get().withMessage("Failure sending request").withException(e).logErr(); @@ -114,7 +112,7 @@ public RuleResponse perform(EventInfo eventInfo) { 0; failed = !complete || (failOnErrorStatusCode && (statusCode == 0 || (statusCode >= 400 && statusCode < 600))); if (captureOutput && (!failed || captureAfterFailure)) { - output = response.get() != null ? new HttpResponseMessage(response.get().asBytes().getBytes(), eventInfo.getEncoder()).getText() : ""; + output = response.get() != null ? new HttpResponseMessage(response.get().toByteArray().getBytes(), eventInfo.getEncoder()).getText() : ""; setVariable(captureVariableSource, eventInfo, captureVariableName, output); } } diff --git a/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/ReshaperObj.java b/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/ReshaperObj.java index 64aec0b..672a9d5 100644 --- a/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/ReshaperObj.java +++ b/src/main/java/synfron/reshaper/burp/core/rules/thens/entities/script/ReshaperObj.java @@ -8,15 +8,11 @@ import synfron.reshaper.burp.core.messages.EventInfo; import synfron.reshaper.burp.core.messages.MessageValue; import synfron.reshaper.burp.core.messages.MessageValueHandler; -import synfron.reshaper.burp.core.messages.WebSocketEventInfo; import synfron.reshaper.burp.core.rules.RuleOperationType; import synfron.reshaper.burp.core.rules.RuleResponse; import synfron.reshaper.burp.core.rules.thens.Then; import synfron.reshaper.burp.core.rules.thens.ThenType; -import synfron.reshaper.burp.core.utils.GetItemPlacement; -import synfron.reshaper.burp.core.utils.Serializer; -import synfron.reshaper.burp.core.utils.SetItemPlacement; -import synfron.reshaper.burp.core.utils.TextUtils; +import synfron.reshaper.burp.core.utils.*; import synfron.reshaper.burp.core.vars.GlobalVariables; import synfron.reshaper.burp.core.vars.Variable; import synfron.reshaper.burp.core.vars.VariableString; @@ -42,19 +38,17 @@ public String getEventVariable(String name) { } public String getSessionVariable(String name) { - EventInfo eventInfo = (EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo"); - if (eventInfo instanceof WebSocketEventInfo) { - WebSocketEventInfo webSocketEventInfo = (WebSocketEventInfo) eventInfo; - return getVariable(webSocketEventInfo.getSessionVariables(), name); - } - return null; + return getVariable(((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getSessionVariables(), name); } private String getVariable(Variables variables, String name) { - Variable variable = variables.getOrDefault(name); - return variable != null ? - TextUtils.toString(variable.getValue()) : - null; + if (variables != null) { + Variable variable = variables.getOrDefault(name); + return variable != null ? + TextUtils.toString(variable.getValue()) : + null; + } + return null; } public void setGlobalVariable(String name, String value) { @@ -75,11 +69,7 @@ public void setSessionVariable(String name, String value) { if (!VariableString.isValidVariableName(name)) { throw new IllegalArgumentException(String.format("Invalid variable name '%s'", name)); } - EventInfo eventInfo = (EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo"); - if (eventInfo instanceof WebSocketEventInfo) { - WebSocketEventInfo webSocketEventInfo = (WebSocketEventInfo) eventInfo; - webSocketEventInfo.getSessionVariables().add(name).setValue(value); - } + ((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getSessionVariables().add(name).setValue(value); } public void deleteGlobalVariable(String name) { @@ -91,11 +81,7 @@ public void deleteEventVariable(String name) { } public void deleteSessionVariable(String name) { - EventInfo eventInfo = (EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo"); - if (eventInfo instanceof WebSocketEventInfo) { - WebSocketEventInfo webSocketEventInfo = (WebSocketEventInfo) eventInfo; - webSocketEventInfo.getSessionVariables().remove(name); - } + ((EventInfo)Dispatcher.getCurrent().getDataBag().get("eventInfo")).getSessionVariables().remove(name); } } 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 52f790e..415bbe1 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 @@ -1,9 +1,7 @@ package synfron.reshaper.burp.core.rules.thens.entities.script; import burp.BurpExtender; -import burp.api.montoya.core.ByteArray; import burp.api.montoya.http.HttpService; -import burp.api.montoya.http.message.requests.HttpRequest; import burp.api.montoya.http.message.responses.HttpResponse; import lombok.Getter; import lombok.Setter; @@ -153,14 +151,14 @@ private void doSend(String body, Dispatcher.Task timeoutTask) { requestMessage.setBody(body); } boolean useHttps = !StringUtils.equalsIgnoreCase(requestUrl.getScheme(), "http"); - HttpResponse response = BurpExtender.getApi().http().issueRequest( - HttpRequest.httpRequest(ByteArray.byteArray(requestMessage.getValue())) + HttpResponse response = BurpExtender.getApi().http().sendRequest( + requestMessage.asAdjustedHttpRequest() .withService(HttpService.httpService( requestUrl.getHost(), requestUrl.getPort() > 0 ? requestUrl.getPort() : (useHttps ? 443 : 80), useHttps )) - ).httpResponse(); + ).response(); if (!dispatcher.isTimeoutReach() && this.executor == executor) { this.executor = null; dispatcher.execute(context -> { diff --git a/src/main/java/synfron/reshaper/burp/core/utils/ObjectUtils.java b/src/main/java/synfron/reshaper/burp/core/utils/ObjectUtils.java index 598f4d3..c8697fc 100644 --- a/src/main/java/synfron/reshaper/burp/core/utils/ObjectUtils.java +++ b/src/main/java/synfron/reshaper/burp/core/utils/ObjectUtils.java @@ -1,8 +1,13 @@ package synfron.reshaper.burp.core.utils; +import burp.BurpExtender; +import burp.api.montoya.utilities.ByteUtils; +import org.apache.commons.lang3.StringUtils; import synfron.reshaper.burp.core.exceptions.WrappedException; +import java.io.ByteArrayOutputStream; import java.lang.reflect.InvocationTargetException; +import java.util.List; import java.util.stream.Stream; public class ObjectUtils { @@ -15,4 +20,26 @@ public static Object construct(Class clazz, Object... args) { throw new WrappedException(e); } } + + public static byte[] asHttpMessage(String statusLine, List headers, byte[] body) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ByteUtils byteUtils = BurpExtender.getApi().utilities().byteUtils(); + int appendIndex = 0; + if (StringUtils.isNotEmpty(statusLine)) { + byteArrayOutputStream.writeBytes(byteUtils.convertFromString(statusLine)); + appendIndex++; + } + for (int headerIndex = 0; headerIndex < headers.size(); headerIndex++) { + if (headerIndex != appendIndex) { + byteArrayOutputStream.writeBytes(byteUtils.convertFromString("\r\n")); + } + byteArrayOutputStream.writeBytes(byteUtils.convertFromString(headers.get(headerIndex))); + appendIndex++; + } + byteArrayOutputStream.writeBytes(byteUtils.convertFromString("\r\n\r\n")); + if (body != null && body.length > 0) { + byteArrayOutputStream.writeBytes(body); + } + return byteArrayOutputStream.toByteArray(); + } } diff --git a/src/main/java/synfron/reshaper/burp/core/utils/Serializer.java b/src/main/java/synfron/reshaper/burp/core/utils/Serializer.java index e452f57..83c656d 100644 --- a/src/main/java/synfron/reshaper/burp/core/utils/Serializer.java +++ b/src/main/java/synfron/reshaper/burp/core/utils/Serializer.java @@ -138,9 +138,14 @@ public Class handledType() { @Override public VariableString deserialize(JsonParser parser, DeserializationContext context) throws IOException { ObjectMapper objectMapper = configureMapper(); - return (parser.currentTokenId() == JsonTokenId.ID_STRING) ? - VariableString.getAsVariableString(parser.getText()) : - objectMapper.readValue(parser, VariableString.class); + return switch (parser.currentTokenId()) { + case JsonTokenId.ID_STRING, + JsonTokenId.ID_FALSE, + JsonTokenId.ID_TRUE, + JsonTokenId.ID_NUMBER_FLOAT, + JsonTokenId.ID_NUMBER_INT -> VariableString.getAsVariableString(parser.getText()); + default -> objectMapper.readValue(parser, VariableString.class); + }; } } diff --git a/src/main/java/synfron/reshaper/burp/core/vars/VariableSource.java b/src/main/java/synfron/reshaper/burp/core/vars/VariableSource.java index f743451..d3d6b26 100644 --- a/src/main/java/synfron/reshaper/burp/core/vars/VariableSource.java +++ b/src/main/java/synfron/reshaper/burp/core/vars/VariableSource.java @@ -12,9 +12,9 @@ public enum VariableSource { Event("e", false, ProtocolType.Any), Global("g", false, ProtocolType.Any), - Session("sn", false, ProtocolType.WebSocket), + Session("sn", false, ProtocolType.Any), Message("m", true, ProtocolType.Any), - Annotation("a", true, ProtocolType.Http), + Annotation("a", true, ProtocolType.Any), File("f", true, ProtocolType.Any), Special("s", true, ProtocolType.Any), CookieJar("Cookie Jar", "cj", true, ProtocolType.Any); diff --git a/src/main/java/synfron/reshaper/burp/core/vars/VariableString.java b/src/main/java/synfron/reshaper/burp/core/vars/VariableString.java index 34e3c2e..9fbbbd1 100644 --- a/src/main/java/synfron/reshaper/burp/core/vars/VariableString.java +++ b/src/main/java/synfron/reshaper/burp/core/vars/VariableString.java @@ -1,7 +1,7 @@ package synfron.reshaper.burp.core.vars; import burp.BurpExtender; -import burp.api.montoya.http.message.cookies.Cookie; +import burp.api.montoya.http.message.Cookie; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; @@ -136,7 +136,7 @@ public String getText(EventInfo eventInfo) Variable value = switch (variable.getVariableSource()) { case Global -> GlobalVariables.get().getOrDefault(variable.getName()); case Event -> eventInfo.getVariables().getOrDefault(variable.getName()); - case Session -> eventInfo instanceof WebSocketEventInfo ? ((WebSocketEventInfo)eventInfo).getSessionVariables().getOrDefault(variable.getName()) : null; + case Session -> eventInfo.getSessionVariables().getOrDefault(variable.getName()); default -> null; }; variableVals.add(value != null ? TextUtils.toString(value.getValue()) : null); @@ -148,11 +148,10 @@ public String getText(EventInfo eventInfo) private String getAnnotation(EventInfo eventInfo, String name) { MessageAnnotation annotation = EnumUtils.getEnumIgnoreCase(MessageAnnotation.class, name); - if (annotation != null && eventInfo instanceof HttpEventInfo) { - HttpEventInfo httpEventInfo = (HttpEventInfo) eventInfo; + if (annotation != null && eventInfo.getAnnotations() != null) { return switch (annotation) { - case Comment -> httpEventInfo.getAnnotations().comment(); - case HighlightColor -> StringUtils.capitalize(httpEventInfo.getAnnotations().highlightColor().name().toLowerCase()); + case Comment -> eventInfo.getAnnotations().notes(); + case HighlightColor -> StringUtils.capitalize(eventInfo.getAnnotations().highlightColor().name().toLowerCase()); }; } return null; diff --git a/src/main/java/synfron/reshaper/burp/core/vars/Variables.java b/src/main/java/synfron/reshaper/burp/core/vars/Variables.java index 37856e1..8e0fe0c 100644 --- a/src/main/java/synfron/reshaper/burp/core/vars/Variables.java +++ b/src/main/java/synfron/reshaper/burp/core/vars/Variables.java @@ -13,10 +13,18 @@ public class Variables { protected final CollectionChangedEvent collectionChangedEvent = new CollectionChangedEvent(); protected HashMap variables = new HashMap<>(); + public static Variables defaultVariables(Variables variables) { + return variables != null ? variables : new Variables(); + } + public List getValues() { return new ArrayList<>(variables.values()); } + public int size() { + return variables.size(); + } + public Variable add(String name) { CaseInsensitiveString key = new CaseInsensitiveString(name); diff --git a/src/main/java/synfron/reshaper/burp/ui/components/IFormComponent.java b/src/main/java/synfron/reshaper/burp/ui/components/IFormComponent.java index 406e0fb..c32e6d4 100644 --- a/src/main/java/synfron/reshaper/burp/ui/components/IFormComponent.java +++ b/src/main/java/synfron/reshaper/burp/ui/components/IFormComponent.java @@ -23,6 +23,10 @@ public interface IFormComponent { default Component getLabeledField(String label, Component innerComponent) { + return getLabeledField(label, innerComponent, true); + } + + default Component getLabeledField(String label, Component innerComponent, boolean span) { JPanel container = new JPanel(); container.setLayout(new MigLayout()); container.setBorder(null); @@ -32,7 +36,7 @@ default Component getLabeledField(String label, Component innerComponent) { } container.add(new JLabel(label), "wrap"); - container.add(innerComponent); + container.add(innerComponent, span ? "grow, push, span" : ""); return container; } @@ -74,6 +78,10 @@ default JTextPane createTextPane() { return addContextMenu(addUndo(new JTextPane()), false, getProtocolType()); } + default T createTextComponent(T textComponent) { + return addContextMenu(addUndo(textComponent), false, getProtocolType()); + } + private static T addContextMenu(T textComponent, boolean supportsVariableTags, ProtocolType protocolType) { JPopupMenu popupMenu = new JPopupMenu(); diff --git a/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenRunScriptComponent.java b/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenRunScriptComponent.java index 27b6218..4f0d62b 100644 --- a/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenRunScriptComponent.java +++ b/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenRunScriptComponent.java @@ -11,7 +11,7 @@ import java.awt.event.ActionEvent; public class ThenRunScriptComponent extends ThenComponent { - private JTextPane script; + private JTextArea script; private JTextField maxExecutionSeconds; public ThenRunScriptComponent(ProtocolType protocolType, ThenRunScriptModel then) { @@ -20,17 +20,17 @@ public ThenRunScriptComponent(ProtocolType protocolType, ThenRunScriptModel then } private void initComponent() { + setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + mainContainer.setLayout(new BorderLayout()); - JScrollPane scrollPane = new JScrollPane(); - script = createTextPane(); - scrollPane.setViewportView(script); + script = createTextComponent(new JTextArea()); + script.setLineWrap(true); script.setText(model.getScript()); script.getDocument().addDocumentListener(new DocumentActionListener(this::onScriptChanged)); - mainContainer.add(new JLabel("Script *"), BorderLayout.PAGE_START); - mainContainer.add(scrollPane, BorderLayout.CENTER); + mainContainer.add(getLabeledField("Script *", script, true), BorderLayout.CENTER); mainContainer.add(getOtherFields(), BorderLayout.PAGE_END); } diff --git a/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenSendMessageComponent.java b/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenSendMessageComponent.java index a106e03..b1958fc 100644 --- a/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenSendMessageComponent.java +++ b/src/main/java/synfron/reshaper/burp/ui/components/rules/thens/ThenSendMessageComponent.java @@ -2,6 +2,7 @@ import synfron.reshaper.burp.core.ProtocolType; import synfron.reshaper.burp.core.messages.WebSocketDataDirection; +import synfron.reshaper.burp.core.messages.WebSocketMessageType; import synfron.reshaper.burp.core.rules.thens.ThenSendMessage; import synfron.reshaper.burp.ui.models.rules.thens.ThenSendMessageModel; import synfron.reshaper.burp.ui.utils.DocumentActionListener; @@ -12,6 +13,8 @@ public class ThenSendMessageComponent extends ThenComponent { private JComboBox dataDirection; + + private JComboBox messageType; private JTextField message; public ThenSendMessageComponent(ProtocolType protocolType, ThenSendMessageModel then) { @@ -21,15 +24,19 @@ public ThenSendMessageComponent(ProtocolType protocolType, ThenSendMessageModel private void initComponent() { dataDirection = createComboBox(WebSocketDataDirection.values()); + messageType = createComboBox(WebSocketMessageType.values()); message = createTextField(true); dataDirection.setSelectedItem(model.getDataDirection()); + messageType.setSelectedItem(model.getMessageType()); message.setText(model.getMessage()); dataDirection.addActionListener(this::onSetEventDirectionChanged); + messageType.addActionListener(this::onSetMessageTypeChanged); message.getDocument().addDocumentListener(new DocumentActionListener(this::onMessageChanged)); mainContainer.add(getLabeledField("Event Direction", dataDirection), "wrap"); + mainContainer.add(getLabeledField("Message Type", messageType), "wrap"); mainContainer.add(getLabeledField("Message", message), "wrap"); mainContainer.add(getPaddedButton(validate)); } @@ -38,6 +45,10 @@ private void onSetEventDirectionChanged(ActionEvent actionEvent) { model.setDataDirection((WebSocketDataDirection) dataDirection.getSelectedItem()); } + private void onSetMessageTypeChanged(ActionEvent actionEvent) { + model.setMessageType((WebSocketMessageType) messageType.getSelectedItem()); + } + private void onMessageChanged(ActionEvent actionEvent) { model.setMessage(message.getText()); } diff --git a/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardItemComponent.java b/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardItemComponent.java index 3260532..9326f71 100644 --- a/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardItemComponent.java +++ b/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardItemComponent.java @@ -16,6 +16,7 @@ import java.util.List; public class WhenWizardItemComponent extends JPanel implements IFormComponent { + private final ProtocolType protocolType; private final WhenWizardItemModel model; private final boolean deletable; private JComboBox messageValue; @@ -23,7 +24,8 @@ public class WhenWizardItemComponent extends JPanel implements IFormComponent { private JComboBox matchType; private JTextField text; - public WhenWizardItemComponent(WhenWizardItemModel model, boolean deletable) { + public WhenWizardItemComponent(ProtocolType protocolType, WhenWizardItemModel model, boolean deletable) { + this.protocolType = protocolType; this.model = model; this.deletable = deletable; initComponent(); @@ -34,7 +36,7 @@ private void initComponent() { JPanel container = new JPanel(new MigLayout()); messageValue = createComboBox(Arrays.stream(MessageValue.values()) - .filter(value -> value.isGettable(ProtocolType.Http)).toArray(MessageValue[]::new)); + .filter(value -> value.isGettable(protocolType)).toArray(MessageValue[]::new)); identifier = createComboBox(model.getIdentifiers().getOptions().toArray(new String[0])); matchType = createComboBox(WhenWizardMatchType.values()); text = createTextField(true); diff --git a/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardOptionPane.java b/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardOptionPane.java index 3083a9b..6739cb5 100644 --- a/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardOptionPane.java +++ b/src/main/java/synfron/reshaper/burp/ui/components/rules/wizard/whens/WhenWizardOptionPane.java @@ -96,9 +96,9 @@ private void onAddItem() { WhenWizardItemModel itemModel = model.addItem() .withListener(whenWizardItemChangedListener); whenWizardItemsComponent.add(new WhenWizardItemComponent( + model.getEventInfo().getProtocolType(), itemModel, - deletable - ), "wrap"); + deletable), "wrap"); whenWizardItemsComponent.revalidate(); whenWizardItemsComponent.repaint(); } @@ -114,7 +114,7 @@ private JPanel getWhenWizardItemList() { boolean deletableItem = false; for (WhenWizardItemModel item : model.getItems()) { item.withListener(whenWizardItemChangedListener); - whenWizardItemsComponent.add(new WhenWizardItemComponent(item, deletableItem), "wrap"); + whenWizardItemsComponent.add(new WhenWizardItemComponent(model.getEventInfo().getProtocolType(), item, deletableItem), "wrap"); deletableItem = true; } return whenWizardItemsComponent; diff --git a/src/main/java/synfron/reshaper/burp/ui/models/rules/thens/ThenSendMessageModel.java b/src/main/java/synfron/reshaper/burp/ui/models/rules/thens/ThenSendMessageModel.java index a37fb2a..4f6f9ee 100644 --- a/src/main/java/synfron/reshaper/burp/ui/models/rules/thens/ThenSendMessageModel.java +++ b/src/main/java/synfron/reshaper/burp/ui/models/rules/thens/ThenSendMessageModel.java @@ -3,6 +3,7 @@ import lombok.Getter; import synfron.reshaper.burp.core.ProtocolType; import synfron.reshaper.burp.core.messages.WebSocketDataDirection; +import synfron.reshaper.burp.core.messages.WebSocketMessageType; import synfron.reshaper.burp.core.rules.thens.ThenSendMessage; import synfron.reshaper.burp.core.vars.VariableString; import synfron.reshaper.burp.ui.models.rules.RuleOperationModelType; @@ -12,12 +13,16 @@ public class ThenSendMessageModel extends ThenModel identifiers = new Select<>(Collections.emptyList(), null); @Getter @@ -35,7 +32,7 @@ public class WhenWizardItemModel { @Getter private final PropertyChangedEvent propertyChangedEvent = new PropertyChangedEvent(); - public WhenWizardItemModel(MessageValue messageValue, HttpEventInfo eventInfo) { + public WhenWizardItemModel(MessageValue messageValue, EventInfo eventInfo) { this.eventInfo = eventInfo; setMessageValue(messageValue); } @@ -58,7 +55,7 @@ public void setMessageValue(MessageValue messageValue) { private void resetText() { try { - text = !hasLongValue(messageValue) ? MessageValueHandler.getValue( + text = !hasLongTextValue(messageValue) ? MessageValueHandler.getValue( eventInfo, messageValue, identifiers.getSelectedOption() == null ? null : VariableString.getAsVariableString(identifiers.getSelectedOption(), false), @@ -70,7 +67,7 @@ private void resetText() { propertyChanged("text", text); } - private boolean hasLongValue(MessageValue messageValue) { + private boolean hasLongTextValue(MessageValue messageValue) { return switch (messageValue) { case HttpRequestBody, HttpRequestHeaders, HttpRequestMessage, HttpResponseBody, HttpResponseHeaders, HttpResponseMessage -> true; default -> false; diff --git a/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/whens/WhenWizardModel.java b/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/whens/WhenWizardModel.java index 37d800f..c0a3ee3 100644 --- a/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/whens/WhenWizardModel.java +++ b/src/main/java/synfron/reshaper/burp/ui/models/rules/wizard/whens/WhenWizardModel.java @@ -7,15 +7,11 @@ 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.HttpDataDirection; -import synfron.reshaper.burp.core.messages.HttpEventInfo; -import synfron.reshaper.burp.core.messages.MessageValue; +import synfron.reshaper.burp.core.messages.*; 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.rules.RulesRegistry; +import synfron.reshaper.burp.core.rules.whens.*; import synfron.reshaper.burp.core.utils.GetItemPlacement; import synfron.reshaper.burp.core.vars.VariableString; import synfron.reshaper.burp.ui.utils.IPrompterModel; @@ -28,7 +24,7 @@ public class WhenWizardModel implements IPrompterModel { @Getter - private final HttpEventInfo eventInfo; + private final EventInfo eventInfo; @Getter private String ruleName; @Getter @@ -44,7 +40,7 @@ public class WhenWizardModel implements IPrompterModel { @Setter @Getter private ModalPrompter modalPrompter; - public WhenWizardModel(HttpEventInfo eventInfo) { + public WhenWizardModel(EventInfo eventInfo) { this.eventInfo = eventInfo; } @@ -133,14 +129,26 @@ public boolean createRule() { whens.addLast(when); } } - WhenEventDirection direction = new WhenEventDirection(); - direction.setDataDirection(requiresResponse ? HttpDataDirection.Response : HttpDataDirection.Request); - whens.addFirst(direction); - rule.setName(ruleName); - rule.setEnabled(false); - rule.setAutoRun(true); - rule.setWhens(whens.toArray(When[]::new)); - BurpExtender.getHttpConnector().getRulesEngine().getRulesRegistry().addRule(rule); + RulesRegistry rulesRegistry = null; + if (eventInfo instanceof HttpEventInfo) { + WhenEventDirection direction = new WhenEventDirection(); + direction.setDataDirection(requiresResponse ? HttpDataDirection.Response : HttpDataDirection.Request); + whens.addFirst(direction); + rulesRegistry = BurpExtender.getHttpConnector().getRulesEngine().getRulesRegistry(); + } else if (eventInfo instanceof WebSocketEventInfo webSocketEventInfo) { + WhenWebSocketEventDirection direction = new WhenWebSocketEventDirection(); + direction.setDataDirection(webSocketEventInfo.getDataDirection()); + whens.addFirst(direction); + rulesRegistry = BurpExtender.getWebSocketConnector().getRulesEngine().getRulesRegistry(); + } + + if (rulesRegistry != null) { + rule.setName(ruleName); + rule.setEnabled(false); + rule.setAutoRun(true); + rule.setWhens(whens.toArray(When[]::new)); + rulesRegistry.addRule(rule); + } setInvalidated(false); return true; } diff --git a/src/main/java/synfron/reshaper/burp/ui/utils/ContextMenuHandler.java b/src/main/java/synfron/reshaper/burp/ui/utils/ContextMenuHandler.java index ad08168..75722c0 100644 --- a/src/main/java/synfron/reshaper/burp/ui/utils/ContextMenuHandler.java +++ b/src/main/java/synfron/reshaper/burp/ui/utils/ContextMenuHandler.java @@ -3,12 +3,19 @@ import burp.api.montoya.http.message.HttpRequestResponse; import burp.api.montoya.ui.contextmenu.ContextMenuEvent; import burp.api.montoya.ui.contextmenu.ContextMenuItemsProvider; +import burp.api.montoya.ui.contextmenu.WebSocketContextMenuEvent; +import burp.api.montoya.ui.contextmenu.WebSocketMessage; import synfron.reshaper.burp.core.messages.HttpEventInfo; +import synfron.reshaper.burp.core.messages.WebSocketDataDirection; +import synfron.reshaper.burp.core.messages.WebSocketEventInfo; +import synfron.reshaper.burp.core.messages.WebSocketMessageType; import synfron.reshaper.burp.core.utils.Log; +import synfron.reshaper.burp.core.vars.Variables; import synfron.reshaper.burp.ui.components.rules.wizard.whens.WhenWizardOptionPane; import synfron.reshaper.burp.ui.models.rules.wizard.whens.WhenWizardModel; import javax.swing.*; +import java.awt.*; import java.awt.event.ActionEvent; import java.util.Collections; import java.util.List; @@ -16,15 +23,27 @@ public class ContextMenuHandler implements ContextMenuItemsProvider { @Override - public List provideMenuItems(ContextMenuEvent event) { + public List provideMenuItems(ContextMenuEvent event) { JMenuItem menuItem = new JMenuItem("Create Rule"); - menuItem.addActionListener(actionEvent -> onCreateRule(event.selectedRequestResponses(), actionEvent)); + menuItem.addActionListener(actionEvent -> onCreateHttpRule(event.selectedRequestResponses(), actionEvent)); return event.selectedRequestResponses().size() == 1 ? Collections.singletonList(menuItem) : Collections.emptyList(); } - private void onCreateRule(List selectedItems, ActionEvent actionEvent) { + @Override + public List provideMenuItems(WebSocketContextMenuEvent event) { + JMenuItem menuItem = new JMenuItem("Create Rule"); + menuItem.addActionListener(actionEvent -> onCreateWebSocketRule(event.selectedWebSocketMessages(), actionEvent)); + return event.selectedWebSocketMessages().size() == 1 ? Collections.singletonList(menuItem) : Collections.emptyList(); + } + + private void onCreateWebSocketRule(List selectedItems, ActionEvent actionEvent) { + WebSocketMessage webSocketMessage = selectedItems.get(0); + openWhenWizard(new WhenWizardModel(new WebSocketEventInfo<>(WebSocketMessageType.Binary, WebSocketDataDirection.from(webSocketMessage.direction()), null, null, webSocketMessage.upgradeRequest(), webSocketMessage.annotations(), webSocketMessage.payload().getBytes(), new Variables()))); + } + + private void onCreateHttpRule(List selectedItems, ActionEvent actionEvent) { HttpRequestResponse httpRequestResponse = selectedItems.get(0); - openWhenWizard(new WhenWizardModel(new HttpEventInfo(null, null, httpRequestResponse.httpRequest(), httpRequestResponse.httpResponse(), httpRequestResponse.messageAnnotations()))); + openWhenWizard(new WhenWizardModel(new HttpEventInfo(null, null, null, httpRequestResponse.request(), httpRequestResponse.response(), httpRequestResponse.annotations(), new Variables()))); } private void openWhenWizard(WhenWizardModel model) {