Tại sao không làm cho các truy vấn không tham số trả về một lỗi?


22

Lỗi SQL là một vấn đề bảo mật rất nghiêm trọng, phần lớn là do rất dễ hiểu sai: cách rõ ràng, trực quan để xây dựng truy vấn kết hợp đầu vào của người dùng khiến bạn dễ bị tổn thương và Cách thức giảm thiểu yêu cầu bạn phải biết về tham số hóa truy vấn và SQL tiêm đầu tiên.

Dường như với tôi rằng cách rõ ràng để khắc phục điều này sẽ là tắt tùy chọn rõ ràng (nhưng sai): sửa công cụ cơ sở dữ liệu để mọi truy vấn nhận được sử dụng các giá trị được mã hóa cứng trong mệnh đề WHERE thay vì các tham số trả về một mô tả hay, mô tả thông báo lỗi hướng dẫn bạn sử dụng tham số thay thế. Điều này rõ ràng sẽ cần phải có tùy chọn từ chối để những thứ như truy vấn đặc biệt từ các công cụ quản trị vẫn sẽ chạy dễ dàng, nhưng nó phải được bật theo mặc định.

Có điều này sẽ tắt SQL tiêm lạnh, gần như chỉ sau một đêm, nhưng theo tôi biết, không có RDBMS thực sự làm điều này. Có bất kỳ lý do tốt tại sao không?


22
bad_ideas_sql = 'SELECT title FROM idea WHERE idea.status == "bad" AND idea.user == :mwheeler'sẽ có cả giá trị được mã hóa cứng và tham số hóa trong một truy vấn duy nhất - hãy thử nắm bắt điều đó! Tôi nghĩ rằng có những trường hợp sử dụng hợp lệ cho các truy vấn hỗn hợp như vậy.
amon

6
Làm thế nào về việc chọn hồ sơ từ hôm naySELECT * FROM jokes WHERE date > DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY score DESC;
Jaydee

10
@MasonWheeler xin lỗi, ý tôi là cố gắng cho phép điều đó. Lưu ý rằng nó được tham số hóa hoàn hảo và không bị SQL tiêm. Tuy nhiên, trình điều khiển cơ sở dữ liệu không thể cho biết liệu nghĩa đen "bad"là thực sự theo nghĩa đen hay là kết quả của việc nối chuỗi. Hai giải pháp tôi thấy là loại bỏ SQL và các DSL nhúng chuỗi khác (vâng, xin vui lòng) hoặc quảng bá các ngôn ngữ mà việc nối chuỗi khó chịu hơn so với sử dụng các truy vấn được tham số hóa (umm, no).
amon

4
và RDBMS sẽ phát hiện xem có nên làm điều này không? Nó sẽ qua đêm khiến bạn không thể truy cập RDBMS bằng dấu nhắc SQL tương tác ... Bạn không còn có thể nhập các lệnh DDL hoặc DML bằng bất kỳ công cụ nào cả.
jwenting

8
Theo một nghĩa nào đó, bạn có thể làm điều này: hoàn toàn không xây dựng các truy vấn SQL, thay vào đó hãy sử dụng ORM hoặc một số lớp trừu tượng khác để tránh bạn cần xây dựng các truy vấn SQL. ORM không có các tính năng bạn cần? Thì SQL là ngôn ngữ dành cho những người muốn viết SQL, đó là lý do tại sao trên toàn bộ nó cho phép họ viết SQL. Vấn đề cơ bản là mã tạo động khó hơn so với vẻ ngoài của nó, nhưng mọi người vẫn muốn làm điều đó và sẽ không hài lòng với các sản phẩm không cho phép chúng.
Steve Jessop

Câu trả lời:


45

Có quá nhiều trường hợp sử dụng một nghĩa đen là cách tiếp cận đúng.

Từ quan điểm hiệu suất, có những lúc bạn muốn có nghĩa đen trong các truy vấn của bạn. Hãy tưởng tượng tôi có một trình theo dõi lỗi trong đó một khi nó đủ lớn để lo lắng về hiệu suất, tôi hy vọng rằng 70% lỗi trong hệ thống sẽ bị "đóng", 20% sẽ "mở", 5% sẽ "hoạt động" và 5 % sẽ ở một số trạng thái khác. Tôi có thể muốn có một truy vấn trả về tất cả các lỗi đang hoạt động

SELECT *
  FROM bug
 WHERE status = 'active'

