Nếu bạn là fan của nhện nhọ hoặc đã từng xem qua phim thì chắc hẳn bạn cũng biết người chú Uncle Ben của nhện đã có một câu nói rất hay:

“Sức mạnh càng lớn thì trách nhiệm cũng càng lớn”

Là một lập trình viên bạn có sức mạnh ở đôi bàn tay với những dòng code của mình để tạo lên các ứng dụng. Cùng với đó bạn cũng phải chịu trách nhiệm giữ an toàn cho những dữ liệu mà bạn lưu trữ trong các ứng dụng của bạn phát triển lên. SQL là một hệ quản trị cơ sở dữ liệu quan hệ để phát triển phần mềm. Tuy nhiên nếu trong quá trình làm việc với SQL rất có thể bạn sẽ dính lỗi SQL Injection là một lỗi bảo mật có thể coi là cơ bản và rất phổ biến trong SQL.

Trong bài viết bài chúng ta sẽ cùng tìm hiểu cách thực hiện SQL trên Laravel để thấy mức độ nghiêm trọng của nó và sau đó sẽ cùng đưa ra đề xuất để ngăn chặn lỗi bảo mật cơ bản này xảy ra trong các phần mềm tương lai của bạn. Đầu tiên bạn cần hiểu SQL Injection là gì. Sau đó chúng ta sẽ cùng đi qua một vài ví dụ thực hiện SQL Injection trong Laravel và các cách để ngăn chặn nó xảy ra.

Bạn có thể đã hoàn toàn sai lầm khi tin rằng các lỗi bảo mật đã được các nhà phát triển công cụ, framework vá trên mã nguồn của họ. Laravel cũng vậy nó cung cấp nhiều phương pháp để làm việc an toàn với SQL. Tuy nhiên bạn vẫn chưa thấy được những vấn đề bảo mật với SQL có thể xảy ra. Dưới đây sẽ là một số trường hợp hoặc tính năng trong Laravel có thể khiến ứng dụng của bạn xuất hiện lỗi SQL Injection.

Trước khi đi vào các ví dụ cụ thể của Laravel SQL Injection thì chúng ta hãy cùng tìm hiểu SQL Injection là gì nhé.

SQL Injection là cái gì mà nguy hiểm vậy?

“Một hacker có thể thực hiện tấn công sử dụng SQL Injection để lấy hết dữ liệu quan trọng như thông tin người dùng trên ứng dụng của bạn, tệ hơn nữa hacker có thể mã hoá hoặc xoá toàn bộ cơ sở dữ liệu ứng dụng của bạn”

Đầu tiên, chúng ta sẽ cùng hiểu SQL qua một ví dụ thực tế. Giả sử bạn có một ứng dụng web hiển thị thông tin của một người dùng qua URL như sau:

https://example.com/profile.php/?user=lochd

Để tìm thông tin người dùng locdh thì phía sau hệ thống sẽ thực hiện câu truy vấn như sau:

SELECT * FROM users WHERE username = 'locdh'

Khi thực hiện tấn công SQL Injection, hacker có thể cố tình sửa thông tin của tham số user vào URL ví dụ như sau:

https://example.com/profile.php/?user=locdh’ OR 1=1;–

Với URL đã thay đổi như trên thì truy vấn đằng sau hệ thống sẽ thực hiện như sau:

SELECT * FROM users WHERE username = 'locdh' OR 1=1;--'

Truy vấn SQL trên sẽ luôn trả về true vì 1=1 thì luôn đúng và với từ khoá OR nếu một bên vế biểu thức là true thì toàn bộ biểu thức sẽ trả về true. Do đó truy vấn trên sẽ trả về toàn bộ dữ liệu của bảng users trong cơ sở dữ liệu. Đây là một ví dụ cơ bản nhất của SQL Injection.

Laravel SQL Injection

Laravel là một Framework PHP open-source. Nó đi theo mô hình MVC và tích hợp sẵn các tính năng như user authentication, routing, and database operations.

