Lỗ hổng bảo mật khủng khiếp của Lotte Cinema (Lưu trữ mật khẩu người dùng – Tưởng dễ mà không đơn giản)

Đă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.

preview

Đă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.

  1. Người dùng nhập tên tài khoản (email) và mật khẩu.
  2. So sánh tên tài khoản và mật khẩu với thông tin trong database.
  3. 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é.

security-breach-data-breach

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:

  1. Sử dụng hàm hash (hàm băm) để mã hóa mật khẩu của người dùng.
  2. Lưu trữ mật khẩu này dưới database.
  3. 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.
  4. 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:

  1. Khi tạo mật khẩu, tạo random một chuỗi kí tự gọi là salt.
  2. 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).
  3. Lưu salt và giá trị đã băm xuống database (Một người dùng sẽ có 1 salt riêng).
  4. 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.

unlock security lock on computer circuit board - computer security concept

Ố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ả -_-) .

n2

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é.

35 thoughts on “Lỗ hổng bảo mật khủng khiếp của Lotte Cinema (Lưu trữ mật khẩu người dùng – Tưởng dễ mà không đơn giản)”

  1. Theo mình thì phán đoán “bọn lotte lưu thẳng mật khẩu của mình thẳng dưới database” hơi chủ quan vì có khả năng nó mã hóa 2 chiều xử lí mã hóa và giải mã ngay trên server và lưu encryted data dưới database, mỗi lần cần show cho bạn thì nó gọi hàm giải mã trên server.
    (thật ra đây cũng chỉ là 1 hướng chống chế, có khả năng nó lưu y nguyên dưới db thiệt =)) )

    Liked by 1 person

      1. à ý mình nói là phán đoán cho rằng “hệ thống của lotte lưu thẩng mật khẩu” là không chính xác lắm. hì hì
        Còn nếu hack dc server thì k cần bàn tới mô hình hay kiến trúc của cái feature sign up này nữa ồi.

        Like

      2. Với kiến truc của cái feature sign up này cho dù hack được cả server lẫn database thì mật khẩu của người dùng vẫn không lộ nhé bạn 🙂

        Like

    1. nếu vậy thì cũng nguy hiểm chứ, xui xẻo thế nào gặp admin lém lỉnh tinh nghịch thì sao

      Like

    2. Mã hóa 2 chiều cũng ko an toàn nha bạn, bởi vì kiểu mã hóa này có thể giải mã đc, các hacker khi tấn công đc vào database, lấy đc mật khẩu của khách hàng dưới dạng mã hóa 2 chiều, sau đó dùng hàm giải mã nó, thế là đi tong tài khoản của khách 🙂 cách duy nhất và an toàn nhất là sử dụng mã hóa 1 chiều có kèm theo đuôi là 1 chuỗi kí tự random, khi khách hàng ấn vào quên mật khẩu, bên server sẽ gửi về gmail của khách hàng (mục đích để xác nhận danh tính), khi khách hàng click vào đường link đó thì ms có quyền reset mật khẩu, đó là cách an toàn nhất và cũng có nhiều trang web sử dụng

      Like

  2. có lẽ vậy nên lotte cinema k dám cho phép thanh toán đặt vé bằng thẻ tín dụng :v

    Like

    1. Với cách 2 thì chạy dictionary 1 lần nó có thể lấy được toàn bộ password trong db. Thêm salt vào thì với mỗi password nó phải chạy dictionary lại từ đầu nhe bạn :D.

      Liked by 1 person

      1. theo mình nghỉ thì người ta cho thêm salt vào để đảm bảo tính duy nhất, dù cho 2 hay nhiều passwords giống nhau thì vẩn không bị trùng mã hash.
        còn khi hacker đã có được db rồi thì dùng dictionary sẽ có được (password + salt) mà salt đã có rồi thì chỉ cần trừ chuổi là có password thôi.

        Like

      2. chưa kể là việc add salt vào như thế nào còn tùy thuộc vào dev nữa. nếu như dev để cho salt được hash 1 lần rồi sau đó mới add vào pw string theo 1 quy luật nào đó (append ngay trước ký tự cuối cùng chẳng hạn) thì cũng ggwp cho đội hacker.

        Quan trọng là dev có đủ trình độ và có mong muốn bảo mật cho người dùng hay không thôi :))

        Like

      3. Anh ơi cho em hỏi là nếu vậy thì salt phải lưu ở một bảng khác chứ nếu chung table thì sau khi lấy dc salt thì chuyện hacker dò dc password là chuyện sớm muộn thôi đúng ạ. Em không hiểu lắm ạ

        Liked by 2 people

  3. Theo mình đoán thì có thể bên Lotte người ta sử dụng mã hóa theo RSA, họ sử dụng key public và private để mã hóa mật khẩu. Khi giải mã thì sử dụng lại các key trên để giải mã. Có thể họ sử dụng 1 cặp key cho tất cả tài khoảng, hoặc mỗi tài khoản 1 cặp key riêng, rồi lưu vào config hệ thống, hoặc lưu vào bảng nào đó của database. Nếu hacker có tất công vào database thì vấn đề tìm được cặp key mã hóa lại cũng khó à nha.

    Liked by 1 person

  4. vâng, và cái project mềnh vừa làm thì ko những lưu thẳng mật khẩu mà còn lưu tất tần tật info của master card, visa. Lúc nhận project xong báo cho KH thì lão ấy mới tá hỏa :))

    Liked by 1 person

    1. Việc đó lâu hơn nhiều bạn nhé ;).
      Nếu không có salt, 2 users có cùng password sẽ có cùng giá trị hash. Hacker chỉ cần brute-force 1 lần để tạo ra Rainbow Table là có thể lấy được password của toàn bộ user.
      Nếu có salt thì 2 user có mật khẩu giống nhau vẫn sẽ có giá trị hash khác nhau. Hacker phải chạy brute-force để tra mật khẩu của TỪNG USER nên bảo mật hơn nhiều.

      Like

      1. Brute-force là gì vậy bạn, salt được gieo random rồi mà vẫn tra lại được mật khẩu ah? Mình ko phải dân chuyên, đọc bài bạn thấy hay hay nên muốn hỏi cho biết

        Like

  5. A Hoàng cho e hỏi mình có thể băm 3 lần để bảo mật cao hơn được ko. Ví dụ….
    Khi Register, password nhập từ Textbox vào được băm lần 1(gọi là P1), rồi ta tạo random Salt băm ra lần 2(được S1). Rồi ta đem P1+S1 để băm tiếp lần 3 và lưu vào CSDL(được PS1, kiểu như thịt heo bằm + thịt bò bằm, trộn với nhau rồi bằm 2 thứ này).
    Như vậy khi Login ta nhập pass từ textbox đem băm lần 1, rồi lấy Salt(S1) đã lưu theo Username, rồi đem 2 cái này băm chung với nhau, đem so sánh với PS1 trong CSDL.. Băm nhiều như vậy(băm 3 lần) tính ra độ bảo mật có cao hơn không a.
    Mong ad và mọi người cho ý kiến

    Like

  6. Cho t hỏi hacker nó mò ntn để ra đc hàm hash của mình để rồi lấy hàm ấy brute force các mật khẩu trong dictionary thế bạn?

    Like

    1. Hash mật khẩu thì quanh đi quẩn lại chỉ có một số thuật toán cơ bản thôi bạn :D.
      Chưa kể nếu website viết bằng framework open source (RoR, ASP.NET MVC, Laravel,…) thì hacker chỉ cần xem source để biết nó dùng thuật toán gì để hack thôi :D.

      Like

  7. Nếu hacker hack được database thì hắn vừa có salt vừa có chuỗi kí tự đã hash
    Hắn vẫn có thể dùng dictionary rồi cộng thêm salt là được mà

    Like

  8. Theo tôi nghĩ “bọn lotte lưu thẳng mật khẩu của mình thẳng dưới database” thì không hẳn là như vậy.
    Đơn giản là họ gửi password chưa mã hóa. Còn cái password được lưu dưới database thì đã được mã hóa rồi.
    Chỉ sợ là nội dung email gửi sai địa chỉ hoặc ai đó bắt được thì bị lộ password.

    Like

    1. Thế cái mật khẩu chưa mã hóa nó ko lưu vào db thì lấy gì gửi lại cho user hả ông trên? Sinh 1 cột lưu password chưa mã hóa và 1 cột đã mã hóa à? ??:D??

      Like

  9. bây giờ có thêm xác thực 2 bước bằng SDT nữa nên bảo mật đã tăng lên gấp bội 😀

    Like

Leave a comment