At 01:06, 08/04/2026
Vài ngày trước, bạn của tôi (không tiện nhắc tên) đã chia sẻ với tôi về việc cậu ấy đang nhờ một người thầy dạy thêm cho cậu ấy để thi vào trường chuyên. Tuy nhiên, thứ tôi quan tâm nhất về việc này là thầy giáo ấy có hẳn một trang cho học sinh làm bài và gửi lên đó để máy chấm. Và mọi chuyện sẽ rất bình thường cho tới khi sự tò mò của tôi trỗi dậy.

Tôi đã sử sụng công cụ Dirsearch (Một công cụ dùng để scan path của 1 trang web theo 1 list từ có sẵn). Lúc đó tôi vẫn nghĩ chắc web này ko có gì để mò đâu cho tới khi...

Dirsearch tìm ra được thư mục .svn =))
Thư mục .svn là một thư mục ẩn được tạo ra và sử dụng bởi Subversion (SVN) — một hệ thống quản lý phiên bản (Version Control System) tương tự như Git (sử dụng thư mục .git). (by Gemini)
Sau khi tìm hiểu về folder .svn, tôi biết được có một công cụ có thể dump được tất cả file trong .svn từ server về máy và tên file tự động được khôi phục (đó là svn-ripper).
Sau khi chạy svn-ripper, tôi đã dump được tất cả các file (lưu ý: chỉ trong giai đoạn development).

Đây là những file có trong .svn mà svn-ripper đã dump được

Sau khi nghiên cứu source code của project, tôi đã tìm ra vấn đề Trong file download.php:

ta thấy có dòng if ((strpos($file,"[".$user['username']."]") > 0)&&($publish == 1))
Ta có thể hiểu: khi user gửi 1 request download tới server thì server sẽ check xem file yêu cầu có thực sự là file của user hay không.
Tuy nhiên, nếu ta để ý kĩ, thì code này đang dính phải 1 lỗ hỏng kinh điển: Path Traversal
Path Traversal (hay còn gọi là Directory Traversal) là một lỗ hổng bảo mật cho phép kẻ tấn công truy cập trái phép vào các tệp tin và thư mục nằm ngoài thư mục gốc của ứng dụng web.
Theo đó, thay vì ta gửi yêu cầu http://x.x.x.x/download.php?file=[{username}].blahblah thì ta có thể chèn thêm các kí tự ../ để có thể ra một folder khác và download file tùy ý.
Tuy nhiên, có một rắc rối ở đây: filename yêu cầu cần có [{username}] thì mới tính là hợp lệ, mà file ta cần download thì làm gì có [{username}] trong tên? vậy ta phải làm gì?
Sau khi suy nghĩ một hồi (hỏi AI thì nó del trả lời :>) thì tôi đã nghĩ ra một giải pháp: Sao ta ko biến [{username}] thành tên một thư mục, sau đó trỏ ra rồi tìm file mình cần?
Tôi đã thử gửi 1 http request http://x.x.x.x/download.php?file=./[{username}]/../../../../data/account.xml (file chứa thông tin đăng nhập), đoán xem?

Tada, ta đã lấy được tất cả thông tin đăng nhập của trang web.
Chưa hết đâu, nếu vậy thì còn đơn giản quá, phần thú vị vẫn còn=)
Sau khi lấy được thông tin đăng nhập rồi thì tôi chợt nghĩ: liệu rằng web này có rce hay không??
RCE (Remote Code Execution) là một trong những lỗ hổng bảo mật nguy hiểm nhất trong thế giới web và hệ thống. Nó cho phép kẻ tấn công thực thi các lệnh hệ điều hành (OS commands) tùy ý trên máy chủ từ xa mà không cần quyền truy cập vật lý hay tài khoản hợp lệ.
Vì vậy, tôi đã đọc lại source code kĩ hơn. Và, tôi đã tìm ra được hướng để khai thác.
Đây là code của file upload.php (file dùng để upload bài làm để cho phần mềm chấm)
<?php
include("init.php");
include("config.php");
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<?php
if (((date_timestamp_get($startTime) + $duringTime*60 - time() > 0)) || ($duringTime == 0)) {
$temp = explode(".", $_FILES["file"]["name"]);
$extension = end($temp);
if (!$_FILES["file"]["name"]) {
$message = "LỖI: Chưa chọn file.";
} else if ($_FILES["file"]["size"] > 10*1024*1024) {
$message = "LỖI: File có dung lượng quá lớn.";
} else if ($_FILES["file"]["error"] > 0) {
$message = "LỖI: Không rõ.";
} else {
$dir = $uploadDir;
move_uploaded_file($_FILES["file"]["tmp_name"], $dir . "/" . $user['id'] . "[" . $user['username'] . "][" . $temp[0] . "]." . $extension);
$message = "Nộp bài thành công";
}
?>
<script>
alert("<?php echo $message; ?>");
window.history.back();
</script>
<?php
} else {
?>
<script>
alert("Đã hết thời gian nộp bài! \n Nộp bài không thành công!");
window.history.back();
</script>
<?php
}
?>
</body>
</html>
- Sau khi user gọi post http request vào upload.php, thì server sẽ kiểm tra xem bạn có up file với tên đầy đủ không, nêu không thì alert ra lỗi, nếu đầy đủ thì sẽ up file đó vào $dir (ở đây là "contests/nop_bai").
Đây rồi, thứ mà tôi nãy giờ đang cần. Ta nhận thấy rằng code không hề check xem ta có nộp .pas hay .cpp không mà mặc cho chúng ta up gì tùy ý. Vậy nên tôi đã dùng Burp để gửi 1 request giả mạo việc gửi file lên server.

Và, server chấp nhận nó thật, tuyệt=)
Sau đó, tôi đi theo đường dẫn /contest/nop_bai/{user_id}[{username}][{filename}].php, server trả về Hello

Tuyệt vời. Server thật sự tồn tại RCE. Vậy nên tôi đã viết nhanh 1 file php với mục đích execute command qua url paramether và up lên server bằng curl rồi ghi đè vô file upload2.php (file này không được sử dụng bởi bất kì file php khác) mà không làm nghi ngờ.
Và đây là những gì tôi đã làm được: 1 page execute command đơn giản

Kết Luận
- Trang web đang tồn tại 2 lỗ hỏng cực kì nghiêm trọng: RCE và Path Traversal.
- Source code từ những năm 2014. Lời khuyên: hãy cập nhật thường xuyên những dòng code của bạn, vì hôm nay nó an toàn thì ngày mai chắc gì còn an toàn!
- Hiện tại thì chủ trang web vẫn chưa phản hồi lại sự liên lạc của tôi. Nếu có đọc được bài này thì xin hãy liên lạc với tôi.
- Dùng PHP nếu bạn đủ giỏi để fix những bug ối dồi ôi (ý kiến riêng, xin đừng ném đá).
Tóm lại, đây chỉ là nội dung mang tính cảnh báo và học tập, không áp dụng đối với server không phải là của mình, tôi xin từ chối mọi trách nhiệm đối với những gì ảnh hưởng đến người khác bởi bài viết này, xin không làm theo!
Hẹn gặp lại, C'ya!