Với sự hỗ trợ của Eloquent ORM, bạn có thể xây dựng một ứng dụng Laravel để đọc và ghi vào cơ sở dữ liệu SQL mà không cần phải viết truy vấn dạng raw của SQL. Nghĩa là bạn không cần phải viết các truy vấn như “SELECT * FROM table” để đọc dữ liệu từ SQL. Tuy nhiên Laravel vẫn hỗ trợ viết truy vấn dạng raw của SQL vì đôi khi bận vẫn cần sử dụng trong một vài trường hợp mà truy vấn qua Eloquent ORM không hỗ trợ và chính những truy vấn này nếu không viết cẩn thận bạn sẽ dính phải lỗi SQL Injection.

Bây giờ chúng ta sẽ cùng đi qua các ví dụ về thực hiện Laravel SQL Injection và các phương pháp để ngăn chặn nó diễn ra.

Ví dụ 1: Sử dụng RawMethods

RawMethods là cách bạn có thể sử dụng truy vấn thô của SQL để lấy dữ liệu từ cơ sở dữ liệu của bạn trong Laravel. Một số ví dụ về RawMethods của Laravel bao gồm selectRaw, whereRaw, và orderByRaw. Tuy nhiên RawMethods rất dễ bị tấn công SQL Injection, cụ thể trong tài liệu của Laravel đã có hẳn một câu như sau để cảnh báo các lập trình viên:

“Hãy nhớ, Laravel không đảm bảo rằng tất cả các truy vấn nào sử dụng biểu thức thô đều được bảo vệ khỏi SQL Injection”

Để chứng minh thực hiện được SQL Injection trong whereRaw RawMethod, chúng ta hãy xem đoạn code sau:

DB::table('posts')
    ->select('postTitle', 'postBody')
    ->whereRaw('id =' . $id)->first(); 

Bạn có thể thấy đoạn code trên sẽ trả về một kết quả duy nhất từ bảng posts hoặc không trả về kết quả nếu không có bài post nào thoả mãn id truyền vào. Tuy nhiên đoạn code này có thể thực thi một hành động khác ngoài mong muốn.

Ví dụ nếu giá trị của id được xác định bởi yêu cầu gửi lên người dùng thì lúc này bạn hãy xem điều gì sẽ xảy ra khi người dùng nhập giá trị id như sau:

https://example.com/post/11 AND 1=1

Yêu cầu HTTP trên sẽ thực hiện truy vấn SQL vào hệ thống như sau:

SELECT postTitle, postBody FROM posts WHERE id = 11 AND 1=1

Lúc này hệ thống sẽ trả về bản ghi có id là 11 như yêu cầu. Điều này do 1=1 luôn đúng. Tuy nhiên nếu hacker thay đổi 1=1 thành một biểu thức luôn sai ví dụ:

SELECT postTitle, postBody FROM posts WHERE id = 11 AND 1=2

Truy vấn này sẽ khiến hệ thống không trả về kết quả hoặc bị crash luôn. Điều này cho thấy có sự tồn tại của lỗi SQL Injection có trong whereRaw RawMethods của Laravel.

Cách ngăn chặn

Tốt nhất bạn nên hạn chế tối đa việc sử dụng truy vấn thô của SQL trong Laravel. Vì làm như vậy bạn đã kế thừa được các tính năng bảo mật của Framework đã cung cấp sẵn. Nhưng nếu bắt buộc bạn phải sử dụng truy vấn SQL thô thì hãy đảm bảo việc bạn đã thực hiện server-side validation đối với toàn bộ dữ liệu đầu vào của người dùng.

Một cách để sửa lỗ hỏng SQL Injection ở ví dụ trên đó là bạn cần xác thực giá trị của id truyền vào là một số nguyên không chấp nhận bất cứ dữ liệu không hợp lệ từ phía người dùng vào truy vấn. Bạn có thể làm như sau:

$validator = Validator::make(['id' => $id], [
    'id' => 'required|numeric'
]);

if ($validator->fails()) {
    abort(404);
}else {
    //Run query
}

Một cách khác là bạn hãy viết lại truy vấn ban đầu bằng cách sử dụng truy vấn được tham số hoá:

DB::table('posts')
    ->select('postTitle', 'postBody')
    ->whereRaw('id = ?', $id)->first();

Ví dụ 2: Sử dụng DB::statement

