Nền tảng An toàn Web & Cơ chế Trình duyệt
17/20
Bài 17 / 20~14 phútNhập môn Threat Modeling & OWASP Sơ cấpMiễn phí… lượt xem

Classic SQLi: Vì sao chuỗi biến thành câu lệnh trong Lexer

Mổ xẻ quá trình Lexer và Parser phân tích câu lệnh SQL: vì sao việc ghép chuỗi khiến dữ liệu người dùng biến thành cấu trúc cú pháp của câu truy vấn.

TL;DR: SQL Injection thường được giới thiệu một cách hời hợt qua payload kinh điển ' OR 1=1 --, nhưng ít ai giải thích điều gì thực sự diễn ra bên trong engine cơ sở dữ liệu. Khi nhận một câu truy vấn thô được tạo bằng cách nối chuỗi, bộ tách từ (Lexer) và bộ phân tích cú pháp (Parser) của cơ sở dữ liệu không thể phân biệt đâu là ý đồ của lập trình viên và đâu là dữ liệu của người dùng. Ký tự nháy đơn đóng chuỗi dữ liệu sớm, biến chuỗi ký tự theo sau thành các token từ khóa (keywords) và toán tử điều khiển cây cú pháp trừu tượng (AST). Cơ chế Prepared Statements giải quyết triệt để vấn đề này bằng cách biên dịch cây AST trước, cố định cấu trúc câu lệnh và đối xử với mọi tham số truyền vào thuần túy là dữ liệu vô hại.

1. Database Engine xử lý và biên dịch câu lệnh SQL theo chu trình nào?

Khi ứng dụng backend (viết bằng Go, Node.js, hay Python như đã làm quen ở 03-curl-va-python-automation-co-ban) gửi một chuỗi văn bản truy vấn sang hệ quản trị cơ sở dữ liệu (PostgreSQL, MySQL), máy chủ cơ sở dữ liệu không thực thi ngay lập tức. Nó phải đưa chuỗi qua một chuỗi xử lý tương tự như một trình biên dịch ngôn ngữ:

  1. Bộ tách từ (Lexical Analyzer / Lexer): Đọc chuỗi ký tự thô và nhóm chúng thành các Tokens có ý nghĩa (Từ khóa như SELECT, FROM, WHERE; Tên bảng/cột; Toán tử =, AND, OR; Hằng số chuỗi hoặc số).
  2. Bộ phân tích cú pháp (Syntax Parser): Kiểm tra xem các token có tuân thủ đúng ngữ pháp SQL hay không và xây dựng Cây Cú pháp Trừu tượng (Abstract Syntax Tree - AST).
  3. Bộ tối ưu hóa truy vấn (Query Optimizer): Lập kế hoạch thực thi (Execution Plan) hiệu quả nhất (dùng Index quét bảng hay tuần tự).
  4. Bộ máy thực thi (Execution Engine): Truy xuất và thao tác các trang dữ liệu trên đĩa/RAM theo AST đã tối ưu.

Cơ chế Lexer và AST trong SQL Injection

Hình 3.3: Dữ liệu người dùng chứa dấu nháy đơn làm thay đổi cấu trúc cây Abstract Syntax Tree (AST) trong khâu phân tích cú pháp.

2. Vì sao ký tự nháy đơn phá vỡ cây cú pháp trừu tượng (AST)?

Hãy quan sát một đoạn mã xác thực đăng nhập backend sai lầm điển hình bằng cách ghép chuỗi:

JavaScript
// Ghep chuoi truc tiep tao cau lenh SQL
const query = "SELECT * FROM users WHERE username = '" + req.body.username + "' AND password = '" + req.body.password + "'";

Trường hợp người dùng hợp lệ

Khi người dùng nhập username: alice và password: secret123:

SQL
SELECT * FROM users WHERE username = 'alice' AND password = 'secret123'

Lexer phân tách câu lệnh thành các token:

  • Keywords: SELECT, FROM, WHERE, AND
  • Identifiers: users, username, password
  • Literals (Dữ liệu chuỗi thuần túy): 'alice', 'secret123'

