Lỗ hổng bảo mật làm lộ thông tin người dùng từ một sản phẩm tìm kiếm việc làm tại Việt Nam

Chúc mừng năm mới 2017!

Chào các bạn, chào mừng các bạn đến với blog trong những ngày đầu năm 2017. Để mở đầu cho năm nay, tôi sẽ kể cho các bạn nghe một câu chuyên có thật xảy ra khoảng cuối năm 2016.

Xin mạn phép dấu tên hệ thống trong câu chuyện này, vì trong quá trình trao đổi, tôi đã nhận lời là sẽ không nêu đích danh công ty hay tổ chức nào. Các bạn có thể tin hoặc không tin, nhưng những trải nghiệm trong quá trình phân tích để tìm ra lỗ hổng, thứ mà tôi muốn chia sẻ với các bạn sau đây sẽ là những điều có giá trị nếu như các bạn đang làm sản phẩm và muốn thông tin người dùng của mình luôn được an toàn.

Và nếu các bạn đồng ý với tôi rồi thì chúng ta tạm gọi website đó là X nhé! Nào cùng bắt đầu!

Cơ duyên

Vào một ngày đẹp trời giữa tuần (27/12/2016), tôi được cấp trên thông báo về việc sẽ trở thành mentor cho một member mới trong team, tạm gọi người này là anh A, vì anh này lớn tuổi hơn tôi. Với tác phong chuyên nghiệp của một developer có đạo đức, tôi luôn tìm hiểu thông tin về những người tôi sẽ phỏng vấn, những người tôi sẽ mentor cho họ, những người tôi sẽ làm việc cùng, etc… Nói lan man một chút, vì sao tôi lại làm vậy? Tôi luôn muốn hiểu rõ những người tôi sẽ làm việc cùng để có cách hành xử, cũng như chuẩn bị sẵn những kiến thức, vấn đề mà tôi thấy họ thiếu nhưng cần trong quá trình làm việc để hỗ trợ họ.

Tôi mang một số thông tin của anh A đi kiểm tra, X là một trong số những website tôi thấy có thông tin về anh A. Sau một hồi loay hoay, tôi bắt đầu xem thông tin của anh A trên X. Một điểm đáng lưu ý là thông tin anh A dưới dạng ứng viên, và để xem được thì tôi phải đăng nhập vào hệ thống.

Và rồi câu chuyện bắt đầu.

Chi tiết nhỏ gây sự chú ý lớn

Trước lúc đăng kí, tôi có play around với X và phát hiện thấy trang web này có nhiều version với ngôn ngữ khác nhau và mỗi version là một bản riêng, từ webapp đến database chứ không dùng internationalization (I18N).

Tại sao tôi lại biết?

Tôi đăng kí thử với services tiếng anh (EN) và services tiếng việt (VN) và không thể dùng thông tin bên EN để login vào VN và ngược lại. Than trời!

Với thói quen bảo mật email cá nhân, tôi thường sử dụng những dịch vụ fake inbox để đăng kí đối với những trang web tôi nghĩ là mình chỉ vào một vài lần.

Tôi sử dụng mailinator làm mail box, và đăng kí trên X. Việc đăng kí này hoàn toàn thành công. Lúc này chỉ số hoài nghi của tôi tăng thêm một ít, bởi vì thường những hệ thống lớn rất hay chặn những đuôi mail đến từ các fake inbox services nhưng với X thì không. Nhưng đây vẫn chưa là điểm mấu chốt.

Tôi thử đăng kí với services tiếng anh (EN), sau khi đăng kí tôi nhận được một email thông báo đăng kí thành công với account và password. Nhưng mà tôi đang nhìn thấy cái gì thế này, một password plain text. Độ nghi hoặc của tôi bắt đầu tăng cao, và với khả năng tiên đoán thiên bẩm (có phần hơi xạo ke) thì tôi nghĩ đâu đó trong hệ thống này sẽ có lỗ hổng. Và tôi không thể để bất cứ kẻ xấu nào lợi dụng sơ hở để trục lợi cho bản thân chúng được (máu siêu anh hùng nổi lên).

Hành trình trở thành siêu anh hùng

Tôi phát hiện được X có cả mobile app cho Android và IOS. Để khai thác, việc dễ dàng nhất là nhắm vào mobile app.

Vì sao?