thay vì chuyển statusnhư là một biến ràng buộc. Tôi muốn có một gói truy vấn khác tùy thuộc vào giá trị được truyền vào status- Tôi muốn thực hiện quét bảng để trả về các lỗi đã đóng và quét chỉ mục trênstatuscột để trả lại các khoản vay đang hoạt động. Bây giờ, các cơ sở dữ liệu khác nhau và các phiên bản khác nhau có các cách tiếp cận khác nhau (ít nhiều thành công) cho phép cùng một truy vấn sử dụng một gói truy vấn khác nhau tùy thuộc vào giá trị của biến liên kết. Nhưng điều đó có xu hướng đưa ra một mức độ phức tạp đáng kể cần được quản lý để cân bằng quyết định có nên phân tích lại một truy vấn hoặc sử dụng lại một kế hoạch hiện có cho một giá trị biến liên kết mới. Đối với một nhà phát triển, nó có thể có ý nghĩa để đối phó với sự phức tạp này. Hoặc có thể có ý nghĩa khi buộc một đường dẫn khác khi tôi có thêm thông tin về dữ liệu của mình sẽ trông như thế nào so với trình tối ưu hóa.

Từ quan điểm phức tạp về mã, cũng có rất nhiều lần nó có ý nghĩa hoàn hảo để có nghĩa đen trong các câu lệnh SQL. Ví dụ: nếu bạn có một zip_codecột có mã zip 5 ký tự và đôi khi có thêm 4 chữ số, sẽ rất hợp lý khi làm một cái gì đó như

SELECT substr( zip_code, 1, 5 ) zip,
       substr( zip_code, 7, 4 ) plus_four

thay vì chuyển vào 4 tham số riêng cho các giá trị số. Đây không phải là những thứ sẽ thay đổi, vì vậy làm cho chúng liên kết các biến chỉ phục vụ để làm cho mã có khả năng khó đọc hơn và tạo ra khả năng ai đó sẽ liên kết các tham số theo thứ tự sai và kết thúc với một lỗi.


12

Việc tiêm SQL xảy ra khi một truy vấn được xây dựng bằng cách ghép văn bản từ một nguồn không đáng tin cậy và không có giá trị với các phần khác của truy vấn. Mặc dù điều đó thường xảy ra với chuỗi ký tự, nhưng đó không phải là cách duy nhất có thể xảy ra. Một truy vấn cho các giá trị số có thể lấy một chuỗi do người dùng nhập ( được cho là chỉ chứa các chữ số) và nối với các tài liệu khác để tạo thành một truy vấn mà không có dấu ngoặc kép thường được liên kết với chuỗi ký tự; mã được tin tưởng quá mức về xác thực phía máy khách có thể có những thứ như tên trường đến từ chuỗi truy vấn HTML. Không có cách nào nhìn vào chuỗi truy vấn SQL có thể thấy nó được tập hợp như thế nào.

Điều quan trọng không phải là liệu một câu lệnh SQL có chứa chuỗi ký tự chuỗi hay không, mà là một chuỗi có chứa bất kỳ chuỗi ký tự nào từ các nguồn không đáng tin hay không , và xác thực cho điều đó sẽ được xử lý tốt nhất trong thư viện xây dựng các truy vấn. Nói chung, C # không có cách nào để viết mã sẽ cho phép một chuỗi bằng chữ nhưng sẽ không cho phép các loại biểu thức chuỗi khác, nhưng người ta có thể có quy tắc thực hành mã hóa yêu cầu các truy vấn được xây dựng bằng lớp xây dựng truy vấn thay vì nối chuỗi và bất kỳ ai chuyển một chuỗi không theo nghĩa đen đến trình xây dựng truy vấn phải chứng minh hành động đó.


1
Là một xấp xỉ cho "nó là một nghĩa đen", bạn có thể kiểm tra xem chuỗi có được thực hiện hay không.
CodeInChaos

1
@CodesInChaos: Đúng và thử nghiệm như vậy có thể đủ chính xác cho mục đích này, miễn là bất kỳ ai có lý do tạo chuỗi trong thời gian chạy đều sử dụng phương thức chấp nhận chuỗi không phải là chữ thay vì thực hiện chuỗi được tạo trong thời gian chạy và sử dụng rằng (đặt cho phương thức chuỗi ký tự không phải là một tên khác sẽ giúp người đánh giá mã dễ dàng kiểm tra tất cả các sử dụng của nó).
supercat

Lưu ý rằng mặc dù không có cách nào để thực hiện điều này trong C #, một số ngôn ngữ khác có các phương tiện có thể thực hiện được (ví dụ mô-đun chuỗi bị nhiễm độc của Perl).
Jules

Ngắn gọn hơn, đây là vấn đề máy khách , không phải vấn đề máy chủ.
Blrfl

7
SELECT count(ID)
FROM posts
WHERE deleted = false

Nếu bạn muốn đưa kết quả của những điều này vào phần cuối của diễn đàn, bạn sẽ cần thêm một tham số giả chỉ để nói sai mỗi lần. Hoặc lập trình viên ngây thơ tìm kiếm cách vô hiệu hóa cảnh báo đó và sau đó tiếp tục.

Bây giờ bạn có thể nói rằng bạn sẽ thêm một ngoại lệ cho enums nhưng điều đó chỉ mở lại lỗ hổng (mặc dù nhỏ hơn). Chưa kể mọi người trước tiên cần được giáo dục để không sử dụng varcharscho những người đó.