Cây AST được hình thành với mệnh đề điều kiện: username bằng 'alice' VÀ password bằng 'secret123'.

Trường hợp kẻ tấn công can thiệp

Nếu kẻ tấn công nhập vào ô username chuỗi: ' OR 1=1 --: Chuỗi hoàn chỉnh được gửi tới database là:

SQL
SELECT * FROM users WHERE username = '' OR 1=1 --' AND password = '...'

Lúc này, Lexer nhìn thấy dấu nháy đơn đầu tiên và hiểu rằng: "Hằng số chuỗi cho cột username đã kết thúc tại đây!". Ký tự tiếp theo là chữ OR, Lexer nhận diện nó không phải là dữ liệu chuỗi mà là Keyword toán tử logic OR! Tiếp đến 1=1 trở thành một biểu thức so sánh luôn đúng (TRUE). Đặc biệt, ký tự comment -- ra lệnh cho Lexer bỏ qua toàn bộ phần đuôi còn lại của câu lệnh.

[!NOTE] Độ ưu tiên toán tử trong AST Trong chuẩn SQL, toán tử AND có độ ưu tiên cao hơn OR (AND binds tighter than OR). Nếu không dùng ký tự comment -- để cắt bỏ phần sau, câu lệnh WHERE username = '' OR '1'='1' AND password = '...' sẽ được AST gom nhóm thành WHERE (username = '') OR ('1'='1' AND password = '...'). Khi mật khẩu không khớp, nhánh AND trả về FALSE, khiến phép OR cũng thất bại! Nhờ có --, nhánh kiểm tra mật khẩu bị cắt bỏ (prune) triệt để khỏi cây AST.

Kết quả là cây AST đã bị biến dạng hoàn toàn:

  • Thay vì kiểm tra cặp username & password, điều kiện truy vấn chỉ còn: username rỗng HOẶC 1 bằng 1 (luôn luôn TRUE).
  • Database trả về bản ghi người dùng đầu tiên trong bảng (thường là tài khoản quản trị viên admin). Kẻ tấn công đăng nhập thành công mà không cần mật khẩu!

Theo mô hình 01-threat-modeling-va-mo-hinh-stride, đây là sự kết hợp nguy hiểm giữa Tampering (sửa đổi logic câu lệnh) và Elevation of Privilege (chiếm quyền tài khoản quản trị).

Hãy thử kiểm tra hiểu biết của bạn về hành vi của bộ tách từ Lexer:

Dự đoán & Phản xạ (Predict)

Giả sử câu truy vấn được viết: SELECT * FROM products WHERE id = USER_INPUT (id là kiểu số nguyên INTEGER). Hacker nhập giá trị USER_INPUT là: 1 OR 1=1. Kịch bản nào sau đây diễn ra trong Database?

3. Bản chất kỹ thuật của payload kinh điển ' OR '1'='1 là gì?

