Sau khi thông báo cho các công ty, đưa ra cách giải quyết và thấy rằng Facebook đã cập nhật trang API docs của họ để nhắc nhở các lập trình viên hãy chú ý tới lỗi này, tôi nghĩ rằng mọi chuyện đã ổn. Nhưng gần đây, trong khi review hộ một người bạn về ứng dụng của anh ta, tôi lại một lần nữa thấy lại bug này.
Thế nên tôi quyết định chia sẻ lại phương pháp tấn công này tại buổi meetup tháng 3 của Ruby Sài Gòn và post lại lên Kipalog để các bạn chưa tham dự buổi meet up lần đó chú ý hơn.
Hai cách cài đặt
Khi bạn bấm vào nút đăng nhập với Facebook trên một ứng dụng di động, ứng dụng này sẽ sử dụng Facebook SDK, gọi tới API của Facebook, để yêu cầu user trên Facebook cho phép ứng dụng của bạn được sử dụng tài khoản Facebook để đăng nhập. Nếu user đồng ý, Facebook API sẽ gọi ngược lại ứng dụng của bạn kèm theo mộtaccess_token
của Facebook.Sử dụng client để lấy dữ liệu từ Facebook
- Đầu tiên, ứng dụng di dộng sẽ sử dụng facebook
access_token
gọi tới Facebook API - Facebook API trả về dữ liệu với trường
user_id
của facebook user - Client sẽ sử dụng
user_id
này để gửi lên server của ứng dụng - Server sẽ kiểm tra trong database để xem có user nào mà
facebook_id
trùng vớiuser_id
được ứng dụng truyền lên hay không? Nếu có thì trả về user đó, nếu không, thì tạo mới user. - Sau đó, server sẽ trả lai
access_token
cho ứng dụng di động
Cách cài đặt này có gì sai? Hãy cùng xem kịch bản để hacker tấn công như thế nào!
Tại bước thứ 3, attacker gửi tới một
facebook_id
giả, và trùng với một trong các facebook_id
có trong hệ thống của bạn. Khi đó server vẫn sẽ tuân theo các bước thứ 4 và thứ 5 để trả về access_token
cho user.Cách tấn công thật đơn giản, nguyên nhân sâu xa là bởi vì trong cách cài đặt này, chúng ta đã mắc phải một lỗi trong lập trình đó là: Để client validate dữ liệu. Khi nhận được
access_token
từ Facebook, phía validate dữ liệu là client - ứng dụng mobile. Ứng dụng mobile sau khi validate dữ liệu, có được facebook_id
gửi lên server và server tin tưởng vào dữ liệu này. Để login một user, trong trường hợp này tất cả các thông tin bạn cần chỉ là một facebook_id
- tức một số 64 bit.Vậy làm sao để có thể sửa lỗi này? Câu trả lời đó là, hãy để việc validate dữ liệu cho server. Chúng ta hãy cùng xem cách cài đặt thứ hai.
Sử dụng server để lấy dữ liệu từ Facebook
- Ngay khi nhận được
access_token
từ Facebook, ứng dụng di động sẽ gửi token này lên server - Server sử dụng
access_token
này để gọi API từ Facebook, cụ thể ở đây là APIhttps://graph.facebook.com/me
- Facebook trả về dữ liệu có chứa
facebook_id
của user - Server sẽ kiểm tra trong database để xem có user nào mà
facebook_id
trùng vớiuser_id
được ứng dụng truyền lên hay không? Nếu có thì trả về user đó, nếu không, thì tạo mới user. - Sau đó, server sẽ trả lai
access_token
cho ứng dụng di động
Vậy với cách cài đặt này, attacker có thể tấn công như nào?
Để tấn công, attacker chỉ cần gửi lên server một valid
access token
của user từ Facebook. access_token
này có thể lấy từ một ứng dụng khác. Ví dụ, trong kịch bản tấn công của mình, tôi lấy access_token
từ Facebook của ứng dụng Quora, và gửi tới server của Kickstarter và AirBnB. Kết quả cả 2 servers kể trên đều nhầm tưởng đó là access_token
đúng, và cho tôi đăng nhập vào hệ thống.access_token
của bạn, thì ứng dụng này có thể có được tài khoản của bạn ở bất cứ nơi nào bạn sử dụng Facebook để login. Thật đáng sợ phải không?Lỗi sai lớn nhất trong cách cài đặt này đó là Không kiểm tra dữ liệu nhận được xem có phải từ nguồn tin tưởng hay không? Bất cứ khi nào bạn nhận được dữ liệu, bạn đều phải kiểm tra xem dữ liệu đó có phải là từ một nguồn đáng tin cậy không? Điều này cũng giống như trong thế giới thật, đâu phải ai nói với bạn điều gì, bạn cũng tin đúng không?
Để sửa lại lỗi này, chúng ta cần thêm một bước kiểm tra xem
access_token
có phải là access_token
do ứng dụng chúng ta sinh ra hay không, bằng cách sử dụng API của facebook GET /debug_token?input_token={input-token}&access_token={access-token}Tổng kết
Hai cách thức tấn công kể trên không chỉ đúng với Facebook API, mà còn có thể áp dụng với bất cứ hệ thống OAuth nào. Vì thế, hãy ghi nhớ 2 điều:- Luôn luôn validate lại dữ liệu từ client gửi lên
- Phải kiểm tra xem dữ liệu có phải từ nguồn tin cậy hay không.
Cập nhật
Facebook API giờ đã thay đổi cách thức, giờ đây khi bạn gọi API/me
với access_token
, Facebook sẽ trả về facebook_id
là id của ứng dụng, chứ không phải global id nữa, nên hai cách tấn công kể trên sẽ phải thay đổi đi đôi chút. Nhưng 2 điều cần lưu ý kể trên vẫn nên được áp dụng với mọi API.
Tác giả: kiennt - Kipalog.