Phần 2: PHÂN TÍCH LỖ HỔNG RCE CỦA MOODLE (CVE-2018-1133)

Phần 2 này mình đã để trì trệ đến 2 tuần rồi, cứ mỗi lần mở lên định viết tiếp thì ôi sao mà nó ngượng tay thế, câu chữ nó chạy cmn đi đâu mất. Nhưng hôm nay hừng hực khí thế vì vừa được gia nhập một team ctf, trong lòng đầy tin tưởng về tương lai tươi sáng phía trước. Nên hạ quyết tâm cống hiến gì đó cho đời…

Okii lên nhạc tí cho sung 😀

Phần 1 mình đã giới thiệu qua về moodle và lỗ hổng này. Phần 2 này mình sẽ tập trung vào phân tích lỗ hổng.

Trong bài phân tích của ripstech, tác giả đã chỉ ra lỗ hổng này nằm ở phần tạo câu hỏi cho học viên của moodle. Cụ thể là dạng câu hỏi calculated question. Đây là dạng câu hỏi mà câu hỏi và đáp án có dạng giống nhau nhưng số liệu được moodle tự động tạo ra bằng cách chọn random từ tập số liệu do người ra đề đặt. Ví dụ người ra đề muốn học sinh trả lời câu tính diện tích một hình chữ nhật cho chiều dài, chiều rộng thuộc tập từ 1 đến 10. Thì moodle sẽ tạo ngẫu nhiên 1 câu hỏi dạng như chiều dài 3, chiều rộng 2 và câu trả lời nếu là 6 thì sẽ được tính điểm. Như vậy sẽ ngăn học viên sao chép đáp án của nhau vì mỗi người có một câu hỏi giống dạng nhưng có số liệu khác nhau.

Lỗ hổng này khá phức tạp, mình sẽ cố gắng phân tích rõ ràng nhất với khả năng có thể.

Trước khi đi vào việc khai thác, ta hãy xem chức năng này khi hoạt động bình thường sẽ như thế nào, hiểu sơ qua cách developer đã lập trình nó. Biết được những điều này, sau khi đã khai thác được ta cũng hiểu developer đã mắc lỗi gì dẫn đến lỗ hổng. Ta bắt đầu bằng việc tạo một user có quyền teacher, và tạo một câu hỏi dạng calculated.

Để tạo thêm user, bạn đăng nhập bằng quyền admin rồi tìm phần adduser ở menu bên trái phần site admin. Sau đó tạo 1 course rồi assign user vừa tạo là teacher của course đó. Thoát ra đăng nhập vào user vừa tạo ta sẽ có quyền tạo câu hỏi cho course vừa tạo. Mình đã tạo user là cô giáo thảo và dạy môn sinh học. Sau một vài thao tác chuột thì ta đến được màn hình tạo câu hỏi calculated hoặc bạn có thể vào url này luôn cho nhanh: /moodle/question/edit.php?courseid=2

Sau khi làm theo hướng dẫn này cuối cùng mình cũng tạo được một câu hỏi. Quá trình tạo yêu cầu phải cấu hình nhiều thuộc tính như: sai số đáp án cho phép, tập giá trị của câu hỏi, điểm…khá mất thời gian và gây bực mình nếu không quen. Vì tính chất bài viết nên mình chỉ tập trung vào phần thuộc tính mấu chốt vấn đề là công thức tính đáp án

Đây là đáp án có giá trị là tổng của hai biến a và b. Để biểu diễn công thức tính giá trị, ta dùng kí hiệu: tên biến đặt trong cặp dấu ngoặc {}. Cái này mình sẽ gọi là placeholder. Sau khi cấu hình các thuộc tính còn lại, ta được câu hỏi như thế này

Ban đầu hướng tiếp cận của mình là trace từng dòng code, từ dòng code đầu tiên của file mà data post lên (file /question/question.php) xem input được xử lí thế nào. Nhưng làm như thế rất mất thời gian vì đầu tiên script load config, check csrf, check quyền user,… rồi linh tinh nhiều thứ xong mới parse data của ta. Cho nên mình đổi sang chiến thuật là đặt break point ở hàm quan trọng này thôi.

