grpc基于http2协议,且client和server交互时要依赖提前约定的proto文件中的接口、字段等,所以目前黑盒扫描方式中通过流量镜像等方式不能适应grpc场景。看到在blackhat asia中的议题《 Hunting Vulnerabilities of gRPC Protocol Armed Mobile/IoT Applications》,之前自己基于grpc反射的特型做过部分研究和实践。

Grpc 反射原理

Server端

1
2
3
4
5
6
7
8
9
10
final Server server = ServerBuilder.forPort(port)
.addService(new HealthStatusManager().getHealthService())
.addService(ProtoReflectionService.newInstance()) //添加反射服务
.build()
.start();
````
当Client调用Server端反射服务时,server端的调用链
io/grpc/protobuf/services/ProtoReflectionService.java,updateIndexIfNecessary 方法检查服务是否有更新并修改服务索引serverReflectionIndex
```java
updateIndexIfNecessary 方法

在io/grpc/protobuf/services/ProtoReflectionService.java handleReflectionRequest方法中处理请求,感觉request请求中的参数使用不同的方法做处理并返回定义的结果值。在handleReflectionRequest方法的结构中支持5种请求参数及对应的处理方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void handleReflectionRequest() {
if (serverCallStreamObserver.isReady()) {
switch (request.getMessageRequestCase()) {
case FILE_BY_FILENAME:
getFileByName(request);
break;
case FILE_CONTAINING_SYMBOL:
getFileContainingSymbol(request);
break;
case FILE_CONTAINING_EXTENSION:
getFileByExtension(request);
break;
case ALL_EXTENSION_NUMBERS_OF_TYPE:
getAllExtensions(request);
break;
case LIST_SERVICES:
listServices(request);
break;
default:
sendErrorResponse(
request,
Status.Code.UNIMPLEMENTED,
"not implemented " + request.getMessageRequestCase());
}
request = null;
if (closeAfterSend) {
serverCallStreamObserver.onCompleted();
} else {
serverCallStreamObserver.request(1);
}
}
}

在获取获取serverReflectionIndex 列表的listServices方法为例,其功能就是获取在server端注册的所有的serverName。

1
2
3
4
5
6
7
8
9
10
11
12
private void listServices(ServerReflectionRequest request) {
ListServiceResponse.Builder builder = ListServiceResponse.newBuilder();
for (String serviceName : serverReflectionIndex.getServiceNames()) {
builder.addService(ServiceResponse.newBuilder().setName(serviceName));
}
serverCallStreamObserver.onNext(
ServerReflectionResponse.newBuilder()
.setValidHost(request.getHost())
.setOriginalRequest(request)
.setListServicesResponse(builder)
.build());
}

所以我们可以通过handleReflectionRequest方法中提供的能力获取grpc server端接口及其字段信息,并mock填充相关字段值后调用FILE_CONTAINING_SYMBOL/getFileContainingSymbol 实际的调用来做漏洞的测试。

1
2
3
4
5
6
7
8
9
private void getFileContainingSymbol(ServerReflectionRequest request) {
String symbol = request.getFileContainingSymbol();
FileDescriptor fd = serverReflectionIndex.getFileDescriptorBySymbol(symbol);
if (fd != null) {
serverCallStreamObserver.onNext(createServerReflectionResponse(request, fd));
} else {
sendErrorResponse(request, Status.Code.NOT_FOUND, "Symbol not found.");
}
}

实践测试

因为grpc 基于http2,所以在zoomeye中获取可能是grpc server端的ip、port信息

1
\x00\x00\x12\x04\x00\x00\x00\x00\x00\x00\x03\x7f\xff\xff\xff\x00\x04\x00\x10\x00\x00\x00\x06\x00\x00 \x00\x00\x00\x04\x08\x00\x00\x00\x00\x00\x00\x0f\x00\x01\x00\x00+\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01

通过探测grpc server是否开启反射、通过反射获取相关服务等,在6920个结果中有168个开启反射服务且同时存在其他业务逻辑结果,扫描结果也包含部分国内大厂接口,对扫描结果的serverName排序展示部分结果:
+——-+——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————+
| 13 | grpc.health.v1.Health,grpc.reflection.v1alpha.ServerReflection |
| 6 | grpc.health.v1.Health,ops.Operations,grpc.reflection.v1alpha.ServerReflection,events.Caching |
| 6 | FlowReport.FlowReport,araali_ui.AraaliUI,grpc.reflection.v1alpha.ServerReflection,araali_relocation.AraaliRelocationServices |
| 5 | com.metapack.allocation.dde.v1.DeliveryDecisionEngine,com.metapack.allocation.dde.v1.DeliveryDecisionEngineDebug,grpc.reflection.v1alpha.ServerReflection |
| 5 | grpc.reflection.v1alpha.ServerReflection,privacy.dlpcontainer.DlpService |
| 4 | com.ps.account.controller.grpc.OrganizationUserGrpcEndpoint,com.ps.account.controller.grpc.OrganizationGrpcEndpoint,com.ps.account.controller.grpc.UserGrpcEndpoint,grpc.health.v1.Health,grpc.reflection.v1alpha.ServerReflection,com.ps.account.controller.grpc.OrganizationRoleGrpcEndpoint |
| 4 | grpc.health.v1.Health,com.gopawnshop.myrestart.grpc.NotificationService,grpc.reflection.v1alpha.ServerReflection |
| 4 | orion.data.orderevent.OrderEventSendService,grpc.health.v1.Health,grpc.reflection.v1alpha.ServerReflection,orion.data.grpc.business.BusinessPingService |
| 4 | api.BdbMetrics,api.BdbUsageMetrics,api.BdbStates,api.BdbStatsCleaner,api.BdbMetrics,api.BdbUsageMetrics,api.BdbStates,api.BdbStatsCleaner,api.BdbMetrics,api.BdbUsageMetrics,api.BdbStates,api.BdbStatsCleaner,api.BdbMetrics,api.BdbUsageMetrics,api.BdbStates,api.BdbStatsCleaner,grpc.channelz.v1.Channelz,grpc.health.v1.Health,grpc.reflection.v1alpha.ServerReflection |
| 3 | service.version.Version,grpc.health.v1.Health,service.poneglyph.Poneglyph,grpc.reflection.v1alpha.ServerReflection |
+——-+——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————+
然后通过反射获取的信息,编写代理服务将其转换为http请求,可以和想有的漏洞扫描工具做结合达到漏洞扫描目的。