Post

TRYHACKME | IronHold

Sau một khoảng thời gian khá lười biếng, tình cờ mình đang lướt trên nền tảng TryHackMe thì vô tình gặp được một chall whitebox mới được release liên quan đến java. Cũng trùng hợp là mình đang nghiên cứu về Java, nên mình cũng thử tải source code về để đọc thử và mình đã giải được - Nên mới có bài viết này.

TRYHACKME | IronHold

IronHold

Mô tả:

SOLVE

Sau khi tải source code được cung cấp về và mở bằng Intellij ta có được một Web app với cấu trúc như dưới:

Nhìn tổng quan thì đây là một trang web quản lý tù nhân với khá là đa dạng các chức năng. Tạm thời chỉ cần biết như vậy thôi :v. Chi tiết từng chức năng sẽ được phân tích bên dưới

Truy cập thử vào file application.properties để xem thông tin cấu hình của server thì ta thu được các thông tin như sau:

  • Web app được xây dựng dựa trên Spring Framework
  • Web app chạy ở port 8080
  • Config chứa các thông tin quan trọng như nội dung của 3 flag và mật khẩu của 2 người dùng là kiosk và warden

Sau đó mình thử Ctrl + Shift + F để tìm thử chuỗi flag thì nhận được kết quả như sau:

Truy cập vào file DataSeeder.java để xem kỹ hơn vị trí mà các flag được đặt từ đó nhận diện được các lỗ hổng để lấy được flag.

FLAG1

Truy cập vào DataSeeder.java và tìm flag1 thì ta nhận được kết quả như sau:

Nhận thấy để lấy được flag1 thì ta phải truy cập được vào trang chủ của người dùng có tên kiosk. Vậy thì hướng khai thác là phải tìm cách đăng nhập vào tài khoản của kiosk.

Kiểm tra thử code trong AuthController thì nhận thấy không có lỗi nào đáng để có thể khai thác. Nên mình chuyển hưởng sang việc tìm kiếm việc cấu hình xem có để lộ thông tin của user này hay không.

Sau khi lướt qua qua một lượt mã nguồn thì mình để ý thấy 1 chi tiết là trong file WebMvcConfig có đăng kí các interceptors - hiểu đơn giản thì nó kiểm tra và phân quyền các request trước khi request đó đến được với controller.

Có thể tham khảo thêm về Interceptors tại đây:

Hướng dẫn và ví dụ Spring Boot Interceptor | openplanning.net

