CVSS 3.1: 9.8 (CRITICAL)
Lộ thông tin đăng nhập hàng loạt + chiếm quyền tài khoản qua API không xác
thực.
Tóm tắt
Phần1
kết thúc với 896 thí sinh bị lộ dữ liệu cá nhân và ảnh chụp CCCD qua hệ thống
Trung tâm Khảo thí của Trường Đại học X, toàn bộ được xây dựng trên nền tảng
“Connections” của Công ty Y. Tôi khép lại báo cáo đó với một câu hỏi cứ lởn
vởn trong đầu không chịu biến mất: nếu hệ thống thi cử không có bất kỳ kiểm
soát truy cập nào, thì nền tảng chính của trường, chạy trên cùng một hạ
tầng, sẽ ra sao?
Câu hỏi đó khiến tôi không yên. Và câu trả lời thì tệ hơn rất nhiều so với
kịch bản xấu nhất mà tôi đã hình dung.
Trường Đại học X vận hành
connections.universityx.vn như
mạng nội bộ trung tâm của trường, cũng được xây dựng trên cùng nền tảng
Connections SaaS của Công ty Y. Đây không phải cổng phụ hay hệ thống thí điểm
nào đó, mà là nơi chứa mọi sinh viên, mọi cựu sinh viên, mọi cán bộ, mọi giảng
viên, toàn bộ dân số kỹ thuật số của trường đại học tập trung tại một chỗ. Sử
dụng cùng các kỹ thuật từ Phần 1 (gọi API không xác thực và liệt kê ID tuần
tự), tôi đã trích xuất 80.053 tài khoản người dùng, mỗi tài khoản chứa
tới 40 trường dữ liệu cá nhân.
Nhưng con số không phải thứ khiến tôi mất ngủ đêm đó. Mà là
trường mật khẩu. API trả về thông tin đăng nhập cho cả tài khoản cán bộ
lẫn sinh viên: một số ở dạng văn bản thuần (plaintext), số khác được che bằng
một thuật toán đơn giản đến mức có thể dịch ngược bằng dữ liệu nằm ngay trong
chính phản hồi API đó. Riêng với 554 tài khoản cán bộ có mật khẩu bị lộ, tôi
thu được 208 bộ thông tin đăng nhập sử dụng được: 50 đọc trực tiếp ở dạng
plaintext, 158 phục hồi offline, mà không cần thực hiện một lần đăng nhập nào.
Và để xác nhận rằng đây không phải rủi ro lý thuyết, tôi đã đăng nhập vào hai
tài khoản: một Phó Hiệu trưởng và một sinh viên. Không bruteforce, không
exploit, tôi chỉ đọc trường mật khẩu từ API rồi gõ vào form đăng nhập. Cả hai
đều vào được ngay lần đầu.
1. Giới thiệu
1.1. Tóm tắt Phần 1
Nếu bạn chưa đọc
Phần 1, đây là phiên bản ngắn gọn. Tôi đã điều tra
tec.universityx.vn, hệ thống
đăng ký thi của Trung tâm Khảo thí, và phát hiện:
- 896 thí sinh bị lộ dữ liệu cá nhân qua API không xác thực
- Số CCCD và ảnh chụp thẻ căn cước ai cũng có thể truy cập
- Nguyên nhân gốc: Nền tảng Connections của Công ty Y không có bất kỳ cơ chế xác thực phía server nào
Vấn đề nằm ở kiến trúc nền tảng, không phải một endpoint bị cấu hình sai.
Mọi bảng, mọi trường, mọi tập tin tải lên đều có thể truy cập bởi bất kỳ ai
biết (hoặc đoán được) ID đối tượng đúng.
1.2. Câu hỏi tự nhiên tiếp theo
Trung tâm Khảo thí chỉ là một triển khai nhỏ: vài trăm thí sinh mỗi đợt thi.
Nhưng tôi biết sự hiện diện của Trường Đại học X trên nền tảng Connections
không dừng lại ở đó.
Trường còn vận hành
connections.universityx.vn, mạng
nội bộ trung tâm, chứa tài khoản của mọi sinh viên, cán bộ, và giảng viên
trên toàn trường. Cùng nhà cung cấp, cùng framework JavaScript, cùng các
script từ cdn.companyy.com, cùng kiến trúc. Và nếu hệ thống thi cử đã để lộ
dữ liệu của 896 người, thì mạng lưới chính của trường đại học, nơi chứa toàn
bộ dân số của trường, sẽ phơi bày những gì?
Tôi không thể không đi tiếp.
1.3. Phạm vi và Đạo đức
Các nguyên tắc đạo đức từ Phần 1 được áp dụng xuyên suốt:
- Mọi truy cập dữ liệu đều sử dụng các endpoint công khai, không xác thực.
- Không có xác thực nào bị vượt qua, vì không có xác thực nào tồn tại để vượt qua.
- Không có dữ liệu nào bị sửa đổi, xóa, hoặc chia sẻ cho bên thứ ba.
- Không thực hiện tấn công brute-force. Việc phục hồi mật khẩu được thực hiện offline bằng dữ liệu đã bị lộ từ chính API đó.
- Chiếm quyền tài khoản chỉ thực hiện một lần duy nhất, nhằm xác nhận mức độ nghiêm trọng của lỗ hổng, và phiên đăng nhập được kết thúc ngay lập tức.
2. Mục tiêu lớn hơn: Mạng lưới Đại học
2.1. Khám phá nền tảng
Tôi mở
connections.universityx.vn trong
trình duyệt, bật mã nguồn lên, và cảm giác đầu tiên là déjà vu. Cùng
framework JavaScript quen thuộc, tải từ cùng CDN:
<script src="https://cdn.companyy.com/js/include.core.isj"></script>
Các hàm API nội bộ y hệt vẫn có sẵn:
CAN.db() để truy vấn cơ sở dữ
liệu, config() để đọc phản hồi
đã cache, và xửLý() để thực hiện
tìm kiếm. Điểm khác biệt duy nhất là dữ liệu: thay vì thí sinh dự thi, nền
tảng này quản lý toàn bộ người dùng của trường đại học.
Tôi đã biết kiến trúc này không có ổ khóa trên cửa. Giờ câu hỏi duy nhất là:
có bao nhiêu thứ đằng sau cánh cửa đó?
2.2. Lập bản đồ không gian tài khoản
Tôi bắt đầu duyệt qua các ID tài khoản tuần tự qua
CAN.db(), cùng kỹ thuật liệt kê
như Phần . Ước tính ban đầu cho dải hoạt động là 7.787-97.744, khoảng 89.958
ID tiềm năng, con số đã đủ lớn rồi. Nhưng khi quá trình thu thập chạy, các
tài khoản cứ liên tục xuất hiện vượt xa giới hạn đó, và tôi bắt đầu nhận ra
quy mô thực sự lớn hơn nhiều so với tôi nghĩ:
Hơn 200.000 ID tiềm năng được dò quét, và 80.053 trong số đó trả về bản ghi
người dùng đầy đủ, từng cái một, đều truy cập được mà không cần xác thực.
Lúc con số vượt qua 50.000 rồi cứ tiếp tục tăng, tôi mới thực sự cảm nhận
được sự khác biệt. 896 ở Phần 1 là một góc khuất nhỏ của trường đại học.
80.053 là toàn bộ trường đại học.
3. Thu thập dữ liệu: 80.053 tài khoản
3.1. Phương pháp trích xuất
Kỹ thuật hoàn toàn giống Phần 1. Với mỗi ID tài khoản trong dải, một lệnh
gọi API duy nhất trả về toàn bộ bản ghi:
CAN.db("taiKhoan.{id}", function() {
var d = config("taiKhoan.{id}");
// d giờ chứa tất cả các trường của tài khoản này
});
Không có token, không cookie, không header xác thực, không gì cả. Nền tảng
tự động tạo một phiên ẩn danh (tôi nhận được session ID kID=186xxxxx) và
phục vụ dữ liệu như thể tôi là người dùng hợp lệ. Giống hệt Phần 1: cơ sở dữ
liệu đơn giản trả lời bất kỳ ai hỏi, không cần biết người hỏi là ai.
3.2. Hai hướng phơi bày
80.053 tài khoản không phải một tập dữ liệu đồng nhất mà chia thành hai câu
chuyện phơi bày rất khác nhau, mỗi câu chuyện mang mức độ nghiêm trọng
riêng:
Câu chuyện thứ nhất là về 78.981 sinh viên và cựu sinh viên: khối lượng dữ
liệu cá nhân bị phơi bày lớn đến mức nào. Câu chuyện thứ hai là về 562 cán
bộ và giảng viên, một nhóm nhỏ hơn nhiều, nhưng với dữ liệu đăng nhập bị
phơi bày chi tiết hơn. Mật khẩu bị lộ ở cả hai nhóm, nhưng tài khoản cán bộ
là nơi tôi tập trung phân tích vì mức độ đặc quyền và tác động của chúng.
4. Hướng 1: Lộ dữ liệu Sinh viên và Cựu sinh viên – 78.981 tài khoản
4.1. Quy mô
Đại đa số tài khoản bị lộ, 21.393 sinh viên đang học và 57.588 cựu sinh
viên, thuộc về những người đã theo học tại Trường Đại học X trong gần hai
thập kỷ qua. Đây là các tài khoản chuẩn (loại 0), mỗi tài khoản chứa tới 40
trường dữ liệu cá nhân.
|
|
| Hình 1: Bản ghi tài khoản sinh viên mẫu được trả về bởi API không xác thực, hiển thị các trường dữ liệu cá nhân bao gồm họ tên, ngày sinh, mã sinh viên, và thông tin liên hệ. |
4.2. Những gì bị lộ cho mỗi sinh viên
Mỗi bản ghi sinh viên là một hồ sơ cá nhân hoàn chỉnh. Bảng dưới đây cho
thấy mức độ đầy đủ của dữ liệu:
80.037 họ tên đầy đủ, 78.637 ngày sinh, 23.970 số điện thoại, 2.750 số căn
cước công dân, và thậm chí 333 tài khoản lộ cả tên bố mẹ. Đây không phải
danh sách tên đăng nhập hay bảng dữ liệu khô khan nào đó, mà là hồ sơ cá
nhân hoàn chỉnh của gần như toàn bộ dân số trường đại học, từ sinh viên năm
nhất đến cựu sinh viên gần hai thập kỷ trước, tất cả nằm trơ ra cho ai muốn
xem thì xem.
4.3. Bức chân dung của sinh viên toàn trường
Dữ liệu vẽ nên một bức tranh chi tiết đến đáng lo ngại về Trường Đại học X.
Tỷ lệ nữ-nam 4:1 (54.815 nữ, 13.522 nam) phản ánh định hướng chuyên ngành
ngoại ngữ của trường. Dữ liệu khóa tuyển sinh trải dài từ K2008 đến K2025,
gần hai thập kỷ sinh viên, tất cả nằm trong một cơ sở dữ liệu mở toang.
Trong 29.640 địa chỉ email, 20.793 là tài khoản Gmail cá nhân, trong khi
5.329 sử dụng tên miền Microsoft 365 của trường. 68 tài khoản có email
gmai.com và 48 có gmail.con, những lỗi đánh máy xác nhận đây là dữ liệu thực
do con người nhập, không phải dữ liệu test.
50.042 tài khoản có quê quán, tập trung nhiều ở miền Bắc Việt Nam, riêng Hà
Nội chiếm 35,5%. Các khoa đông nhất là Quản trị Kinh doanh & Du lịch
(5.579), tiếng Anh (5.510), và tiếng Trung (4.422), trải rộng trên 42 tên
khoa và 56 chương trình đào tạo.
Bảng thống kê chi tiết đầy đủ (khoa, ngành, phân bố địa lý, nhân khẩu học)
có trong Phụ lục.
4.4. Tại sao điều này quan trọng
Việc lộ dữ liệu sinh viên đáng lo ở cả quy mô lẫn chiều sâu. Bất kỳ ai có
trình duyệt đều có thể xây dựng hồ sơ về gần 80.000 người: tên, ngày sinh,
số điện thoại, quê quán, ngành học, và năm nhập học. Với 2.750 người, số căn
cước công dân cũng bị lộ, một thứ không bao giờ có thể thay đổi được.
Và không chỉ dữ liệu cá nhân, mật khẩu của sinh viên cũng bị lộ qua API.
Nhưng phân tích chi tiết về thông tin đăng nhập, tôi tập trung vào nhóm thứ
hai, nơi mức độ đặc quyền khiến hậu quả nghiêm trọng hơn nhiều.
5. Hướng 2: Lộ dữ liệu Cán bộ và Giảng viên – 562 tài khoản (kèm mật khẩu)
5.1. Nhóm nhỏ hơn, vấn đề lớn hơn
Tài khoản cán bộ và giảng viên chỉ chiếm một phần nhỏ: 562 trên 80.053. Mật
khẩu bị lộ ở cả tài khoản sinh viên lẫn cán bộ, nhưng tài khoản cán bộ đặc
biệt nguy hiểm vì bốn trường dữ liệu đăng nhập rõ ràng: username, password,
password_unmasked, và password_type, kết hợp với quyền quản trị mà các tài
khoản này nắm giữ.
API đang trả về thông tin đăng nhập thật, không chỉ dữ liệu cá nhân, mà là
credential thực sự của cán bộ trường đại học.
5.2. Trường mật khẩu
Trong lúc ánh xạ các trường được trả về cho tài khoản cán bộ, tôi nhìn thấy
trường ợ và phải dừng lại một lúc.
Trường này có dữ liệu ở cả tài khoản sinh viên lẫn cán bộ, nhưng với tài
khoản cán bộ và quản trị, nó chứa thứ trông rõ ràng là thông tin đăng nhập
có đặc quyền cao. Tôi ngồi nhìn chằm chằm vào màn hình, đọc đi đọc lại mấy
lần vì không tin vào mắt mình. Nền tảng này, cái nền tảng mà hàng chục nghìn
người đang dùng, đang trả về mật khẩu trong phản hồi API cho bất kỳ ai hỏi.
Tôi kiểm tra kỹ hơn, và dữ liệu đăng nhập bị lộ cho 561 tài khoản, chia
thành ba loại: