Hành trình khai thác lỗ hổng phần mềm CVE-2020–5902

Khanh Ta Quang
Jul 13 · 10 min read

Chắc hẳn nhiều người có thể đã biết đến lỗ hổng mới được công bố tồn tại trên hệ thống BigIP của F5, rất được cồng đồng quan tâm gần đây do lượt tweet mã khai thác quá nhiều. Sau khi đọc thì tôi cũng tò mò về root cause của lỗ hổng và càng thấy thú vị hơn về mitigation bypass được công bố vài ngày sau đó, do đó tôi quyết định tìm hiểu sâu hơn về lỗ hổng này. Bài viết chia sẻ lại một số câu hỏi mà tôi đã đặt ra và trả lời trong quá trình hiểu về lỗ hổng.

Bài viết này được viết sau khi tìm hiểu và trao đổi, hướng dẫn bởi anh QuangNX (@sovietw0rm) và QuangBX (@buxu). Xin chân thành cảm ơn các chuyên gia.

Câu chuyện

Mã khai thác được lan truyền nhiều trên mạng xã hội chỉ vài ngày ngắn ngủi sau khi F5 ra KB về lỗ hổng này. Tôi bắt đầu với một số câu hỏi trong đầu: tại sao lỗ hổng lại tồn tại — tại sao với chuỗi ..; lại có thể bypass auth, hơn nữa lại chỉ có thể áp dụng với /tmui/login.jsp mà không phải là các endpoint khác.

Theo KB, tôi tải xuống phiên bản BigIP Virtual Edition 15.1.0.0 là một phiên bản có lỗi. Chúng ta có đoạn cấu hình httpd như sau:

// httpd.conf
<Location /tmui>
….
<RequireAll>
AuthType Basic
AuthName “Restricted area”
AuthPAM_Enabled on
AuthPAM_ExpiredPasswordsSupport on
AuthPam_ValidateIP On
AuthPAM_IdleTimeout 1200
AuthPAM_DashboardTimeout Off
require valid-user
Require all granted</RequireAll>
</Location>

