Bài giới thiệu Apache Dubbo và phân tích lỗ hổng CVE 2019-17564

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.

  1.     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 ở đó.

  2.     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:

  1. Provider - Nhà cung cấp - nhà cung cấp sẽ tạo dịch vụ của mình để đăng ký
  2. Container - Vùng chứa - nơi dịch vụ được khởi tạo, tải và chạy
  3. 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ý
  4. Registry - Cơ quan đăng ký - nơi dịch vụ sẽ được đăng ký
  5. 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, ConsumerRegistry 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.

    RegistryMonitor 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:

  1. Container chịu trách nhiệm khởi tạo, tải và chạy Provider.
  2. Provider đăng ký các dịch vụ của mình đến Register trong quá trình khởi tạo.
  3. Consumer đăng ký các dịch vụ họ cần từ Register khi nó bắt đầu.
  4. Register trả lại danh sách Provider cho Consumer và khi thay đổi xảy ra, Register sẽ đẩy dữ liệu đã thay đổi đến Consumer.
  5. Consumer lựa chọn một trong các Provider 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ọn Provider khác khi xảy ra lỗi.
  6. Cả ConsumerProvider 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ê đến Monitormỗ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&register=true&release=2.7.3&server=tomcat&side=provider&timestamp=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 POSTrequest. 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/

Nguồn: Viblo

Bình luận
Vui lòng đăng nhập để bình luận
Một số bài viết liên quan