Nếu bạn muốn thực hiện một truy vấn, thì Laravel có hỗ trợ sử dụng phương thức DB:statement cho việc đó. Nó sẽ chấp nhận truy vấn SQL thô như một tham số, nhưng không vì thế mà nó được bảo mật hoàn toàn bởi các tính năng bảo mật có sẵn của Laravel. Để xem cách hoạt động của câu lệnh DB:statement hãy xem đoạn code sau:

DB::statement("UPDATE users SET password=".$newPassword. "  WHERE username =" . $username);

Đoạn code trên sẽ thực hiện thay đổi mật khẩu cho tài khoản người dùng cụ thể bằng cách cung cấp một username chính xác. Tuy nhiên, trong tình huống người dùng nhập “Locdh OR 1=1” làm username và “123456” làm mật khẩu thì một kết quả khác sẽ được đưa ra khi thực hiện thay đổi mật khẩu.

Lúc này truy vấn như sau sẽ được thực hiện:

UPDATE users SET password="123456 " WHERE username ="Locdh" OR 1=1

Truy vấn trên sẽ đặt lại mật khẩu của tất cả người dùng có trên hệ thống về “123456” . Với kiểu tấn công này hacker sẽ có được quyền truy cập vào tất cả các tài khoản người dùng đang có trên hệ thống.

Cách ngăn chặn

Xác thực dữ liệu người dùng đưa lên vẫn là giải pháp cho vấn đề này. Ngoài ra bạn cũng có thể sử dụng truy vấn được tham số hoá ví dụ như sau:

DB::statement("UPDATE users SET password=?  WHERE username =?", [$password, $username]);

Ví dụ 3: Thông báo lỗi

Các ứng dụng được xây dựng bằng Laravel có thể hiển thị thông tin nhạy cảm như truy vấn cơ sở dữ liệu trong các trường hợp phát sinh lỗi không mong muốn.

DB::statement("SELECT * FROM users WHERE id=".$id );

Đoạn code trên sẽ hiển thị thông báo lỗi sau trong trình duyệt của người dùng khi nhập một id không hợp lệ (giá trị không phải số nguyên):

Hiển thị thông báo lỗi lên trình duyệt người dùng

Như đã thấy ở ảnh minh hoạ bên trên thì trình duyệt đang hiển thị toàn bộ các truy vấn nhạy cảm mà hệ thống đang thực hiện. Khi có được thông tin này hacker có thể hiểu được logic bên trong ứng dụng của bạn và từ đó tìm ra cách tấn công vào ứng dụng.

Cách ngăn chặn

Hãy test kỹ code của bạn để tìm ra các trường hợp phát sinh lỗi để sửa trước khi đưa lên phiên bản production. Ngoài ra cách tốt nhất là bạn nên tắt hoàn toàn hoạt động thông báo lỗi khi xảy ra trên trình duyệt ở phiên bản production. Bạn có thể tìm thấy cách tắt ở tài liệu của Laravel.

Để tắt hiển thị lỗi trên trình duyệt bạn hãy mở tệp .env và thay đổi giá trị cho APP_DEBUG từ true sang false. Nhìn sẽ như sau:

APP_DEBUG=false

Kết luận

Tóm lại lỗ hỏng SQL Injection vẫn có thể tồn tại trong Laravel. Nhưng việc xác thực dữ liệu đầu vào của người dùng và dùng các truy vấn được tham số hoá có thể giúp bạn giảm được các nguy cơ xảy ra lỗi này.

Bảo mật cho ứng dụng Laravel của bạn cần một quá trình bảo trì thường xuyên. Tuy SQL Injection là một lỗi bảo mật rất cơ bản nhưng thực sự nó rất nguy hiểm nếu hacker khai thác được nó trên bất kỳ ứng dụng nào.

Các ví dụ trên chưa phải là tất cả những lỗ hổng SQL Injection có thể xảy ra chính vì vậy bạn cần test thật kỹ những dòng code của mình vì thực tế sẽ có thể xảy ra nhiều trường hợp khác nhau.

Cảm ơn bạn đã theo dõi bài viết, hi vọng bài viết đã cung cấp cho bạn góc nhìn cũng như các thông tin phòng tránh hữu ích để bảo mật cho ứng dụng Laravel của mình.


Khóa học PHP Laravel Fullstack - Khóa học dành người mới bắt đầu

Chi tiết - tại đây