Hướng dẫn sử dụng Bandit: Quét lỗ hổng bảo mật Python (SAST) và tích hợp CI/CD

Code chạy mượt chưa chắc đã an toàn

Nhiều lập trình viên nghĩ rằng tính năng chạy đúng, test case pass 100% nghĩa là xong việc. Nhưng thực tế thì khác. Bạn có thể dựng một cánh cửa đóng mở êm ái, song nếu quên then cài hoặc nhét chìa dưới thảm, kẻ gian vẫn đột nhập dễ dàng mà không cần phá khóa.

Trong các dự án Python, những lỗi ngớ ngẩn xuất hiện rất thường xuyên. Lập trình viên hardcode mật khẩu database vào file cấu hình để debug cho nhanh rồi quên xóa. Có người vẫn băm mật khẩu bằng MD5 vì quen tay. Người khác lại dùng os.system() nối chuỗi trực tiếp từ request của người dùng. Mắt người đọc hàng chục nghìn dòng code mỗi sprint rất khó phát hiện hết.

Theo báo cáo của NIST, chi phí sửa một lỗi bảo mật ở môi trường production cao gấp 30 lần so với lúc đang viết code. Nếu chờ hacker khai thác mới xử lý, cái giá phải trả sẽ rất đắt. Vì vậy, ta cần công cụ tự động soi từng dòng lệnh ngay trên máy local.

Hiểu đúng về SAST và Bandit

SAST là gì?

SAST là viết tắt của Static Application Security Testing (kiểm thử bảo mật ứng dụng tĩnh). Nói một cách đơn giản: công cụ quét toàn bộ mã nguồn của bạn mà không cần chạy chương trình.

Nó đối chiếu từng cấu trúc code với kho mẫu lỗi bảo mật đã biết. Cách tiếp cận này giúp áp dụng triệt để nguyên lý Shift-Left Security. Lỗi được chặn ngay tại IDE của lập trình viên thay vì trôi nổi lên server production.

Cơ chế hoạt động của Bandit

Bandit là bộ scanner bảo mật mã nguồn mở dành riêng cho hệ sinh thái Python. Thay vì chỉ tìm kiếm chuỗi ký tự bằng regex thông thường, Bandit phân tích cú pháp mã nguồn thành cây cú pháp trừu tượng AST (Abstract Syntax Tree). Nhờ hiểu được ngữ cảnh ngữ pháp, Bandit chạy các plugin kiểm tra những nguy cơ phổ biến:

  • B102 / B307 (eval/exec): Chạy chuỗi tùy ý, mở đường cho tấn công thực thi mã độc từ xa (RCE).
  • B602 / B605 (subprocess/shell injection): Gọi lệnh hệ thống với tham số shell=True.
  • B105 / B106 (hardcoded secrets): Để lộ mật khẩu, API token hoặc private key trong code.
  • B303 / B324 (insecure crypto): Dùng thuật toán băm yếu như MD5 hay SHA1 cho mục đích bảo mật.

Thực hành: Cài đặt, quét mã và dựng pipeline CI/CD

Bước 1: Cài đặt Bandit

Cài đặt gói Bandit trực tiếp qua pip trong môi trường ảo của bạn:

pip install bandit

Bước 2: Tạo kịch bản mẫu chứa lỗ hổng bảo mật

Tạo file demo_app.py chứa 3 lỗi kinh điển sau:

import os
import hashlib

# Lỗi 1: Hardcode mật khẩu quản trị trực tiếp trong code
ADMIN_PASSWORD = "SuperSecretPassword123!"

# Lỗi 2: Dùng thuật toán băm MD5 đã bị phá vỡ từ lâu
def hash_password(pwd: str) -> str:
    return hashlib.md5(pwd.encode('utf-8')).hexdigest()

# Lỗi 3: Nối chuỗi gọi lệnh shell, dễ dính Command Injection
def ping_host(host_ip: str) -> None:
    cmd = "ping -c 1 " + host_ip
    os.system(cmd)

if __name__ == "__main__":
    print("Hashed:", hash_password("test"))
    # Hacker có thể chèn chuỗi: 127.0.0.1; rm -rf / hoặc whoami
    ping_host("127.0.0.1; whoami")