Bởi vì hầu hết các mobile app đều phải call API để lấy dữ liệu, cũng như thực hiện các chức năng lưu trữ (thêm, xoá, sửa) trên server. Và đầu API là cái dễ khai thác nhất, vì với mỗi request ta sẽ có một bộ headers, params, cookies, etc… tương ứng, và nếu bạn tinh mắt thì khả năng phát hiện lỗ hổng trong những bộ input đó là dễ dàng hơn.

Vì tôi rành code Java nên tôi tải app Android từ một nguồn APK. Sau đó dùng một vài thủ thuật reverse engineering để xem mã nguồn của app.

Lúc này là 21h ngày 27/12/2016, tôi bắt đầu đọc source code của mobile app. Tôi skim qua các package, và tìm thấy package tên là vn.x.android.app.api. Lúc này tôi bắt đầu tìm kiếm những thứ liên quan tới API và có từ khoá là user.

Và trong package nêu trên, tôi phát hiện ra file APISettings.java, file này chứa cách construct lên những url để request lên server. Hầu hết các url được construct đều có hai param thiết yếu là method và token cùng với một số param chuyên biệt cho từng method. Trong đó method là cái sẽ quyết định API trả về cái gì, và token là cái giúp API có thể chứng thực được request không phải đến từ nguồn bậy bạ. Và token này được fixed trong source code. Có thể dễ dàng hiểu được họ muốn secure những public API không liên quan tới user ví dụ như getBlogs, getJobs, etc… chẳng hạn vậy. Nhưng liệu những API liên quan đến user thì thế nào?

Tôi thử với một vài API thì thấy trả về lỗi nếu token không tồn tại, hoặc sai. Tiếp đến tôi thử đến API login, API này được contruct dạng

method=login&token=fixedtoken&username=blahblah&password=blahblah

Trời ơi, một hàm login dùng GET method, không thể tin được.

Phần này dành cho các bạn nào chưa biết dùng GET cho API login nó tồi tệ như thế nào. Đối với một GET request, thông tin bạn gửi đi sẽ gắn hết lên url param, và nếu ai sniff được cái url đó thì tất cả thông tin họ đều nhìn thấy. Cho dù là bạn có secure bằng HTTPS đi chăng nữa, thì việc dùng HTTPS chỉ giúp bạn secure được gói tin chứ không ảnh hưởng lên url. Tại sao lại như vậy? Giải thích một chút về mặt HTTPS, SSL connection sẽ nằm giữa TCP layer và HTTP layer, client và server sẽ tạo một secure TCP connection, thông qua SSL/TLS protocol, và sau đó nó sẽ gửi HTTP request đã được encrypt, bao gồm cả URL thông qua TCP connection vừa tạo. Tuy nhiên, URL data sẽ được lưu lại browser history, và cái này thì insecure, lại còn là một loại longterm data, trừ khi user tự xoá, nên mình mới nói nó nguy hiểm. Mặt khác, một số loại log như nginx log, apache log, etc… có lưu lại url, và nếu những cái log này bị leak ra thì hậu quả thế nào, chắc các bạn cũng biết.

Thấy chưa, thấy nguy hiểm thế nào chưa.

Mặt khác, password ở đây gửi đi lại dưới dạng plain text không hề mã hoá cho dù là hash hay encode. Nếu sử dụng HTTPS, và ném đống thông tin đó trong body thì ok, bạn muốn làm sao cũng được vì gói tin đã được secure rồi, chỉ có ai có được certificate mới có thể decode để đọc gói tin. Tuy nhiên, đây lại là GET, và còn không có cả HTTPS nữa, ôi trời ơi. Lúc này tôi mới than trời!

Con bug đầu tiên hiện ra

Cứ cho là vậy đi, tôi bắt đầu call API login thử, sau nhiều tổ hợp thử thì tôi phát hiện ra, API này vẫn chạy cho dù token bị rỗng. Lưu ý là bị rỗng nhé, nếu bạn bỏ luôn token thì nó sẽ báo lỗi.

Tôi có thể giải thích cho các bạn nguyên nhân của con bug này cực kì dễ dàng luôn. Đó là một thánh nào đó code phần verify param trong request chỉ verify token khác null chứ không kiểm tra xem nó có valid hay không.

Vậy từ lỗ hổng này, chúng ta có thể khai thác được gì?

