Lỗ hổng chuyển tiền hay là suy nghĩ như một hacker

Nhắc lại: trong một hội thảo gần đây, tôi có nói đầu năm 2017 tôi tìm được một lỗ hổng cho phép trộm tiền từ bất kỳ tài khoản nào của 4-5 ngân hàng ở Việt Nam.

  Cơ quan tình báo Nga bị hacker xâm nhập và lấy cắp mất 75.000GB dữ liệu
  25 mật khẩu tệ nhất 2018, đừng sử dụng nếu không muốn bị hacker tấn công

Hình ở trên là một slide trong bài nói chuyện của tôi. Đây là một buổi giảng dạy về an ninh mạng, nên tôi dùng form này để hướng dẫn cho khách tham dự cách suy nghĩ như một hacker. Tôi thấy đây là một cách tiếp cận hiệu quả, mà không cần phải đi sâu vào các chi tiết kỹ thuật, bởi vì không phải ai nhìn vào ô số tiền cũng có thể nghĩ ngay đến “àh, tôi sẽ nhập vào số âm”.

Lỗ hổng mà tôi phát hiện là có thật, nó không nằm ở chỗ số tiền chuyển khoản âm, mà là ở chỗ hoán đổi tài khoản của người nhận và người gửi. Một số bạn đọc thắc mắc, nghi ngờ về lổ hổng này. Tôi thấy nghi ngờ là đúng, chính tôi cũng không tin vào mắt mình khi tìm thấy lỗ hổng này.

Tôi không thể tiết lộ ngân hàng nào hoặc sản phẩm nào, vì lý do hiển nhiên, nhưng tôi có thể giải thích thêm về chi tiết kỹ thuật lỗ hổng khá thú vị này. Thiền sư gamma95 từng nói thật khó để thuyết phục người khác mía đường có vị ngọt, nếu họ chưa từng nếm thử bao giờ. Do đó, tin hay không thì tùy duyên vậy ;-).

Một giải pháp Mobile Banking thường bao gồm mobile app và một máy chủ API cung cấp các chức năng như truy vấn tài khoản hay chuyển tiền.

Câu hỏi: nếu được giao nhiệm vụ thiết kế API chuyển tiền trên máy chủ, API của bạn sẽ như thế nào?

Câu trả lời không khó, nhưng nếu chưa làm bao giờ thì cũng có nhiều thứ phải suy nghĩ. Trước khi xem tiếp, bạn hãy thử viết ra câu trả lời của riêng bạn.

Để chuyển tiền, máy chủ cần ít nhất 3 thông số:

1/ Tài khoản nhận

2/ Tài khoản gửi

3/ Số tiền

Tài khoản nhận và số tiền sẽ do người dùng nhập vào mobile app, rồi mobile app chuyển lên máy chủ. Nhưng tài khoản gửi thì lấy ở đâu ra?

Nếu cho phép người dùng chọn, lỡ họ nhập vào tài khoản của người khác thì sao? Tức là máy chủ phải kiểm tra người dùng có quyền chuyển tiền ra khỏi tài khoản đó hay không (authorization). Muốn như vậy thì trước tiên máy chủ phải xác thực người dùng (authentication).

Trên web, sau khi khách hàng đăng nhập, máy chủ sẽ trả về một HTTP cookie, browser ghi nhớ và sử dụng cookie để xác thực các yêu cầu tiếp theo. Thường các công nghệ làm web phổ biến (như .NET, Spring, PHP, hay Ruby On Rails) đã làm sẵn phần này, lập trình viên không cần phải làm gì thêm.

Vấn đề là trên mobile không có sẵn công nghệ nào như HTTP cookie, nên các lập trình viên thường phải tự chế ra cách xác thực. Có người sử dụng các giải pháp như OAuth hay JWT. Trong trường hợp này, sản phẩm dùng một giải pháp xác thực tự chế.

Trước khi nói về giải pháp đó, sẵn đang ở đây, tôi muốn nói thêm một chút về các giải pháp như cookie, OAuth, hay JWT. Tựu trung lại thì các giải pháp này đều giống nhau ở chỗ sau khi khách hàng đăng nhập, máy chủ sẽ cấp phát cho mobile app một token đại diện cho khách hàng. Mobile app sẽ đính kèm token khi gửi yêu cầu đến máy chủ. Máy chủ kiểm tra token như thế nào là mấu chốt của vấn đề.

