Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:51:02 +08:00
commit ff1f4bd119
252 changed files with 72682 additions and 0 deletions

View File

@@ -0,0 +1,305 @@
---
name: sast-bandit
description: >
Python security vulnerability detection using Bandit SAST with CWE and OWASP mapping.
Use when: (1) Scanning Python code for security vulnerabilities and anti-patterns,
(2) Identifying hardcoded secrets, SQL injection, command injection, and insecure APIs,
(3) Generating security reports with severity classifications for CI/CD pipelines,
(4) Providing remediation guidance with security framework references,
(5) Enforcing Python security best practices in development workflows.
version: 0.1.0
maintainer: SirAppSec
category: appsec
tags: [sast, bandit, python, vulnerability-scanning, owasp, cwe, security-linting]
frameworks: [OWASP, CWE]
dependencies:
python: ">=3.8"
packages: [bandit]
references:
- https://github.com/PyCQA/bandit
- https://bandit.readthedocs.io/
- https://owasp.org/www-project-top-ten/
---
# Bandit Python SAST
## Overview
Bandit is a security-focused static analysis tool for Python that identifies common security vulnerabilities and coding anti-patterns. It parses Python code into Abstract Syntax Trees (AST) and executes security plugins to detect issues like hardcoded credentials, SQL injection, command injection, weak cryptography, and insecure API usage. Bandit provides actionable reports with severity classifications aligned to industry security standards.
## Quick Start
Scan a Python file or directory for security vulnerabilities:
```bash
# Install Bandit
pip install bandit
# Scan single file
bandit suspicious_file.py
# Scan entire directory recursively
bandit -r /path/to/python/project
# Generate JSON report
bandit -r project/ -f json -o bandit_report.json
# Scan with custom config
bandit -r project/ -c .bandit.yaml
```
## Core Workflow
### Step 1: Install and Configure Bandit
Install Bandit via pip:
```bash
pip install bandit
```
Create a configuration file `.bandit` or `.bandit.yaml` to customize scans:
```yaml
# .bandit.yaml
exclude_dirs:
- /tests/
- /venv/
- /.venv/
- /node_modules/
skips:
- B101 # Skip assert_used checks in test files
tests:
- B201 # Flask app run with debug=True
- B301 # Pickle usage
- B601 # Shell injection
- B602 # Shell=True in subprocess
```
### Step 2: Execute Security Scan
Run Bandit against Python codebase:
```bash
# Basic scan with severity threshold
bandit -r . -ll # Report only medium/high severity
# Comprehensive scan with detailed output
bandit -r . -f json -o report.json -v
# Scan with confidence filtering
bandit -r . -i # Show only high confidence findings
# Exclude specific tests
bandit -r . -s B101,B601
```
### Step 3: Analyze Results
Bandit reports findings with:
- **Issue Type**: Vulnerability category (e.g., hardcoded_password, sql_injection)
- **Severity**: LOW, MEDIUM, HIGH
- **Confidence**: LOW, MEDIUM, HIGH
- **CWE**: Common Weakness Enumeration reference
- **Location**: File path and line number
Example output:
```
>> Issue: [B105:hardcoded_password_string] Possible hardcoded password: 'admin123'
Severity: Medium Confidence: Medium
CWE: CWE-259 (Use of Hard-coded Password)
Location: app/config.py:12
```
### Step 4: Prioritize Findings
Focus remediation efforts using this priority matrix:
1. **Critical**: HIGH severity + HIGH confidence
2. **High**: HIGH severity OR MEDIUM severity + HIGH confidence
3. **Medium**: MEDIUM severity + MEDIUM confidence
4. **Low**: LOW severity OR LOW confidence
### Step 5: Remediate Vulnerabilities
For each finding, consult the bundled `references/remediation_guide.md` for secure coding patterns. Common remediation strategies:
- **Hardcoded Secrets (B105, B106)**: Use environment variables or secret management services
- **SQL Injection (B608)**: Use parameterized queries with SQLAlchemy or psycopg2
- **Command Injection (B602, B605)**: Avoid `shell=True`, use `shlex.split()` for argument parsing
- **Weak Cryptography (B303, B304)**: Replace MD5/SHA1 with SHA256/SHA512 or bcrypt for passwords
- **Insecure Deserialization (B301)**: Avoid pickle, use JSON or MessagePack with schema validation
### Step 6: Integrate into CI/CD
Add Bandit to CI/CD pipelines to enforce security gates:
```yaml
# .github/workflows/security-scan.yml
name: Security Scan
on: [push, pull_request]
jobs:
bandit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Bandit
run: pip install bandit
- name: Run Bandit
run: bandit -r . -f json -o bandit-report.json
- name: Check for high severity issues
run: bandit -r . -ll -f txt || exit 1
```
Use the bundled script `scripts/bandit_analyzer.py` for enhanced reporting with OWASP mapping.
## Security Considerations
- **Sensitive Data Handling**: Bandit reports may contain code snippets with hardcoded credentials. Ensure reports are stored securely and access is restricted. Use `--no-code` flag to exclude code snippets from reports.
- **Access Control**: Run Bandit in sandboxed CI/CD environments with read-only access to source code. Restrict write permissions to prevent tampering with security configurations.
- **Audit Logging**: Log all Bandit executions with timestamps, scan scope, findings count, and operator identity for security auditing and compliance purposes.
- **Compliance**: Bandit supports SOC2, PCI-DSS, and GDPR compliance by identifying security weaknesses. Document scan frequency, remediation timelines, and exception approvals for audit trails.
- **False Positives**: Review LOW confidence findings manually. Use inline `# nosec` comments sparingly and document justifications in code review processes.
## Bundled Resources
### Scripts (`scripts/`)
- `bandit_analyzer.py` - Enhanced Bandit wrapper that parses JSON output, maps findings to OWASP Top 10, generates HTML reports, and integrates with ticketing systems. Use for comprehensive security reporting.
### References (`references/`)
- `remediation_guide.md` - Detailed secure coding patterns for common Bandit findings, including code examples for SQLAlchemy parameterization, secure subprocess usage, and cryptographic best practices. Consult when remediating specific vulnerability types.
- `cwe_owasp_mapping.md` - Complete mapping between Bandit issue codes, CWE identifiers, and OWASP Top 10 categories. Use for security framework alignment and compliance reporting.
### Assets (`assets/`)
- `bandit_config.yaml` - Production-ready Bandit configuration with optimized test selection, exclusion patterns for common false positives, and severity thresholds. Use as baseline configuration for projects.
- `pre-commit-config.yaml` - Pre-commit hook configuration for Bandit integration. Prevents commits with HIGH severity findings.
## Common Patterns
### Pattern 1: Baseline Security Scan
Establish security baseline for legacy codebases:
```bash
# Generate baseline report
bandit -r . -f json -o baseline.json
# Compare future scans against baseline
bandit -r . -f json -o current.json
diff <(jq -S . baseline.json) <(jq -S . current.json)
```
### Pattern 2: Security Gating in Pull Requests
Block merges with HIGH severity findings:
```bash
# Exit with error if HIGH severity issues found
bandit -r . -lll -f txt
if [ $? -ne 0 ]; then
echo "HIGH severity security issues detected - blocking merge"
exit 1
fi
```
### Pattern 3: Progressive Security Hardening
Incrementally increase security standards:
```bash
# Phase 1: Block only CRITICAL (HIGH severity + HIGH confidence)
bandit -r . -ll -i
# Phase 2: Block HIGH severity
bandit -r . -ll
# Phase 3: Block MEDIUM and above
bandit -r . -l
```
### Pattern 4: Suppressing False Positives
Document exceptions inline with justification:
```python
# Example: Suppressing pickle warning for internal serialization
import pickle # nosec B301 - Internal cache, not user input
def load_cache(file_path):
with open(file_path, 'rb') as f:
return pickle.load(f) # nosec B301
```
## Integration Points
- **CI/CD**: Integrate as GitHub Actions, GitLab CI, Jenkins pipeline stage, or pre-commit hook. Use `scripts/bandit_analyzer.py` for enhanced reporting.
- **Security Tools**: Combine with Semgrep for additional SAST coverage, Safety for dependency scanning, and SonarQube for code quality metrics.
- **SDLC**: Execute during development (pre-commit), code review (PR checks), and release gates (pipeline stage). Establish baseline scans for legacy code and enforce strict checks for new code.
- **Ticketing Integration**: Use `scripts/bandit_analyzer.py` to automatically create Jira/GitHub issues for HIGH severity findings with remediation guidance.
## Troubleshooting
### Issue: Too Many False Positives
**Solution**:
1. Use confidence filtering: `bandit -r . -i` (HIGH confidence only)
2. Exclude test files: `bandit -r . --exclude /tests/`
3. Customize `.bandit.yaml` to skip specific tests for known safe patterns
4. Review and suppress with inline `# nosec` comments with justification
### Issue: Scan Performance on Large Codebases
**Solution**:
1. Exclude dependencies: Add `/venv/`, `/.venv/`, `/site-packages/` to `.bandit.yaml` exclude_dirs
2. Use multiprocessing: Bandit automatically parallelizes for directories
3. Scan only changed files in CI/CD: `git diff --name-only origin/main | grep '.py$' | xargs bandit`
### Issue: Missing Specific Vulnerability Types
**Solution**:
1. Check enabled tests: `bandit -l` (list all tests)
2. Ensure tests are not skipped in `.bandit.yaml`
3. Combine with Semgrep for additional coverage (e.g., business logic vulnerabilities)
4. Update Bandit regularly: `pip install --upgrade bandit`
### Issue: Integration with Pre-commit Hooks
**Solution**:
Use the bundled `assets/pre-commit-config.yaml`:
```yaml
- repo: https://github.com/PyCQA/bandit
rev: '1.7.5'
hooks:
- id: bandit
args: ['-ll', '--recursive', '--configfile', '.bandit.yaml']
```
Install hooks: `pre-commit install`
## References
- [Bandit Documentation](https://bandit.readthedocs.io/)
- [Bandit GitHub Repository](https://github.com/PyCQA/bandit)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [CWE Database](https://cwe.mitre.org/)
- [Python Security Best Practices](https://python.readthedocs.io/en/stable/library/security_warnings.html)

View File

@@ -0,0 +1,9 @@
# Assets Directory
Place files that will be used in the output Claude produces:
- Templates
- Configuration files
- Images/logos
- Boilerplate code
These files are NOT loaded into context but copied/modified in output.

View File

@@ -0,0 +1,211 @@
# Bandit Configuration File
# Production-ready configuration for Python security scanning
# Directories to exclude from scanning
exclude_dirs:
# Python environments
- /venv/
- /.venv/
- /env/
- /.env/
- /virtualenv/
- /.virtualenv/
- /site-packages/
- /dist-packages/
# Testing and build artifacts
- /tests/
- /test/
- /.pytest_cache/
- /.tox/
- /build/
- /dist/
- /.eggs/
- /*.egg-info/
# Version control and IDE
- /.git/
- /.svn/
- /.hg/
- /.idea/
- /.vscode/
- /__pycache__/
# Node modules and other language dependencies
- /node_modules/
- /vendor/
# Documentation and examples
- /docs/
- /examples/
# Tests to skip (use sparingly and document reasons)
skips:
# B101: Test for use of assert
# Commonly safe in test files and development code
# Consider keeping this enabled for production code
# - B101
# B311: Standard pseudo-random generators
# Only skip if using for non-security purposes (e.g., data generation)
# NEVER skip for security tokens, session IDs, or cryptographic operations
# - B311
# B404-B412: Import checks
# Skip only if you've reviewed and whitelisted specific imports
# - B404 # subprocess import
# - B405 # xml.etree.cElementTree import
# - B406 # xml.etree.ElementTree import
# - B407 # xml.expat import
# - B408 # xml.dom.minidom import
# - B409 # xml.dom.pulldom import
# - B410 # lxml import
# - B411 # xml.sax import
# - B412 # httpoxy
# Specific tests to run (comment out to run all tests)
# Use this to focus on specific security checks
# tests:
# - B201 # Flask app run with debug=True
# - B301 # Pickle usage
# - B302 # Use of insecure MD2, MD4, MD5, or SHA1 hash
# - B303 # Use of insecure MD2, MD4, MD5, or SHA1 hash
# - B304 # Use of insecure cipher mode
# - B305 # Use of insecure cipher mode
# - B306 # Use of mktemp
# - B307 # Use of eval
# - B308 # Use of mark_safe
# - B310 # Audit URL open for permitted schemes
# - B311 # Standard pseudo-random generators
# - B313 # XML bad element tree
# - B314 # XML bad element tree (lxml)
# - B315 # XML bad element tree (expat)
# - B316 # XML bad element tree (sax)
# - B317 # XML bad element tree (expatreader)
# - B318 # XML bad element tree (expatbuilder)
# - B319 # XML bad element tree (xmlrpc)
# - B320 # XML bad element tree (pulldom)
# - B321 # FTP-related functions
# - B323 # Unverified context
# - B324 # Use of insecure hash functions
# - B601 # Paramiko call with shell=True
# - B602 # subprocess call with shell=True
# - B603 # subprocess without shell equals true
# - B604 # Function call with shell=True
# - B605 # Starting a process with a shell
# - B606 # Starting a process without shell
# - B607 # Starting a process with a partial path
# - B608 # Possible SQL injection
# - B609 # Use of wildcard injection
# - B610 # SQL injection via Django raw SQL
# - B611 # SQL injection via Django extra
# - B701 # jinja2 autoescape false
# - B702 # Test for use of mako templates
# - B703 # Django autoescape false
# Plugin configuration
# Customize individual plugin behaviors
# Shell injection plugin configuration
shell_injection:
# Additional commands to check for shell injection
# Default: ['os.system', 'subprocess.call', 'subprocess.Popen']
no_shell:
- os.system
- subprocess.call
- subprocess.Popen
- subprocess.run
# Hard-coded password plugin configuration
hardcoded_tmp_directory:
# Directories considered safe for temporary files
# tmp_dirs:
# - /tmp
# - /var/tmp
# Output configuration (for reference - set via CLI)
# These are applied at runtime, not in config file
# output_format: json
# output_file: bandit-report.json
# verbose: true
# level: LOW # Report severity: LOW, MEDIUM, HIGH
# confidence: LOW # Report confidence: LOW, MEDIUM, HIGH
# Severity and confidence thresholds
# LOW: Report all issues (default)
# MEDIUM: Report MEDIUM and HIGH severity issues only
# HIGH: Report only HIGH severity issues
# Example usage commands:
#
# Basic scan:
# bandit -r . -c .bandit.yaml
#
# Scan with MEDIUM and HIGH severity only:
# bandit -r . -c .bandit.yaml -ll
#
# Scan with HIGH confidence only:
# bandit -r . -c .bandit.yaml -i
#
# Generate JSON report:
# bandit -r . -c .bandit.yaml -f json -o bandit-report.json
#
# Scan with enhanced analyzer script:
# python scripts/bandit_analyzer.py . --config .bandit.yaml --html report.html
# Progressive security hardening approach:
#
# Phase 1 - Baseline scan (all findings):
# bandit -r . -c .bandit.yaml
#
# Phase 2 - Block CRITICAL (HIGH severity + HIGH confidence):
# bandit -r . -c .bandit.yaml -ll -i
#
# Phase 3 - Block HIGH severity:
# bandit -r . -c .bandit.yaml -ll
#
# Phase 4 - Block MEDIUM and above:
# bandit -r . -c .bandit.yaml -l
#
# Phase 5 - Report all findings:
# bandit -r . -c .bandit.yaml
# Integration with CI/CD:
#
# GitHub Actions:
# - name: Run Bandit
# run: |
# pip install bandit
# bandit -r . -c .bandit.yaml -ll -f json -o bandit-report.json
# bandit -r . -c .bandit.yaml -ll || exit 1
#
# GitLab CI:
# bandit:
# image: python:3.11
# script:
# - pip install bandit
# - bandit -r . -c .bandit.yaml -ll
# allow_failure: false
#
# Jenkins:
# stage('Security Scan') {
# steps {
# sh 'pip install bandit'
# sh 'bandit -r . -c .bandit.yaml -ll -f json -o bandit-report.json'
# }
# }
# False positive handling:
#
# Inline suppression (use sparingly and document):
# import pickle # nosec B403 - Internal use only, not exposed to user input
#
# Line-specific suppression:
# result = eval(safe_expression) # nosec B307
#
# Block suppression:
# # nosec
# import xml.etree.ElementTree as ET
#
# NOTE: Always document WHY you're suppressing a finding
# Security team should review all nosec comments during code review

View File

@@ -0,0 +1,217 @@
# Pre-commit Hook Configuration for Bandit
#
# This configuration integrates Bandit security scanning into your git workflow,
# preventing commits that introduce HIGH severity security vulnerabilities.
#
# Installation:
# 1. Install pre-commit: pip install pre-commit
# 2. Copy this file to .pre-commit-config.yaml in your repository root
# 3. Install hooks: pre-commit install
# 4. (Optional) Run on all files: pre-commit run --all-files
#
# Usage:
# - Hooks run automatically on 'git commit'
# - Bypass hooks temporarily: git commit --no-verify (use sparingly!)
# - Update hooks: pre-commit autoupdate
# - Test hooks: pre-commit run --all-files
repos:
# Python code formatting and linting
- repo: https://github.com/psf/black
rev: 23.12.1
hooks:
- id: black
language_version: python3.11
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/pycqa/flake8
rev: 7.0.0
hooks:
- id: flake8
args: ['--max-line-length=100', '--extend-ignore=E203,W503']
# Security scanning with Bandit
- repo: https://github.com/PyCQA/bandit
rev: '1.7.5'
hooks:
- id: bandit
name: Bandit Security Scan
args:
# Block HIGH and MEDIUM severity issues
- '-ll'
# Recursive scan
- '--recursive'
# Use custom config if present
- '--configfile'
- '.bandit.yaml'
# Skip low-priority tests to reduce false positives
# Uncomment to skip specific tests:
# - '-s'
# - 'B101,B601'
# Only scan Python files
files: \.py$
# Exclude test files (adjust pattern as needed)
exclude: |
(?x)^(
tests/.*|
test_.*\.py|
.*_test\.py
)$
# Alternative Bandit configuration with stricter settings
# Uncomment to use this instead of the above
# - repo: https://github.com/PyCQA/bandit
# rev: '1.7.5'
# hooks:
# - id: bandit
# name: Bandit Security Scan (Strict)
# args:
# # Block only HIGH severity with HIGH confidence (Critical findings)
# - '-ll'
# - '-i'
# - '--recursive'
# - '--configfile'
# - '.bandit.yaml'
# files: \.py$
# Alternative: Run Bandit with custom script for enhanced reporting
# Uncomment to use enhanced analyzer
# - repo: local
# hooks:
# - id: bandit-enhanced
# name: Bandit Enhanced Security Scan
# entry: python scripts/bandit_analyzer.py
# args:
# - '.'
# - '--config'
# - '.bandit.yaml'
# - '--min-priority'
# - '4' # HIGH priority
# language: python
# files: \.py$
# pass_filenames: false
# Additional security and quality checks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
# Prevent commits to main/master
- id: no-commit-to-branch
args: ['--branch', 'main', '--branch', 'master']
# Check for merge conflicts
- id: check-merge-conflict
# Detect private keys
- id: detect-private-key
# Check for large files (>500KB)
- id: check-added-large-files
args: ['--maxkb=500']
# Check YAML syntax
- id: check-yaml
args: ['--safe']
# Check JSON syntax
- id: check-json
# Check for files that would conflict on case-insensitive filesystems
- id: check-case-conflict
# Ensure files end with newline
- id: end-of-file-fixer
# Trim trailing whitespace
- id: trailing-whitespace
# Check for debugger imports
- id: debug-statements
# Dependency security scanning
- repo: https://github.com/Lucas-C/pre-commit-hooks-safety
rev: v1.3.3
hooks:
- id: python-safety-dependencies-check
files: requirements.*\.txt$
# Secret detection
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
exclude: package.lock.json
# Configuration for progressive security hardening
#
# Phase 1: Start with warnings only (for legacy codebases)
# Set bandit args to ['-r', '.', '--configfile', '.bandit.yaml', '--exit-zero']
# This runs Bandit but doesn't block commits
#
# Phase 2: Block HIGH severity only
# Set bandit args to ['-lll', '--recursive', '--configfile', '.bandit.yaml']
#
# Phase 3: Block MEDIUM and HIGH severity
# Set bandit args to ['-ll', '--recursive', '--configfile', '.bandit.yaml']
#
# Phase 4: Block all findings (strictest)
# Set bandit args to ['-l', '--recursive', '--configfile', '.bandit.yaml']
# Bypassing hooks (use judiciously)
#
# Skip all hooks for a single commit:
# git commit --no-verify -m "Emergency hotfix"
#
# Skip specific hook:
# SKIP=bandit git commit -m "Commit message"
#
# Note: All bypasses should be documented and reviewed in code review
# Troubleshooting
#
# Hook fails with "command not found":
# - Ensure pre-commit is installed: pip install pre-commit
# - Reinstall hooks: pre-commit install
#
# Hook fails with import errors:
# - Install dependencies: pip install -r requirements.txt
# - Update hooks: pre-commit autoupdate
#
# Too many false positives:
# - Adjust exclude patterns in .bandit.yaml
# - Use inline # nosec comments with justification
# - Adjust severity threshold in args (-l, -ll, -lll)
#
# Performance issues:
# - Exclude virtual environments in .bandit.yaml
# - Use 'files' and 'exclude' patterns to limit scope
# - Consider running stricter checks only on CI/CD
# CI/CD Integration
#
# Run pre-commit checks in CI/CD:
#
# GitHub Actions:
# - name: Pre-commit checks
# uses: pre-commit/action@v3.0.0
#
# GitLab CI:
# pre-commit:
# image: python:3.11
# script:
# - pip install pre-commit
# - pre-commit run --all-files
#
# Jenkins:
# stage('Pre-commit') {
# steps {
# sh 'pip install pre-commit'
# sh 'pre-commit run --all-files'
# }
# }

View File

@@ -0,0 +1,157 @@
# Bandit Test to CWE and OWASP Mapping
Complete mapping between Bandit test IDs, Common Weakness Enumeration (CWE), and OWASP Top 10 2021 categories.
## Table of Contents
- [Cryptographic Issues](#cryptographic-issues)
- [Injection Vulnerabilities](#injection-vulnerabilities)
- [Security Misconfiguration](#security-misconfiguration)
- [Insecure Deserialization](#insecure-deserialization)
- [Access Control Issues](#access-control-issues)
## Cryptographic Issues
### OWASP A02:2021 - Cryptographic Failures
| Test ID | Description | CWE | Severity |
|---------|-------------|-----|----------|
| B302 | Use of insecure MD2, MD4, MD5, or SHA1 hash function | CWE-327 | MEDIUM |
| B303 | Use of insecure MD2, MD4, or MD5 hash function | CWE-327 | MEDIUM |
| B304 | Use of insecure MD2, MD4, MD5, or SHA1 hash function | CWE-327 | MEDIUM |
| B305 | Use of insecure cipher mode | CWE-327 | MEDIUM |
| B306 | Use of insecure and deprecated function (mktemp) | CWE-377 | MEDIUM |
| B307 | Use of possibly insecure function (eval) | CWE-78 | MEDIUM |
| B311 | Standard pseudo-random generators are not suitable for security | CWE-330 | LOW |
| B323 | Unverified context with insecure default | CWE-327 | MEDIUM |
| B324 | Use of insecure hash functions in hashlib | CWE-327 | HIGH |
| B401 | Use of insecure telnet protocol | CWE-319 | HIGH |
| B402 | Use of insecure FTP protocol | CWE-319 | HIGH |
| B403 | Use of insecure pickle import | CWE-502 | LOW |
| B404 | Use of insecure subprocess import | CWE-78 | LOW |
| B413 | Use of pycrypto | CWE-327 | HIGH |
| B501 | Use of weak cryptographic key | CWE-326 | HIGH |
| B502 | Use of weak SSL/TLS protocol | CWE-327 | HIGH |
| B503 | Use of insecure SSL/TLS cipher | CWE-327 | MEDIUM |
| B504 | SSL with no version specified | CWE-327 | LOW |
| B505 | Use of weak cryptographic hash | CWE-327 | MEDIUM |
**Remediation Strategy**: Replace weak cryptographic algorithms with strong alternatives. Use SHA-256 or SHA-512 for hashing, AES-256 for encryption, and TLS 1.2+ for transport security. For password hashing, use bcrypt, scrypt, or Argon2.
## Injection Vulnerabilities
### OWASP A03:2021 - Injection
| Test ID | Description | CWE | Severity |
|---------|-------------|-----|----------|
| B308 | Use of mark_safe | CWE-80 | MEDIUM |
| B313 | XML bad element tree | CWE-611 | MEDIUM |
| B314 | XML bad element tree (lxml) | CWE-611 | MEDIUM |
| B315 | XML bad element tree (expat) | CWE-611 | MEDIUM |
| B316 | XML bad element tree (sax) | CWE-611 | MEDIUM |
| B317 | XML bad element tree (expatreader) | CWE-611 | MEDIUM |
| B318 | XML bad element tree (expatbuilder) | CWE-611 | MEDIUM |
| B319 | XML bad element tree (xmlrpc) | CWE-611 | HIGH |
| B320 | XML bad element tree (pulldom) | CWE-611 | HIGH |
| B321 | FTP-related functions are being called | CWE-319 | HIGH |
| B405 | XML mini DOM import | CWE-611 | LOW |
| B406 | XML etree import | CWE-611 | LOW |
| B407 | XML expat import | CWE-611 | LOW |
| B408 | XML minidom import | CWE-611 | LOW |
| B410 | XML etree import (lxml) | CWE-611 | LOW |
| B411 | XML standard library imports | CWE-611 | LOW |
| B412 | Deprecated httpoxy vulnerability | CWE-807 | LOW |
| B601 | Paramiko call with shell=True | CWE-78 | HIGH |
| B602 | subprocess call with shell=True | CWE-78 | HIGH |
| B603 | subprocess without shell=True | CWE-78 | LOW |
| B604 | Function call with shell=True | CWE-78 | HIGH |
| B605 | Starting a process with a shell | CWE-78 | HIGH |
| B606 | Starting a process without shell | CWE-78 | LOW |
| B607 | Starting a process with a partial path | CWE-78 | LOW |
| B608 | Possible SQL injection vector through string formatting | CWE-89 | MEDIUM |
| B609 | Use of wildcard injection | CWE-78 | MEDIUM |
| B610 | Potential SQL injection via Django raw SQL | CWE-89 | MEDIUM |
| B611 | Potential SQL injection via Django extra | CWE-89 | MEDIUM |
**Remediation Strategy**: Never concatenate user input into commands, queries, or markup. Use parameterized queries for SQL, safe XML parsers with DTD processing disabled, and avoid `shell=True` in subprocess calls. Use `shlex.split()` for argument parsing.
## Security Misconfiguration
### OWASP A05:2021 - Security Misconfiguration
| Test ID | Description | CWE | Severity |
|---------|-------------|-----|----------|
| B201 | Flask app run with debug=True | CWE-489 | HIGH |
| B310 | Audit URL open for permitted schemes | CWE-939 | MEDIUM |
| B506 | Test for use of yaml load | CWE-20 | MEDIUM |
| B507 | SSH with no host key verification | CWE-295 | MEDIUM |
| B701 | jinja2 autoescape false | CWE-94 | HIGH |
| B702 | Test for use of mako templates | CWE-94 | MEDIUM |
| B703 | Django autoescape false | CWE-94 | MEDIUM |
**Remediation Strategy**: Disable debug mode in production, validate and sanitize all inputs, enable autoescape in template engines, use safe YAML loaders (`yaml.safe_load()`), and enforce strict host key verification for SSH connections.
## Insecure Deserialization
### OWASP A08:2021 - Software and Data Integrity Failures
| Test ID | Description | CWE | Severity |
|---------|-------------|-----|----------|
| B301 | Pickle and modules that wrap it can be unsafe | CWE-502 | MEDIUM |
**Remediation Strategy**: Avoid using pickle for untrusted data. Use JSON, MessagePack, or Protocol Buffers with strict schema validation. If pickle is necessary, implement cryptographic signing and validation of serialized data.
## Access Control Issues
### OWASP A01:2021 - Broken Access Control
| Test ID | Description | CWE | Severity |
|---------|-------------|-----|----------|
| B506 | Test for use of yaml load (arbitrary code execution) | CWE-20 | MEDIUM |
**Remediation Strategy**: Use `yaml.safe_load()` instead of `yaml.load()` to prevent arbitrary code execution. Implement proper access controls and input validation for all YAML processing.
## Hardcoded Credentials
### OWASP A02:2021 - Cryptographic Failures
| Test ID | Description | CWE | Severity |
|---------|-------------|-----|----------|
| B105 | Possible hardcoded password string | CWE-259 | LOW |
| B106 | Possible hardcoded password function argument | CWE-259 | LOW |
| B107 | Possible hardcoded password default argument | CWE-259 | LOW |
**Remediation Strategy**: Never hardcode credentials. Use environment variables, secret management services (HashiCorp Vault, AWS Secrets Manager), or encrypted configuration files with proper key management.
## Priority Matrix
Use this matrix to prioritize remediation efforts:
| Priority | Criteria | Action |
|----------|----------|--------|
| **CRITICAL** | HIGH Severity + HIGH Confidence | Immediate remediation required |
| **HIGH** | HIGH Severity OR MEDIUM Severity + HIGH Confidence | Remediate within 1 sprint |
| **MEDIUM** | MEDIUM Severity + MEDIUM Confidence | Remediate within 2 sprints |
| **LOW** | LOW Severity OR LOW Confidence | Address during refactoring |
| **INFORMATIONAL** | Review only | Document and monitor |
## OWASP Top 10 2021 Coverage
| OWASP Category | Bandit Coverage | Notes |
|----------------|-----------------|-------|
| A01:2021 Broken Access Control | Partial | Covers YAML deserialization |
| A02:2021 Cryptographic Failures | Excellent | Comprehensive crypto checks |
| A03:2021 Injection | Excellent | SQL, command, XML injection |
| A04:2021 Insecure Design | None | Requires manual review |
| A05:2021 Security Misconfiguration | Good | Debug mode, templating |
| A06:2021 Vulnerable Components | None | Use Safety or pip-audit |
| A07:2021 Authentication Failures | Partial | Hardcoded credentials only |
| A08:2021 Data Integrity Failures | Good | Deserialization issues |
| A09:2021 Security Logging Failures | None | Requires manual review |
| A10:2021 SSRF | Partial | URL scheme validation |
## References
- [OWASP Top 10 2021](https://owasp.org/Top10/)
- [CWE Database](https://cwe.mitre.org/)
- [Bandit Documentation](https://bandit.readthedocs.io/)

View File

@@ -0,0 +1,622 @@
# Bandit Finding Remediation Guide
Comprehensive secure coding patterns and remediation strategies for common Bandit findings.
## Table of Contents
- [Hardcoded Credentials](#hardcoded-credentials)
- [SQL Injection](#sql-injection)
- [Command Injection](#command-injection)
- [Weak Cryptography](#weak-cryptography)
- [Insecure Deserialization](#insecure-deserialization)
- [XML External Entity (XXE)](#xml-external-entity-xxe)
- [Security Misconfiguration](#security-misconfiguration)
---
## Hardcoded Credentials
### B105, B106, B107: Hardcoded Passwords
**Vulnerable Code:**
```python
# B105: Hardcoded password string
DATABASE_PASSWORD = "admin123"
# B106: Hardcoded password in function call
db.connect(host="localhost", password="secret_password")
# B107: Hardcoded password default argument
def connect_db(password="default_pass"):
pass
```
**Secure Solution:**
```python
import os
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Use environment variables
DATABASE_PASSWORD = os.environ.get("DATABASE_PASSWORD")
if not DATABASE_PASSWORD:
raise ValueError("DATABASE_PASSWORD environment variable not set")
# Use environment variables in function calls
db.connect(
host=os.environ.get("DB_HOST", "localhost"),
password=os.environ.get("DB_PASSWORD")
)
# Use secret management service (example with AWS Secrets Manager)
import boto3
from botocore.exceptions import ClientError
def get_secret(secret_name):
session = boto3.session.Session()
client = session.client(service_name='secretsmanager', region_name='us-east-1')
try:
response = client.get_secret_value(SecretId=secret_name)
return response['SecretString']
except ClientError as e:
raise Exception(f"Failed to retrieve secret: {e}")
DATABASE_PASSWORD = get_secret("prod/db/password")
```
**Best Practices:**
- Use environment variables with `.env` files (never commit `.env` to version control)
- Use secret management services (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault)
- Implement secret rotation policies
- Use configuration management tools (Ansible Vault, Kubernetes Secrets)
---
## SQL Injection
### B608: SQL Injection via String Formatting
**Vulnerable Code:**
```python
# String formatting (UNSAFE)
user_id = request.GET['id']
query = f"SELECT * FROM users WHERE id = {user_id}"
cursor.execute(query)
# String concatenation (UNSAFE)
query = "SELECT * FROM users WHERE username = '" + username + "'"
cursor.execute(query)
# Percent formatting (UNSAFE)
query = "SELECT * FROM users WHERE email = '%s'" % email
cursor.execute(query)
```
**Secure Solution with psycopg2:**
```python
import psycopg2
# Parameterized queries (SAFE)
user_id = request.GET['id']
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_id,))
# Multiple parameters
query = "SELECT * FROM users WHERE username = %s AND active = %s"
cursor.execute(query, (username, True))
# Named parameters
query = "SELECT * FROM users WHERE username = %(username)s AND email = %(email)s"
cursor.execute(query, {'username': username, 'email': email})
```
**Secure Solution with SQLAlchemy ORM:**
```python
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session
# Using ORM (SAFE)
with Session(engine) as session:
stmt = select(User).where(User.username == username)
user = session.execute(stmt).scalar_one_or_none()
# Using bound parameters with raw SQL (SAFE)
with Session(engine) as session:
result = session.execute(
text("SELECT * FROM users WHERE username = :username"),
{"username": username}
)
```
**Secure Solution with Django ORM:**
```python
from django.db.models import Q
# Django ORM (SAFE)
users = User.objects.filter(username=username)
# Complex queries (SAFE)
users = User.objects.filter(Q(username=username) | Q(email=email))
# Raw SQL with parameters (SAFE)
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("SELECT * FROM users WHERE username = %s", [username])
```
**Best Practices:**
- Always use parameterized queries or prepared statements
- Never concatenate user input into SQL queries
- Use ORM when possible for automatic escaping
- Validate and sanitize inputs at application boundaries
- Apply least privilege principle to database accounts
---
## Command Injection
### B602, B604, B605: Shell Injection in Subprocess
**Vulnerable Code:**
```python
import subprocess
import os
# shell=True with user input (VERY UNSAFE)
filename = request.GET['file']
subprocess.call(f"cat {filename}", shell=True)
# os.system with user input (VERY UNSAFE)
os.system(f"ping -c 1 {hostname}")
# String concatenation (UNSAFE)
cmd = "curl " + user_url
subprocess.call(cmd, shell=True)
```
**Secure Solution:**
```python
import subprocess
import shlex
from pathlib import Path
# Use list of arguments without shell=True (SAFE)
filename = request.GET['file']
subprocess.run(["cat", filename], check=True, capture_output=True)
# Validate input before use
def validate_filename(filename):
"""Validate filename to prevent path traversal."""
# Allow only alphanumeric, dash, underscore, and dot
if not re.match(r'^[a-zA-Z0-9_.-]+$', filename):
raise ValueError("Invalid filename")
# Resolve to absolute path and check it's within allowed directory
file_path = Path(UPLOAD_DIR) / filename
if not file_path.resolve().is_relative_to(Path(UPLOAD_DIR).resolve()):
raise ValueError("Path traversal detected")
return file_path
filename = validate_filename(request.GET['file'])
subprocess.run(["cat", str(filename)], check=True, capture_output=True)
# Use shlex.split() for complex commands
import shlex
command_string = "ping -c 1 example.com"
subprocess.run(shlex.split(command_string), check=True, capture_output=True)
# Whitelist approach for restricted commands
ALLOWED_COMMANDS = {
'ping': ['ping', '-c', '1'],
'traceroute': ['traceroute', '-m', '10'],
}
command_type = request.GET['command']
target = request.GET['target']
if command_type not in ALLOWED_COMMANDS:
raise ValueError("Command not allowed")
# Validate target (e.g., IP address or hostname)
if not re.match(r'^[a-zA-Z0-9.-]+$', target):
raise ValueError("Invalid target")
cmd = ALLOWED_COMMANDS[command_type] + [target]
subprocess.run(cmd, check=True, capture_output=True, timeout=10)
```
**Best Practices:**
- Never use `shell=True` with user input
- Pass arguments as list, not string
- Validate and whitelist all user inputs
- Use `shlex.split()` for parsing command strings
- Implement timeouts to prevent DoS
- Run subprocesses with minimal privileges
---
## Weak Cryptography
### B303, B304, B324: Weak Hash Functions
**Vulnerable Code:**
```python
import hashlib
import md5 # Deprecated
# MD5 (WEAK)
password_hash = hashlib.md5(password.encode()).hexdigest()
# SHA1 (WEAK)
token = hashlib.sha1(user_data.encode()).hexdigest()
```
**Secure Solution:**
```python
import hashlib
import secrets
import bcrypt
from argon2 import PasswordHasher
# SHA-256 for general hashing (ACCEPTABLE for non-password data)
data_hash = hashlib.sha256(data.encode()).hexdigest()
# SHA-512 (BETTER for general hashing)
data_hash = hashlib.sha512(data.encode()).hexdigest()
# bcrypt for password hashing (RECOMMENDED)
def hash_password(password: str) -> bytes:
"""Hash password using bcrypt with salt."""
salt = bcrypt.gensalt(rounds=12) # Cost factor 12
return bcrypt.hashpw(password.encode(), salt)
def verify_password(password: str, hashed: bytes) -> bool:
"""Verify password against bcrypt hash."""
return bcrypt.checkpw(password.encode(), hashed)
# Argon2 for password hashing (BEST - winner of Password Hashing Competition)
ph = PasswordHasher(
time_cost=2, # Number of iterations
memory_cost=65536, # Memory usage in KiB (64 MB)
parallelism=4, # Number of parallel threads
)
def hash_password_argon2(password: str) -> str:
"""Hash password using Argon2."""
return ph.hash(password)
def verify_password_argon2(password: str, hashed: str) -> bool:
"""Verify password against Argon2 hash."""
try:
ph.verify(hashed, password)
return True
except:
return False
# HMAC for message authentication
import hmac
def create_signature(message: str, secret_key: bytes) -> str:
"""Create HMAC-SHA256 signature."""
return hmac.new(
secret_key,
message.encode(),
hashlib.sha256
).hexdigest()
```
### B501, B502, B503: Weak SSL/TLS Configuration
**Vulnerable Code:**
```python
import ssl
import requests
# Weak SSL version (UNSAFE)
context = ssl.SSLContext(ssl.PROTOCOL_SSLv3)
# Disabling certificate verification (VERY UNSAFE)
requests.get('https://example.com', verify=False)
```
**Secure Solution:**
```python
import ssl
import requests
# Strong SSL/TLS configuration (SAFE)
context = ssl.create_default_context()
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.maximum_version = ssl.TLSVersion.TLSv1_3
# Restrict cipher suites
context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS')
# Enable certificate verification (default in requests)
response = requests.get('https://example.com', verify=True)
# Custom CA bundle
response = requests.get('https://example.com', verify='/path/to/ca-bundle.crt')
# For urllib
import urllib.request
import certifi
url = 'https://example.com'
response = urllib.request.urlopen(url, context=context, cafile=certifi.where())
```
**Best Practices:**
- Use TLS 1.2 or TLS 1.3 only
- Disable weak cipher suites
- Always verify certificates in production
- Use certificate pinning for critical connections
- Regularly update SSL/TLS libraries
---
## Insecure Deserialization
### B301: Pickle Usage
**Vulnerable Code:**
```python
import pickle
# Deserializing untrusted data (VERY UNSAFE)
user_data = pickle.loads(request.body)
# Loading from file (UNSAFE if file is from untrusted source)
with open('user_session.pkl', 'rb') as f:
session = pickle.load(f)
```
**Secure Solution:**
```python
import json
import msgpack
from cryptography.fernet import Fernet
# Use JSON for simple data (SAFE)
user_data = json.loads(request.body)
# Use MessagePack for binary efficiency (SAFE)
user_data = msgpack.unpackb(request.body)
# If pickle is absolutely necessary, use cryptographic signing
import hmac
import hashlib
import pickle
SECRET_KEY = os.environ['SECRET_KEY'].encode()
def secure_pickle_dumps(obj):
"""Serialize with HMAC signature."""
pickled = pickle.dumps(obj)
signature = hmac.new(SECRET_KEY, pickled, hashlib.sha256).digest()
return signature + pickled
def secure_pickle_loads(data):
"""Deserialize with signature verification."""
signature = data[:32] # SHA256 is 32 bytes
pickled = data[32:]
expected_signature = hmac.new(SECRET_KEY, pickled, hashlib.sha256).digest()
if not hmac.compare_digest(signature, expected_signature):
raise ValueError("Invalid signature - data may be tampered")
return pickle.loads(pickled)
# Better: Use itsdangerous for secure serialization
from itsdangerous import URLSafeSerializer
serializer = URLSafeSerializer(SECRET_KEY)
# Serialize (signed and safe)
token = serializer.dumps({'user_id': 123, 'role': 'admin'})
# Deserialize (verified)
data = serializer.loads(token)
```
**Best Practices:**
- Avoid pickle for untrusted data
- Use JSON, MessagePack, or Protocol Buffers
- If pickle is required, implement cryptographic signing
- Use `itsdangerous` library for secure token serialization
- Restrict pickle to internal, trusted data only
---
## XML External Entity (XXE)
### B313-B320, B405-B412: XML Parsing Vulnerabilities
**Vulnerable Code:**
```python
import xml.etree.ElementTree as ET
from lxml import etree
# Unsafe XML parsing (VULNERABLE to XXE)
tree = ET.parse(user_xml_file)
root = tree.getroot()
# lxml unsafe parsing
parser = etree.XMLParser()
tree = etree.parse(user_xml_file, parser)
```
**Secure Solution:**
```python
import xml.etree.ElementTree as ET
from lxml import etree
import defusedxml.ElementTree as defusedET
# Use defusedxml (RECOMMENDED)
tree = defusedET.parse(user_xml_file)
root = tree.getroot()
# Disable external entities in ElementTree
ET.XMLParser.entity = {} # Disable entity expansion
# Secure lxml configuration
parser = etree.XMLParser(
resolve_entities=False, # Disable entity resolution
no_network=True, # Disable network access
dtd_validation=False, # Disable DTD validation
load_dtd=False # Don't load DTD
)
tree = etree.parse(user_xml_file, parser)
# Alternative: Use JSON instead of XML when possible
import json
data = json.loads(request.body)
```
**Best Practices:**
- Use `defusedxml` library for all XML parsing
- Disable DTD processing and external entity resolution
- Validate XML against strict schema (XSD)
- Consider using JSON instead of XML for APIs
- Never parse XML from untrusted sources without defusedxml
---
## Security Misconfiguration
### B201: Flask Debug Mode
**Vulnerable Code:**
```python
from flask import Flask
app = Flask(__name__)
# Debug mode in production (VERY UNSAFE)
app.run(debug=True, host='0.0.0.0')
```
**Secure Solution:**
```python
from flask import Flask
import os
app = Flask(__name__)
# Use environment-based configuration
DEBUG = os.environ.get('FLASK_DEBUG', 'false').lower() == 'true'
ENV = os.environ.get('FLASK_ENV', 'production')
if ENV == 'production' and DEBUG:
raise ValueError("Debug mode cannot be enabled in production")
app.config['DEBUG'] = DEBUG
app.config['ENV'] = ENV
app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
# Use production WSGI server
if ENV == 'production':
# Deploy with gunicorn or uwsgi, not app.run()
# gunicorn -w 4 -b 0.0.0.0:8000 app:app
pass
else:
app.run(debug=DEBUG, host='127.0.0.1', port=5000)
```
### B506: YAML Load
**Vulnerable Code:**
```python
import yaml
# Arbitrary code execution (VERY UNSAFE)
config = yaml.load(user_input, Loader=yaml.Loader)
```
**Secure Solution:**
```python
import yaml
# Safe YAML loading (SAFE)
config = yaml.safe_load(user_input)
# For complex objects, use schema validation
from schema import Schema, And, Use, Optional
config_schema = Schema({
'database': {
'host': And(str, len),
'port': And(Use(int), lambda n: 1024 <= n <= 65535),
},
Optional('debug'): bool,
})
config = yaml.safe_load(user_input)
validated_config = config_schema.validate(config)
```
### B701, B702, B703: Template Autoescape
**Vulnerable Code:**
```python
from jinja2 import Environment
# Autoescape disabled (XSS VULNERABLE)
env = Environment(autoescape=False)
template = env.from_string(user_template)
output = template.render(name=user_input)
```
**Secure Solution:**
```python
from jinja2 import Environment, select_autoescape
from markupsafe import Markup, escape
# Enable autoescape (SAFE)
env = Environment(
autoescape=select_autoescape(['html', 'xml'])
)
# Or for all templates
env = Environment(autoescape=True)
# Explicitly mark safe content
def render_html(content):
# Sanitize first
clean_content = escape(content)
return Markup(clean_content)
# Django: Ensure autoescape is enabled (default)
# In Django templates:
# {{ user_input }} <!-- Auto-escaped -->
# {{ user_input|safe }} <!-- Only use after sanitization -->
```
**Best Practices:**
- Always enable autoescape in template engines
- Never mark user input as safe without sanitization
- Use Content Security Policy (CSP) headers
- Validate and sanitize all user inputs
- Use templating libraries with secure defaults
---
## General Security Principles
1. **Defense in Depth**: Implement multiple layers of security controls
2. **Least Privilege**: Grant minimum necessary permissions
3. **Fail Securely**: Errors should not expose sensitive information
4. **Input Validation**: Validate all inputs at trust boundaries
5. **Output Encoding**: Encode data based on output context
6. **Secure Defaults**: Use secure configurations by default
7. **Keep Dependencies Updated**: Regularly update security libraries
8. **Security Testing**: Include security tests in CI/CD pipelines
## Additional Resources
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
- [Python Security Best Practices](https://python.readthedocs.io/en/stable/library/security_warnings.html)
- [CWE Top 25](https://cwe.mitre.org/top25/)