Tại bộ lọc authInterceptor áp dụng cho tất cả mọi đường dẫn (/**), nghĩa là mặc định, mọi Request gửi đến máy chủ đều sẽ bị bộ lọc này chặn lại để kiểm tra tính hợp lệ của phiên đăng nhập. Ngoại trừ các đường dẫn được khai báo trong excludePathPatterns sẽ được phép đi thẳng vào trong mà không cần qua sự kiểm tra của authInterceptor.

Để ý thì trong đây có 1 endpoint khá là đặc biệt đó là /actuator/** bởi vì đây là endpoint giúp giám sát ứng dụng trên môi trường production.

Sau khi đọc bài viết này:

Exploring Spring Boot Actuator Misconfigurations | Wiz Blog

Misconfigurations in Spring Boot Actuator’s endpoints can leak environment variables, passwords, and API keys, and even lead to remote code execution.

Exploring Spring Boot Actuator Misconfigurations | Wiz Blog

và xem tập cấu hình application.properties thì mình nhận thấy có lỗi cấu hình khi có thể truy cập hầu hết các endpoint của actuator ngoại trừ 2 endpoint là heapdump và threaddump.

Truy cập vào /actuator thì nhận được kết quả như sau:

Kết quả trả về là một loạt các endpoint mà actuator cung cấp. Để ý thì trong danh sách endpoint này có một endpoint /actuator/env chứa các biến môi trường của hệ thống, nên mình thử truy cập vào endpoint này xem có gì đặc biệt không:

Kết quả trả về cho ta được thông tin xác thực của kiosk, còn các flag thì đều bị ẩn (Tất nhiên là làm sao mà lấy được flag của chall dễ như thế được :v). Lý do là Acuator đã Sanitizer các từ khóa này thông qua:

Từ việc có được thông tin đăng nhập của user kiosk thì mình dùng thông tin đó để đăng nhập vào trang web. Sau khi đăng nhập thành công thì ta nhận được kết quả ở trang chủ (Flag thật đã được ẩn :v):

FLAG2

Sau khi có được flag 1 thì mình lại quay trở lại phần seeder trong source code để tìm kiếm xem flag 2 được tạo và lưu trữ ở đâu từ đó thu hẹp phạm vi tìm kiếm (Trick lỏ time :v). Kết quả thu được như sau:

Lần này thì flag lại được lưu ở trong database nên mình nghĩ ngay đến việc tìm kiếm lỗ hổng SQL Injection để khai thác:

Sau khi lần mò 1 lúc thì mình tìm kiếm được đoạn code chứa lỗ hổng SQL Injection khá là rõ ràng trong InmateController:

Đây là giao diện của tính năng này trên web:

Để ý thì lỗ hổng SQLi trên khá là đơn giản vì không có một lớp filter nào mà nhận mọi đầu vào từ người dùng nên việc khai thác khá là dễ dàng. Ngoài ra nhìn vào cấu trúc của câu truy vấn dính lỗ hổng thì có thể nghĩ đến ngay cách khai thác thông qua UNION.

Để khai thác được thông qua union thì ta cần tìm số cột trong truy vấn, như đã thấy ở bên trên thì ta có thể thấy số cột trong truy vấn này là 3.

Do trong trường hợp này ta biết số cột của truy vấn nhưng trong trường hợp mà không biết được số cột của truy vấn thì ta có thể tìm số cột của truy vấn bằng cách đặt giá trị cho cột là null và tăng số lượng lên đến khi truy vấn không còn lỗi là có thể suy ra số cột của truy vấn.

Tiếp đến là chúng ta cần kiểm tra kiểu dữ liệu của 2 truy vấn để trích xúât dữ liệu thông qua UNION không bị lỗi.

Từ những dữ kiện trên ta xây dựng được câu lệnh truy vấn SQLi như sau:

1
' UNION SELECT id, title, summary FROM case_files --

Kết quả ta nhận được flag (Đã ẩn flag thật :v)

FLAG3

Quay trở lại với Seeder ban đầu thì ta thấy flag 3 được đặt ở thông báo của Admin.

Ctrl + Shift + F chuỗi AdminNotice thì nhận được kết quả như sau:

Nhận thấy chuỗi kết quả nằm trong file AdminController:

Tức là muốn lấy được flag3 thì cần phải truy cập vào /admin nhưng như đã đề cập ở phần Interceptor ban đầu thì /admin chỉ có thể truy cập bởi những người dùng được ủy quyền, trong bài này thì chỉ có 2 role là OFFICER và WARDEN. Nhưng user kiosk mang role OFFICER lại không thể truy cập được /admin nên chứng tỏ cần có role WARDEN thì mới truy cập được vào đường dẫn này.

Như đã đề cập ở ban đầu thì thông qua biến môi trường của actuator thì password của user Warden đã bị Sanitizer. Vì vậy cách khả thi lúc này là tìm cách leo thang lên quyền WARDEN.

Sau khi lục lọi trong source code thì mình tìm được đoạn code dính lỗ hổng giúp chúng ta có thể leo lên quyền WARDEN

Giao diện tính năng sửa đổi thông tin user:

Trên giao diện web đã ẩn chức năng cập nhập Role, nhưng lại server vẫn xử lý việc cập nhật Role.

Để nâng quyền của user từ OFFICER len WARDEN thì ta chỉ cần thêm trường role trong request update profile như sau:

Kết quả là đã leo lên được quyền OFFICER và truy cập và /admin là lấy được flag (Đã ẩn :v):

FLAG 4

Để Hoàn thành được bài chall này thì cần phải tìm thêm flag cuối, tuy nhiên từ đầu đến giờ khi tìm kiếm các chuỗi flag thì không thấy thông tin nào liên quan đến flag này

Nên mình đoán là flag cuối này sẽ được đặt ở server và yêu cầu phải khai thác lỗ hổng nào đó để RCE để mà có thể tìm flag trên server. Và gợi ý của TryHackMe cũng đã củng cố thêm nhận định đó của mình:

Lại ngồi lần mò trong source code thì mình tìm được một route có chứa lỗ hổng Insecure Deserialize cũng không có một hành động xác thực dữ liệu được truyền vào:

Truy cập thử tính năng thì kiểm tra nó vẫn hoạt động:

Kiểm tra tập cấu hình pom.xml thì có thêm được thông tin là ứng dụng này sử dụng commons-collection tồn tại nhiều gadget chain có thể rce.

Ở đây mình dùng ysoserial để tạo các payload:

Sau khi thử lần lượt các payload thì có payload CommonsCollections6 hoạt động và cho ra kết quả như sau:

Tuy nhiên khi dùng payload sau để revershell thì lại không nhận được kết quả:

Gửi request thành công tuy nhiên lại không nhận được shell.

Ngồi mò mẫm nhưng không có kết quả nên mình quyết định tìm kiếm thông tin về gadget chain này

Sau khi tìm kiếm trên google về CommonsCollections6 thì mình tìm được chuỗi gadget chain như sau:

Nhận thấy câu lệnh hệ thông được đưa vào Runtime.exec nên mình thử search google về phương thức này. Và mình tìm được bài viết này:

$@|sh – Or: Getting a shell environment from Runtime.exec

If you happen to have command execution via Java's Runtime.exec on a Unix system, you may already have noticed that it doesn't behave like ...

Hiểu 1 cách đơn giản thì Runtime.exec của java có sử dụng StringTokenizer để chia command chúng ta nhập vào thành từng chuỗi nhỏ thông qua khoảng trắng từ đó dẫn đến các câu lệnh phức tạp mà ta truyền vào sẽ bị hệ thống hiểu sai và bị lỗi thực thi.

Cũng trong lúc tìm kiếm thì mình kiếm được một trang web giúp chúng ta tạo được payload cho Runtime.exec bằng cách loại bỏ các khoảng trắng không cần thiết như trong bài viết bên trên đã mô tả:

Runtime.exec Payload Generater

There is no description

Và payload ban đầu sẽ trở thành như sau:

Tạo payload và import lên server thì lần này câu lệnh đã được thực thi thành công và đã get được shell

Tìm kiếm trên server thì ta nhận được file chứa flag:

Nhận Xét

Nhìn tổng quan thì thử thách này cũng khá là đơn giản do các lỗ hổng rất dễ phát hiện và khai thác bởi vì chúng chẳng có một lớp filter nào cả. Tuy nhiên nó vẫn khiến mình mất khá nhiều thời gian - do phần lớn thời gian phải giải quyết Flag cuối cùng nhưng đổi lại thì mình cũng học thêm được khá nhiều thứ mới mẻ liên quan đến Java.

Hy vọng bài viết này sẽ hữu ích đối với mọi người :v

This post is licensed under CC BY 4.0 by the author.