Với lỗ hổng này cùng với việc X không có cơ chế lock user sau nhiều lần đăng nhập sai password nên có thể lợi dung API này để brute force nhằm dò password của user. Rõ ràng là nghiêm trọng, tuy nhiên vẫn có thể work around bằng cách chặn spamming request.

Chuyện vẫn chưa kết thúc

Với tư duy của một lập trình viên có tâm (tự sướng), tôi nghĩ rằng API getUserInfo sẽ nhận vào token là token sau khi server trả về khi đăng nhập, nên tôi dùng token vừa nhận được sau khi đăng nhập để thử. Nhưng ôi thôi, ngó lại source code mới tá hoả, là token mà API này sử dụng là fixed token trong source code. Than trời một lần nữa!

Rồi tôi lại test API này với tổ hợp một số bộ data mà tôi tạo ra sẵn. Nhưng ôi thôi, lại lỗi cũ, token rỗng vẫn chạy, user info trả về ầm ầm. Đến đây thì câu chuyện thật sự kết thúc, tôi call API với user mới vừa tạo, mà nước mắt rưng rưng khi thấy kết quả trả về bao gồm cả user_id với giá trị 4410111, hơn gần 4 triệu rưỡi user đã lọt vào tay tôi, một cách quá dễ dàng. Và tôi tin chắc luôn, ông code cái API này với ông code cái API login kia cùng là một người nè, sai giống nhau quá mà!

Siêu nhân biến hình

Với tấm lòng hiệp nghĩa, tôi quyết định rằng mình phải báo lỗi này để họ fix nhanh nhất có thể, nhằm giúp người giúp đời.

Tôi hì hục soạn mail, hì hục ngồi làm video PoC, và liên hệ với họ qua email và fanpage. Tất cả được gửi đi vào lúc 1h sáng ngày 28/12/2016.

Quá trình xử lý lỗi

Tôi bắt đầu phát hiện lỗi lúc 23h 27/12/2016.

1h 28/12/2016 tôi email việc phát hiện lỗ hổng cho X.

11h 28/12/2016 tôi nhận được phản hồi đầu tiên từ X.

17h 28/12/2016 X thông báo với tôi về việc lỗ hổng đã được fix.

Thực sự X làm tôi rất hài lòng vì họ đã có những hành động cụ thể và nhanh chóng đối với lỗi mà tôi đã báo.

Tối 29/12/2016 tôi được mời đi ăn tối cùng CEO, CTO và Operation Manager của X, trong buổi này tôi đã được trao đổi nhiều hơn về X, cũng như học hỏi được rất nhiều điều.

Lời kết

Thật ra những lỗ hổng dạng này không có gì mới. Đã có rất nhiều vụ tương tự như lộ thông tin 3 triệu người dùng của một website được báo bởi Juno_Okyo, hay gần đây nhất là 2 triệu người dùng lozi bị lộ thông tin của Tôi đi code dạo.

Những lỗi dạng này là cực kì dễ phòng tránh, nếu các bạn developer có một chút kiến thức về bảo mật, đọc kĩ những dòng code mình viết ra, cũng như hiểu rõ về logic của hệ thống mình đang làm. Nếu các bạn muốn trau đồi thêm kiến thức về bảo mật, các bạn có thể tìm đọc ebook Bảo mật nhập môn của Tôi đi code dạo. Trong sách các bạn sẽ được biết thêm về những lỗi bảo mật thường gặp cũng như các phương pháp phòng tránh, và còn nhiều kiến thức thú vị khác.

Đây là một lời cảnh tỉnh nữa đối với những developer chỉ code để chạy được mà không quan tâm đúng sai đẹp xấu. Hãy là developer có tâm, hãy coi trọng những dòng code mình viết ra, vì đó là cái phản ánh chính xác nhất về bản thân bạn!

Lưu ý: Việc post bài của tôi cũng chỉ mang tính chất cảnh tỉnh chứ không có ý khoe khoang hay gì khác. Bất kì một hành động tấn công, phá hoại hệ thống nào nhằm “thể hiện bản thân” đều là những hành động ngu dốt, thiếu suy nghĩ có thể đưa các bạn đứng trước vành móng ngựa. Hãy suy nghĩ cẩn thận trước khi hành động nhé!

Một suy nghĩ 9 thoughts on “Lỗ hổng bảo mật làm lộ thông tin người dùng từ một sản phẩm tìm kiếm việc làm tại Việt Nam

Bình luận