Lỗ hổng bảo mật khủng khiếp của Lotte Cinema
Đăng nhập là một chức năng đơn giản nhất mà hơn 90% các ứng dụng web cần phải có. Tuy nhiên, đôi khi ta lại không được hướng dẫn cách thực hiện chức năng "Đăng nhập" một cách đúng đắn, bài bản, dẫn đến những lỗi dở khóc dở cười, hoặc những lỗ hổng bảo mật khủng khiếp . Đến cả Lotte Cinema, một ...
Đăng nhập là một chức năng đơn giản nhất mà hơn 90% các ứng dụng web cần phải có. Tuy nhiên, đôi khi ta lại không được hướng dẫn cách thực hiện chức năng "Đăng nhập" một cách đúng đắn, bài bản, dẫn đến những lỗi dở khóc dở cười, hoặc những lỗ hổng bảo mật khủng khiếp. Đến cả Lotte Cinema, một trang web được khá nhiều người dùng còn mắc lỗi sơ đẳng này.
Đăng nhập hả? Chỉ cần một bảng User, hai cột Username và Password là xong
Kể cũng buồn cười. Ngày xưa khi đi học, mình được hướng dẫn cách làm chức năng đăng nhập như thế này.
- Người dùng nhập tên tài khoản (email) và mật khẩu.
- So sánh tên tài khoản và mật khẩu với thông tin trong database.
- Nếu đúng, cho người dùng đăng nhập, lưu thông tin vào session hoặc cookies.
Bước 1 và 3 không có gì đáng bàn, nhưng bước 2 mới là điều đáng nói. Đa phần tụi mình đều lưu trực tiếp tên tài khoản và mật khẩu vào database, sau đó đem ra so sánh.
public void Register(string username, string password) { Database.SaveUser(username, password); } public bool Login(string username, string password) { string pw = Database.GetPasswordByUsername(username); return pw == password; }
Đây là cách củ chuối nhất và ngu nhất. Database là một trong những nơi hay bị tấn công, dễ làm thất thoát dữ liệu. Trong quá khứ, lỗi SQL Injection từng làm thất thoát hàng triệu thông tin khách hàng và thông tin credit card. Chưa tính đến chuyện hacker bên ngoài, nhiều khi thằng Database Admin hứng lên, nó có thể mò được mật khẩu của khách hàng, lớn chuyện chưa?
Cách lưu trữ mật khẩu đúng phải là làm sao để chỉ người dùng mới biết được mật khẩu của họ. Làm sao ư? Hãy xem phần dưới nhé.
Vậy mã hóa là được chứ gì, lắm trò!!
Ừ, cách giải quyết cũng khá đơn giản. Bạn có thể dùng hàm hash để mã hóa mật khẩu như sau:
- Sử dụng hàm hash (hàm băm) để mã hóa mật khẩu của người dùng.
- Lưu trữ mật khẩu này dưới database.
- Khi người dùng đăng nhập, hash mật khẩu đã nhập, so sánh với mật khẩu đã lưu dưới database.
- Hàm hash này phải là hàm hash một chiều, không thể dựa theo mật khẩu đã hash để suy ngược ra đầu vào.
public void Register(string username, string password) { string hashedPassword = HashHelper.Hash(password); Database.SaveUser(username, hashedPassword); } public bool Login(string username, string password) { string pw = Database.GetHashedPasswordByUsername(username); return pw == HashHelper.Hash(password); }
Cách này đảm bảo chỉ người dùng biết mật khẩu của họ, dù là lập trình viên hay database admin, có nắm được cả code lẫn database cũng không tài nào mò ra mật khẩu. Tuy nhiên, cách này có một vấn đề: Hai mật khẩu giống nhau khi hash sẽ có kết quả giống nhau. Hacker có thể mò ra mật khẩu bằng cách dùng dictionary attack – hash toàn bộ các mật khẩu có thể trong từ điển, rồi so sánh kết quả với mật khẩu đã hash dưới database.
Thế nhưng, vỏ quýt dày có móng tay nhọn. Đây là cách lưu trữ mật khẩu đúng mà hiện nay các framework đều áp dụng:
- Khi tạo mật khẩu, tạo random một chuỗi kí tự gọi là salt.
- Salt sẽ được cộng vào sau mật khẩu, toàn bộ chuỗi mật khẩu và salt sẽ bị băm (hash).
- Lưu salt và giá trị đã băm xuống database (Một người dùng sẽ có 1 salt riêng).
- Khi người dùng đăng nhập, lấy salt của người dùng, cộng nó với mật khẩu họ nhập vào, hash ra rồi so với giá trị trong database.
public void Register(string username, string password) { string salt = SaltHelper.getRandomSalt(); string hashedPassword = HashHelper.Hash(password + salt); Database.SaveUser(username, hashedPassword, salt); } public bool Login(string username, string password) { string salt = Database.getSaltByUsername(username); string pw = Database.getHashedPasswordFromUsername(username); return pw == hash(password + salt); }
Với cách này, khi người dùng quên mật khẩu, hệ thống không tài nào mò ra mật khẩu để gửi cho họ. Cách giải quyết duy nhất là reset mật khẩu, random ra một mật khẩu mới rồi gửi cho người dùng.
Ối giời phức tạp thế, cùng lắm thì lộ password trên trang của mình thôi mà
Nói nhỏ một bí mật (mà chắc ai cũng biết) cho các bạn nghe nè: Hầu như người dùng chỉ sử dụng 1 username/mật khẩu duy nhất cho toàn bộ các tài khoản trên mạng. Nếu hacker tìm được mật khẩu từ trang của bạn, chúng sẽ thử với các account facebook, gmail, tài khoản ngân hàng, … của người đó. Mất 1 account là xem như mất sạch sành sanh. Kinh khủng chưa!. Không tin à, bạn thử ngẩm lại xem, bạn có dùng chung 1 email/mật khẩu cho Gmail, Facebook, Evernote, … và nhiều trang khác không?
Nói đi nói lại một hồi, cũng đến cái "Lỗ hổng bảo mật khủng khiếp của Lotte Cinema"
Một ngày đẹp trời nọ, mình định dẫn gấu đi xem phim, ăn uống rồi beep. Định đặt vé online mà quên mất mật khẩu lottecinema.com, mình mò mẫm phần đăng nhập, tìm hoài mới thấy mục "Quên mật khẩu". Nhập địa chỉ mail và chứng minh nhân dân, mình mau chóng nhận được một email gửi từ lottecinema, trong đó có cả username và mật khẩu của mình (Mail gì đã cụt lủn lại còn sai chính tả -_-) .
Thật là tiện quá đi mất, khỏi phải reset mật khẩu. Khoan, có cái gì sai sai ở đây!! Vậy là bọn lotte lưu thẳng mật khẩu của mình thẳng dưới database à. Lỡ database bị thất thoát dữ liệu là toàn bộ các tài khoản khác của mình (Và các thành viên lotte cinema khác) cũng đi tong theo. _Thật là đáng sợ!! _Lỗi này mình phát hiện năm ngoái, đến cách đây mấy ngày vẫn còn y nguyên. Thế mới biết bộ phận IT của lottecinema giỏi giang thế nào. Các bạn có tài khoản lotte cinema thì nhớ cẩn thận nghe.
Tác giả không phải dân chuyên về bảo mật, hệ thống mạng nên có gì sai sót mong các bỏ qua và góp ý hộ nhé.
Bản gốc: Blog Tôi đi code dạo.