
Có những lỗi bảo mật rất cơ bản, nhưng đôi khi lại đến từ những thứ mình ít nghi ngờ nhất. Điều này không chỉ đúng trong lập trình, mà đôi khi trong cuộc sống cũng vậy. Có những vấn đề nằm ngay trước mắt, nhưng vì quá quen thuộc nên chúng ta không nghĩ nó có thể là nguyên nhân.
Như một số bạn có thể đã biết qua trang About của mình, gần đây mình vừa tham gia một công ty mới. Nói là mới với mình, nhưng công ty thì đã hoạt động hơn 20 năm. Với một hệ thống tồn tại lâu như vậy, source code thay đổi qua nhiều giai đoạn, công nghệ cũng thay đổi theo thời gian, và chắc chắn sẽ có những phần từng hợp lý ở quá khứ nhưng không còn phù hợp hoặc an toàn ở hiện tại.
Tài khoản Twilio bị khóa
Một ngày, bọn mình nhận được thông báo rằng tài khoản Twilio bị khóa. Lý do là có kẻ xấu sử dụng key để spam. Việc đầu tiên cả team làm là tìm xem key bị lộ từ đâu.
Ban đầu, có vài khả năng được đặt ra. Có thể key đã bị hardcode trong source code. Có thể source code đã từng bị leak, vì hệ thống đã qua tay nhiều developer trong nhiều năm. Cũng có thể server quá cũ, phải hỗ trợ nhiều phiên bản PHP khác nhau, và đã bị hacker scan theo một cách nào đó.
Tất cả đều là những giả thuyết hợp lý.
Nhưng sau một thời gian kiểm tra, nguyên nhân thật sự lại đơn giản hơn rất nhiều.
File .env có thể truy cập từ internet
Mình phát hiện ra rằng file .env có thể được truy cập trực tiếp từ internet.
Điều đó có nghĩa là toàn bộ key, token, credential và thông tin nhạy cảm trong file này đều có khả năng đã bị lộ. Đây rất có thể là lý do khiến tài khoản Twilio của bọn mình bị khóa tới hai lần.
Ban đầu, cả team nghi ngờ source code bị lộ vì đã có nhiều developer từng tham gia dự án rồi rời đi. Nhưng hóa ra vấn đề không nằm ở việc ai đó có source code hay không. Vấn đề nằm ở việc một file cấu hình nhạy cảm lại đang public ra internet.
Điều đáng nói là mình không dùng tool bảo mật đặc biệt nào để tìm ra vấn đề này. Mình chỉ lần theo source code, xem các key được dùng ở đâu, kiểm tra cấu hình, rồi thử truy cập trực tiếp vào file .env.
Và kết quả là: xem được thật.
Cách mình xử lý
Việc đầu tiên mình làm là chuyển toàn bộ key đang bị hardcode trong source code ra file .env. Đây là bước cần thiết, vì credential không nên nằm trực tiếp trong source code.
Nhưng vấn đề là nếu .env vẫn có thể truy cập từ internet, thì việc chuyển key vào đó cũng chưa giải quyết được gì. Nó chỉ chuyển rủi ro từ chỗ này sang chỗ khác.
Vì hệ thống đang chạy trên LAMP stack, mình bắt đầu xử lý bằng cách cập nhật rule bảo mật cho Apache. Cách đơn giản nhất là thêm rule vào từng file .htaccess, nhưng server có rất nhiều khách hàng và mỗi khách hàng lại có thư mục riêng. Nếu cập nhật từng .htaccess, công việc sẽ rất mất thời gian và dễ bị thiếu sót.
Vì vậy, mình chọn cách xử lý ở cấp server bằng cách thêm cấu hình trực tiếp vào /etc/httpd/conf.d/.
Nội dung cấu hình như sau:
<FilesMatch "(^\.ht|^\.env|\.env$|\.env\.|^\.git|^\.svn|^\.DS_Store)">
Order allow,deny
Deny from all
</FilesMatch>
<FilesMatch "\.(bak|backup|swp|old|orig|save|sql|sql\.gz|tar|tar\.gz|zip|log|ini|conf|sh)$">
Order allow,deny
Deny from all
</FilesMatch>
Cấu hình này giúp chặn truy cập trực tiếp vào các file nhạy cảm như .env, .git, file backup, log, SQL dump, file cấu hình và một số loại file thường không nên public.
Sau đó, mình áp dụng thay đổi này cho hơn 10 server. Sau khi kiểm tra lại, các file nhạy cảm không còn truy cập trực tiếp được nữa và hệ thống tiếp tục hoạt động ổn định.
Bài học rút ra
Điều khiến mình suy nghĩ nhiều nhất trong sự việc này là lỗi không hề phức tạp. Nó không phải một kỹ thuật tấn công cao siêu. Nó không phải một lỗ hổng khó hiểu trong framework hay thư viện nào đó. Nó chỉ là một file .env bị public.
Nhưng hậu quả thì rất thật.
Một file cấu hình nhỏ có thể chứa toàn bộ credential quan trọng của hệ thống. Nếu nó bị lộ, hacker không cần phải hack sâu vào server. Họ chỉ cần đọc những gì đã được mở sẵn.
Đây cũng là lời nhắc nhở rằng với những hệ thống lâu năm, không thể chỉ nhìn vào code mới. Có những thứ tồn tại từ rất lâu, từng được xem là bình thường, nhưng ở thời điểm hiện tại lại trở thành rủi ro bảo mật nghiêm trọng.
Mình tin chắc hệ thống vẫn có thể còn những vấn đề bảo mật khác ở đâu đó. Với một codebase hơn 20 năm tuổi, điều đó không có gì lạ. Nhưng ít nhất, khi phát hiện được vấn đề nào, chúng ta nên xử lý triệt để vấn đề đó.
Gặp ở đâu, fix ở đó.
Và đôi khi, chính những lỗi cơ bản nhất lại là những lỗi nguy hiểm nhất.