Trong các bài viết bảo mật cơ bản, bạn thường thấy payload ' OR '1'='1 hoặc ' OR 1=1 --. Hãy giải phẫu từng thành phần của payload này dưới góc độ cú pháp:

  1. Dấu nháy đơn đầu tiên ('): Đóng chuỗi literal hiện thời do lập trình viên mở ra trong mã nguồn.
  2. Toán tử OR: Thêm một nhánh điều kiện logic mới vào mệnh đề WHERE.
  3. Biểu thức 1=1 (hoặc '1'='1'): Tạo ra một mệnh đề luôn luôn trả về giá trị chân lý TRUE. Do quy tắc của phép tính OR, toàn bộ biểu thức A OR TRUE sẽ luôn đánh giá là TRUE với mọi bản ghi.
  4. Ký hiệu bình luận (-- hoặc /*): Hủy bỏ (comment out) toàn bộ phần còn lại của câu lệnh SQL gốc (ví dụ mệnh đề kiểm tra password phía sau), giúp câu lệnh không bị lỗi cú pháp do thừa dấu nháy.
SQL
-- Vi du cau lenh bi vo hieu hoa phan sau boi dau comment
SELECT * FROM users WHERE username = 'admin' --' AND password = 'xxx'

4. Prepared Statements bảo vệ câu truy vấn ở tầng Lexer ra sao?

Tại sao các hàm lọc từ khóa như xóa chữ OR hay xóa dấu ' luôn thất bại? Tương tự như XSS, hacker có vô số cách để lách qua (viết hoa thường lộn xộn, dùng comment xen kẽ như /**/OR/**/).

Cách giải quyết triệt để và duy nhất được chấp nhận trong kỹ thuật phần mềm hiện đại là Prepared Statements (Parameterized Queries).

JavaScript
// Su dung Parameterized Query chuan muc voi thu vien pg (Node.js)
const text = 'SELECT * FROM users WHERE username = $1 AND password = $2';
const values = [req.body.username, req.body.password];

const res = await pool.query(text, values);

Cơ chế hoạt động bên trong:

  1. Pha 1: Biên dịch câu lệnh (Prepare): Ứng dụng gửi câu lệnh có chứa các ký tự giữ chỗ ($1, $2 hoặc ?) sang cơ sở dữ liệu. Cơ sở dữ liệu chạy Lexer và Parser để tạo ra cây AST hoàn chỉnh và đóng băng cấu trúc đó lại. Cây cú pháp này quy định cố định: Ở vị trí $1, đây bắt buộc phải là một giá trị dữ liệu (Data Literal).
  2. Pha 2: Thực thi với tham số (Execute): Ứng dụng gửi các giá trị biến [username, password] sang. Lúc này, cơ sở dữ liệu hoàn toàn không chạy lại Lexer hay Parser. Dù giá trị của $1 có chứa bao nhiêu dấu nháy đơn, bao nhiêu chữ OR, hay bao nhiêu ký hiệu --, cơ sở dữ liệu vẫn coi nó là một chuỗi văn bản thuần túy không có khả năng thực thi!

So sánh cây AST giữa câu truy vấn ghép chuỗi bị biến dạng và Prepared Statement cố định an toàn

Hình 3.4: Prepared Statement biên dịch cây AST từ trước và phân tách rạch ròi giữa code và data; dữ liệu truyền sau không bao giờ làm biến dạng cấu trúc lệnh.

5. Tổng kết: Tách biệt tuyệt đối dữ liệu và mã lệnh

Cội nguồn của mọi lỗ hổng Injection (SQL Injection, Command Injection, XSS) đều bắt nguồn từ một lỗi thiết kế duy nhất: Trộn lẫn dữ liệu không tin cậy vào luồng điều khiển mã lệnh.

Khi dữ liệu và mã lệnh được phân tách rõ ràng thông qua hai kênh độc lập (như cơ chế Prepared Statements), lỗ hổng SQL Injection sẽ biến mất hoàn toàn.

Bài tiếp theo: 04-idor-so-cap-va-rbac

Tự kiểm tra
0/6 câu đã trả lời
  1. Q1
    Vì sao việc ghép chuỗi trực tiếp (String Concatenation) trong truy vấn database lại dẫn tới lỗ hổng SQL Injection?
  2. Q2
    Tại sao Prepared Statements (Parameterized Queries) lại ngăn chặn triệt để SQL Injection?
  3. Q3
    Trong payload kinh điển '' OR '1'='1', toán tử 'OR' đóng vai trò kỹ thuật gì?
  4. Q4
    Ký hiệu hai gạch ngang '--' (hoặc '#' trong MySQL) có ý nghĩa gì trong câu lệnh SQL bị tấn công?
  5. Q5
    Đối với trường dữ liệu kiểu số (ví dụ: WHERE id = 10), kẻ tấn công có cần dùng ký tự nháy đơn (') để khai thác SQL Injection không?
  6. Q6
    Tại sao phương pháp lọc từ khóa bằng Regex (Blacklist - ví dụ loại bỏ 'SELECT', 'UNION') lại bị coi là sai lầm nguy hiểm?

Bài này có giúp bạn hiểu bản chất không?

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

IDOR Sơ cấp: Kiểm soát quyền truy cập mức đối tượng