산적과 함께 FastAPI 백엔드 확보: 실제 SAST 분석

작성자

카테고리:

← 피드로
DEV Community · MARIELA ESTEFANY RAMOS LOZA · 2026-07-07 개발(SW)

1. Introduction

In modern software development, shipping features fast often comes at the cost of security. Developers write hundreds of lines of code per day, and subtle vulnerabilities can slip through unnoticed — until an attacker finds them first.

This is exactly where SAST (Static Application Security Testing) tools come in. Unlike dynamic analysis, SAST tools scan your source code without executing it, catching security flaws early in the development cycle before they ever reach production.

In this article, I’ll walk you through applying Bandit — one of the most popular open-source SAST tools for Python — to the backend of a real web vulnerability scanner application built with FastAPI. I’ll show you the exact commands I ran, the findings Bandit reported, and how I fixed the most critical vulnerability.

2. Tool Overview: What is Bandit?

Bandit is an open-source SAST tool developed by the PyCQA (Python Code Quality Authority) community. It was originally created at OpenStack and is now one of the most widely used security linters for Python projects.

How it works:
Bandit parses each Python file into an Abstract Syntax Tree (AST) and runs a set of security-focused plugins against the tree nodes. Each plugin maps to a specific vulnerability class (e.g., hardcoded passwords, insecure SSL, dangerous function calls).

Key facts:

  • 🐍 Language: Python only
  • 📦 Install: pip install bandit
  • 📄 Output formats: TXT, JSON, HTML, XML, CSV, SARIF
  • 🔌 IDE integration: VS Code, PyCharm (via plugins)
  • ✅ CI/CD ready: GitHub Actions, GitLab CI, Jenkins
  • 📋 License: Apache 2.0 (free and open-source)

Vulnerability categories covered:

  • Hardcoded credentials and secrets
  • Insecure use of cryptography
  • SQL injection patterns
  • Dangerous function calls (eval, exec, os.system)
  • SSL/TLS misconfigurations
  • Insecure temp file usage
  • Use of assert in production code

3. Target Application

The application analyzed is VulnScan Pro, a web vulnerability scanner platform built as part of a university security engineering project (Course: SI784 – 2026).

Stack:

  • Backend: Python 3.12 + FastAPI
  • Database: SQLite (development) / MySQL (production)
  • Authentication: JWT + bcrypt
  • AI Integration: DeepSeek AI for vulnerability analysis

The backend contains approximately 2,410 lines of Python code spread across these key modules:

File Purpose main.py FastAPI app setup, CORS, middleware auth.py JWT authentication, bcrypt hashing scanner.py Core vulnerability scanning engine (737 lines) models.py SQLAlchemy database models routes/ API route handlers vulnerable_app.py Intentionally vulnerable test application

4. Installation & Setup

Installing Bandit is straightforward with pip:

# Install Bandit
pip install bandit

# Verify installation
bandit --version
# bandit 1.9.4

Enter fullscreen mode Exit fullscreen mode

Optional: Create a Bandit configuration file (.bandit) to skip test directories:

[bandit]
exclude_dirs = ['tests', '__pycache__']
skips = ['B101']

Enter fullscreen mode Exit fullscreen mode

Tip: You can also install Bandit as a pre-commit hook to catch issues before every commit:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/PyCQA/bandit
    rev: 1.9.4
    hooks:
      - id: bandit
        args: ["-ll"]

5. Running the Analysis

I ran Bandit against the entire backend directory using the following commands:

Full scan (all severity levels):

bandit -r ./backend -f txt

Enter fullscreen mode Exit fullscreen mode

Scan targeting medium and high severity only:

bandit -r ./backend -f txt --severity-level medium

Enter fullscreen mode Exit fullscreen mode

Export as HTML report:

bandit -r ./backend -f html -o bandit_report.html

Enter fullscreen mode Exit fullscreen mode

Real output summary from the scan:

