RPC là gì?
Gọi thủ tục từ xa (RPC - Remote Procedure Calls) là một kỹ thuật mạnh mẽ để xây dựng các ứng dụng phân tán. Nó dựa trên việc mở rộng cách gọi thủ tục cục bộ thông thường để thủ tục được gọi không cần tồn tại trong cùng không gian địa chỉ với thủ tục gọi. Hai quy trình có thể nằm trên cùng một hệ thống hoặc chúng có thể nằm trên các hệ thống khác nhau có mạng kết nối chúng.
-
Môi trường gọi bị tạm dừng, các tham số thủ tục được chuyển qua mạng tới môi trường nơi thủ tục được thực thi và thủ tục được thực thi ở đó.
-
Khi thủ tục kết thúc và tạo ra kết quả của nó, kết quả của nó được chuyển trở lại môi trường gọi, nơi quá trình thực thi tiếp tục như thể trở lại từ một lệnh gọi thủ tục thông thường.
RPC có thể được xem là một giao thức request-respone thông thường tuy nhiên nó được dùng cho việc giao tiếp giữa các server với nhau (server-server) nhiều hơn là client-server. Việc này có ý nghĩa rất quan trọng vì trong các hệ thống phân tán (distributed system), application code ở nhiều server hơn là một server. Ví dụ thường thấy nhất chính là kiến trúc Microservices.
Điều này nghĩa là: một request phía client có thể sẽ phải cần nhiều service chạy trên các server này để tổng hợp thông tin rồi mới response cho client. Sự liên lạc giữa các server lúc này sẽ là vấn đề mà trước đó tất cả service chạy trên 1 server thì khoẻ re, vì local call nên chẳng ngại gì cả. Chính xác là khi đó, khi một server muốn "nói chuyện" với server khác sẽ cần phải encode data (JSON, XML), phía nhận cũng phải làm công việc ngược lại là decode data mới hiểu thằng kia nói gì với mình rôi lại phải encode lại tiếp. Việc này tiêu tốn khá nhiều tài nguyên xử lý (CPU) mà lẽ ra chỉ cần làm ở bước đầu và cuối (đầu nhận và trả về cuối cùng).
Giới thiệu về apache dubbo
Dubbo là một RPC và microservice framework mã nguồn mở của Alibaba.
Nó giúp tăng cường quản trị dịch vụ và làm cho các ứng dụng nguyên khối truyền thống có thể được cấu trúc lại một cách trơn tru thành một kiến trúc phân tán có thể mở rộng.
Trong bài viết này, tôi sẽ giới thiệu về Dubbo và các tính năng quan trọng nhất của nó.
Architecture
Dubbo bao gồm một số thành phần:
- Provider - Nhà cung cấp - nhà cung cấp sẽ tạo dịch vụ của mình để đăng ký
- Container - Vùng chứa - nơi dịch vụ được khởi tạo, tải và chạy
- Consumer - Người tiêu dùng - người yêu cầu các dịch vụ từ xa; người tiêu dùng sẽ đăng ký dịch vụ cần thiết trong sổ đăng ký
- Registry - Cơ quan đăng ký - nơi dịch vụ sẽ được đăng ký
- Monitor - Giám sát - ghi lại số liệu thống kê cho các dịch vụ, ví dụ, tần suất gọi dịch vụ trong một khoảng thời gian nhất định
Kết nối giữa Provider
, Consumer
và Registry
là liên tục, vì vậy bất cứ khi nào Provider
gặp sự cố, Registry
có thể phát hiện lỗi và thông báo cho Consumer
.
Registry
và Monitor
là tùy chọn. Consumer
có thể kết nối trực tiếp với các Provider
, nhưng sự ổn định của toàn hệ thống sẽ bị ảnh hưởng.
Hoạt động của kiến trúc dubbo được mô tả như sau:
Container
chịu trách nhiệm khởi tạo, tải và chạyProvider
.Provider
đăng ký các dịch vụ của mình đếnRegister
trong quá trình khởi tạo.Consumer
đăng ký các dịch vụ họ cần từRegister
khi nó bắt đầu.Register
trả lại danh sáchProvider
choConsumer
và khi thay đổi xảy ra,Register
sẽ đẩy dữ liệu đã thay đổi đếnConsumer
.Consumer
lựa chọn một trong cácProvider
dựa trên thuật toán cân bằng tải mềm, sau đó thực hiện lệnh gọi, tự động chọnProvider
khác khi xảy ra lỗi.- Cả
Consumer
vàProvider
sẽ đếm số lần gọi dịch vụ và thời gian sử dụng trong bộ nhớ, đồng thời gửi số liệu thống kê đếnMonitor
mỗi phút.
Dubbo có các tính năng sau: Kết nối, Mạnh mẽ, Khả năng mở rộng và Khả năng nâng cấp.
Danh sách lỗ hổng java deserialization liên quan apache dubbo:
- CVE-2020-11995
- CVE-2020-1948
- CVE-2019-17564
Phân tích CVE 2019-17564
Apache Dubbo hỗ trợ nhiều giao thức như dubbo
, hessian
, http
.....Tất cả đều được liệt kê trên trang https://dubbo.apache.org/en/docs/v2.7/user/references/protocol/. Mặc định thì Dubbo RPC Framework sử dụng giao thức dubbo
.
CVE-2019-17564 là một lỗ hổng deserialization xảy ra khi giao thức Apache Dubbo HTTP được sử dụng, Kẻ tấn công có thể gửi một yêu cầu POST với một đối tượng độc hại đến một URL của dịch vụ Apache Dubbo HTTP, điều này sẽ dẫn đến việc thực thi mã từ xa.
Các phiên bản bị ảnh hưởng
- 2.7.0 <= Apache Dubbo <= 2.7.4
- 2.6.0 <= Apache Dubbo <= 2.6.7
- Apache Dubbo = 2.5.x
Dựng lại môi trường
Để thuận tiện cho việc thiết lập môi trường, hãy truy cập trang chủ dự án github của dubbo-Sample và tải về dubbo-sample (https://github.com/apache/dubbo-samples)
Sau khi tải mã nguồn về, lựa chọn dubbo-sample-http và mở như là 1 project trong Intelij
Sau khi load project xong xuôi. Chúng ta sẽ sửa lại phiên bản dubbo thành 2.7.3 để có thể khai thác. Chú ý thêm vào <version>2.7.3</version>
ở dòng 81 thì mới có thể thay đổi thành công
Tiếp theo chúng ta sẽ cần phải có zookeeper để cho dubbo kết nối đến. Zookeeper là một dịch vụ (một server) tập trung cho việc duy trì thông tin cấu hình, đặt tên, cung cấp sự đồng bộ phân tán, cung cấp dịch vụ nhóm. Nói cách khác, Zookeeper dịch vụ phối hợp phân tán cho các ứng dụng phân tán. Client kết nối tới một ZooKeeper server (một single node). Client duy trì kết nối TCP thông qua việc gửi nhận request, response, lắng nghe sự kiện… Nếu server bị chết thì client sẽ được kết nối tới một server khác. Các bạn có thể tải zookeeper tại đây: https://zookeeper.apache.org/releases.html.
Sau khi tải zookeeper về các bạn khởi chạy nó lên zkServer.bat
. Lưu ý là trước khi chạy các bạn tạo 1 file zoo.cfg trong thư mục conf với nội dung copy từ file zoo_sample.cfg:
Nếu hiển thị như thế này là đã thành công rồi nhé.
Phân tích và debug.
Khỏi chạy lớp HttpProvider. Nhận thấy provider đã đăng kí 1 dịch vụ với zookeeper http://172.22.224.1:8089/org.apache.dubbo.samples.http.api.DemoService?anyhost=true&application=http-provider&bean.name=org.apache.dubbo.samples.http.api.DemoService&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.samples.http.api.DemoService&methods=sayHello&pid=8700®ister=true&release=2.7.3&server=tomcat&side=provider×tamp=1618451446458
.
Do đây là dịch vụ Http nên chúng ta hoàn toàn có thể bắt request bằng burpsuite để sửa đổi. Tạo 1 request đến dịch vụ trên, và bắt lại bằng burpsuite để tiện sửa đổi
Request này sẽ đi qua method org.apache.dubbo.remoting.http.servlet.DispatcherServlet.service
do lớp DispatcherServlet
kế thừa lớp javax.servlet.http.HttpServlet
là nơi xử lý toàn bộ các request http được gửi tới server. Đặt breakpoint tại dòng 43.
Có 1 cách dễ dàng để nhận ra luồn hoạt động của chương trình là quan sát stack trace bên góc trái màn hình Intelij. Với đỉnh satck là nơi chúng ta đang đứng.
Bấm F7 để nhảy tới method tiếp theo đó là: /org/apache/dubbo/rpc/protocol/http/HttpProtocol.handle
Để ý tiếp ở dòng 211. Đoạn code này sẽ check xem request chúng ta gửi lên thuộc loại method nào. Nếu khác với POST
thì sẽ trả về 500 luôn mà không xử lý gì tiếp theo. Do đó để có thể đi tiếp đến dòng 216 thì chúng ta cần sửa lại request thành POST
request và thử lại.
Sau khi lặp lại các bước trên chúng ta đặt breakpoint tại dòng 216 và bấm F7 để nhảy tới method xử lý tiếp theo là org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter.handleRequest
Nhiệm vụ của method này là đọc một remote invocation (lệnh gọi từ xa) từ request, thực thi nó và ghi kết quả lệnh gọi từ xa vào response. Để ý method readRemoteInvocation
có nhiệm vụ đọc request của chúng ta. Nhảy vào method đó để xem tiếp
Ta thấy sau khi nhảy vào method trên nó lại tiếp tục gọi đến 1 method cùng tên nhằm đọc được InputStream trong request. Chú ý đến param request.getInputStream()
.
Đến đây thì đối với một số người đã nghiên cứu về java deserialization ở đây sẽ nhận ra ngay vấn đề đã nằm ở chỗ này rồi. Đó là đọc 1 stream bất kì mà người dùng nhập vào trong POST
request. Chú ý ở dòng 119 chương trình đã tạo lại ObjectInputStream từ stream chúng ta gửi lên và truyền vào method doReadRemoteInvocation
. Nhảy vào method này để đọc tiếp.
Đến đay thì chương trình thực hiện readObject(). Từ đây ta đã có endpoind để trigger lỗ hổng java deserialization rồi. Tiếp theo chỉ là vấn đề tìm 1 chain để có thể khai thác tiếp. Vấn đề này phụ thuộc vào các gói jar trong project và tùy vào tình hình thực tế. Ở đây để đơn giản tôi sẽ thêm vào commons-collectons:3.2
để demo lỗ hổng này.
Sau khi load lại chương trình. Tôi sử dụng 1 extension khá hay ho của burp suite là Deserialization Scanne
. Nó có thể gen ra payload và gửi trực tiếp lên server. Ở đây tôi dùng gadget chain CommonsCollection 5 nhé.
Và đây là kết quả của mình.
Tham khảo
https://www.geeksforgeeks.org/remote-procedure-call-rpc-in-operating-system/ https://www.baeldung.com/dubbo https://dubbo.apache.org/en/docs/v2.7/user/preface/architecture/ https://y4er.com/post/apache-dubbo-cve-2019-17564/