Tìm hiểu về Web Security - Phần 2: User Input
Never trust anything a user puts into your app. -
Không bao giờ tin tưởng bất cứ điều gì người dùng đưa vào ứng dụng của bạn.
Security List
Danh sách bảo mật mạng có nhiều loại khác nhau.
-
Danh sách trắng là danh sách các giá trị được phép. Ví dụ, chính sách CORS chỉ cho phép yêu cầu từ một danh sách các trang web cụ thể.
-
Danh sách đen là danh sách các giá trị bị cấm. Ví dụ, chặn một số điện thoại trên điện thoại của bạn.
-
Danh sách cho phép là danh sách các tệp, ứng dụng và quy trình được tin tưởng và cho phép chạy.
-
Danh sách xám là danh sách các giá trị được phép tạm thời. Được gọi là xám vì nó nằm giữa đen và trắng, thường được sử dụng để kiểm soát truy cập. Ví dụ, người dùng chỉ cần quyền ghi vào cơ sở dữ liệu cho một nhiệm vụ cụ thể và sau đó bạn muốn thu hồi quyền truy cập đó.
Tất cả đều là các công cụ trong bộ công cụ của bạn, được sử dụng cùng với các chiến thuật khác. Danh sách trắng thường hữu ích hơn danh sách đen. Nếu kẻ tấn công tìm ra một điều mới, nó sẽ được phép thông qua danh sách đen của bạn, nhưng vẫn không được phép thông qua danh sách trắng của bạn.
Input Validation - Xác thực đầu vào
Kiểm tra đầu vào không chỉ giúp ngăn chặn các cuộc tấn công, mà còn giúp đảm bảo dữ liệu của bạn được định dạng chính xác để tránh các lỗi xảy ra.
Kiểm tra cú pháp (syntactic validation) đảm bảo rằng đầu vào có đúng cú pháp. Ví dụ, kiểm tra đầu vào của người dùng trong trường email có đúng định dạng email hay không.
Kiểm tra ngữ nghĩa (semantic validation) kiểm tra các giá trị của đầu vào của người dùng. Ví dụ, kiểm tra xem họ đã nhập một ngày đã qua trong trường ngày hay chưa.
Client-side
Bạn thường gặp phương thức kiểm tra phía máy khách, ví dụ như khi một trường bắt buộc trong một biểu mẫu.
Lớp phòng thủ đầu tiên trên phía máy khách được tích hợp sẵn trong các thuộc tính kiểm tra HTML. Điều này cũng đúng trong các framework. Các yếu tố biểu mẫu HTML có các thuộc tính như required
sẽ làm việc thay cho bạn. Các ví dụ khác bao gồm minlength
, maxlength
và pattern
. Với pattern
, bạn có thể kiểm tra đầu vào với một biểu thức chính quy.
Bằng cách chỉ cho phép các giá trị chúng ta biết là đúng định dạng, chúng ta đang sử dụng phương pháp chống tham nhập dữ liệu bằng cách đưa vào danh sách trắng. Nếu chúng ta chỉ đưa vào danh sách đen một số giá trị xác định là độc hại, chúng ta có thể bỏ sót các giá trị độc hại khác và cho phép dữ liệu không đúng định dạng được đưa vào ứng dụng của chúng ta.
Phần tử HTML <input> có 21 giá trị khả dụng cho thuộc tính type
. Nhiều giá trị trong số đó tự động kiểm tra đầu vào của người dùng, ví dụ như email
.
<label for="email">e-mail address</label>
<input type="email" id="email" name="email">
Chỉ với vài thao tác đơn giản như vậy, đầu vào của bạn sẽ được xác thực để đảm bảo rằng người dùng nhập vào đúng định dạng email. Bất cứ thứ gì khác sẽ không hợp lệ.
Nếu không thể sử dụng các điều khiển biểu mẫu HTML, gửi dữ liệu đến phía máy chủ hoặc cần thực hiện xác thực ngữ nghĩa, bạn sẽ cần xác minh dữ liệu trông giống như mong đợi bằng cách sử dụng JavaScript hoặc ngôn ngữ lập trình mà bạn lựa chọn. Ví dụ, TypeScript có thể xác thực các loại tự động. Trong JavaScript, việc xác thực cú pháp có thể trông như thế này. Bên cạnh đó, trong JavaScript bạn cũng có thể sử dụng Constraint Validation API cho các phần tử đó để thiết lập thông báo lỗi tùy chỉnh và nhận thông tin về độ hợp lệ của dữ liệu đầu vào người dùng.
const email = document.getElementById("email");
const emailRegExp =
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
function isValidEmail() {
if (email.value.length > 0 && typeof(email.value) === string) {
return emailRegExp.test(email.value);
}
return false
}
Server-side
Ngay cả khi máy khách đã có các phòng thủ, bạn vẫn cần xác thực đầu vào phía máy chủ. Nếu bạn gửi các phản hồi biểu mẫu qua HTTP, bạn có thể đã sử dụng công cụ Postman để kiểm tra điểm cuối của mình có thể lấy dữ liệu biểu mẫu mà không cần điền vào biểu mẫu. Tuy nhiên, điều này cũng có thể được thực hiện bởi bất kỳ ai khác.
Trên phía máy chủ, kiểm tra đầu vào giống như trên máy khách, trừ khi bạn thường xác thực toàn bộ biểu mẫu trong phần thân của yêu cầu HTTP thay vì xác thực từng trường riêng lẻ. Nếu bạn muốn xác thực cú pháp và ngữ nghĩa cho toàn bộ biểu mẫu một cách nhanh chóng, bạn có thể sử dụng kiểm tra schema. Tương tự như việc tạo một kiểu trong ngôn ngữ có kiểu tĩnh, bạn có thể tạo một tệp schema với định dạng mà bạn mong đợi và kiểm tra nó với một gói như ajv.
Encoding - Mã hóa
Mã hóa là quá trình chuyển đổi dữ liệu sang định dạng khác. Để đảm bảo an toàn và tiện lợi, phương pháp mã hóa của bạn nên phù hợp với ngữ cảnh. Ví dụ, React sẽ không chấp nhận các phần tử con trong một đối tượng, vì vậy bạn phải mã hóa chúng thành một mảng.
Ngoài ra, mã hóa còn có thể bao gồm mã hóa, nhưng điều này không thuộc phạm vi của bài đăng này.
Một loại mã hóa khác là tuần tự hóa, tức là chuyển đổi dữ liệu thành một chuỗi để truyền nó dễ dàng. Trong JavaScript, phương thứcJSON.stringify
được sử dụng để tuần tự hóa JSON.
Ngược lại, phương thức JSON.parse
lấy một chuỗi JSON và chuyển đổi nó thành một đối tượng JavaScript. JSON.parse
không thực thi mã, tuy nhiên, nếu bạn mã hóa một đoạn mã vào một chuỗi JSON hợp lệ, nó có thể được thực thi ở bất kỳ nơi nào bạn truyền nó.
Để ngăn chặn điều này, chúng ta sử dụng phương pháp làm sạch dữ liệu.
Sanitization - Làm sạch dữ liệu
Việc tuần tự hóa và phân tích mã đảm bảo tính hợp lệ của mã của bạn, trong khi việc làm sạch đảm bảo rằng mã của bạn an toàn và đúng ý. Tuy nhiên, việc làm sạch này còn phụ thuộc vào ngữ cảnh. HTML trong chuỗi trên phía sau không gây vấn đề, nhưng HTML trong một trang web có thể thực thi và cần được xử lý bởi một trình làm sạch dữ liệu đặc biệt để đảm bảo tính an toàn. Trình làm sạch này sẽ loại bỏ các thẻ và thuộc tính có thể thực thi mã, chẳng hạn như <script> và onClick
, cũng như các ký tự bất hợp pháp.
Bên cạnh đó, việc "thoát ký tự" - Escaped characters sẽ giúp chuyển đổi các ký tự mà trình phân tích có thể nhận ra là mã để thực thi thành các ký tự khác mà không gây ra lỗi. Thường thì việc này sẽ được thực hiện bằng cách sử dụng "escape characters". Ví dụ, ký tự /s có thể mang ý nghĩa gì đó, tuy nhiên, khi chúng ta thêm ký tự thoát /
vào trước nó, ký tự này sẽ được biến thành //s
và không còn có ý nghĩa gì nữa.
XSS
Nếu bạn cho phép người dùng nhập dữ liệu và sau đó hiển thị nó trên một trang, đó là một điểm tấn công cho Cross-Site Scripting (XSS). Kẻ tấn công có thể chèn mã thực thi vào đầu vào của họ, và khi trang web của bạn cố gắng hiển thị đầu vào đó, mã đó sẽ được thực thi. Trong các bài viết tiếp theo của chuỗi này, tôi sẽ thảo luận thêm về các biện pháp phòng thủ chống lại XSS. Tuy nhiên, hiện tại, hãy nhớ rằng việc xác thực, mã hóa và làm sạch dữ liệu đầu vào từ người dùng là một trong những biện pháp phòng thủ cơ bản nhất.
SQL Injection
Tương tự như XSS, nhưng đối với cơ sở dữ liệu, SQL Injection là khi kẻ tấn công bao gồm các lệnh SQL trong dữ liệu được gửi đến cơ sở dữ liệu và lệnh SQL đó được thực thi. Điều này cho phép kẻ tấn công truy cập tất cả dữ liệu của bạn và thậm chí xóa toàn bộ các bảng.
Cách tốt nhất để ngăn chặn loại tấn công này là không bao giờ sử dụng đầu vào của người dùng trong một lệnh SQL. Nếu không thể tránh được, bạn có thể sử dụng tham số hóa và các thủ tục lưu trữ. Để biết thêm chi tiết, hãy xem hướng dẫn của OWASP.
Command Injection
Tương tự như SQL Injection, nhưng áp dụng cho máy tính của bạn, Command Injection là khi kẻ tấn công bao gồm một lệnh hệ điều hành độc hại trong đầu vào của người dùng. Thay vì được thực thi trong ứng dụng của bạn, mã này được thực thi bởi các lệnh hệ thống trên máy tính của bạn.
Để ngăn chặn loại tấn công này, cách tốt nhất là không bao giờ sử dụng đầu vào của người dùng trong một lệnh hệ điều hành trên máy chủ của bạn. Nếu bạn nhất quyết cần phải sử dụng đầu vào của người dùng vì một lý do nào đó, hãy thực hiện việc xác thực, mã hóa và làm sạch đầu vào trước khi sử dụng.
Client-Side Authorization - Phân quyền phía người dùng
Phân quyền phía người dùng (Client-Side Authorization) là khi một số quyền hạn được giao cho người dùng trên trình duyệt của họ. Tuy nhiên, đây là một vấn đề bảo mật vì người dùng có quyền truy cập vào tất cả mọi thứ trong trình duyệt. Nếu bạn sử dụng các biến trạng thái hoặc router để tải các thành phần dựa trên vai trò của người dùng, người dùng có thể dễ dàng tìm hiểu và thay đổi giá trị trong DevTools hoặc chỉ cần thay đổi URL. Khi bạn bắt đầu nhìn thấy các mẫu trong các tham số URL, đó sẽ dễ dàng để đoán những giá trị khác có thể được sử dụng.
Để ngăn chặn tấn công này, bạn cần xác thực người dùng mỗi khi họ cố gắng truy cập thông tin trên máy chủ của bạn. Nếu không, bất kỳ ai cũng có thể giả mạo người dùng hoặc vai trò khác. Điều này đặc biệt quan trọng nếu có đầu vào của người dùng và đầu vào đó được đưa vào cơ sở dữ liệu. Do đó, bạn cần kiểm tra người dùng và đầu vào của họ, bằng cách xác thực, mã hóa và làm sạch đầu vào để ngăn chặn các tấn công Command Injection và SQL Injection.
Tổng kết
Các nhà phát triển web thường xây dựng rất nhiều biểu mẫu, do đó, việc học cách bảo vệ bản thân khỏi đầu vào người dùng độc hại là vô cùng quan trọng. Lời khuyên quan trọng:
Đừng bao giờ tin tưởng bất kỳ đầu vào nào từ người dùng.
All rights reserved