Bước 3: Chạy quét mã nguồn

Để quét file đơn lẻ vừa tạo:

bandit demo_app.py

Khi quét toàn bộ thư mục dự án (ví dụ thư mục src/), hãy thêm cờ đệ quy -r:

bandit -r src/

Bandit sẽ phân tích và in kết quả chi tiết kèm vị trí dòng, mức độ nghiêm trọng (Severity: Low, Medium, High) cùng độ tin cậy (Confidence):

  • Dòng 5: Báo lỗi [B105:hardcoded_password_string] (Severity: Low, Confidence: Medium).
  • Dòng 9: Báo lỗi [B303:md5] (Severity: Medium, Confidence: High).
  • Dòng 14: Báo lỗi [B605:start_process_with_a_shell] (Severity: High, Confidence: High).

Xử lý ngoại lệ và xác thực dữ liệu đầu vào

Để khắc phục lỗi Command Injection ở hàm ping_host, bạn cần kiểm tra định dạng IP trước khi gọi subprocess. Biểu thức chính quy (Regex) là giải pháp phổ biến nhất trong trường hợp này.

Khi cần kiểm tra nhanh tính chính xác của regex pattern trước khi đưa vào code Python, bạn có thể dùng regex tester tại toolcraft.app/vi/tools/developer/regex-tester. Công cụ chạy ngay trên trình duyệt, không cần cài đặt, giúp bạn xác nhận biểu thức hoạt động đúng với cả IPv4 và IPv6.

Nếu gặp trường hợp bắt buộc phải dùng hàm đặc biệt nhưng hoàn toàn an toàn (ví dụ dùng MD5 chỉ để so khớp checksum file tải về, không phải băm mật khẩu), hãy thêm chú thích # nosec ở cuối dòng để Bandit bỏ qua:

# Chỉ tính checksum file tạm, không dùng cho bảo mật
checksum = hashlib.md5(file_chunk).hexdigest()  # nosec B303

Bước 4: Cấu hình quy chuẩn qua file bandit.yaml

Trong các dự án thực tế, bạn nên đặt file bandit.yaml ở thư mục gốc để quản lý rules và loại trừ thư mục tests hoặc môi trường ảo:

# Bỏ qua B101 (assert_used) vì assert thường dùng trong file test
skips: ['B101']
exclude_dirs:
  - 'tests'
  - 'venv'
  - '.venv'

Lệnh chạy kèm file cấu hình:

bandit -c bandit.yaml -r .

Bước 5: Tự động hóa với GitHub Actions

Kỷ luật cá nhân là không đủ khi làm việc nhóm. Giải pháp hiệu quả nhất là dựng cổng chặn tự động trên CI/CD pipeline: mọi Pull Request vi phạm bảo mật mức Medium trở lên đều bị chặn merge.

Tạo file .github/workflows/security-scan.yml với nội dung sau:

name: Python Security Scan

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  bandit-scan:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v4

      - name: Set up Python 3.11
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install Bandit
        run: pip install bandit

      - name: Run Bandit Scan
        # Cờ -ll: Chỉ fail build khi có lỗi từ mức Medium trở lên
        # Cờ -ii: Chỉ xét cảnh báo có độ tin cậy từ Medium trở lên
        # Cờ -x: Loại trừ thư mục không cần quét
        run: bandit -r . -ll -ii -x ./tests,./venv

Mỗi khi có commit hoặc Pull Request mới, GitHub Actions sẽ kích hoạt runner, tải code và chạy Bandit. Nếu phát hiện lỗ hổng nghiêm trọng, job sẽ trả về mã lỗi khác 0 và báo đỏ. Nhánh chính của dự án luôn được bảo vệ tự động 24/7.

Lời kết

Bảo mật không phải rào cản làm chậm tiến độ dự án. Nó là một phần của tiêu chuẩn code sạch. Tích hợp Bandit vào workflow hàng ngày chỉ mất 10 phút cài đặt, nhưng nó giúp bạn loại bỏ phần lớn sai sót bảo mật cơ bản trước khi code đến tay người dùng thật.

Share: