Skip to content

Commit

Permalink
grpc: add websocket support (#5509)
Browse files Browse the repository at this point in the history
Websocket message en/decoding support for gRPC messages.

Signed-off-by: amitpanwar789 <amitpanwar02705@gmail.com>
  • Loading branch information
amitpanwar789 committed Jul 2, 2024
1 parent 3350e8c commit f4f286a
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 6 deletions.
4 changes: 4 additions & 0 deletions addOns/grpc/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this add-on will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## Unreleased

### Added
- gRPC WebSocket Support Added

### Fixed
- Do not try to decode non-gRPC responses when active scanning, which would lead to unnecessary warnings.

Expand Down
13 changes: 13 additions & 0 deletions addOns/grpc/grpc.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ zapAddOn {
manifest {
author.set("ZAP Dev Team")
url.set("https://www.zaproxy.org/docs/desktop/addons/grpc-support/")
extensions {
register("org.zaproxy.addon.grpc.internal.websocket.ExtensionGrpcWebSocket") {
classnames {
allowed.set(listOf("org.zaproxy.addon.grpc.internal.websocket"))
}
dependencies {
addOns {
register("websocket")
}
}
}
}
}
}

Expand All @@ -18,6 +30,7 @@ crowdin {
}

dependencies {
zapAddOn("websocket")
testImplementation(project(":testutils"))
implementation("io.grpc:grpc-protobuf:1.61.1")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.zaproxy.addon.grpc.internal.DecoderUtils;
import org.zaproxy.addon.grpc.internal.HttpPanelGrpcView;
import org.zaproxy.addon.grpc.internal.VariantGrpc;
import org.zaproxy.zap.extension.httppanel.component.split.request.RequestSplitComponent;
Expand Down Expand Up @@ -98,7 +99,9 @@ public String getName() {

@Override
public HttpPanelView getNewView() {
return new HttpPanelGrpcView(new ResponseBodyByteHttpPanelViewModel());
return new HttpPanelGrpcView(
new ResponseBodyByteHttpPanelViewModel(),
DecoderUtils.DecodingMethod.BASE64_ENCODED);
}

@Override
Expand All @@ -118,7 +121,9 @@ public String getName() {

@Override
public HttpPanelView getNewView() {
return new HttpPanelGrpcView(new RequestBodyByteHttpPanelViewModel());
return new HttpPanelGrpcView(
new RequestBodyByteHttpPanelViewModel(),
DecoderUtils.DecodingMethod.BASE64_ENCODED);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ public class DecoderUtils {
public static final int LENGTH_DELIMITED_WIRE_TYPE = 2;
public static final int BIT32_WIRE_TYPE = 5;

public enum DecodingMethod {
BASE64_ENCODED,
DIRECT
}

static boolean isGraphic(byte ch) {
// Check if the character is printable
// Printable characters have unicode values greater than 32 (excluding control
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ public class HttpPanelGrpcView implements HttpPanelView, HttpPanelViewModelListe
private ProtoBufMessageEncoder protoBufMessageEncoder;
private AbstractByteHttpPanelViewModel model;

public HttpPanelGrpcView(AbstractByteHttpPanelViewModel model) {
private final DecoderUtils.DecodingMethod decodingMethod;

public HttpPanelGrpcView(
AbstractByteHttpPanelViewModel model, DecoderUtils.DecodingMethod decodingMethod) {
httpPanelGrpcArea = new HttpPanelGrpcArea();
RTextScrollPane scrollPane = new RTextScrollPane(httpPanelGrpcArea);
scrollPane.setLineNumbersEnabled(false);
Expand All @@ -60,6 +63,7 @@ public HttpPanelGrpcView(AbstractByteHttpPanelViewModel model) {
model.addHttpPanelViewModelListener(this);
protoBufMessageDecoder = new ProtoBufMessageDecoder();
protoBufMessageEncoder = new ProtoBufMessageEncoder();
this.decodingMethod = decodingMethod;
}

@Override
Expand Down Expand Up @@ -126,7 +130,11 @@ public void save() {
try {
protoBufMessageEncoder.encode(EncoderUtils.parseIntoList(text));
byte[] encodedMessage = protoBufMessageEncoder.getOutputEncodedMessage();
this.model.setData(Base64.getEncoder().encode(encodedMessage));
if (decodingMethod == DecoderUtils.DecodingMethod.BASE64_ENCODED) {
this.model.setData(Base64.getEncoder().encode(encodedMessage));
} else {
this.model.setData(encodedMessage);
}
} catch (Exception e) {
showInvalidMessageFormatError(e.getMessage());
}
Expand All @@ -147,8 +155,13 @@ public void dataChanged(HttpPanelViewModelEvent e) {
httpPanelGrpcArea.setBorder(null);
try {
body = DecoderUtils.splitMessageBodyAndStatusCode(body);
body = Base64.getDecoder().decode(body);
byte[] payload = DecoderUtils.extractPayload(body);
byte[] payload;
if (decodingMethod == DecoderUtils.DecodingMethod.BASE64_ENCODED) {
body = Base64.getDecoder().decode(body);
payload = DecoderUtils.extractPayload(body);
} else {
payload = body;
}
if (payload.length == 0) {
httpPanelGrpcArea.setText("");
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2024 The ZAP Development Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zaproxy.addon.grpc.internal.websocket;

import java.util.List;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.extension.Extension;
import org.parosproxy.paros.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.zaproxy.addon.grpc.ExtensionGrpc;
import org.zaproxy.addon.grpc.internal.DecoderUtils;
import org.zaproxy.addon.grpc.internal.HttpPanelGrpcView;
import org.zaproxy.zap.extension.httppanel.view.HttpPanelView;
import org.zaproxy.zap.extension.websocket.ExtensionWebSocket;
import org.zaproxy.zap.extension.websocket.ui.httppanel.component.WebSocketComponent;
import org.zaproxy.zap.extension.websocket.ui.httppanel.models.ByteWebSocketPanelViewModel;
import org.zaproxy.zap.view.HttpPanelManager;

public class ExtensionGrpcWebSocket extends ExtensionAdaptor {

public static final String NAME = "ExtensionGrpcWebSocket";
private static final List<Class<? extends Extension>> DEPENDENCIES =
List.of(ExtensionGrpc.class, ExtensionWebSocket.class);

public ExtensionGrpcWebSocket() {
super(NAME);
}

@Override
public List<Class<? extends Extension>> getDependencies() {
return DEPENDENCIES;
}

@Override
public void hook(ExtensionHook extensionHook) {
if (hasView()) {
HttpPanelManager manager = HttpPanelManager.getInstance();
manager.addRequestViewFactory(WebSocketComponent.NAME, new WebSocketGrpcViewFactory());
manager.addResponseViewFactory(WebSocketComponent.NAME, new WebSocketGrpcViewFactory());
}
}

@Override
public boolean canUnload() {
return true;
}

@Override
public void unload() {
HttpPanelManager manager = HttpPanelManager.getInstance();
manager.removeRequestViewFactory(WebSocketComponent.NAME, WebSocketGrpcViewFactory.NAME);
manager.removeResponseViewFactory(WebSocketComponent.NAME, WebSocketGrpcViewFactory.NAME);
}

@Override
public String getUIName() {
return Constant.messages.getString("grpc.websocket.name");
}

@Override
public String getDescription() {
return Constant.messages.getString("grpc.websocket.desc");
}

private static final class WebSocketGrpcViewFactory
implements HttpPanelManager.HttpPanelViewFactory {
public static final String NAME = "WebSocketGrpcViewFactory";

@Override
public String getName() {
return NAME;
}

@Override
public HttpPanelView getNewView() {
return new HttpPanelGrpcView(
new ByteWebSocketPanelViewModel(), DecoderUtils.DecodingMethod.DIRECT);
}

@Override
public Object getOptions() {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ <h2>See also</h2>
<td><a href="variant.html">gRPC Variant</a></td>
<td>for information about the gRPC variant.</td>
</tr>
<tr>
<td>&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td><a href="websocket.html">gRPC WebSocket</a></td>
<td>for information about the gRPC WebSocket Support.</td>
</tr>

</table>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ <h2>See also</h2>
<td><a href="grpc.html">gRPC</a></td>
<td>for an overview of the gRPC add-on.</td>
</tr>
<tr>
<td>&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td><a href="websocket.html">gRPC WebSocket</a></td>
<td>for information about the gRPC WebSocket Support.</td>
</tr>

</table>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
<TITLE>gRPC WebSocket</TITLE>
</HEAD>
<BODY>
<H1>gRPC WebSocket Support</H1>

The gRPC WebSocket support feature enables the WebSocket add-on to decode and encode gRPC messages seamlessly. This integration allows users to inspect, modify, and manage gRPC communication over WebSockets.
<h2>See also</h2>
<table>
<tr>
<td>&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td><a href="grpc.html">gRPC</a></td>
<td>for an overview of the gRPC add-on.</td>
</tr>
<tr>
<td>&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td><a href="variant.html">gRPC Variant</a></td>
<td>for information about the gRPC variant.</td>
</tr>

</table>

</BODY>
</HTML>
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
<index version="2.0">
<indexitem text="grpc" target="addon.grpc" />
<indexitem text="grpc variant" target="addon.grpc.variant" />
<indexitem text="grpc websocket" target="addon.grpc.websocket" />
</index>
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
<mapID target="addon.grpc.icon" url="../common/images/grpc.png"/>
<mapID target="addon.grpc" url="contents/grpc.html"/>
<mapID target="addon.grpc.variant" url="contents/variant.html" />
<mapID target="addon.grpc.websocket" url="contents/websocket.html" />
</map>
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<tocitem text="Add Ons" tocid="addons">
<tocitem text="gRPC Support" image="addon.grpc.icon" target="addon.grpc">
<tocitem text="Variant" target="addon.grpc.variant"/>
<tocitem text="WebSocket" target="addon.grpc.websocket"/>
</tocitem>
</tocitem>
</tocitem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ grpc.encoder.nested.message.missing.braces.error = Invalid Format: Extra or miss
grpc.encoder.nested.message.newline.error = Invalid Format: Nested message must contain a newline immediately after every curly brace
grpc.name = gRPC Support
grpc.panel.view.name = gRPC
grpc.websocket.desc = Provides the WebSocket gRPC view and edit features
grpc.websocket.name = gRPC Websocket Support

0 comments on commit f4f286a

Please sign in to comment.