Gần đây mình có nhận được lời mời tham gia Discord (private program) trên hackerone từ một người em xã hội, do nó không có thời gian nên bảo mình chơi rồi nó submit hộ. Ban đầu thấy có 2 resolved nên cũng khá hứng thú nên bay vào làm.

Ngày đầu tiên, không có gì nổi bật ngoài việc tự chat với bản thân, tự invite tự accept,... và tự kỷ.

Ngày thứ hai, có vẻ hay ho hơn khi... Booom

Okay! Cũng như các ứng dụng chat khác như Slack, Telegram, facebook,.. thì Discord cũng có chức năng hiển thị xem trước cho liên kết (Open graph). Đại khái là bạn gửi 1 đường link và Discord sẽ hiển thị như hình bên dưới.

View source nội dung xem trước thì mình thấy hình ảnh có đường dẫn:

https://images-ext-2.discordapp.net/external/2vCPitI58j9cVHqZINzJf0AhDJSzmV_-_BqqTrQpIlU/https/discord.com/assets/f7a4131e47f50b48b3f85f73c47ff1dc.png?width=400&height=210

Chú ý phần phía sau: https/discord.com/assets/f7a4131e47f50b48b3f85f73c47ff1dc.png có vẻ như là 1 URL. Mình thử đổi thành https/evil.com/aaaa, thì nhận được status code 401.

Tới đây thì ngồi đọc open graph và tự tạo 1 file html

    <head>
        …
        <meta property="og:image" content="https://attacker.com/test.png" />
        …
    </head>
</html>

Sau đó gửi liên kết vô form chat và ta được chế độ xem trước.

URL hình ảnh sẽ là:

https://images-ext-1.discordapp.net/external/QTSmVz5HvWf9an846ACYl45i8Ia233El1Y27rCAnJh8/https/attacker.com/test.png

Nói sơ qua về URL phía trên. Discord sẽ kiểm tra liên kết người dùng nhập vào và tìm kiếm các hình ảnh, video trong liên kết đó thông qua các thuộc tính của thẻ meta như og:image, og:video,... và tạo 1 proxy_url để trỏ đến các resources đó.

Như vậy, ở đây có khả năng khai thác SSRF. Nếu thành công mình có thể leak được local resources. Thử đổi og:image phía trên cho trỏ đến file khác, ví dụ như test.html thì mình nhận được status code 415 khi truy cập proxy_url

Sau nhiều lần thử các file khác nhau và các kỹ thuật bypass vốn có thì có vẻ như proxy_url (og:image) chỉ chấp nhận các file có signature là hình ảnh.

Chuyển sang thử property khác, với og:video mình cũng nhận được 1 proxy_url y như og:image. Áp dụng các kỹ thuật đã làm với og:image

Đầu tiên thử đổi URL sang localhost (http://localhost)

proxy_url của video không trả về :|. Có thể chỗ này server chỉ nhận file có đuôi .mp4 hoặc file có signature video. Thử với file có đuổi .mp4 -> http://localhost/test.mp4

proxy_url trả về là https://localhost/test.mp4, như vậy là chỉ cần có .mp4 là được, nhưng http ??? https ???. Thử truy cập vào proxy_url xem có gì.

Server trả về 403 kèm theo dòng error code: 1003. Có vẻ có gì đó, có thể do mình request vô file không tồn tại nên server trả về 403, điều này dễ thấy khi ta sử dụng htaccess để cấu hình server trả về cùng 1 mã lỗi.

Quay lại với ext .mp4, có nhiều cách để bypass nhưng ở đây mình chọn cách tạo 1 file mp4 và run code php trong đó – (redi.mp4).

<?php
    header("Location: http://localhost/")
?>

Chỉnh lại property og:video https://attacker.com/redi.mp4.

Thêm dòng sau vào file .htaccess để chạy code php trong file .mp4:

AddType application/x-httpd-php .mp4

Server vẫn trả về 403 và error code 1003, sau 1 hồi thử các payload abcxyz thì có vẻ như mình không thể truy cập vào internal (n00b). Nhưng mình phát hiện ra là mình có thể thay đổi content-type trong response (với og:image thì content-type được server set cứng là image/png hoặc image/jpeg tùy vào nội dung file là png hay jpg). Sửa nội dung file redi.mp4 thành:

<?php
	header("Content-type: text/html");
?>
<script>alert(orgin)</script>

Và kết quả:

Cuối cùng thì vẫn chưa có SSRF nhưng cũng có XSS gỡ gạc lại.

Lỗi được fix khá nhanh, chưa tới 1 ngày. Và nâng số resolved lên 3.