Initial commit
This commit is contained in:
305
skills/appsec/sast-bandit/SKILL.md
Normal file
305
skills/appsec/sast-bandit/SKILL.md
Normal 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)
|
||||
9
skills/appsec/sast-bandit/assets/.gitkeep
Normal file
9
skills/appsec/sast-bandit/assets/.gitkeep
Normal 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.
|
||||
211
skills/appsec/sast-bandit/assets/bandit_config.yaml
Normal file
211
skills/appsec/sast-bandit/assets/bandit_config.yaml
Normal 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
|
||||
217
skills/appsec/sast-bandit/assets/pre-commit-config.yaml
Normal file
217
skills/appsec/sast-bandit/assets/pre-commit-config.yaml
Normal 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'
|
||||
# }
|
||||
# }
|
||||
157
skills/appsec/sast-bandit/references/cwe_owasp_mapping.md
Normal file
157
skills/appsec/sast-bandit/references/cwe_owasp_mapping.md
Normal 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/)
|
||||
622
skills/appsec/sast-bandit/references/remediation_guide.md
Normal file
622
skills/appsec/sast-bandit/references/remediation_guide.md
Normal 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/)
|
||||
Reference in New Issue
Block a user