Tôi đã qua mặt hệ thống chấm điểm của Violympic như thế nào?

- Chắc hẳn với nhiều người trong chúng ta, nhất là với các thế hệ học sinh, sinh viên thì Violympic đã không còn là một cái tên quá xa lạ. Với mình thì Violympic là một cuộc thi vừa “dễ” lại vừa thú vị. Nó dễ ở chỗ mọi thứ trong bài thi đều là trắc nghiệm, không quan trọng cách bạn làm, chỉ quan tâm đến kết quả, còn thú vị ở chỗ bạn có thể tham dự kì thi ở bất cứ nơi đâu chỉ với một cái máy tính kêt nối internet. Hơn nữa, cuộc thi còn được nhiều đơn vị lớn tham dự tổ chức.

image

Mới đây, khi tìm hiểu thuật toán của Violympic, mình đã tìm ra được kẽ hở cho phép gian lận điểm bài thi, và nhân đây khi Violympic đã sửa đổi và nâng cấp thuật toán, mình xin được chia sẻ lại câu chuyện này.

image

Lý do mình chọn Violympic?

- Mình chọn Violympic để nghiên cứu là vì một hôm tình cờ đọc được bình luận trên một bài viết nói về việc Violympic sử dụng mã hóa trên trang bài thi khiến cho thí sinh không thể gian lận bằng cách đọc mã nguồn trang thi để lấy đáp án như trước nữa. Điều đó khiến mình tò mò về thuật toán mã hóa đang được Violympic sử dụng.

Đầu tiên, mời các bạn xem video PoC của mình:

- 2 đoạn script khai thác như trong video PoC mình để ở cuối bài viết, vì Violympic đã thay đổi thuật toán chấm điểm nên cách này không còn áp dụng được nữa, chỉ sử dụng cho mục đích nghiên cứu thôi.

Sau đây là cách mình thực hiện nghiên cứu:

- Theo thói quen khi thực hiện nghiên cứu các ứng dụng web, mình bắt các gói tin bằng Fiddler trong khi làm bài thi để xem Violympic trao đổi những gì với máy chủ.

5 endpoints chính mà mình tìm được:

image
  • - /Play*, /Exam*: 2 endpoints này dùng để tải trang bài thi.
  • - /GetXmlData*: Lấy nội dung bài thi.
  • - /Timer*: Tính giờ
  • - /SendExamScore*: Gửi kết quả bài thi

(*.aspx)

- Nói sơ qua thì mình thấy cách chấm bài của hệ thống khá ngược, vì thường kết quả bài làm phải được chấm trên server, nhưng ở đây đáp án bài làm lại được gửi về cho client tự chấm, sau đó client mã hóa điểm và gửi lên cho server lưu lại. Điều này nghĩa là nếu client tìm được hàm mã hóa thì có thể tùy ý chọn điểm.

- Và vì thế nên mình đi sâu vào việc nghiên cứu dữ liệu đã mã hóa được gửi đến endpoint /SendExamScore* (nó chính là tham số encrypt ở trên bức ảnh kia).

- Đây là giá trị đã được mã hóa của tham số encrypt:

939df49b94c58e4b20b5d0a3a47ba40aea33a80058e9146dc2a81de695238e57191340dbbf9129136e2e7465a2898e398af2a6920cb1bfde9e06895dec080759cc17fd93d130e5c980902fb91c14811a9712c5f6c45cb1fc0c9d9d4fc0115cf1d72243329d1b55e572b7e49f00c9ad09d0a1b730e9898fec027c8775963c55a5d011fff43384ac7e2d96d2e8465b0da0e744e2fb767c104b219450e8d717f3043cfaa410f11ace998217a37943525994a2c38a36d3f7b310300878265462739a

- Ban đầu mình nghĩ rằng có thể tham số này được mã hóa bằng Javascript nên đã thử tìm kiếm trong các file Javascript trên trang.

- Nhưng mình không tìm được dấu tích nào của hàm mã hóa này, thậm chí cả cụm từ encrypt cũng không có luôn @@.

image

- Sau một hồi tìm nhưng không ra, mình quyết định dịch ngược file Flash được nhúng trong bài thi.

- Dấu hiệu đầu tiên cho biết mình đang đi đúng hướng đó chính là cái endpoint gửi bài có chứa tham số encrypt.

image

- Tiếp theo là tìm được hàm mã hóa

public function hmweb_Encrypt(_arg1:String, _arg2:String):String{

   var _local3 = “ECB”;

   var _local4:Rijndael = new Rijndael(192, 128);

   return (_local4.encrypt(_arg2, _arg1, _local3));

}

Từ đây mình rút ra được phương thức mã hóa là AES, mode ECB, và để giải mã thì cần phải có key mã hóa nữa.

- Key mã hóa này được biến đổi từ một tham số lưu trên trang dưới cái tên token

image

- Từ token biến thành key mã hóa thì nó còn phải trải qua 1 loạt bước replace kí tự nữa, để cho khỏi dài dòng thì tất cả các đoạn code được sử dụng mình sẽ để ở cuối bài.

- Và đây là key mã hóa cuối cùng:

ejjvfxzwsjfftjfd72duytrf

- Dịch ngược lại được kết quả:

score=100&time=80&hash=552c086d708dcc5062b364caa27b4fe7&which_round=2&block=11&key=&examscore=100&doneexam=2&which_exam=2&strpoint=YELVE&userid=47988663&point=20&subpoint=0&realtime=85&

image

- Bạn có thể tự kiểm chứng bằng cách test trên trang web: https://www.tools4noobs.com/online_tools/decrypt/

- Phần lớn dữ liệu sau khi dịch ra đã có thể hiểu được, trừ tham số hash, qua việc thử nghiệm mình thấy rằng hash sai thì kết quả bài thi không được công nhận. Vì vậy mình phải tìm thêm cách tạo hash

image

- hash được tạo ra bằng cách nối chuỗi 5 giá trị gồm TotalPoint (tổng điểm) + totalUsedTime (tổng thời gian) + round (vòng thi) + key (key này khác key mã hóa) + “DrAgOnItE” (làm rối), sau khi nối chuỗi thì mã hóa lại bằng md5.

- Vậy là những thứ cần thiết đã có đủ, mình đã tổng hợp chúng lại thành 2 đoạn script khai thác và sử dụng như video PoC ở trên.

Sau đây là 2 đoạn script khai thác:

- Chrome snippet: https://gist.github.com/t-rekttt/0ba25221d5ef6dfcd5d462a26547eb76

- Encrypt.py: https://gist.github.com/t-rekttt/f8f38652a1ecc946d57f6b5f0b70a10c