Thí dụ thế này cho dễ hiểu nhé. Mỗi lần bạn gửi xe máy, nhà xe cho bạn một cái phiếu. Cái phiếu này có vai trò giống như token mà tôi nói ở trên. Muốn lấy lại xe, bạn phải đưa lại cái phiếu và nhà xe sẽ phải kiểm tra như sau:

1/ Cái phiếu còn hợp lệ. Như thế nào là hợp lệ thì tùy chỗ, thường thì họ sẽ dựa vào ngày tháng năm, con dấu, chữ ký ghi trên phiếu. Ví dụ như nếu nơi đó không gửi xe qua đêm mà bạn đưa phiếu ngày hôm qua thì tức là phiếu có vấn đề.

2/ Cái phiếu được phát cho chiếc xe mà bạn đang muốn lấy ra. Chỗ này quan trọng nha, vì nếu không, tôi có thể gửi vô một chiếc xe đạp, dùng phiếu của chiếc xe đạp để trộm một chiếc xe máy xịn. Để bịt lỗ hổng này, nhà xe thường ghi số phiếu lên yên xe. Khi bạn dắt xe ra, họ sẽ kiểm tra xem số trên phiếu và số trên yên xe có giống nhau không. Ai suy nghĩ như một hacker sẽ thấy ngay lỗ hổng: lỡ ai xóa số trên yên xe, ghi lại số khác thì sao? Giải pháp thường gặp là ghi số xe vào phiếu giữ xe. Khi bạn dắt xe ra, nhà xe sẽ kiểm tra số xe trên phiếu có trùng với số xe trên biển số hay không.

Quay trở lại câu chuyện thiết kế API chuyển tiền. Ngoài 3 thông số tài khoản người nhận, tài khoản người gửi và số tiền, mobile app cần phải đính kèm token của khách hàng. Máy chủ sẽ phải kiểm tra như sau:

1/ Token hợp lệ. Tương tự như phiếu gửi xe, token hợp lệ sẽ có, ví dụ như, chữ ký điện tử hợp lệ, còn hạn sử dụng, chưa bị thu hồi (revoked), v.v. Nhiều nơi sử dụng các giải pháp stateless như OAuth hay JWT, nhưng các giải pháp này có nhiều vấn đề. Vấn đề gì thì tôi muốn để dành làm bài tập về nhà cho bạn đọc.

2/ Token là của một người dùng và người đó được phép chuyển tiền ra khỏi tài khoản gửi. Trường hợp mỗi khách hàng chỉ có một tài khoản, trên token có thể ghi luôn số tài khoản đó (giống như trên phiếu gửi xe có ghi số xe). Trường hợp mỗi khách hàng có thể có nhiều tài khoản, trên token có thể ghi một user ID (là một số điện thoại chẳng hạn). Từ user ID này có thể lấy ra (từ core banking chẳng hạn) danh sách tài khoản mà người dùng có thể chuyển tiền ra. Máy chủ sẽ phải kiểm tra để đảm bảo số tài khoản gửi nằm trong danh sách này.

Tôi phát hiện máy chủ đã không thực hiện bước thứ 2. Nói nôm na, tôi sử dụng một token hợp lệ của chính tôi để vượt qua bước kiểm tra thứ 1, nhưng tôi đổi số tài khoản gửi bằng số tài khoản của nạn nhân mà tôi muốn trộm tiền. Trước khi chuyển tiền, máy chủ có yêu cầu phải xác thực OTP qua SMS. Nhưng, thay vì gửi OTP đến số điện thoại của tài khoản gửi, máy chủ lại gửi OTP đến số điện thoại nằm trong token, tức là số điện thoại của tôi.

Tôi viết là “hiểu nôm na” vì sản phẩm này không xác thực bằng token, mà dùng một giải pháp “nhà trồng được” khá rối rắm, nhưng có thể tóm tắt thế này:

1/ Giải pháp sử dụng chữ ký điện tử để xác thực khách hàng.

2/ Máy chủ tạo ra một bộ khóa RSA cho mỗi khách hàng.

3/ Mobile app dùng khóa riêng (private key) của khách hàng để ký tất cả các yêu cầu gửi đến máy chủ, kể cả các yêu cầu xác thực.

Câu hỏi hiển nhiên là: làm sao mobile app có được khóa riêng của khách hàng? Vì bộ khóa được tạo trên máy chủ (đây là một lỗ hổng khác), nên mobile app sẽ phải tải khóa riêng từ máy chủ. Ai có suy nghĩ như một hacker sẽ thấy ngay một vấn đề con gà và quả trứng ở đây: để xác thực thì cần phải có khóa riêng, nhưng để có khóa riêng thì cần phải xác thực trước đã!

“Giải pháp” mà họ sử dụng là thêm vào một API cho phép mobile app tải về khóa riêng của bất kỳ khách hàng nào, chỉ cần biết số điện thoại của khách hàng đó. Nói cách khác, đây là một unauthenticated API giúp tôi có thể lấy khóa riêng và từ đó giả danh (impersonate) bất kỳ tài khoản nào.

Tóm lại thì giải pháp này chỉ an toàn khi người ta không hiểu được giao thức giữa máy chủ và mobile app. Đây là một sự vi phạm nguyên lý Kerckhoff khi thiết kế giao thức bảo mật.

Một hệ thống an toàn là một hệ thống mở, ai cũng biết cách thức nó hoạt động, nhưng không ai có thể phá vỡ nó. Nếu sự an toàn của hệ thống phụ thuộc vào giả định rằng không ai biết cách nó hoạt động ra sao, không sớm thì muộn hệ thống đó sẽ bị tấn công. Làm rối rắm mã hay che dấu cách thức hoạt động của phần mềm không phải là không có tác dụng trong việc tăng cường bảo mật cho hệ thống, nhưng chúng không thể là phương thức bảo vệ duy nhất. Dịch ngược mã phần mềm khó, nhưng rất nhiều người làm được.

Thiết kế của hệ thống cần phải là thiết kế mở vì chỉ như vậy mới chống lại được các hành vi phá hoại, xâm nhập từ bên trong, vốn là mối nguy lớn nhất cho các hệ thống ngân hàng. Rất nhiều nhân viên ngân hàng hiểu giao thức giữa mobile app và máy chủ, nếu những người này muốn trộm tiền, làm sao để ngăn chặn họ? Ngày hôm nay họ còn là nhân viên, nhưng nếu sau này họ nghĩ việc thì sao? Chỉ có thể trả lời được câu hỏi này bằng cách xây dựng hệ thống với giả định rằng kẻ tấn công đã biết rõ mã nguồn và tài liệu thiết kế.

Happy hacking!

Cập nhật: tôi muốn viết thêm một chút về thiết kế giải pháp an ninh mạng (security engineering) vì đề tài này liên quan đến tuyến bài chính về tình hình và chính sách an ninh mạng ở Việt Nam.

Thiết kế và xây dựng giải pháp xác thực cho một sản phẩm mobile banking là một công việc lai giữa applied security và product security. Việc này không dễ cũng không khó, nhưng tôi nghĩ ở Việt Nam chưa có mấy người làm. Đa số người làm security ở Việt Nam tập trung vào tìm kiếm lỗ hổng, hoặc network security, rất ít người có thể xây dựng giải pháp để giải quyết các vấn đề security thường gặp như identity access management bao gồm authentication, authorization và auditing, key/secret management hay data protection.

Những gì tôi viết ở trên, những câu hỏi tôi đặt ra chứa đựng nhiều gợi mở cho những ai mới bắt đầu trong lĩnh vực này. Nếu có chỗ khó hiểu, bạn hãy để lại còm, tôi sẽ tìm cách giải thích, nhưng tôi khuyên là nên đọc kỹ, suy ngẫm, rồi tự thiết kế một giải pháp xác thực, tìm cách phá vỡ nó và lặp lại. Đây là cách học tốt nhất.

TechTalk via Vnhacker.blogspot