Run metrics:
    Total issues (by severity):
        Undefined:  0
        Low:       55
        Medium:     1
        High:       2
    Total issues (by confidence):
        Undefined:  0
        Low:        0
        Medium:     3
        High:      55
    Total lines of code: 2,410
    Total lines skipped (#nosec): 0
    Files skipped: 0

Enter fullscreen mode Exit fullscreen mode

Bandit scanned 2,410 lines of Python code across all backend files and found 58 total issues in under 2 seconds: 2 High, 1 Medium, and 55 Low severity findings.

6. Results & Findings

Summary Table

# Severity Rule ID Description File OWASP Top 10 1 🔴 HIGH B501 SSL cert verification disabled scanner.py:31 A02 – Cryptographic Failures 2 🔴 HIGH B501 SSL cert verification disabled scanner.py:610 A02 – Cryptographic Failures 3 🟡 MEDIUM B108 Insecure temp directory /tmp report_routes.py:17 A05 – Security Misconfiguration 4-58 🟢 LOW B101 assert used in test code tests/*.py

🔴 Finding #1 — HIGH — SSL Certificate Verification Disabled

Rule: B501 request_with_no_cert_validation

File: scanner.py, lines 31 and 610

OWASP Top 10: A02:2021 – Cryptographic Failures

CWE: CWE-295 — Improper Certificate Validation

# VULNERABLE CODE — scanner.py, lines 24-35
def safe_get(url: str, timeout: int = TIMEOUT, allow_redirects: bool = True, **kwargs):
    try:
        return requests.get(
            url,
            timeout=timeout,
            headers=HEADERS_UA,
            allow_redirects=allow_redirects,
            verify=False,   # ← THIS IS THE PROBLEM
            **kwargs
        )
    except RequestException:
        return None

Enter fullscreen mode Exit fullscreen mode

verify=False completely disables SSL certificate validation. This means the scanner itself is vulnerable to Man-in-the-Middle (MITM) attacks — an attacker intercepting traffic between the scanner and a target site could inject malicious responses, causing the scanner to report false results or leak sensitive scan data.

🟡 Finding #2 — MEDIUM — Insecure Temp Directory Usage

Rule: B108 hardcoded_tmp_directory

File: routes/report_routes.py, line 17

OWASP Top 10: A05:2021 – Security Misconfiguration

CWE: CWE-377 — Insecure Temporary File

# VULNERABLE CODE — report_routes.py, line 17
REPORTS_DIR = os.getenv("REPORTS_DIR", "/tmp/vulnscan_reports")

Enter fullscreen mode Exit fullscreen mode

Using /tmp as a fallback for storing security reports is a risk. On shared Linux systems, /tmp is world-readable, meaning other processes could read generated vulnerability reports containing sensitive scan results about target systems.

🟢 Findings #4-58 — LOW — assert in Test Code

Rule: B101 assert_used

Files: tests/test_scanner.py

CWE: CWE-703

All 55 Low-severity findings are assert statements in the test suite — which is standard Python testing practice. These are false positives in this context and can be suppressed by configuring Bandit to skip the tests/ directory.

7. Fixing the Critical Vulnerability

Fixing B501 — SSL Certificate Verification

Before (Vulnerable):

def safe_get(url: str, timeout: int = TIMEOUT, allow_redirects: bool = True, **kwargs):
    try:
        return requests.get(
            url,
            timeout=timeout,
            headers=HEADERS_UA,
            allow_redirects=allow_redirects,
            verify=False,   # SSL verification disabled — MiTM risk!
            **kwargs
        )
    except RequestException:
        return None

Enter fullscreen mode Exit fullscreen mode

After (Fixed):

import certifi

def safe_get(url: str, timeout: int = TIMEOUT, allow_redirects: bool = True, **kwargs):
    """
    Safe HTTP GET with proper SSL verification using Mozilla's CA bundle.
    """
    try:
        return requests.get(
            url,
            timeout=timeout,
            headers=HEADERS_UA,
            allow_redirects=allow_redirects,
            verify=certifi.where(),   # Use trusted Mozilla CA bundle
            **kwargs
        )
    except requests.exceptions.SSLError:
        # SSL error — log it, don't silently ignore
        return None
    except RequestException:
        return None

Enter fullscreen mode Exit fullscreen mode

# Install certifi
pip install certifi

# Re-run Bandit to confirm the fix
bandit scanner.py --severity-level medium
# Result: No issues identified. ✅

Enter fullscreen mode Exit fullscreen mode

Why certifi.where()? The certifi package provides Mozilla’s curated collection of root certificates, ensuring reliable SSL validation across all platforms without depending on the OS certificate store.

Fixing B108 — Temp Directory

# BEFORE
REPORTS_DIR = os.getenv("REPORTS_DIR", "/tmp/vulnscan_reports")

# AFTER — use an application-specific directory with restricted permissions
import tempfile, pathlib

DEFAULT_REPORTS_DIR = pathlib.Path(__file__).parent / "reports"
REPORTS_DIR = pathlib.Path(os.getenv("REPORTS_DIR", str(DEFAULT_REPORTS_DIR)))
REPORTS_DIR.mkdir(mode=0o700, parents=True, exist_ok=True)  # Owner-only permissions

Enter fullscreen mode Exit fullscreen mode

8. Pros & Cons of Bandit

✅ Strengths

Feature Detail Zero configuration Works instantly with pip install bandit Extremely fast Analyzed 2,410 LOC in under 2 seconds SARIF output Native GitHub Code Scanning integration Granular control Skip specific rules, files, or directories Educational Every finding links to CWE, OWASP, and detailed docs

⚠️ Limitations

Limitation Impact Python only Cannot analyze JS, TypeScript, Go, etc. High Low-severity noise 55 of 58 findings were test assert statements No data flow tracking Cannot follow tainted data across function calls No dependency analysis Won’t catch CVEs in requirements.txt packages No runtime context May flag dead code or unreachable paths

Effective false positive rate: ~95% of Low findings in this scan were test-file noise. With a proper .bandit config, the actionable issue count drops to exactly 3 real findings.

9. Conclusion

Applying Bandit to VulnScan Pro’s FastAPI backend in under 5 minutes revealed 3 real security issues in 2,410 lines of code — most critically, an SSL certificate verification bypass (verify=False) in the core scanning engine that could expose the tool to MITM attacks.

The irony here is significant: a security scanner was itself vulnerable to the kind of attack it was designed to detect. This underscores why SAST is essential even for security-focused projects.

Key lessons from this analysis:

  1. verify=False in requests is a red flag — always use certifi or your system CA bundle
  2. Never use /tmp as a default for sensitive data without explicit permission restrictions
  3. Configure Bandit to skip test directories to eliminate false positive noise

For any Python project, adding Bandit to your CI/CD pipeline takes less than 10 minutes and can catch critical security issues automatically on every push:

# .github/workflows/sast.yml
- name: Run Bandit SAST
  run: bandit -r ./backend --severity-level medium -f sarif -o bandit.sarif

- name: Upload SARIF
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: bandit.sarif

Enter fullscreen mode Exit fullscreen mode

SAST is not a silver bullet, but it is the cheapest and fastest layer of your DevSecOps defense-in-depth strategy. Ship secure code — scan before you push.

Written by Mariela Ramos · SI784 Security Engineering · 2026

Tools: Bandit 1.9.4 · Python 3.12 · FastAPI 0.111 · OWASP Top 10 2021

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다