通用技术 mitmproxy grpc 代理抓包

黑水 · October 07, 2020 · Last by 黑水 replied at April 16, 2021 · 4808 hits

之前在 mitmproxy 上支持了 http2 trailer,pr 已经合并到 5.2 版本,可以正常代理 grpc 请求了,介绍下如何使用。
grpc 原始信息不可读,可以写个自定义视图显示反序列化后的内容。

先用 grpc 官方示例启动一个 grpc 服务:

python greeter_server.py

客户端就用 bloomrpc,不另外写代码了。导入对应的 proto 文件,向 localhost:50051 发送请求验证服务有没有正常工作:

因为 mitmproxy 目前只支持 TLS 上的 HTTP/2,还需要再用 nginx 转发并加上 TLS。
参考这篇文章生成自签名证书:

openssl genrsa -des3 -out rootCA.key 4096
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt
openssl genrsa -out localhost.key 2048
openssl req -new -key localhost.key -out localhost.csr # Common Name 输入域名 localhost,其他随便填一下
openssl req -in localhost.csr -noout -text
openssl x509 -req -in localhost.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out localhost.crt -days 500 -sha256
openssl x509 -in localhost.crt -text -noout
cat localhost.crt localhost.key > localhost.pem

配置 nginx:

server {
    listen 6556 ssl http2;
    server_name  localhost;

    ssl_certificate ~/localhost.pem;
    ssl_certificate_key ~/localhost.pem;

    location / {
        grpc_pass grpc://localhost:50051;
    }
}

配置好启动 nginx,在 bloomrpc 里导入证书:

向 localhost:6556 发送请求验证 nginx 有没有正常工作:

启动 mitmproxy,因为是自签名证书,多加两个参数:

mitmweb --certs localhost=~/localhost.pem -k

MacOS 上,通过下面的命令重新启动 bloomrpc,可以让 bloomrpc 发出的请求经过代理:

export http_proxy=http://127.0.0.1:8080;export https_proxy=http://127.0.0.1:8080;
open /Applications/BloomRPC.app

再次向 localhost:6556 发送请求,验证 mitmweb 有没有正常工作:

然后写自定义视图,先安装 mitmproxy 依赖:

pip install mitmproxy

新建 grpc_addon.py 内容如下:

from mitmproxy import contentviews, http
import helloworld_pb2

class GrpcView(contentviews.View):
    name = 'grpc'

    def __call__(self, data, **metadata) -> contentviews.TViewResult:
        # 参考 https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md,grpc 请求 body 的第一个字节代表有没有被压缩,示例中没有压缩,所以不需要额外处理。二到五字节是 protobuf 内容的长度。
        compressed_flag = data[0]
        message_length = int.from_bytes(data[1:5], 'big')
        message = data[5:message_length + 5]
        # 之后就是反序列化 protobuf
        if isinstance(metadata['message'], http.HTTPRequest):
            message_method = helloworld_pb2.HelloRequest
        elif isinstance(metadata['message'], http.HTTPResponse):
            message_method = helloworld_pb2.HelloReply

        target = message_method()
        target.ParseFromString(message)

        return 'deserialized grpc', contentviews.format_text(str(target))

grpc_view = GrpcView()

def load(l):
    contentviews.add(grpc_view)

def done():
    contentviews.remove(grpc_view)

重新启动 mitmproxy,加载 grpc_addon.py

mitmweb --certs localhost=~/localhost.pem -k -s grpc_addon.py

再次向 localhost:6556 发送请求,可以查看反序列化后的内容了:

也可以改写响应内容,在 grpc_addon.py 加上如下内容后再发送请求看看:

class Rewrite:
    def response(self, flow):
        data = flow.response.raw_content
        compressed_flag = data[0]
        message_length = int.from_bytes(data[1:5], 'big')
        message = data[5:message_length + 5]
        target = helloworld_pb2.HelloReply()
        target.ParseFromString(message)
        target.message = 'Hello, rewrited'
        modified_message = target.SerializeToString()
        # 改写后内容长度可能会变,所以要重新计算长度
        modified_response = data[0].to_bytes(1, byteorder='big') + len(modified_message).to_bytes(4, byteorder='big') + modified_message
        flow.response.headers['content-length'] = str(len(modified_response))
        flow.response.raw_content = modified_response

addons = [
    Rewrite()
]
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 6 条回复 时间 点赞

赞~~
grpc 感觉越来越火~

基于二进制的应用层协议貌似没有长命的……

mitmproxy 可以像代理 http 一样直接代理 Grpc 协议吗?

黑水 #4 · April 13, 2021 Author
踏雪寻梅 回复

直接代理是指什么呢?mitmproxy 代理后不影响客户端功能,Charles 会影响。

import time

from mitmproxy.script import concurrent


@concurrent  # Remove this and see what happens
def request(flow):
    # This is ugly in mitmproxy's UI, but you don't want to use mitmproxy.ctx.log from a different thread.
    print(f"handle request: {flow.request.host}{flow.request.path}")
    time.sleep(5)
    print(f"start  request: {flow.request.host}{flow.request.path}")

启动命令:mitmdump -p 8333 -s examples/addons/nonblocking.py --set upstream_cert=false --ssl-insecure --flow-detail 3
发送 HTTP 请求: curl http://www.xxx.com --proxy http://0.0.0.0:8333

启动 mitmproxy 代理,如果是 HTTP 请求的话,request 函数中可以接收处理,可以根据 host (flow.request.pretty_host) 是否等于 www.xxx.com 决定是否代理到不同服务。

我现在想解决的是 mitmproxy 能正常接收 grpc 的请求,同样根据 host 转发到不同服务。请问下针对这块楼主有经验吗?

6Floor has deleted
黑水 #7 · April 16, 2021 Author
踏雪寻梅 回复

不用 -s examples/addons/nonblocking.py,mitmdump 也能抓到 grpc 请求的。
没用过from mitmproxy.script import concurrent ,用官方文档的例子 grpc 也是正常的 https://docs.mitmproxy.org/stable/addons-scripting/

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up