Phần 2: 80.000+ Tài Khoản – Từ Sinh Viên Đến Phó Hiệu Trưởng, Không Ai Được Bảo Vệ
Cập nhật (23 tháng 6, 2026): Lỗ hổng cốt lõi đã được khắc phục
Công ty Y đã triển khai xác thực phía server trên cổng API XHR. Lỗ hổng IDOR tài khoản và lộ mật khẩu trên nền tảng này không còn khai thác được. Xem chi tiết kiểm tra lại bên dưới.
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ần 1 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 1. Ướ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ĩ:
| Thông số | Giá trị |
|---|---|
| ID hoạt động thấp nhất | 7.787 |
| ID hoạt động cao nhất | 215.212 |
| Tổng dải ID | 207.426 |
| Tài khoản có dữ liệu | 80.053 |
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:
| Loại tài khoản | Số lượng | Loại tài khoản (loai_tk) |
|---|---|---|
| Sinh viên đang học | 21.393 | Loại 0 |
| Cựu sinh viên | 57.588 | Chủ yếu loại 0 |
| Cán bộ & Giảng viên | 562 | Chủ yếu loại 2, với 6 loại 1 (admin) |
| Doanh nghiệp / Đối tác | 166 | Loại 3 |
| Khách | 344 | Loại 3 |
| Tổng | 80.053 |
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:
| Trường | Có dữ liệu | Tỷ lệ |
|---|---|---|
| Họ tên (ho_ten) | 80.037 | 100,0% |
| Mã sinh viên (ma_sinh_vien) | 79.448 | 99,2% |
| Ngày sinh (ngay_sinh) | 78.637 | 98,2% |
| Giới tính (gioi_tinh) | 70.028 | 87,5% |
| Quê quán (que_quan) | 50.042 | 62,5% |
| Khoa (khoa) | 41.340 | 51,6% |
| Ngành (nganh) | 41.226 | 51,5% |
| Khóa học (khoa_hoc) | 39.768 | 49,7% |
| Chức vụ (chuc_vu) | 36.390 | 45,5% |
| Email (email) | 29.640 | 37,0% |
| Số điện thoại (sdt) | 23.970 | 29,9% |
| Dân tộc (dan_toc) | 7.845 | 9,8% |
| Ảnh đại diện (avatar_id) | 6.586 | 8,2% |
| Số CCCD (cmnd_cccd) | 2.750 | 3,4% |
| Tên mẹ | 344 | 0,4% |
| Tên bố | 333 | 0,4% |
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:
| Loại tài khoản | Có thông tin đăng nhập | Có mật khẩu | Mật khẩu trống |
|---|---|---|---|
| Cán bộ (loại 2) | 547 | 546 | 1 |
| Admin (loại 1) | 6 | 4 | 2 |
| Bên ngoài (loại 3) | 8 | 4 | 4 |
| Tổng | 561 | 554 | 7 |
554 tài khoản có mật khẩu không rỗng chia thành hai nhóm:
| Loại | Số lượng | Định dạng | Ví dụ |
|---|---|---|---|
| Plaintext | 50 (9,0%) | Chuỗi mật khẩu thô | mypassword123 |
| Đã che (masked) | 504 (91,0%) | 2_ký_tự_đầu**2_ký_tự_cuối[độ_dài] |
xx**xx[13] |
Năm mươi mật khẩu ở dạng văn bản thuần, nằm ngay đó trong phản hồi API, đọc được bằng mắt thường. Còn 504 cái còn lại thì được “che”, nhưng như tôi sắp phát hiện ra, cái lớp che đó mỏng đến mức gần như vô nghĩa.
Hình 2: Bản ghi tài khoản cán bộ từ phản hồi API, lưu ý các trường thông tin đăng nhập bao gồm username và dữ liệu mật khẩu, được phục vụ cho một phiên ẩn danh không xác thực.
5.3. Thuật toán che mật khẩu
Các mật khẩu bị che tuân theo một mẫu nhất quán:
2 ký tự đầu + "**" + 2 ký tự cuối + "[" + tổng độ dài + "]"
Ví dụ, một mật khẩu bị che như xx**xx[13] tiết lộ:
- Bắt đầu bằng: 2 ký tự đầu
- Kết thúc bằng: 2 ký tự cuối
- Tổng độ dài: 13 ký tự
- Chưa biết: chỉ 9 ký tự ở giữa
Đây không phải mã hóa (encryption), cũng không phải hàm băm (hashing). Đây là một mặt nạ hiển thị giữ lại thông tin về mật khẩu gốc, và những thông tin đó thu hẹp đáng kể không gian tìm kiếm để phục hồi.
Rồi tôi nhận ra một điều còn tệ hơn.
5.4. Phục hồi mật khẩu offline
Dữ liệu cần thiết để đoán các mật khẩu này nằm ngay trong cùng phản hồi API.
Lúc đó tôi mới thấy cái nghịch lý: người dùng Việt Nam thường xây dựng mật khẩu từ thông tin cá nhân như ngày sinh, thành phần tên, hay số điện thoại, và phản hồi API cho mỗi tài khoản cán bộ bao gồm tất cả các trường này ngay cạnh mật khẩu bị che. Nền tảng không chỉ đưa ra cánh cửa đã khóa, mà còn đặt luôn chìa khóa ngay bên cạnh kèm hướng dẫn sử dụng.
Tôi viết một bộ sinh từ điển offline kết hợp:
- Ngày sinh (nhiều định dạng:
ddmmyyyy,dmyyyy,dd/mm/yyyy) - Tên, họ (đã bỏ dấu tiếng Việt)
- Số điện thoại (đầy đủ và 4–6 chữ số cuối)
- Các mẫu phổ biến:
tên + ngày sinh,ngày sinh + tên,sdt + tên
Định dạng mật khẩu bị che đóng vai trò bộ xác thực: một mật khẩu ứng viên có thể được xác minh ngay lập tức nếu 2 ký tự đầu, 2 ký tự cuối, và độ dài khớp với mặt nạ, không cần đăng nhập. Không liên hệ với server. Hoàn toàn offline.
Kết quả trên 554 mật khẩu bị lộ:
| Chỉ số | Số lượng | Tỷ lệ |
|---|---|---|
| Tài khoản có mật khẩu bị lộ | 554 | – |
| Plaintext (không cần phục hồi) | 50 | 9,0% |
| Đã che – phục hồi offline | 158 | 28,5% |
| Đã che – không phục hồi được | 346 | 62,5% |
| Tổng thông tin đăng nhập sử dụng được | 208 | 37,5% |
Phân tích phương pháp phục hồi cho 208 bộ thông tin đăng nhập sử dụng được:
| Phương pháp | Số lượng | Mô tả |
|---|---|---|
| Plaintext | 50 | Mật khẩu lưu dạng văn bản thuần, không cần phục hồi |
| Known plaintext | 63 | Mật khẩu khớp chính xác một mẫu phổ biến đã biết |
| Từ điển (khớp duy nhất) | 78 | Một ứng viên từ điển duy nhất khớp mặt nạ |
| Từ điển (khớp xếp hạng) | 17 | Nhiều ứng viên khớp; xác định đúng theo thứ tự ưu tiên |
208 bộ thông tin đăng nhập thu được mà không hề chạm vào trang đăng nhập. Các mật khẩu không bị “bẻ khóa” theo nghĩa truyền thống, chúng hoặc được đọc thẳng ở dạng plaintext, hoặc được tái tạo từ dữ liệu tiểu sử do chính endpoint không xác thực cung cấp.
5.5. Những cán bộ này là ai?
Đây không phải tài khoản thử nghiệm hay hồ sơ bị bỏ hoang. 562 tài khoản cán bộ bao gồm cá nhân thuộc 35 chức vụ khác nhau, từ cố vấn học tập đến trưởng phòng:
| Chức vụ | Số lượng |
|---|---|
| Cố vấn học tập | 147 |
| Nhân viên hành chính | 110 |
| Chuyên viên | 71 |
| Giảng viên | 56 |
| Ủy viên | 22 |
| Trợ lý hành chính | 17 |
| Trợ lý giáo vụ | 17 |
| Lãnh đạo khoa | 14 |
| Trưởng bộ môn | 12 |
| Phó trưởng bộ môn | 20 |
| Các chức vụ khác (25 loại) | 76 |
Mức độ đầy đủ dữ liệu cá nhân của tài khoản cán bộ rất đáng chú ý: 99,6% có username, 98,6% có mật khẩu dưới một dạng nào đó, và 92,9% có địa chỉ email:
| Trường | Có dữ liệu | Trên 562 |
|---|---|---|
| Tên đăng nhập | 560 | 99,6% |
| Mật khẩu (bất kỳ dạng nào) | 554 | 98,6% |
| 522 | 92,9% | |
| Số điện thoại | 389 | 69,2% |
| Ngày sinh | 333 | 59,3% |
| Số CCCD | 30 | 5,3% |
| Mật khẩu phục hồi/plaintext | 208 | 37,0% |
5.6. Sự phi lý
Tôi cần nói thẳng, vì càng viết tôi càng thấy tình huống này vô lý đến khó tin:
API phục vụ mật khẩu cho người dùng ẩn danh, cả sinh viên lẫn cán bộ, dù ở dạng che hay plaintext. Cùng API đó phục vụ luôn dữ liệu cá nhân cần thiết để phục hồi mật khẩu bị che. Và không cần xác thực cho bất kỳ bước nào trong quy trình này. Nền tảng vừa đưa ra cánh cửa đã khóa, vừa đặt chìa khóa ngay bên cạnh, vừa chỉ cho biết cửa nào cần mở.
Tại thời điểm này, tôi có 208 bộ thông tin đăng nhập hoạt động được của cán bộ trường đại học, mà chưa hề đăng nhập vào bất cứ đâu, chưa tương tác với bất kỳ form đăng nhập nào. Nhưng tôi cần chứng minh rằng những thông tin đăng nhập này thực sự hoạt động, rằng đây không chỉ là rủi ro trên giấy.
6. Chiếm quyền tài khoản – Proof of Concept
6.1. Chọn mục tiêu
Để chứng minh tác động thực tế, tôi chọn hai tài khoản: một sinh viên bình thường và một Phó Hiệu trưởng của Trường Đại học X, tài khoản có đặc quyền cao nhất mà tôi có thể xác định. Cả hai đều được truy xuất qua cùng lệnh gọi API không xác thực:
CAN.db("taiKhoan.xxxx", function() {
var d = config("taiKhoan.xxxx");
console.log(d["a"]); // username
console.log(d["ợ"]); // password
});
API trả về tên đăng nhập và mật khẩu cho cả hai. Với cả hai tài khoản, mật khẩu có thể phục hồi được. Tôi nín thở và mở trang đăng nhập.
6.2. Đăng nhập
Tôi đọc thông tin đăng nhập từ phản hồi API, truy cập trang đăng nhập, nhập tên đăng nhập và mật khẩu, rồi nhấn “Đăng nhập.” Toàn bộ quá trình không kịch tính như tôi tưởng, không bruteforce, không công cụ bẻ khóa, không chiếm phiên, không exploit, chỉ là đọc một trường từ API rồi gõ nó vào form.
Cả hai tài khoản đều đăng nhập thành công ngay lần đầu, sinh viên lẫn Phó Hiệu trưởng.
Với tài khoản sinh viên, tôi truy cập được giao diện nền tảng mạng nội bộ của trường. Với tài khoản Phó Hiệu trưởng, tôi có toàn quyền truy cập quản trị. Cảm giác lúc đó không phải hào hứng mà là lo lắng thật sự, vì nếu tôi làm được, bất kỳ ai cũng làm được.
Hình 3: Giao diện nền tảng mạng nội bộ, được truy cập bằng thông tin đăng nhập của một sinh viên, đọc trực tiếp từ phản hồi API không xác thực.
6.3. Những gì có thể truy cập
Với tài khoản sinh viên, nền tảng mở ra giao diện mạng nội bộ đầy đủ. Với thông tin đăng nhập quản trị của Phó Hiệu trưởng, phạm vi truy cập mở rộng đáng kể hơn:
- Toàn quyền quản lý người dùng
- Truy cập thông báo nội bộ và truyền thông
- Khả năng chỉnh sửa hồ sơ người dùng
- Truy cập các chức năng và cài đặt quản trị
Tôi chụp ảnh màn hình để ghi nhận việc truy cập, rồi kết thúc cả hai phiên ngay lập tức. Không thực hiện hành động nào, không sửa đổi dữ liệu nào, và không khám phá thêm gì dưới các tài khoản bị chiếm quyền. Điều cần chứng minh đã được chứng minh.
7. Bản “vá” không thực sự vá
7.1. Phản hồi của nhà cung cấp: tec.universityx.vn
Sau khi thực hiện công bố có trách nhiệm (responsible disclosure) vào tháng 2 năm 2026, Công ty Y đã triển khai các thay đổi trên tec.universityx.vn (hệ thống thi từ Phần 1). Tôi thận trọng lạc quan khi nghe tin. Bản “vá” gồm hai phần:
Làm sạch dữ liệu: Một số trường PII đã được xóa nội dung cho bản ghi thí sinh. Họ tên, số điện thoại, email, và số CCCD đã được loại bỏ khỏi phản hồi API.
Lớp che giấu (obfuscation): Dữ liệu còn lại được bọc trong một sơ đồ mã hóa Base64 tùy chỉnh gọi là “b6x.”
Phần thứ nhất là một bước đi đúng hướng. Phần thứ hai thì không.
7.2. Mã hóa b6x: Mật mã từ thế kỷ thứ 9
Trước bản vá, API trả về các trường ở dạng văn bản thuần:
{
"16xxxxx": "Tran Thi Hxxx", // Họ tên
"16xxxxx": "23xxxxxxxx", // Số CCCD
"16xxxxx": "09xxxxxxxx", // Số điện thoại
"16xxxxx": "hoaixxxx@gmail.com" // Email
}
Sau bản vá, các trường được mã hóa thành một khối duy nhất:
{
"i": "16xxxxx",
"_5a9fxxxxxxxxxxxxxxxxxxxxxxxxxxxx": "wqD2xxxxxxxxxxxxxxxxxxxx..."
}
Thoạt nhìn, điều này trông như đã được mã hóa đàng hoàng. Nhưng khi tôi bắt đầu đọc kỹ JavaScript mà trình duyệt tải về, tôi nhận ra đây chỉ là mật mã thay thế đơn bảng (monoalphabetic substitution cipher), một kỹ thuật mà nhà toán học Al-Kindi đã phá giải từ thế kỷ thứ 9 trong Bản thảo về Giải mã Thông điệp Mật mã (khoảng năm 850 SCN). Hơn một nghìn năm trước.
Toàn bộ “mã hóa” chỉ là phép thay thế ký tự đơn giản giữa hai bảng chữ cái:
Tùy chỉnh (b6x): OsCmIBxZDQxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxtz0dV:e5vFb
Chuẩn B64: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=
Và hàm giải mã? Nó được giao cùng trong gói JavaScript mà mọi trình duyệt đều tải về:
d64 = function(v, k) {
switch(k) {
case "x":
if (!v) return "";
v = strtr(v,
"OsCmIBxZDQxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxtz0dV:e5vFb",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
);
return decodeURIComponent(
atob(v).split("").map(function(c) {
return "%" + c.charCodeAt(0).toString(16);
}).join("")
);
}
};
Năm dòng code để vô hiệu hóa toàn bộ biện pháp “bảo mật.” Khóa, thuật toán, và cách triển khai đều được giao thẳng cho trình duyệt của kẻ tấn công như một phần của quá trình tải trang bình thường. Bảo mật bằng che giấu (security through obscurity), và còn không phải kiểu che giấu tốt.
7.3. connections.universityx.vn: Hoàn toàn không thay đổi
Và đây mới là điều khiến tôi thực sự bực bội. Trong khi Công ty Y bỏ công áp dụng bản vá mỹ phẩm cho hệ thống thi, nền tảng mạng nội bộ tại connections.universityx.vn, nơi chứa 80.053 tài khoản và mật khẩu cán bộ, không nhận được bất kỳ thay đổi nào. Toàn bộ 80.053 tài khoản vẫn có thể truy cập đầy đủ, mật khẩu cán bộ vẫn nằm trong phản hồi API.
Họ vá cửa sổ và để ngỏ cửa chính.
7.4. Kết quả kiểm tra lại
Vào ngày 10 tháng 3 năm 2026, tôi quay lại và kiểm tra lại một cách có hệ thống mọi vector tấn công trên cả hai nền tảng:
| Kiểm tra | Mục tiêu | Kết quả |
|---|---|---|
| T1: Framework JS có thể truy cập | Cả hai | CÒN LỖ HỔNG |
| T2: Tự tạo phiên không cần xác thực | Cả hai | CÒN LỖ HỔNG |
| T3: IDOR tài khoản | connections.universityx.vn | CÒN LỖ HỔNG |
| T4: Truy cập bảng thí sinh | tec.universityx.vn | CÒN LỖ HỔNG (chỉ có b6x) |
| T5: Tìm kiếm bảng đăng ký | tec.universityx.vn | ĐÃ VÁ |
| T6: Tải dữ liệu hàng loạt | Cả hai | CÒN LỖ HỔNG |
| T7: Xác thực XHR | tec.universityx.vn | ĐÃ VÁ |
| T8: Truy cập ảnh CDN | tec.universityx.vn | ĐÃ VÁ |
| T9: Lộ trường mật khẩu | connections.universityx.vn | CÒN LỖ HỔNG |
| T10: Truy cập nền tảng mạng nội bộ | connections.universityx.vn | CÓ THỂ TRUY CẬP |
Kết quả: 7 trên 10 kiểm tra vẫn còn lỗ hổng. Tìm kiếm đăng ký và CDN ảnh đã bị hạn chế, và một endpoint XHR đã thêm kiểm tra token. Nhưng lỗ hổng cốt lõi, truy cập cơ sở dữ liệu không xác thực qua IDOR, vẫn có thể khai thác hoàn toàn trên cả hai nền tảng.
Nhà cung cấp chữa triệu chứng mà bỏ mặc căn bệnh.
8. Đánh giá tác động
8.1. Quy mô phơi bày
| Loại | Số lượng |
|---|---|
| Tổng tài khoản bị lộ | 80.053 |
| Họ tên đầy đủ | 80.037 |
| Ngày sinh | 78.637 |
| Số điện thoại | 23.970 |
| Địa chỉ email | 29.640 |
| Số CCCD/CMND | 2.750 |
| Quê quán | 50.042 |
| Ảnh đại diện | 6.586 |
| Tên người thân | 677 (333 bố + 344 mẹ) |
| Tài khoản có dữ liệu đăng nhập | 561 |
| Tài khoản có mật khẩu | 554 (50 plaintext + 504 đã che) |
| Thông tin đăng nhập sử dụng được | 208 (50 plaintext + 158 phục hồi) |
| Chiếm quyền tài khoản đã chứng minh | Có (sinh viên + Phó Hiệu trưởng) |
8.2. Độ phức tạp tấn công
Đây không phải một cuộc tấn công tinh vi. Toàn bộ chuỗi khai thác chỉ cần:
- Mở trình duyệt.
- Mở developer console.
- Gõ một lệnh gọi API.
- Đọc phản hồi.
CVSS 3.1 Vector: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N – 9.8 Critical.
Không cần công cụ đặc biệt, không cần chuyên môn kỹ thuật ngoài JavaScript cơ bản, không cần tương tác từ nạn nhân. Hoàn toàn có thể tự động hóa và thực hiện được ở quy mô lớn. Một kẻ tấn công có động cơ có thể trích xuất toàn bộ 80.053 tài khoản trong vài giờ.
8.3. Các kịch bản rủi ro thực tế
Đánh cắp danh tính và lừa đảo: 2.750 số CCCD kết hợp với họ tên đầy đủ, ngày sinh, và địa chỉ. Chỉ cần bấy nhiêu thông tin là đủ để mở tài khoản ngân hàng giả, vay tín dụng đen, hoặc thực hiện SIM-swap. Những người này sẽ chỉ biết khi đã quá muộn, khi có ai đó gọi đến đòi nợ cho khoản vay họ chưa từng đăng ký.
Phishing có mục tiêu: 29.640 địa chỉ email và 23.970 số điện thoại kết hợp với ngữ cảnh cá nhân chi tiết, khoa nào, ngành gì, năm nào nhập học, tạo nên nguyên liệu cho những chiến dịch social engineering mà người nhận gần như không thể phân biệt với thông tin liên lạc hợp pháp từ trường. Một email giả từ “phòng đào tạo” gửi đúng tên, đúng khoa, đúng khóa học sẽ có tỷ lệ thành công rất cao.
Chiếm quyền tài khoản dây chuyền: 208 bộ thông tin đăng nhập cán bộ có thể được thử trên các hệ thống khác: email trường, cổng nội bộ, dịch vụ công. Tài khoản Phó Hiệu trưởng bị chiếm quyền có thể bị lợi dụng để di chuyển ngang (lateral movement) vào các hệ thống nhạy cảm hơn, và với quyền quản trị, khả năng gây thiệt hại là rất lớn.
Tổn hại tổ chức: Truy cập quản trị trái phép có thể cho phép sửa đổi hồ sơ sinh viên, kết quả học tập, hoặc thông báo chính thức. Một tài khoản bị chiếm quyền có thể thay đổi điểm số hoặc gửi thông báo giả cho toàn trường, và không ai biết đó không phải Phó Hiệu trưởng thật.
Người thân gặp rủi ro: 677 tài khoản lộ tên bố mẹ và năm sinh, dữ liệu có thể được sử dụng trong social engineering hoặc vượt qua xác minh danh tính. Sự phơi bày không dừng lại ở những người đã đăng ký mà lan ra cả gia đình họ.
8.4. Khung pháp lý: Luật Bảo vệ dữ liệu cá nhân Việt Nam
Việt Nam bảo vệ dữ liệu cá nhân thông qua hai tầng pháp luật: Nghị định 13/2023/NĐ-CP (có hiệu lực từ ngày 1 tháng 7 năm 2023) và Luật Bảo vệ dữ liệu cá nhân – Luật số 91/2025/QH15 (được thông qua ngày 26 tháng 6 năm 2025, có hiệu lực từ ngày 1 tháng 1 năm 2026). Kết hợp lại, chúng tạo thành khung pháp lý yêu cầu các tổ chức bảo vệ dữ liệu cá nhân, trao quyền kiểm soát thông tin cho công dân, và áp đặt chế tài nghiêm khắc đối với vi phạm.
Cả Trường Đại học X và Công ty Y đều đang vi phạm trực tiếp, có bằng chứng rõ ràng khung pháp lý này. Không cần phải diễn giải hay suy đoán gì cả, các vi phạm mang tính kiến trúc, có hệ thống, và đang diễn ra ngay lúc này.
Luật định nghĩa gì là dữ liệu được bảo vệ
Nghị định 13, Điều 2, Khoản 3 phân loại các thông tin sau là dữ liệu cá nhân cơ bản: họ tên, ngày sinh, giới tính, nơi sinh, nơi cư trú, quốc tịch, hình ảnh cá nhân, số điện thoại, số CMND/CCCD, và thông tin quan hệ gia đình. Từng loại một trong số này đã bị phơi bày trong cuộc điều tra, với hơn 80.000 cá nhân, mà không có bất kỳ kiểm soát truy cập nào.
Nghị định 13, Điều 2, Khoản 4 định nghĩa dữ liệu cá nhân nhạy cảm là dữ liệu mà khi bị vi phạm sẽ ảnh hưởng trực tiếp đến quyền và lợi ích hợp pháp của cá nhân, bao gồm quan điểm chính trị và tôn giáo, dữ liệu dân tộc, và dữ liệu về tiền án tiền sự. Cuộc điều tra này đã phơi bày dân tộc của 7.845 tài khoản và tôn giáo của 5.476 tài khoản.
Luật 91/2025, Điều 2 củng cố các định nghĩa này và bổ sung rằng dữ liệu cá nhân nhạy cảm được xác định theo danh mục do Chính phủ ban hành, và rằng ngay cả dữ liệu đã được định danh lại (de-identified), một khi được tái định danh, vẫn là dữ liệu cá nhân (Điều 2, Khoản 1).
Ai chịu trách nhiệm?
Cả hai văn bản pháp luật đều vạch ra cùng một ranh giới rõ ràng giữa hai vai trò:
-
Bên Kiểm soát dữ liệu cá nhân: tổ chức quyết định mục đích và phương tiện xử lý dữ liệu cá nhân (Nghị định 13, Điều 2, Khoản 9; Luật 91, Điều 2, Khoản 7). Trường Đại học X là Bên Kiểm soát dữ liệu cá nhân. Trường quyết định thu thập dữ liệu sinh viên và cán bộ, quyết định lưu trữ những dữ liệu gì, và chọn triển khai nền tảng Connections. Dữ liệu thuộc về sinh viên và cán bộ của Trường Đại học X. Trách nhiệm bắt đầu từ đây.
-
Bên Xử lý dữ liệu cá nhân: tổ chức xử lý dữ liệu thay mặt Bên Kiểm soát, thông qua hợp đồng hoặc thỏa thuận (Nghị định 13, Điều 2, Khoản 10; Luật 91, Điều 2, Khoản 8). Công ty Y là Bên Xử lý dữ liệu cá nhân. Họ xây dựng, lưu trữ, và vận hành nền tảng Connections chứa và phục vụ dữ liệu của Trường Đại học X. Họ là người quyết định rằng nền tảng không cần xác thực.
Những gì được yêu cầu – và không được thực hiện
Nghị định 13, Điều 26 yêu cầu các biện pháp bảo vệ dữ liệu phải được áp dụng ngay từ đầu và xuyên suốt toàn bộ vòng đời xử lý. Các biện pháp này phải bao gồm cả biện pháp tổ chức và kỹ thuật (Khoản 2). Nền tảng của Công ty Y không có biện pháp kỹ thuật nào: không xác thực, không kiểm soát truy cập, không mã hóa, API phục vụ dữ liệu cá nhân thô cho bất kỳ ai truy cập.
Nghị định 13, Điều 27 yêu cầu bên kiểm soát và bên xử lý phải kiểm tra an ninh mạng của hệ thống và thiết bị được sử dụng để xử lý dữ liệu cá nhân, và phải xóa hoặc tiêu hủy dữ liệu trên thiết bị không thể khôi phục (Khoản 4). Nền tảng không có kiểm tra an ninh mạng nào: bất kỳ trình duyệt nào cũng có thể truy vấn cơ sở dữ liệu trực tiếp.
Luật 91/2025, Điều 3 thiết lập các nguyên tắc cốt lõi: dữ liệu cá nhân chỉ được thu thập và xử lý trong phạm vi xác định, cho mục đích rõ ràng và hợp pháp (Khoản 2); phải triển khai các biện pháp thể chế, kỹ thuật, và nhân lực hiệu quả để bảo vệ dữ liệu cá nhân (Khoản 4); và tổ chức phải chủ động phòng ngừa, phát hiện, ngăn chặn, và xử lý kịp thời mọi vi phạm (Khoản 5). Công ty Y vi phạm từng nguyên tắc một, và Trường Đại học X không xác minh rằng bất kỳ nguyên tắc nào đang được tuân thủ.
Luật 91/2025, Điều 12 yêu cầu rõ ràng việc mã hóa dữ liệu cá nhân, tức chuyển đổi sang dạng không thể nhận diện mà không có giải mã (Khoản 1). Công ty Y làm điều ngược lại: họ lưu mật khẩu ở dạng plaintext và dữ liệu cá nhân trong phản hồi API thô, không mã hóa, ai cũng có thể truy cập.
Nghị định 13, Điều 38 quy định nghĩa vụ của Bên Kiểm soát dữ liệu: triển khai biện pháp tổ chức và kỹ thuật để chứng minh tuân thủ (Khoản 1), duy trì nhật ký xử lý (Khoản 2), báo cáo vi phạm theo Điều 23 (Khoản 3), chỉ chọn Bên Xử lý có biện pháp bảo vệ phù hợp (Khoản 4), và chịu trách nhiệm với chủ thể dữ liệu về thiệt hại do xử lý gây ra (Khoản 6). Trường Đại học X chọn Công ty Y làm Bên Xử lý mà không xác minh (hoặc bất chấp) sự vắng mặt hoàn toàn của các biện pháp bảo mật.
Nghị định 13, Điều 39 quy định nghĩa vụ của Bên Xử lý dữ liệu: chỉ nhận dữ liệu sau khi có hợp đồng hoặc thỏa thuận (Khoản 1), xử lý dữ liệu theo thỏa thuận đó (Khoản 2), triển khai đầy đủ mọi biện pháp bảo vệ theo quy định của nghị định (Khoản 3), và chịu trách nhiệm với chủ thể dữ liệu về thiệt hại gây ra trong quá trình xử lý (Khoản 4). Công ty Y thất bại ở mọi điểm.
Luật yêu cầu gì khi phát hiện vi phạm
Nghị định 13, Điều 23 quy định rằng khi phát hiện vi phạm bảo vệ dữ liệu, Bên Kiểm soát phải thông báo cho Bộ Công an (Cục An ninh mạng và Phòng, chống tội phạm sử dụng công nghệ cao) trong vòng 72 giờ theo Mẫu số 03 (Khoản 1). Bên Xử lý phải thông báo cho Bên Kiểm soát sớm nhất có thể (Khoản 2). Thông báo phải bao gồm: bản chất vi phạm, thời gian và địa điểm, các loại dữ liệu cá nhân bị ảnh hưởng, số lượng chủ thể dữ liệu liên quan, và các biện pháp đã thực hiện để khắc phục thiệt hại (Khoản 3).
Luật 91/2025, Điều 23 tăng cường yêu cầu này: khi phát hiện vi phạm có thể gây hại cho an ninh quốc gia, trật tự xã hội, tính mạng, sức khỏe, danh dự, nhân phẩm, hoặc tài sản của chủ thể dữ liệu, việc thông báo cho cơ quan bảo vệ dữ liệu phải diễn ra trong vòng 72 giờ (Khoản 1). Bên Kiểm soát phải lập báo cáo sự cố chính thức và hợp tác với cơ quan chức năng để xử lý vi phạm (Khoản 2).
Luật 91/2025, Điều 21 yêu cầu Bên Kiểm soát và Bên Xử lý phải chuẩn bị và duy trì Đánh giá tác động xử lý dữ liệu, nộp cho cơ quan bảo vệ dữ liệu trong vòng 60 ngày kể từ khi bắt đầu xử lý dữ liệu (Khoản 1). Đánh giá này phải được cập nhật hàng năm (Khoản 2) và phải luôn sẵn sàng cho Bộ Công an kiểm tra (Nghị định 13, Điều 24, Khoản 4). Không có bằng chứng rằng Trường Đại học X hoặc Công ty Y đã nộp đánh giá như vậy.
Hậu quả là gì?
Luật 91/2025 không để lại chỗ cho sự mơ hồ về chế tài. Điều 8 quy định rõ:
- Vi phạm có thể bị xử lý kỷ luật, xử phạt hành chính, hoặc truy cứu trách nhiệm hình sự tùy theo tính chất và mức độ nghiêm trọng (Khoản 1).
- Đối với cá nhân mua bán hoặc buôn bán dữ liệu cá nhân trái phép: phạt tới 10 lần doanh thu thu được từ vi phạm (Khoản 3).
- Đối với tổ chức vi phạm quy định chuyển dữ liệu xuyên biên giới: phạt tới 5% doanh thu hàng năm tại Việt Nam (Khoản 4).
- Đối với các vi phạm bảo vệ dữ liệu khác: mức phạt hành chính tối đa 3 tỷ đồng (~120.000 USD) cho tổ chức (Khoản 5).
- Cá nhân vi phạm tương tự chịu mức phạt tối đa bằng một nửa mức phạt tổ chức (Khoản 6).
- Nếu vi phạm gây thiệt hại, bồi thường là bắt buộc theo quy định pháp luật (Khoản 1).
Nghị định 13, Điều 4 bổ sung rằng vi phạm có thể bị xử lý qua biện pháp kỷ luật, chế tài hành chính, hoặc truy cứu hình sự tùy theo mức độ nghiêm trọng.
80.053 chủ thể dữ liệu có quyền đòi hỏi gì
Quyền của chủ thể dữ liệu không phải lý thuyết suông. Chúng có thể thi hành:
- Quyền được biết về các hoạt động xử lý dữ liệu (Luật 91, Điều 4, Khoản 1a; Nghị định 13, Điều 9, Khoản 1)
- Quyền rút lại sự đồng ý (Luật 91, Điều 4, Khoản 1b; Nghị định 13, Điều 9, Khoản 2)
- Quyền truy cập và chỉnh sửa dữ liệu (Luật 91, Điều 4, Khoản 1c; Nghị định 13, Điều 9, Khoản 3)
- Quyền yêu cầu xóa và hạn chế xử lý (Luật 91, Điều 4, Khoản 1d; Nghị định 13, Điều 9, Khoản 5–6)
- Quyền khiếu nại, khởi kiện, và yêu cầu bồi thường thiệt hại (Luật 91, Điều 4, Khoản 1đ; Nghị định 13, Điều 9, Khoản 9–10)
- Quyền yêu cầu cơ quan có thẩm quyền và tổ chức liên quan triển khai biện pháp bảo vệ dữ liệu (Luật 91, Điều 4, Khoản 1e)
Mỗi người trong 80.053 cá nhân bị phơi bày đều có các quyền này. Mỗi người đều có thể yêu cầu Trường Đại học X và Công ty Y trả lời. Mỗi người đều có thể yêu cầu dữ liệu của mình được bảo mật, được thông báo về sự cố vi phạm, và được bồi thường thiệt hại.
Kết luận pháp lý
Trường Đại học X không thể núp sau Công ty Y. Pháp luật quy định rõ ràng Bên Kiểm soát phải chịu trách nhiệm trong việc lựa chọn Bên Xử lý có biện pháp bảo vệ phù hợp (Nghị định 13, Điều 38, Khoản 4) và chịu trách nhiệm bồi thường thiệt hại (Nghị định 13, Điều 38, Khoản 6). Bằng việc thuê ngoài nền tảng cho một nhà cung cấp hoàn toàn không có kiến trúc bảo mật, Trường Đại học X không thuê ngoài trách nhiệm, mà khuếch đại trách nhiệm pháp lý của mình.
Công ty Y không thể viện cớ không biết. Họ xây dựng nền tảng, họ quyết định rằng JavaScript phía client là đủ để bảo mật, họ chọn trả về mật khẩu trong phản hồi API, và họ không mã hóa dữ liệu cá nhân như Luật 91, Điều 12 yêu cầu. Mọi quyết định kiến trúc dẫn đến sự phơi bày này đều là của họ.
Pháp luật yêu cầu họ hành động, không phải lúc nào thuận tiện, không phải khi nào muốn, mà ngay bây giờ:
- Thông báo cho Bộ Công an trong vòng 72 giờ kể từ khi phát hiện vi phạm.
- Chuẩn bị và nộp Đánh giá tác động xử lý dữ liệu.
- Triển khai xác thực, kiểm soát truy cập, và mã hóa thực sự.
- Thông báo cho toàn bộ 80.053 cá nhân bị ảnh hưởng.
- Loại bỏ ngay lập tức thông tin đăng nhập bị lộ khỏi API.
- Thực hiện kiểm toán bảo mật toàn diện nền tảng Connections, không chỉ triển khai tại Trường Đại học X, mà toàn bộ khách hàng đang chạy trên cùng kiến trúc.
80.053 người đã tin tưởng Trường Đại học X với dữ liệu cá nhân của họ. Sự tin tưởng đó được ủy thác cho Công ty Y. Cả hai đều thất bại. Pháp luật nói rằng sự thất bại đó có hậu quả. Đã đến lúc những hậu quả đó được thực thi.
9. Kết luận
Ở Phần 1, tôi phát hiện 896 thẻ căn cước bị lộ qua cổng thi của một trường đại học. Câu hỏi tiếp theo, “còn nền tảng lớn hơn thì sao?”, dẫn đến câu trả lời mà tôi ước mình đã không tìm thấy: 80.053 tài khoản, mật khẩu nằm trong API cho cả sinh viên lẫn cán bộ, và cả hai loại tài khoản đều bị chiếm quyền chỉ bằng cách đọc một trường dữ liệu.
Nguyên nhân gốc không thay đổi từ Phần 1, vẫn là cùng lỗi kiến trúc: nền tảng Connections của Công ty Y không triển khai bất kỳ cơ chế xác thực hay phân quyền phía server nào. Framework JavaScript chạy trong trình duyệt người dùng là thứ duy nhất đứng giữa khách truy cập và cơ sở dữ liệu, và đó không phải ranh giới bảo mật, đó là sự vắng mặt của ranh giới.
Nhưng điều khiến tôi trăn trở nhất không phải con số hay kỹ thuật, mà là trường mật khẩu. Lưu trữ thông tin đăng nhập trong phản hồi API mà client có thể truy cập, dù đã che, không phải lỗi thiết kế đơn thuần. Đó là sự hiểu lầm căn bản về cách hệ thống xác thực nên hoạt động. Mật khẩu không bao giờ được rời khỏi server, dưới bất kỳ dạng nào, trong bất kỳ hoàn cảnh nào. Và việc thuật toán che có thể bị dịch ngược bằng dữ liệu từ chính phản hồi đó nâng mức độ từ rò rỉ dữ liệu lên thành xâm phạm toàn bộ hệ thống xác thực.
Phản hồi ban đầu của nhà cung cấp, xóa một số giá trị trường và thêm mật mã thay thế phía client, cho thấy một khuôn mẫu đáng lo ngại: chữa triệu chứng nhìn thấy mà bỏ qua nguyên nhân gốc. Tại lần kiểm tra lại ngày 10 tháng 3, 7 trên 10 vector tấn công vẫn có thể khai thác, và nền tảng mạng nội bộ không nhận được bất kỳ thay đổi bảo mật nào. (Xem Mục 10 cho bản cập nhật ngày 23 tháng 6, khi nhà cung cấp đã triển khai xác thực phía server và khắc phục lỗ hổng cốt lõi.)
Tôi biết rằng đằng sau 80.053 ID tài khoản là con người thật. Sinh viên đã tin tưởng trường đại học với thông tin cá nhân của mình, cựu sinh viên mong đợi dữ liệu của mình được bảo mật sau khi rời trường, và cả sinh viên lẫn cán bộ đều có thông tin đăng nhập bị phơi bày cho bất kỳ ai muốn xem. Tài khoản của một sinh viên và một Phó Hiệu trưởng đều bị chiếm quyền không bằng exploit tinh vi mà bằng cách đọc một trường trong phản hồi API rồi gõ nó vào form đăng nhập. Nếu tôi, một người nghiên cứu bảo mật với ý định tốt, có thể làm được điều này, thì bất kỳ ai cũng có thể.
Bản vá mà nền tảng này cần không phải thêm một lớp che giấu phía client, mà là triển khai những gì đáng lẽ phải tồn tại từ ngày đầu: xác thực phía server, kiểm soát truy cập dựa trên vai trò, và nguyên tắc cơ bản rằng cơ sở dữ liệu không nên trả lời câu hỏi từ người lạ.
10. Cập nhật kiểm tra lại – Ngày 23 tháng 6, 2026
Ngày 23 tháng 6 năm 2026, mười ngày sau khi báo cáo này được công bố, tôi kiểm tra lại toàn bộ các vector tấn công trên cả hai nền tảng. Công ty Y đã cập nhật mã nguồn ngay trong ngày đó (phiên bản 14484523062026), và kết quả khác biệt đáng kể so với lần kiểm tra ngày 10 tháng 3.
Những gì đã thay đổi
Bản sửa quan trọng nhất mang tính kiến trúc: cổng API XHR giờ đã thực thi xác thực phía server. Cả connections.universityx.vn/xhr/ lẫn endpoint phụ tại xhr.companyy.com/xhr/ đều trả về HTTP 403 với {"error":403,"code":"access_denied"} cho các yêu cầu POST không xác thực. Đây là lần đầu tiên tôi quan sát thấy kiểm soát truy cập phía server trên nền tảng này.
Với cổng API bị khóa, các hệ quả kéo theo là tức thì:
- IDOR tài khoản không còn hoạt động. Toàn bộ các ID tài khoản được kiểm tra (bao gồm dải gốc 7.787–215.212) đều trả về null. Cơ sở dữ liệu không còn trả lời câu hỏi từ người lạ.
- Lộ mật khẩu đã được loại bỏ. Với bản ghi tài khoản bị chặn, trường mật khẩu (
ợ) không còn truy cập được. 208 bộ thông tin đăng nhập cán bộ được ghi nhận trong báo cáo này không còn truy xuất được nữa. - Liệt kê hàng loạt bị chặn trên
connections.universityx.vn. Endpoint tải hàng loạt trả về mảng rỗng. - Mật mã b6x đã bị gỡ bỏ khỏi
tec.universityx.vn. Lớp mật mã thay thế đơn bảng được ghi nhận trong Mục 7.2 đã biến mất hoàn toàn.
Những gì vẫn còn lỗ hổng
Framework JavaScript và việc tự tạo phiên vẫn bị lộ trên cả hai nền tảng. Khách truy cập ẩn danh vẫn nhận được session ID và truy cập vào toàn bộ bề mặt API phía client. Tuy nhiên, đây là lỗ hổng mức độ thấp khi đứng riêng, vì chúng chỉ trở nên nguy hiểm khi kết hợp với truy cập dữ liệu, thứ giờ đã bị chặn.
Trên tec.universityx.vn (hệ thống thi từ Phần 1), bản sửa chưa hoàn chỉnh:
| Vấn đề | Trạng thái |
|---|---|
| Thí sinh #1662402 vẫn lộ ngày sinh, giới tính, và ID tham chiếu ảnh CCCD | CÒN LỖ HỔNG |
| Tải hàng loạt trả về 50.403 ID thí sinh (bản ghi hầu hết trống) | CÒN LỖ HỔNG |
| Các node CDN ảnh (i0/i3) phản hồi trở lại sau khi đã được sửa vào tháng 3 | BỊ HỒI QUY |
Bảng điểm
| Nền tảng | Kiểm tra | Đã sửa | Còn lỗ hổng | Điểm |
|---|---|---|---|---|
| connections.universityx.vn (Phần 2) | 13 | 11 | 2 | 85% đã sửa |
| tec.universityx.vn (Phần 1) | 13 | 6 | 5 | 46% đã sửa |
| Trước đó (10 tháng 3, cả hai) | 10 | 3 | 7 | 30% đã sửa |
Vậy đã đủ an toàn chưa?
Với connections.universityx.vn, nền tảng được ghi nhận trong báo cáo này: có, lỗ hổng nghiêm trọng đã được khắc phục. 80.053 tài khoản và mật khẩu cán bộ không còn truy cập được bởi người dùng không xác thực. Nhà cung cấp đã chuyển từ “không có bảo mật” sang “thực thi xác thực phía server tại cổng API,” đây là bản sửa kiến trúc đúng hướng thay vì thêm một lớp che giấu phía client.
Tuy nhiên, bản sửa là một cổng xác thực được gắn thêm vào kiến trúc hiện có, không phải thiết kế lại từ đầu. Framework phía client vẫn tải, phiên vẫn được tự tạo, và mô hình dữ liệu bên dưới có lẽ vẫn trả về tất cả các trường cho người dùng đã xác thực mà không phân quyền theo vai trò. Một phiên đã xác thực bị xâm phạm hoặc có đặc quyền thấp vẫn có thể truy cập nhiều dữ liệu hơn mức cần thiết. Một cuộc kiểm toán bảo mật đầy đủ cần xác minh rằng kiểm soát truy cập sau xác thực cũng chặt chẽ tương đương.
Với tec.universityx.vn, bức tranh còn lẫn lộn. Hệ thống thi vẫn rò rỉ dữ liệu thí sinh một phần và đã bị hồi quy về truy cập ảnh CDN. Các phát hiện từ Phần 1 chưa được giải quyết hoàn toàn.
Nhà cung cấp đã có tiến bộ thực sự. Nhưng liệu tiến bộ đó đã đủ hay chưa phụ thuộc vào việc họ có tiếp tục hay không: khuôn mẫu cho đến nay là vá lỗi từng bước để phản ứng với các báo cáo được công bố, thay vì rà soát bảo mật toàn diện nền tảng. Bước tiếp theo nên là một cuộc kiểm thử xâm nhập chuyên nghiệp trên bề mặt tấn công sau xác thực, điều nằm ngoài phạm vi của nghiên cứu này.
Phụ lục
A. Dòng thời gian Công bố có Trách nhiệm
| Ngày | Sự kiện |
|---|---|
| 2026-02-25 | Phát hiện lỗ hổng trên tec.universityx.vn (Phần 1) |
| 2026-02-26 | Điều tra connections.universityx.vn; trích xuất 80.053 tài khoản |
| 2026-02-26 | Hoàn thành báo cáo kỹ thuật |
| 2026-02-27 | Gửi yêu cầu CVE đến MITRE |
| 2026-02-28 | Công ty Y xác nhận nhận báo cáo, xác nhận bắt đầu khắc phục |
| 2026-03-10 | Kiểm tra lại: 7/10 vector tấn công vẫn còn lỗ hổng |
| 2026-03-24 | Phần 1 được công bố (đã ẩn danh) |
| 2026-06-13 | Phần 2 được công bố (báo cáo này) |
| 2026-06-23 | Kiểm tra lại: 85% đã sửa trên connections.universityx.vn, 46% trên tec.universityx.vn |
B. Phân loại Kỹ thuật Tấn công
| Kỹ thuật | Framework | Ứng dụng |
|---|---|---|
| IDOR | OWASP Top 10 | Liệt kê ID tài khoản tuần tự (7.787–215.212) |
| Broken Access Control | OWASP Top 10 | Không xác thực trên bất kỳ endpoint API nào |
| Credential Exposure | OWASP Top 10 | Mật khẩu được trả về trong phản hồi API |
| Broken Authentication | OWASP Top 10 | Không xác thực phía server; chỉ có phía client |
| Security Misconfiguration | OWASP Top 10 | Truy cập cơ sở dữ liệu mở mặc định |
| Insufficient Cryptography | CWE-327 | Mật mã thay thế đơn bảng làm “mã hóa” |
C. Tóm tắt Nguồn Dữ liệu
Tất cả thống kê trong báo cáo này được trích xuất từ các tập dữ liệu sau:
| Tập tin | Bản ghi | Mô tả |
|---|---|---|
| universityx_conn_accounts.csv | 80.053 | Tập dữ liệu tài khoản đầy đủ (40 trường mỗi tài khoản) |
| universityx_conn_current_students.csv | 21.393 | Tập con sinh viên đang học |
| universityx_conn_old_students.csv | 57.588 | Tập con cựu sinh viên |
| universityx_conn_staff.csv | 562 | Tập con cán bộ & giảng viên (44 trường, bao gồm thông tin đăng nhập) |
| universityx_conn_companies.csv | 166 | Tập con tài khoản doanh nghiệp/đối tác |
| universityx_conn_guests.csv | 344 | Tập con tài khoản khách |
| credentials_exposed.csv | 561 | Tất cả tài khoản có trường thông tin đăng nhập |
| unmasked_staff.csv | 208 | Thông tin đăng nhập phục hồi thành công/plaintext |
Tổng các tập con: 21.393 + 57.588 + 562 + 166 + 344 = 80.053 (khớp chính xác tập dữ liệu chính).
D. Thuật ngữ
| Thuật ngữ | Định nghĩa |
|---|---|
| CCCD | Căn cước công dân |
| CMND | Chứng minh nhân dân – định dạng cũ |
| IDOR | Insecure Direct Object Reference – Tham chiếu đối tượng trực tiếp không an toàn |
| Trường Đại học X | Tên ẩn danh cho trường đại học bị ảnh hưởng |
| Công ty Y | Tên ẩn danh cho nhà cung cấp nền tảng |
| Nền tảng Z | Nền tảng SaaS “Connections” do Công ty Y vận hành |
| PII | Personally Identifiable Information – Thông tin nhận dạng cá nhân |
| b6x | Bảng chữ cái Base64 tùy chỉnh được sử dụng bởi lớp che giấu của Nền tảng Z |
E. Bản ghi Tài khoản Mẫu (Đã biên tập)
{
"account_id": "[REDACTED]",
"ho_ten": "[REDACTED]",
"ngay_sinh": "xx/xx/1999",
"gioi_tinh": "Nữ",
"sdt": "098XXXXXXX",
"email": "[redacted]@gmail.com",
"cmnd_cccd": "001XXXXXXXXX",
"que_quan": "[Tỉnh]",
"ma_sinh_vien": "19XXXXXX",
"khoa": "Khoa [REDACTED]",
"nganh": "[REDACTED]",
"khoa_hoc": "20xx-20xx",
"loai_tk": "0"
}
F. Bảng Thống kê Chi tiết
Phụ lục này chứa các bảng thống kê chi tiết được tham chiếu trong Mục 3.5.
Phân bố Tên miền Email (29.640 địa chỉ email):
| Tên miền | Số lượng | Ý nghĩa |
|---|---|---|
| gmail.com | 20.793 | Tài khoản cá nhân |
| ms.universityx.edu.vn | 5.329 | Tài khoản Microsoft 365 chính thức của trường |
| s.universityx.edu.vn | 1.309 | Hệ thống email sinh viên |
| universityx.edu.vn | 1.124 | Email giảng viên/cán bộ |
| Khác | 1.085 | Bao gồm lỗi đánh máy: gmai.com (68), gmail.con (48), yahoo.com (63), icloud.com (55), qq.com (39) |
Phân bố Giới tính:
| Giới tính | Số lượng | Tỷ lệ |
|---|---|---|
| Nữ | 54.815 | 68,5% |
| Nam | 13.522 | 16,9% |
| Chưa đặt / Không rõ | 11.716 | 14,6% |
Phân bố Địa lý – 15 quê quán nhiều nhất (trong 50.042 có dữ liệu):
| Tỉnh/Thành phố | Số lượng | Tỷ lệ |
|---|---|---|
| Hà Nội | 17.775 | 35,5% |
| Nam Định | 2.839 | 5,7% |
| Thái Bình | 2.587 | 5,2% |
| Hà Tây | 2.567 | 5,1% |
| Hải Dương | 2.158 | 4,3% |
| Hải Phòng | 2.082 | 4,2% |
| Bắc Ninh | 1.588 | 3,2% |
| Bắc Giang | 1.501 | 3,0% |
| Vĩnh Phúc | 1.466 | 2,9% |
| Hà Nam | 1.289 | 2,6% |
| Hưng Yên | 1.216 | 2,4% |
| Phú Thọ | 1.185 | 2,4% |
| Thanh Hóa | 1.178 | 2,4% |
| Nghệ An | 972 | 1,9% |
| Ninh Bình | 941 | 1,9% |
10 Khoa lớn nhất:
| Khoa | Số lượng |
|---|---|
| Quản trị Kinh doanh & Du lịch | 5.579 |
| Tiếng Anh | 5.510 |
| Tiếng Trung | 4.422 |
| Trung tâm Đào tạo Từ xa | 3.887 |
| Công nghệ Thông tin | 3.315 |
| Tiếng Nhật | 2.829 |
| Tiếng Hàn | 2.705 |
| Tiếng Pháp | 2.102 |
| Tiếng Đức | 1.750 |
| Quốc tế học | 1.694 |
41.340 tài khoản có dữ liệu khoa, trải rộng trên 42 tên khoa.
10 Ngành lớn nhất:
| Ngành | Số lượng |
|---|---|
| Ngôn ngữ Anh | 9.298 |
| Ngôn ngữ Trung Quốc | 3.792 |
| Ngôn ngữ Nhật | 2.814 |
| Ngôn ngữ Hàn Quốc | 2.099 |
| Ngôn ngữ Đức | 1.741 |
| Quốc tế học | 1.642 |
| Ngôn ngữ Nga | 1.542 |
| Ngôn ngữ Pháp | 1.497 |
| Công nghệ Thông tin | 1.490 |
| Quản trị Kinh doanh | 1.397 |
41.226 tài khoản có dữ liệu ngành, trải rộng trên 56 chương trình.
Loại hình đào tạo:
| Loại hình | Số lượng |
|---|---|
| Chính quy | 32.076 |
| Đào tạo từ xa | 4.035 |
| Vừa làm vừa học | 1.576 |
| Văn bằng hai | 1.017 |
| Liên kết quốc tế | 402 |
| Song ngành | 366 |
Năm nhập học – Top 10:
| Năm | Số lượng |
|---|---|
| K2019 | 4.720 |
| K2020 | 4.526 |
| K2021 | 4.281 |
| K2018 | 3.941 |
| K2022 | 3.615 |
| K2024 | 3.023 |
| K2023 | 3.017 |
| K2025 | 2.918 |
| K2017 | 2.805 |
| K2015 | 2.031 |
39.768 tài khoản có dữ liệu năm nhập học, trải dài từ K2008 đến K2025.
Tình trạng Sinh viên:
| Tình trạng | Số lượng |
|---|---|
| Đang theo học | 19.325 |
| Đã tốt nghiệp | 10.226 |
| Đã thôi học | 1.460 |
| Bảo lưu | 284 |
| Khác | 5 |
Dân tộc – 7.845 tài khoản:
| Dân tộc | Số lượng |
|---|---|
| Kinh (đa số) | 7.363 |
| Tày | 196 |
| Mường | 100 |
| Nùng | 77 |
| Sán Dìu | 23 |
| Thái | 19 |
| Dao | 14 |
| Khác (11 nhóm) | 53 |
Quốc tịch – 5.655 tài khoản: 5.621 Việt Nam, cùng 34 công dân nước ngoài từ Trung Quốc, Nhật Bản, Indonesia, Hàn Quốc, Thái Lan, Philippines, Đài Loan, New Zealand, và các nước khác.
Tôn giáo – 5.476 tài khoản: 5.134 không, 215 Phật giáo, 104 Công giáo, 14 Tin lành, 8 khác, 1 Cơ đốc giáo.
Lưu ý: Mã nguồn tái tạo chi tiết, thông tin đăng nhập cán bộ, và dữ liệu chưa biên tập đã được giữ lại, không công bố. Toàn bộ chi tiết kỹ thuật đã được chia sẻ với các bên bị ảnh hưởng trong quá trình công bố có trách nhiệm.