Security Blog

Xem mã nguồn, sao chép khóa, chiếm trọn tất cả

#67

October 2, 2026 · By Marketing team

← All posts

Một nhà nghiên cứu mở mã nguồn trang của ClickUp, tìm thấy một khóa API được hardcode trong mã JavaScript, và dùng nó để lấy về 959 địa chỉ email cùng 3.165 cờ tính năng nội bộ chỉ trong một yêu cầu. Khóa đó không có phạm vi, không có rate limit, và không có hạn sử dụng.

Một nhà nghiên cứu bảo mật truy cập clickup.com. Mở mã nguồn trang. Tìm thấy một khóa API được hardcode trong mã JavaScript. Sao chép nó. Gửi một yêu cầu GET.

Nhận về 959 địa chỉ email và 3.165 cờ tính năng nội bộ. Nhân viên từ Home Depot. Fortinet. Autodesk. Tenable. Rakuten. Mayo Clinic.

Một chuỗi. Một yêu cầu. Tất cả.

Chuyện này xảy ra thế nào

Có người cần frontend gọi một API. API đó yêu cầu xác thực. Thế là họ đặt khóa trong mã JavaScript. Ship đi, làm việc khác, sprint sau tính sau.

Đây không phải một cuộc tấn công tinh vi. Không có exploit, không có zero-day, không có social engineering. Chỉ là view-source: và curl. Một trình duyệt và một terminal. Kiểu việc mà một thực tập sinh tò mò làm ngay trong ngày đầu tiên.

Khóa không có scope — nó truy cập được mọi thứ mà API hiển lộ. Không có rate limit — một yêu cầu trả về tất cả. Không có hạn sử dụng — khóa hoạt động cho tới khi có ai đó để ý. Không có yếu tố thứ hai — sở hữu chuỗi đó là cửa kiểm soát duy nhất.

Góc nhìn về tiền bạc

Người ta nói về việc dữ liệu bị lộ. Hãy nói về giá trị của dữ liệu này.

959 địa chỉ email doanh nghiệp thuộc các công ty Fortune 500. Đó là danh sách mục tiêu spear-phishing mà các tác nhân đe dọa sẵn sàng trả tiền. Tên, vai trò, và việc các công ty này dùng ClickUp — đó là bối cảnh social engineering khiến tấn công phishing phát huy tác dụng.

3.165 cờ tính năng nội bộ. Đó là một lộ trình. Nó cho đối thủ biết ClickUp đang xây gì, đang thử gì, thứ gì đang nằm sau cổng. Nó cho kẻ tấn công biết tính năng nào đang dang dở và nhiều khả năng còn lỗ hổng.

Đây không phải một sự cố về quyền riêng tư. Đây là một vụ rò rỉ thông tin kinh doanh.

Tại sao chuyện này cứ lặp lại

Đây là sự cố lộ thông tin xác thực trong mã nguồn thứ tư mà chúng tôi viết trong tháng này. CLI của Bitwarden bị thu thập thông tin xác thực vì chúng nằm trong các tệp plaintext. Biến môi trường của Vercel có thể giải mã được vì cờ "sensitive" không được bật mặc định. Một nhà phát triển mất 634 mật khẩu Chrome vì khóa giải mã nằm trên cùng ổ đĩa.

Mẫu hình luôn giống nhau: một thông tin xác thực tồn tại dưới dạng chuỗi — trong tệp, trong biến, trong mã nguồn trang — và một thứ gì đó đọc nó. Cái "thứ gì đó" thay đổi. Mẫu hình thì không.

Khóa API trong JavaScript là phiên bản lộ liễu nhất vì không cần bất kỳ cuộc tấn công nào. Khóa được công bố. Nó được phục vụ cho mọi khách truy cập. Trình duyệt tải nó xuống, hiển thị nó, và cho bất kỳ ai nhấp chuột phải thấy.

Đáng lẽ phải làm khác đi

Lẽ ra lời gọi API không bao giờ được xác thực bằng một khóa tĩnh từ phía client. Các lựa chọn:

  • Backend proxy. Frontend gọi backend của chính bạn, nơi giữ khóa ở phía server và chuyển tiếp lời gọi API. Khóa không bao giờ chạm tới trình duyệt.
  • Session-scoped tokens. Frontend nhận một token ngắn hạn, phạm vi hẹp sau khi xác thực. Token hết hạn. Token chỉ làm được những gì người dùng đã xác thực được phép làm. Nó không phải khóa chủ.
  • Không dùng khóa nào cả. Nếu dữ liệu là công khai, hãy phục vụ nó mà không cần xác thực. Nếu không công khai, đừng phục vụ cho mã JavaScript chưa xác thực.

Hardcode một khóa API trong mã phía client giống như để chìa khóa nhà dưới tấm thảm chùi chân rồi công bố địa chỉ của bạn.

Vấn đề vòng đời của thông tin xác thực

Khóa ClickUp này có lẽ được tạo một lần, dán vào một tệp JavaScript, commit vào một repo, đưa lên production, và không ai nghĩ tới nó nữa. Không ai xoay vòng nó. Không ai thu hẹp phạm vi của nó. Không ai đặt hạn sử dụng. Không ai theo dõi nó truy cập những gì.

Đó là vòng đời của phần lớn khóa API ở phần lớn tổ chức. Được tạo vội, dán vào chỗ cần dùng, rồi bị lãng quên. Chúng tích tụ trong các codebase, tệp cấu hình, pipeline CI/CD, và cả mã nguồn trang — mỗi chiếc là một cánh cửa không bao giờ khóa.

Vấn đề không phải tổ chức của bạn có khóa kiểu này hay không. Vấn đề là bạn có bao nhiêu chiếc, và liệu bạn có biết không nếu ai đó sao chép một chiếc ngay hôm nay.