Đây là hàm có nhiệm vụ eval biểu thức để ra đáp án đúng, dùng để so sánh với đáp án do học sinh nộp. Trước khi đưa vào eval, 2 placeholder trong biểu thức được thay bằng số bất kì do moodle tạo bằng hàm substitute_variables và check filter bằng hàm qtype_calculated_find_formula

Hàm substitute_variables thay các placeholder bằng các giá trị tương ứng trong tham số array dataset. dataset là mảng các giá trị bất kì đã được moodle tạo tương ứng với từng placeholder, ở đây 1.8 cho a và 2.3 cho b.

Như vậy sau khi thay 2 placeholder, biểu thức thành 1.8 + 2.3

Sau đó biểu thức được đưa qua hàm qtype_calculated_find_formula để check các kí tự nguy hiểm. Hàm này có 2 điểm cần chú ý. Thứ nhất là

Nó dùng regex tìm các đoạn có dạng

rồi thay bằng kí tự 1. hmm… Những đoạn được tìm và thay thế có dạng một placeholder đúng không. Vậy việc thay thế không hợp lí lắm nhỉ vì các place holder trong formula cũng đã được thay bằng giá trị số sau khi qua hàm substitute_variables rồi mà. Đây sẽ là một mắt xích thú vị. Lưu ý thêm là việc thay thế chỉ có tác dụng trong hàm filter, còn formula vẫ được giữ nguyên.

Đoạn này sẽ giới hạn các kí tự còn lại trong $formula là

1
-+/*%>:^\~<!–?=&|!.0-9eE

Hiện giờ $formula là 1.8 + 2.3 nên chắc chắn qua filter. Kết quả lệnh eval như thế này

Nếu vi phạm hàm sẽ trả về false và formula không đến được eval. Vậy là moodle đã chặn các kí tự rất cần thiết cho việc exploit như ( ) để gọi hàm, “ để gọi system…., nhưng có 1 điểm sáng là ta có thể dùng kí tự comment // hoặc /*

Mục tiêu của ta là làm sao tạo 1 formula chứa payload, payload được bảo toàn sau khi qua substitute_variables, không bị thay bằng 1 số bất kì sau đó bypass được check trong qtype_calculated_find_formula để đến được eval.

Cần phải quay lại hàm substitute_variables một chút. Hàm này nhận 1 tham số là dataset và thay các placeholder bằng cách tìm các index tương ứng với placeholder. Thế nếu place holder của ta không có trong dataset thì nó không bị thay đúng không. => Phải làm sao để moodle nhận diện sai placeholder. => Cách làm là tạo 1 biểu thức có placeholder lồng nhau như thế này.

Như vậy moodle nhận diện a và b là 2 placeholder. Lí do tại sao thì mình không rõ vì không tìm hiểu xem array dataset được tạo thế nào. Cứ coi nó là

Vậy đầu tiên placeholder {b} được thay bằng giá trị 1.8 và biểu thức trở thành {a1.8} . Nó đã thành 1 placeholder mới và sẽ không được thay thế vì đâu có placeholder đó trong dataset. Khi đến hàm check filter nó như thế này

Giờ thì đầu xuôi đuôi lọt rồi, vì {a1.8} thỏa mãn tìm kiếm trong phần đầu của hàm qtype_calculated_find_formula nên nó sẽ được thay bằng kí tự ‘1’ và qua được filter.

Khi đến eval, biểu thức vẫn giữ nguyên là {a1.8} => Ta có thể đặt các kí tự bị filter như thế này và bypass được.

Lắp ghép lại, ta được kịch bản thế này:

Tạo đáp án có dạng {(payload){xyz}}, tìm cách comment để payload khi đến eval đúng cú pháp và được thực thi.

Và ta đã có RCE

Như vậy ta đã hiểu nguyên lí của cve này đúng không. Mình xin dừng lại tại đây. Nếu bạn có hứng thú tiến sâu hơn nữa có thể đọc bài phân tích của chính tác giả https://blog.ripstech.com/2018/moodle-remote-code-execution/#developing-a-bypass. Thực tế là sau khi tác giả report, moodle đã fix vài lần xong tác giả lại bypass được 😍