Cấu hình này bắt buộc các HTTP requests đến /tmui/* phải xác thực thông qua PAM. Nếu thỏa mãn (authenticated), request sẽ được gửi qua cho ứng dụng servlet xử lý qua AJP. Ở đây tôi không có nhiều kiến thức về tomcat nên chỉ có thể hiểu sơ qua vậy.

// /config/httpd/conf.d/proxy_ajp.confProxyPassMatch ^/tmui/(.*\.jsp.*)$ ajp://localhost:8009/tmui/$1 retry=5
ProxyPassMatch ^/tmui/Control/(.*)$ ajp://localhost:8009/tmui/Control/$1 retry=5
ProxyPassMatch ^/tmui/deal/?(.*)$ ajp://localhost:8009/tmui/deal/$1 retry=5
ProxyPassMatch ^/tmui/graph/(.*)$ ajp://localhost:8009/tmui/graph/$1 retry=5
ProxyPassMatch ^/tmui/service/(.*)$ ajp://localhost:8009/tmui/service/$1 retry=5
ProxyPassMatch ^/hsqldb(.*)$ ajp://localhost:8009/tmui/hsqldb$1 retry=5

Các requests này sẽ được handle bởi các servlet định nghĩa trong web.xml (/usr/local/www/tmui/WEB-INF/web.xml). Về cơ bản, các ứng dụng servlet của F5 sử dụng không hề có cơ chế xác thực mà chỉ phụ thuộc vào PAM module với httpd (/config/httpd/modules/mod_auth_pam.so).

Tôi có nhớ lại bài nói của Orange về việc xử lý path không đồng nhất giữa các thành phần trong server stack tại BlackHat 2018: Breaking Parser Logic: Take your path normalization off and pop 0days out. Thời điểm đó tôi cũng chỉ đọc cho biết vậy thôi nhưng chưa có ý định xem cụ thể nó là gì. Tại đây tác giả có trình bày một “lỗ hổng” tồn tại mặc định khi sử dụng apache proxy và tomcat khi normalize path. Nguyên nhân là do Tomcat hỗ trợ một tính năng là path parameter. Đây không phải là lần đầu tiên mà vấn đề security liên quan đến tính năng này được nhắc đến, tuy nhiên có lẽ Orange là người đầu tiên (công bố) tận dụng để nâng cao mức độ nguy hiểm.

Về cơ bản, tính năng path parameter cho phép xác định một số biến và giá trị thông qua URL, phân cách bởi các ký tự ;/ theo dạng: /path/name;param1=value1;param2=value2/. Tuy nhiên, do httpd không hỗ trợ tính năng này nên nó sẽ xem ..; hoặc ; như một phần hợp lệ của request mà không “normalize”. Đây chính là nguyên nhân dẫn đến lỗ hổng này.

Image for post
Image for post

Chúng ta sẽ tìm hiểu cách thức tomcat parse các URI để rõ hơn vấn đề này. Tìm kiếm từ khóa path parameter trên mã nguồn của tomcat, tôi tìm thấy đoạn code sau tại hàm postParseRequest của lớp CoyoteAdapter:

if (undecodedURI.getType() == MessageBytes.T_BYTES) {
// Copy the raw URI to the decodedURI
decodedURI.duplicate(undecodedURI);
// Parse (and strip out) the path parameters
parsePathParameters(req, request);
// URI decoding
// %xx decoding of the URL
try {
req.getURLDecoder().convert(decodedURI.getByteChunk(), connector.getEncodedSolidusHandlingInternal());
} catch (IOException ioe) {
response.sendError(400, "Invalid URI: " + ioe.getMessage());
}
// Normalization
if (normalize(req.decodedURI(), connector.getAllowBackslash())) {
// Character decoding
convertURI(decodedURI, request);
// URIEncoding values are limited to US-ASCII supersets.
// Therefore it is not necessary to check that the URI remains
// normalized after character decoding
} else {
response.sendError(400, "Invalid URI");
}
}
Image for post
Image for post

Hàm parsePathParameters parse các path parameter từ URL và loại bỏ các thành phần này ra khỏi URL, bao gồm dấu “;”

Image for post
Image for post

Hàm normalize() sẽ normalize các chuỗi trong path bao gồm \, //, /.//../

Image for post
Image for post

Trong PoC video (debug tomcat) dưới đây, có vẻ như Eclipse cập nhật thông tin biến hơi chậm, thực tế thì uri đã được normalized sau khi thực thi hàm normalize().

Về câu hỏi tại sao chỉ post đến /tmui/login.jsp, tôi tiến hành phân tích hàm register_hooks của module mod_f5_auth_cookie.so. Để xác định hàm này, các bạn có thể tìm kiếm giá trị MODULE_MAGIC_COOKIE trong cấu trúc STANDARD20_MODULE_STUFF khi khai báo một module.

Image for post
Image for post

Hook handler sẽ so sánh uri của request với một số chuỗi URL có sẵn, trong đó có /tmui/login.jsp và trả về mã OK. Phần này tôi có một số ngoại cảm về “how it work”, bao gồm việc module này trả về OK thì module mod_auth_pam.so có check nữa hay không, đành dành cho bạn nào muốn tìm hiểu sâu hơn về viết module cho httpd.

Nhân tiện, a QuangNX vừa gửi lại cho tôi một bài viết của NCCGroup cũng phân tích về root cause của lỗ hổng này ngay trước thời điểm đăng bài viết, bạn đọc có thể tham khảo.

Khi câu chuyện đi xa hơn

Sau vài ngày (08/07/2020) thì một nhóm TeamAres cũng lấy số bằng việc công bố một blog bypass mitigation được F5 đề xuất trên KB. Nhóm này phát hiện có thể (ab)use endpoint /hsqldb để có thể thực hiện RCE — thực thi các Java static method tuỳ ý (hỗ trợ bởi hsqldb). Xin lưu ý rằng đây là bypass mitigation mà F5 đã đề xuất trên KB, không phải là bypass bản cập nhật. Mặc dù bản cập nhật họ cũng có cấu hình httpd y hệt, tuy nhiên endpoint /hsqldb đã bị họ bỏ đi, không còn truy cập thông qua giao diện web được nữa. (Tôi không rõ tại sao lại có endpoint này, có thể là cho quá trình phát triển/gỡ lỗi)

Có 2 điều làm cho tôi chú ý về writeup này:

  • Endpoint /hsqldb có thể bypass authen với một ký tự ;, thay vì chuỗi ..; như với exploit ban đầu
  • Tác giả nói rằng khi khai thác lỗ hổng này, họ kết nối tới CSDL thông qua /hsqldb; thì bị lỗi, cho nên đã sử dụng exploit ban đầu và encode ký tự ; (0x3b) để bypass regex pattern của httpd, tức /tmui/login.jsp/..%3b/hsqldb/

Với câu hỏi thứ nhất, tại sao bypass auth chỉ với ký tự ;, chúng ta hãy xem cấu hình httpd

# HSQLDB#
<Location /hsqldb>
<RequireAll>
AuthType Basic
AuthName “BIG\-IP”
AuthPAM_Enabled on
AuthPAM_IdleTimeout 1200
require valid-user
Require all granted</RequireAll></Location>

Theo cấu hình này, truy cập /hsqldb sẽ cần phải xác thực qua PAM. Tài liệu cấu hình httpd cho directive Location có ghi:

The enclosed directives will be applied to the request if the path component of the URL meets any of the following criteria:

* The specified location matches exactly the path component of the URL.

* The specified location, which ends in a forward slash, is a prefix of the path component of the URL (treated as context root).

* The specified location, with the addition of a trailing slash, is a prefix of the path component of the URL (also treated as a context root).

Nghĩa là, location này chỉ match chính xác với cụm /hsqldb hoặc /hsqldb/*. Như vậy, nếu sử dụng /hsqldb; thì request của chúng ta sẽ không bị hạn chế bởi directive này. Mặt khác, như đã nói về phần đầu của bài viết về URL path parameter, tomcat sẽ xem request này giống như /hsqldb (code parse path parameter sẽ loại bỏ ký tự ;) và servlet tương ứng sẽ được thực thi.

At this point, we proved that authenticated remote code execution was possible. Attempting to use /hsqldb; with a POST request resulted in a connection error, so we took another look at the mitigation regex “.*\.\.;.*” and noticed that the authentication bypass was “..;”. We then changed our exploit to work with that regex, allowing us direct access to HQSLDB.

Với câu hỏi thứ 2, ngay khi đọc đến đoạn này trong bài viết của TeamAres, tôi tự hỏi là làm sao có thể bypass rule này của httpd chỉ bằng cách encode ký tự chấm phẩy (;). Tôi thấy vô lý và cần kiểm chứng (vì bản thân tôi tin rằng uri sẽ được decode trước khi matching). Sau khi cấu hình httpd như trong khuyến nghị của F5, tôi thực hiện truy cập theo pattern kia (encoded url) nhưng server đều trả về 404, nghĩa là không thể vượt qua được matching của httpd. Có chăng kết nối được tạo ra bởi SQL client (mã khai thác) có gì đó vi diệu!?. Tôi chạy thử exploit nhưng không thành công, tôi cũng đã thử nhờ vài người khác, độc lập chạy thử nhưng đều không được :)

Đến đây, tôi tạm chấp nhận rằng, có thể tác giả đúng, với một cấu hình và ngữ cảnh nào đó, hoặc do tác giả cố tình làm vậy vì một lý do nào đó. Tuy nhiên, rõ ràng GET request đến /hsqldb; đã vượt được xác thực, tại sao tác giả lại “failed” khi sử dụng path này để exploit. Vậy liệu có thể khai thác với /hsqldb; hay không?. Tôi băn khoăn rằng nguyên nhân failed ở đây là do phía server hay client, tức từ mã khai thác của chính tác giả.

Tôi muốn xem các request thực sự được tạo bởi mã khai thác Java sẽ như thế nào, tuy nhiên lại gặp khó trong vấn đề làm sao để MITM ứng dụng Java. Ở đây hoàn toàn có thể đọc log phía server, nhưng tôi thấy quen với việc dùng Burp Suite để trực quan và tiện hơn nên tôi sử dụng một cách khác, đó là viết một đoạn script để serve http và relay đến máy chủ BigIP thông qua proxy Burp Suite, đóng vai trò giống như reverse + upstream proxy. Từ đây hoàn toàn có thể xem được nội dung các request.

Tôi bỏ cấu hình mitigation và thực hiện chạy mã khai thác với lần lượt hai path là /tmui/login.jsp/..;/hsqldb//hsqldb;

Image for post
Image for post

Quan sát trên Burp Suite tôi thấy rằng ở lần chạy thứ hai với path là /hsqldb;, request POST được thực hiện đến /. Điều này khiến tôi tin rằng nguyên nhân tác giả không thể exploit đến từ mã khai thác/thư viện phía client. Tôi sẽ không đi sâu tìm hiểu chính xác nguyên nhân ở đây mà tập trung vào xác nhận phỏng đoán rằng lỗ hổng này có thể khai thác thành công nếu gửi crafted payload đến /hsqldb;.

UPDATE 01: thực tế, ký tự ; đã break connection string khi code parse chuỗi này, có thể là convention, cho nên trong exploit (bằng mã Java dưới đây) thực sự vẫn phải dùng %3b thay cho ký tự ;.

Image for post
Image for post

Mỗi lần chạy exploit thành công, có hai HTTP requests được gửi đến web server, trong đó request đầu tiên như là việc khởi tạo và request sau tương ứng với execute hàm call. Xin nói thêm rằng ở đây tôi đã gộp hai lệnh call trong PoC vào làm một để có thể giảm số lượng request cần thiết. Bốn bytes đầu post data là độ dài của dữ liệu (tương tự Content-Length). Request khởi tạo trả về chứa 4 bytes có thể xem như là “session id”, các bytes này sẽ được sử dụng sau đó để authenticate (:D :D :D). Ngoài ra còn một số bytes khác nhưng vì tôi thấy không thay đổi nên tôi tạm thời bỏ qua.

Image for post
Image for post

Tôi replay các requests khai thác tới /hsqldb; với post data được chỉnh sửa tương ứng và đã có thể khai thác thành công. Kết quả tôi thống kê ngày 09/07/2020 với mã khai thác này thì vẫn còn rất nhiều máy chủ vẫn có thể bị khai thác với danh sách tìm kiếm từ Shodan, tiếc là không thể so sánh với exploit “gốc” được do có việc bận mấy ngày tiếp đó.

Hiện tại, F5 đã cập nhật lại mitigation trên K52145254. Riêng về phần cấu hình httpd cho TMUI, họ đã thay đổi pattern để chặn khai thác với các uri chứa bất kỳ chuỗi ; hoặc hsqldb. Ngoài ra, họ cũng phát hiện thêm rằng sử dụng chuỗi /hsqldb%0a cũng có thể bypass auth thành công. Tình cờ là thời điểm tôi viết bài này, ngay trước khi đọc lại thông tin trên F5 thì tôi cũng đã phát hiện ra điểm này. Tuy nhiên thì tôi thấy đã dành đủ thời gian cho lỗ hổng này, phần còn lại dành cho bạn đọc nào có hứng thú.

Lời kết

M̶e̶d̶i̶u̶m̶ ̶t̶h̶ự̶c̶ ̶s̶ự̶ ̶k̶h̶ó̶ ̶đ̶ể̶ ̶v̶i̶ế̶t̶ ̶v̶à̶ ̶đ̶ọ̶c̶ ̶t̶e̶c̶h̶n̶i̶c̶a̶l̶ ̶b̶l̶o̶g̶.̶

UPDATE 02: Định dạng lại văn bản, do lần đầu tôi viết bài trên nền tảng này nên chưa nắm hết chức năng soạn thảo.

Tài liệu tham khảo

https://doriantaylor.com/policy/http-url-path-parameter-syntax

https://support.f5.com/csp/article/K52145254

https://www.criticalstart.com/f5-big-ip-remote-code-execution-exploit/

Breaking Parser Logic! — Take Your Path Normalization Off and Pop 0days Out

nightst0rm

NightSt0rm is a group of IT security researchers…

Khanh Ta Quang

Written by

nightst0rm

NightSt0rm is a group of IT security researchers, enthusiasts , who share the same interests. We are focusing on Hacking, Cryptography, Malware analyst & Computer forensics.

Khanh Ta Quang

Written by

nightst0rm

NightSt0rm is a group of IT security researchers, enthusiasts , who share the same interests. We are focusing on Hacking, Cryptography, Malware analyst & Computer forensics.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store