Security & Code Scanning: AI phát hiện lỗ hổng bảo mật (OWASP) ngay khi code
7/19/2026 · 12p đọc
title: "Security & Code Scanning: AI phát hiện lỗ hổng bảo mật (OWASP) ngay khi code"
series: "AI-Native Solutions Architect: Từ Coder đến Kiến trúc sư AI"
season: "Season 1 — AI-Native Coding & Tooling"
order: 8
audience: "Software Engineer hướng tới Solutions Architect"
reading_time: "12 phút"
tags: ["security", "owasp", "sast", "code-review", "ai-native", "shift-left-security", "sql-injection"]
Security & Code Scanning: AI phát hiện lỗ hổng bảo mật (OWASP) ngay khi code
Một PR thêm endpoint tìm kiếm khách hàng theo tên công ty. Reviewer đọc qua logic nghiệp vụ, thấy ổn, approve. Ba tháng sau, security audit cuối dự án (hoặc tệ hơn: một báo cáo bug bounty) chỉ ra rằng endpoint đó nối chuỗi trực tiếp input người dùng vào câu SQL. Lỗi đã nằm trong production ba tháng, đã qua ít nhất hai lần release, và giờ phải: patch khẩn cấp, đánh giá phạm vi dữ liệu có thể đã bị truy cập, viết báo cáo incident, và trong trường hợp xấu là thông báo cho khách hàng bị ảnh hưởng.
Đây không phải kịch bản hiếm. Phần lớn codebase thực chiến không có SAST (Static Application Security Testing) chạy trên mỗi PR — hoặc có nhưng chỉ chạy ở CI cuối cùng, sau khi code đã merge, đôi khi bị team bỏ qua vì "false positive nhiều quá, tắt bớt rule đi". Kết quả: lỗ hổng bảo mật phổ biến — SQL injection, XSS, broken access control, insecure deserialization — được phát hiện muộn nhất có thể, ở giai đoạn chi phí sửa cao nhất: sau khi code đã chạy ở nhiều môi trường, đã có test phụ thuộc vào hành vi cũ, và trong trường hợp tệ nhất là sau khi đã bị khai thác.
Bài này không nói AI sẽ thay bạn làm security audit. Nó nói về một kỹ thuật cụ thể: dùng AI như một lớp review bảo mật chạy ngay trong lúc bạn code — trước khi commit, trước khi PR, trước khi SAST tool chuyên dụng kịp chạm vào. Rẻ hơn, nhanh hơn, nhưng có giới hạn rõ ràng mà một Solutions Architect cần biết để không đặt niềm tin sai chỗ.
Vấn đề
Ba lý do khiến lỗ hổng bảo mật hay lọt qua giai đoạn viết code:
1. Áp lực tốc độ đè lên awareness. Khi đang tập trung làm cho tính năng chạy đúng, developer hiếm khi tự hỏi "input này có thể bị inject không", "user hiện tại có được phép truy cập resource này không". Tư duy an ninh là một context riêng, và context-switching giữa "làm cho chạy" và "làm cho an toàn" tốn effort — nên thường bị bỏ qua cho đến review, hoặc bỏ qua luôn.
2. AI code generation làm vấn đề nặng hơn, không nhẹ hơn. Đây là điểm quan trọng cho một team đang dùng AI-assisted coding: LLM được train trên lượng lớn code có sẵn trên GitHub, bao gồm cả code có lỗ hổng. Khi bạn prompt "viết endpoint tìm kiếm user theo tên", AI hoàn toàn có thể generate ra query nối chuỗi trực tiếp — vì đó là pattern phổ biến, đơn giản, và cực kỳ hay gặp trong tutorial/blog code chất lượng thấp. AI tăng tốc độ viết code, nhưng không tự động tăng tốc độ viết code an toàn. Nếu bạn không yêu cầu review bảo mật, AI không chủ động cảnh báo.
3. SAST/DAST chuyên dụng chạy đúng lúc nhưng không đúng "điểm chi phí thấp nhất". Semgrep, SonarQube, Snyk Code, CodeQL — các công cụ này rất mạnh, deterministic, tích hợp CI/CD tốt. Nhưng chúng thường chạy ở PR hoặc pipeline, tức là sau khi code đã viết xong, đôi khi sau khi đã có 2-3 commit chồng lên nhau. Chi phí sửa một lỗi phát hiện lúc đang gõ code khác hẳn chi phí sửa sau khi đã merge, đã có test phụ thuộc, đã có người khác build tiếp lên trên.
Đây chính là lý do khái niệm "shift-left security" tồn tại từ trước AI: dịch chuyển kiểm tra bảo mật càng sớm càng tốt trong vòng đời phát triển. AI-assisted coding chỉ làm kỹ thuật này khả thi rẻ hơn — vì trước đây "review bảo mật ngay lúc code" cần một security engineer ngồi cạnh, giờ có thể là một prompt.
Kỹ thuật cốt lõi
Ý tưởng: dùng AI (trong IDE — Cursor, Copilot Chat, Claude Code, hoặc bất kỳ trợ lý code nào có khả năng đọc ngữ cảnh file) như một security review layer chạy tại thời điểm code, có framework tham chiếu rõ ràng là OWASP Top 10, thay vì hỏi chung chung "code này có an toàn không".
Điểm mấu chốt: AI không thay thế SAST/DAST, nó bổ trợ ở một tầng khác của pipeline shift-left.
flowchart LR
A[Viết code] --> B["AI review theo OWASP<br/>(ngay khi code, tại IDE)"]
B --> C[Commit]
C --> D["SAST tự động<br/>(Semgrep/CodeQL/SonarQube — CI)"]
D --> E[Code Review con người]
E --> F[Merge]
F --> G["DAST / Penetration Test<br/>(staging/pre-prod)"]
G --> H[Production]
style B fill:#2d5,stroke:#333,stroke-width:1px
style D fill:#59c,stroke:#333,stroke-width:1px
style G fill:#c55,stroke:#333,stroke-width:1px
Ba tầng này khác nhau về bản chất, không thể thay thế nhau:
| Tầng | Công cụ | Điểm mạnh | Giới hạn |
|---|---|---|---|
| AI review lúc code | Claude/Copilot/Cursor + prompt OWASP | Nhanh, có ngữ cảnh nghiệp vụ, phát hiện sớm nhất, không cần cấu hình | Không deterministic, có thể bỏ sót, phụ thuộc chất lượng prompt |
| SAST tự động | Semgrep, CodeQL, SonarQube, Snyk Code | Deterministic, quét toàn bộ codebase, tích hợp CI, có rule engine đã kiểm chứng | Không hiểu ngữ cảnh nghiệp vụ, nhiều false positive, không bắt được business logic flaw |
| DAST / Pentest | OWASP ZAP, Burp Suite, pentest thủ công | Kiểm tra hành vi runtime thật, phát hiện lỗ hổng chain nhiều bước | Chạy muộn (staging/pre-prod), chi phí cao, cần chuyên gia |
Vì sao dùng OWASP Top 10 làm khung tham chiếu thay vì hỏi AI review chung chung? Vì nó ép AI trả lời có cấu trúc, theo danh mục đã được cộng đồng bảo mật kiểm chứng qua nhiều năm (Injection, Broken Access Control, Cryptographic Failures, Security Misconfiguration, v.v.), thay vì trả lời cảm tính "code trông ổn". Khi bạn chỉ định rõ danh mục cần soi (ví dụ: Injection và Broken Access Control), AI tập trung tìm đúng loại lỗi đó thay vì lướt qua chung chung.
Thực hành
Prompt mẫu: review code theo OWASP Top 10
Đây là prompt template dùng được ngay — dán vào Claude Code, Cursor Chat, hoặc bất kỳ AI coding assistant nào, kèm đoạn code cần review:
Bạn là một Security Engineer đang review code trước khi merge.
Review đoạn code dưới đây theo khung OWASP Top 10 (2021), đặc biệt chú ý:
- A01: Broken Access Control
- A03: Injection (SQL, NoSQL, Command, LDAP...)
- A02: Cryptographic Failures
- A05: Security Misconfiguration
Với MỖI lỗ hổng tìm được, trả lời theo format:
1. Vị trí (số dòng hoặc tên function)
2. Loại lỗ hổng (map vào đúng mã OWASP, ví dụ A03:2021)
3. Mức độ nghiêm trọng: Critical / High / Medium / Low
4. Kịch bản khai thác cụ thể (input mẫu kẻ tấn công sẽ dùng)
5. Cách sửa — kèm code fix cụ thể, không mô tả chung chung
Nếu không tìm thấy lỗ hổng nào trong một danh mục, ghi rõ "Không phát hiện" —
không được bỏ qua danh mục mà không nói gì.
Ngữ cảnh: đây là code NestJS + TypeORM (MySQL) trong module CRM quản lý khách hàng.
Chỉ review đoạn code sau, không giả định phần code không được cung cấp:
---
[DÁN CODE CẦN REVIEW VÀO ĐÂY]
---
Ba chi tiết quan trọng khiến prompt này hiệu quả hơn "review code này giúp tôi":
- Chỉ định rõ danh mục OWASP cần soi — ép AI không lướt qua hời hợt.
- Yêu cầu format cố định (vị trí, mã OWASP, mức độ, kịch bản khai thác, fix) — buộc AI đưa ra bằng chứng cụ thể thay vì nhận định chung chung "có vẻ ổn".
- Yêu cầu khai báo "Không phát hiện" cho danh mục không có lỗi — tránh AI im lặng bỏ qua phần khó, tạo cảm giác an toàn giả.
Ví dụ thật: SQL Injection trong code tìm kiếm khách hàng
Giả sử bạn (hoặc AI) vừa viết endpoint tìm khách hàng theo tên công ty trong một service NestJS + TypeORM:
// customers.service.ts — CÓ LỖ HỔNG, KHÔNG DÙNG TRONG PRODUCTION
import { Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
@Injectable()
export class CustomersService {
constructor(private readonly dataSource: DataSource) {}
async searchByCompanyName(companyName: string) {
// Nối chuỗi trực tiếp input người dùng vào câu SQL — SQL Injection
const query = `
SELECT id, name, email, phone, workspace_id
FROM customers
WHERE company_name LIKE '%${companyName}%'
`;
return this.dataSource.query(query);
}
}
Với input như companyName = "x' UNION SELECT id, email, password, NULL, NULL FROM users -- ", câu query thực thi trở thành:
SELECT id, name, email, phone, workspace_id
FROM customers
WHERE company_name LIKE '%x' UNION SELECT id, email, password, NULL, NULL FROM users -- %'
Kẻ tấn công đọc được bảng users (bao gồm cả cột password nếu không hash đúng cách) thông qua một endpoint tưởng chừng vô hại là "tìm khách hàng theo tên công ty".
Output mẫu khi AI review theo prompt OWASP ở trên (rút gọn để minh hoạ, không phải bịa số liệu — đây là dạng phản hồi thực tế mà một AI review có thể đưa ra khi được prompt đúng cách):
1. Vị trí: CustomersService.searchByCompanyName(), dòng nối chuỗi query
2. Loại lỗ hổng: A03:2021 – Injection (SQL Injection)
3. Mức độ nghiêm trọng: Critical
4. Kịch bản khai thác:
Input companyName = "x' UNION SELECT id, email, password, NULL, NULL FROM users -- "
sẽ khiến câu SQL đọc toàn bộ bảng users, bao gồm password hash,
thông qua một endpoint search công khai. Kẻ tấn công không cần
quyền admin, chỉ cần gọi API search.
5. Cách sửa: dùng parameterized query, KHÔNG nối chuỗi trực tiếp.
Fix bằng parameterized query (dùng dataSource.query với tham số, hoặc tốt hơn — dùng QueryBuilder của TypeORM):
// customers.service.ts — ĐÃ SỬA, dùng parameterized query
import { Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
@Injectable()
export class CustomersService {
constructor(private readonly dataSource: DataSource) {}
async searchByCompanyName(companyName: string) {
// Tham số hoá qua placeholder ? — driver MySQL tự escape giá trị,
// không cho phép input phá vỡ cấu trúc câu SQL
const query = `
SELECT id, name, email, phone, workspace_id
FROM customers
WHERE company_name LIKE ?
`;
return this.dataSource.query(query, [`%${companyName}%`]);
}
}
Hoặc, idiomatic hơn với TypeORM Repository (khuyến nghị cho code NestJS thật trong dự án — tránh raw SQL khi không thật sự cần):
// customers.service.ts — dùng QueryBuilder, an toàn theo mặc định
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Customer } from './entities/customer.entity';
@Injectable()
export class CustomersService {
constructor(
@InjectRepository(Customer)
private readonly customerRepo: Repository<Customer>,
) {}
async searchByCompanyName(companyName: string, workspaceId: string) {
return this.customerRepo
.createQueryBuilder('customer')
.where('customer.company_name LIKE :name', { name: `%${companyName}%` })
.andWhere('customer.workspace_id = :workspaceId', { workspaceId }) // chặn luôn Broken Access Control
.getMany();
}
}
Chú ý ở bản fix thứ hai: prompt ban đầu yêu cầu soi cả A01 (Broken Access Control), nên AI review tốt sẽ không chỉ sửa injection mà còn chỉ ra thiếu điều kiện lọc theo workspace_id — nếu không có, một user ở workspace A có thể tìm thấy khách hàng của workspace B chỉ bằng cách gọi đúng endpoint search. Đây là lý do prompt nên luôn chỉ định nhiều hơn một danh mục OWASP liên quan đến ngữ cảnh, không chỉ hỏi về injection.
Áp dụng vào quy trình team
Cách đưa kỹ thuật này vào thói quen hàng ngày, không phải một lần dùng thử:
- Trước khi mở PR, chạy prompt OWASP review trên diff của mình (
git diffdán vào AI), không đợi reviewer con người phát hiện. - Với module xử lý input trực tiếp từ HTTP request (search, filter, upload, webhook nhận payload ngoài) — luôn review bắt buộc, đây là bề mặt tấn công cao nhất.
- Lưu prompt template này thành snippet dùng chung trong team (ví dụ trong
.cursorruleshoặc file hướng dẫn nội bộ) để mọi người dùng cùng một chuẩn, không mỗi người hỏi một kiểu. - Không tắt SAST tool hiện có vì "đã có AI review rồi" — hai lớp này bổ trợ, không thay thế.
Cạm bẫy thường gặp
1. Coi AI review là đủ, bỏ qua SAST/DAST và pentest thật. AI review dựa trên pattern matching xác suất từ dữ liệu huấn luyện — nó không chạy code, không phân tích data flow toàn bộ codebase như CodeQL, không mô phỏng tấn công runtime như ZAP hay pentest thủ công. Một team nghĩ "đã AI review mỗi PR rồi, không cần SAST nữa" đang đánh đổi một công cụ deterministic, có audit trail, đã kiểm chứng — lấy một công cụ xác suất, không có garantee coverage. Dùng cả hai, không chọn một.
2. AI dễ bỏ sót lỗ hổng logic nghiệp vụ tinh vi (business logic vulnerability). OWASP Top 10 chủ yếu là các pattern kỹ thuật phổ biến (injection, misconfiguration, broken auth) — những thứ xuất hiện nhiều trong dữ liệu huấn luyện nên AI nhận diện tốt. Nhưng lỗ hổng như "API cho phép user thường đổi role field trong request body khi update profile của chính mình, leo thang thành admin" hoặc "endpoint tính giá đơn hàng tin tưởng discount_amount gửi từ client thay vì tính lại ở server" — đây là lỗi đặc thù nghiệp vụ, không phải pattern chung, AI hoàn toàn có thể đọc code và nói "không thấy vấn đề" vì cú pháp hoàn toàn hợp lệ. Loại lỗi này cần người hiểu domain (và thường cả pentest có kịch bản nghiệp vụ cụ thể) mới phát hiện được.
3. Prompt hời hợt cho kết quả hời hợt — và tạo cảm giác an toàn giả. Hỏi "code này có an toàn không" thường nhận câu trả lời chung chung kiểu "nhìn ổn, nhưng nên thêm validation". Nếu team lấy câu trả lời đó làm bằng chứng "đã review bảo mật", rủi ro còn lớn hơn không review — vì tạo cảm giác an tâm sai. Luôn dùng prompt có cấu trúc, chỉ định rõ danh mục OWASP, yêu cầu bằng chứng (kịch bản khai thác cụ thể), và yêu cầu khai báo rõ cả phần "không phát hiện" thay vì im lặng.
🧭 Góc nhìn Solutions Architect
Nếu AI review là lớp phòng thủ sớm nhất trong pipeline của bạn, câu hỏi kiến trúc thật sự là: lớp nào ở phía sau nó sẽ bắt được cái nó bỏ lỡ — và bạn có bằng chứng nào (audit log, coverage report) cho thấy lớp đó thực sự chạy, hay chỉ tồn tại trên giấy? Với một lỗ hổng broken access control xuyên nhiều service (ví dụ service A tin tưởng service B đã kiểm tra quyền), AI review từng file riêng lẻ có đủ ngữ cảnh để thấy toàn cảnh không, hay bạn cần một cơ chế review ở tầng kiến trúc cao hơn?
🔗 Bài viết liên quan
- Automated Unit Testing: AI viết test tự động — một lớp phòng thủ khác chạy sớm trong vòng đời code, bổ trợ cho security review.
- AI-Assisted Debugging: phân tích stack trace & logs — kỹ thuật dùng AI đọc bằng chứng runtime, áp dụng được khi điều tra sự cố bảo mật đã xảy ra.
Bài trước: Automated Unit Testing · Bài tiếp theo: AI for Database Schema Design