Vấn đề thực sự của tiêm là lập trình xây dựng chuỗi truy vấn. Giải pháp cho điều đó là một cơ chế thủ tục được lưu trữ và thực thi việc sử dụng nó hoặc một danh sách trắng các truy vấn được phép.


2
Nếu giải pháp của bạn cho "mọi thứ quá dễ quên - hoặc không biết ngay từ đầu - sử dụng truy vấn tham số" là "làm cho mọi người nhớ - và biết ở nơi đầu tiên - sử dụng các procs được lưu trữ", thì bạn Tôi đang thiếu toàn bộ điểm của câu hỏi.
Mason Wheeler

5
Tôi đã thấy SQL tiêm thông qua các thủ tục được lưu trữ tại nơi làm việc của tôi. Nó chỉ ra các thủ tục lưu trữ bắt buộc cho tất cả mọi thứ là BAD. Luôn có 0,5% đó là các truy vấn động thực sự (bạn không thể tham số toàn bộ mệnh đề where, chứ đừng nói đến việc tham gia bảng).
Joshua

Trong ví dụ trong câu trả lời này, bạn có thể thay thế deleted = falsebằng NOT deleted, điều này tránh được nghĩa đen. Nhưng điểm chung là hợp lệ.
psmears

5

TL; DR : Bạn sẽ phải hạn chế tất cả các nghĩa đen, không chỉ những từ trong WHEREmệnh đề. Vì lý do tại sao họ không, nó cho phép cơ sở dữ liệu vẫn tách rời khỏi các hệ thống khác.

Thứ nhất, tiền đề của bạn là thiếu sót. Bạn muốn hạn chế chỉ WHEREcác mệnh đề, nhưng đó không phải là nơi duy nhất người dùng nhập liệu có thể đi. Ví dụ,

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Điều này cũng dễ bị tổn thương khi tiêm SQL:

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) FROM item; DROP TABLE user_info; SELECT CASE(WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Vì vậy, bạn không thể chỉ giới hạn nghĩa đen trong WHEREmệnh đề. Bạn phải hạn chế tất cả các chữ.

Bây giờ chúng tôi còn lại với câu hỏi, "Tại sao lại cho phép chữ?" Hãy ghi nhớ điều này: trong khi cơ sở dữ liệu quan hệ được sử dụng bên dưới một ứng dụng được viết bằng ngôn ngữ khác với tỷ lệ lớn thời gian, không có yêu cầu nào bạn phải sử dụng mã ứng dụng để sử dụng cơ sở dữ liệu. Và ở đây chúng tôi có một câu trả lời: bạn cần chữ để viết mã. Sự thay thế duy nhất khác là yêu cầu tất cả các mã được viết bằng một số ngôn ngữ độc lập với cơ sở dữ liệu. Vì vậy, có chúng cung cấp cho bạn khả năng viết "mã" (SQL) trực tiếp trong cơ sở dữ liệu. Đây là một sự tách rời có giá trị, và nó sẽ là không thể nếu không có chữ. (Hãy thử viết bằng ngôn ngữ yêu thích của bạn đôi khi không có chữ. Tôi chắc chắn bạn có thể tưởng tượng điều này sẽ khó khăn như thế nào.)

Như một ví dụ phổ biến, nghĩa đen thường được sử dụng trong quần thể bảng liệt kê giá trị / tra cứu:

CREATE TABLE user_roles (role_id INTEGER, role_name VARCHAR(50));
INSERT INTO user_roles (1, 'normal');
INSERT INTO user_roles (2, 'admin');
INSERT INTO user_roles (3, 'banned');

Nếu không có chúng, bạn sẽ cần phải viết mã bằng ngôn ngữ lập trình khác chỉ để điền vào bảng này. Khả năng làm như vậy trực tiếp trong SQL là có giá trị .

Sau đó, chúng tôi lại có thêm một câu hỏi: tại sao các thư viện khách ngôn ngữ lập trình lại không làm điều đó? Và ở đây chúng tôi có một câu trả lời rất đơn giản: họ sẽ thực hiện lại toàn bộ trình phân tích cơ sở dữ liệu cho từng phiên bản cơ sở dữ liệu được hỗ trợ . Tại sao? Bởi vì không có cách nào khác để đảm bảo bạn đã tìm thấy mọi nghĩa đen. Biểu thức thông thường không đủ. Ví dụ: phần này chứa 4 chữ riêng biệt trong PostgreSQL:

SELECT $lit1$I'm a literal$lit1$||$lit2$I'm another literal $$ with nested string delimiters$$ $lit2$||'I''m ANOTHER literal'||$$I'm the last literal$$;

Cố gắng làm điều đó sẽ là một cơn ác mộng bảo trì, đặc biệt là vì cú pháp hợp lệ thường thay đổi giữa các bản phát hành cơ sở dữ liệu chính.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.