
测试代码——开发者眼中最安全的角落,却是黑客眼里最甜美的果实。
2023年,某知名云服务商遭遇数据泄露,攻击者并非直接攻破生产环境,而是通过一个暴露的测试API端点,获取了数据库访问凭证。这个测试端点,本该在部署前就被清理干净。但测试代码中硬编码的密钥,成了整条防线上的裂缝。
数据不会说谎:根据Verizon《2023年数据泄露调查报告》,超过60%的安全事件与内部系统漏洞相关,其中测试环境暴露的问题占比逐年上升。测试代码中常见的风险点包括:
**测试代码的漏洞不是bug,是给黑客留的钥匙。**
你的测试代码可能运行在Jenkins、GitLab CI等持续集成环境中。这些环境往往拥有访问生产数据库、云服务凭证的权限。一旦测试代码被攻破,攻击者就能通过这个“后门”直取核心资产。
Bandit是Python生态中最成熟的安全扫描工具之一。它不是一个通用安全工具,而是专门针对Python代码的安全风险分析器。
Bandit的工作原理:它不是一个简单的字符串匹配工具。Bandit对Python代码进行AST(抽象语法树)解析,识别出潜在的安全风险模式。这意味着它能理解代码结构,减少误报率。
核心能力:
安装Bandit:一行命令搞定
pip install bandit
验证安装:
bandit --version
输出示例:
bandit 1.7.6
python version: 3.11.4
为什么选择Bandit而不是其他工具?
**Bandit不是万能药,但它是你代码安全的守门人。**
创建一个包含常见安全漏洞的测试文件test_vulnerable.py:
# test_vulnerable.py - 一个包含安全漏洞的测试文件
#CE9178">"color:#569CD6;font-weight:bold">import os
#CE9178">"color:#569CD6;font-weight:bold">import pickle
#CE9178">"color:#569CD6;font-weight:bold">import hashlib
#CE9178">"color:#569CD6;font-weight:bold">import requests
#CE9178">"color:#569CD6;font-weight:bold">import sqlite3
# 漏洞1:硬编码密码
DB_PASSWORD = #CE9178">"SuperSecret123!"
#CE9178">"color:#569CD6;font-weight:bold">def test_database_connection():
#CE9178">"""测试数据库连接"""
conn = sqlite3.connect(#CE9178">'test.db')
cursor = conn.cursor()
# 漏洞2:SQL注入风险 - 直接拼接用户输入
user_input = #CE9178">"admin' OR '1'='1"
query = f#CE9178">"SELECT * FROM users WHERE username = '{user_input}'"
cursor.execute(query)
# 漏洞3:不安全的反序列化
data = pickle.loads(b#CE9178">"malicious_payload")
# 漏洞4:不安全的哈希算法
password = #CE9178">"my_password"
hashed = hashlib.md5(password.encode()).hexdigest()
# 漏洞5:不安全的HTTP请求(无SSL验证)
response = requests.get(#CE9178">'http://api.example.com/data', verify="color:#569CD6;font-weight:bold">False)
# 漏洞6:使用eval执行动态代码
user_code = #CE9178">"">print('Hello')"
eval(user_code)
conn.close()
执行基础扫描命令:
bandit -r .
输出示例:
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
[main] INFO running on Python 3.11.4
Run started:2024-01-15 14:30:00.123456
Test results:
>> Issue: [B105:hardcoded_password_string] Possible hardcoded password: 'SuperSecret123!'
Severity: Medium Confidence: Medium
Location: test_vulnerable.py:5:1
More Info: https://bandit.readthedocs.io/en/latest/plugins/b105_hardcoded_password_string.html
>> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection vector through string-based query construction.
Severity: Medium Confidence: Medium
Location: test_vulnerable.py:15:28
More Info: https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html
>> Issue: [B301:pickle] Pickle and modules that wrap it can be unsafe when used to deserialize untrusted data
Severity: Medium Confidence: High
Location: test_vulnerable.py:19:15
More Info: https://bandit.readthedocs.io/en/latest/plugins/b301_pickle.html
>> Issue: [B303:md5] Use of insecure MD2, MD4, MD5, or SHA1 hash function.
Severity: Medium Confidence: High
Location: test_vulnerable.py:23:15
More Info: https://bandit.readthedocs.io/en/latest/plugins/b303_md5.html
>> Issue: [B501:request_with_no_cert_validation] Requests call with verify=False disabling SSL certificate checks
Severity: Medium Confidence: High
Location: test_vulnerable.py:27:19
More Info: https://bandit.readthedocs.io/en/latest/plugins/b501_request_with_no_cert_validation.html
>> Issue: [B307:eval] Use of possibly insecure function - consider using safer alternatives
Severity: Medium Confidence: High
Location: test_vulnerable.py:31:4
More Info: https://bandit.readthedocs.io/en/latest/plugins/b307_eval.html
--------------------------------------------------
6 issues found. (Sev: 0 High, 6 Medium, 0 Low)
创建.bandit.yml配置文件:
# .bandit.yml
# 排除某些测试,减少误报
exclude_dirs:
- ./tests/unit
- ./docs
# 仅包含特定测试
tests:
- B105 # 硬编码密码
- B301 # 不安全的反序列化
- B303 # 不安全的哈希
- B307 # eval使用
- B501 # 不安全的请求
- B608 # SQL注入
# 设置严重级别阈值
skips:
- B101 # 跳过assert语句检测(测试代码中常见)
# 自定义输出格式
output_format: json
使用配置文件运行:
bandit -r . -c .bandit.yml -f json -o bandit_report.json
查看生成的JSON报告:
{
"errors": [],
"generated_at": "2024-01-15T14:35:00Z",
"metrics": {
"_totals": {
"CONFIDENCE.HIGH": 5,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 1,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 0,
"SEVERITY.MEDIUM": 6,
"loc": 35,
"nosec": 0
}
},
"results": [
{
"code": "5 DB_PASSWORD = \"SuperSecret123!\"\n",
"filename": "./test_vulnerable.py",
"issue_confidence": "MEDIUM",
"issue_severity": "MEDIUM",
"issue_text": "Possible hardcoded password: 'SuperSecret123!'",
"line_number": 5,
"line_range": [5],
"more_info": "https://bandit.readthedocs.io/en/latest/plugins/b105_hardcoded_password_string.html",
"test_id": "B105"
}
// ... 更多结果
]
}
**扫描不是终点,修复才是。**
问题代码:
DB_PASSWORD = #CE9178">"SuperSecret123!"
修复代码:
#CE9178">"color:#569CD6;font-weight:bold">import os
DB_PASSWORD = os.environ.get(#CE9178">'DB_PASSWORD')
#CE9178">"color:#569CD6;font-weight:bold">if "color:#569CD6;font-weight:bold">not DB_PASSWORD:
#CE9178">"color:#569CD6;font-weight:bold">raise ValueError("DB_PASSWORD environment variable ">not ">set")
问题代码:
query = f#CE9178">"SELECT * FROM users WHERE username = '{user_input}'"
cursor.execute(query)
修复代码:
query = #CE9178">"SELECT * FROM users WHERE username = ?"
cursor.execute(query, (user_input,))
问题代码:
data = pickle.loads(b#CE9178">"malicious_payload")
修复代码:
#CE9178">"color:#569CD6;font-weight:bold">import json
data = json.loads(b#CE9178">'{"safe": "payload"}')
问题代码:
hashed = hashlib.md5(password.encode()).hexdigest()
修复代码:
#CE9178">"color:#569CD6;font-weight:bold">import bcrypt
# 生成盐值并哈希
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(password.encode(), salt)
问题代码:
response = requests.get(#CE9178">'http://api.example.com/data', verify="color:#569CD6;font-weight:bold">False)
修复代码:
#CE9178">"color:#569CD6;font-weight:bold">import requests
#CE9178">"color:#569CD6;font-weight:bold">from requests.packages.urllib3.exceptions "color:#569CD6;font-weight:bold">import InsecureRequestWarning
# 关闭警告(仅用于演示,生产环境必须验证)
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
# 使用HTTPS并验证证书
response = requests.get(#CE9178">'https://api.example.com/data', verify="color:#569CD6;font-weight:bold">True)
问题代码:
eval(user_code)
修复代码:
#CE9178">"color:#569CD6;font-weight:bold">import ast
# 使用AST解析代替eval
#CE9178">"color:#569CD6;font-weight:bold">try:
tree = ast.parse(user_code, mode=#CE9178">'exec')
# 仅允许安全操作
#CE9178">"color:#569CD6;font-weight:bold">for node "color:#569CD6;font-weight:bold">in ast.walk(tree):
#CE9178">"color:#569CD6;font-weight:bold">if "color:#4EC9B0">isinstance(node, ast.Call) "color:#569CD6;font-weight:bold">and "color:#4EC9B0">isinstance(node.func, ast.Name):
#CE9178">"color:#569CD6;font-weight:bold">if node.func.id "color:#569CD6;font-weight:bold">not "color:#569CD6;font-weight:bold">in ['">print']:
#CE9178">"color:#569CD6;font-weight:bold">raise ValueError("Unsafe operation detected")
exec(compile(tree, filename=#CE9178">'<ast>', mode='exec'))
#CE9178">"color:#569CD6;font-weight:bold">except SyntaxError:
#CE9178">"color:#569CD6;font-weight:bold">raise ValueError("Invalid Python code")
修复完成后,重新运行Bandit验证:
bandit -r .
输出应该显示0个问题:
No issues found.
**修复一个漏洞,堵住一个后门。**
手动扫描不够,将Bandit集成到持续集成流程中,实现自动化安全检测。
创建.gitlab-ci.yml文件:
stages:
- test
- security
bandit-scan:
stage: security
image: python:3.11
script:
- pip install bandit
- bandit -r . -f json -o bandit_report.json
- |
# 检查是否有高严重性问题
if jq -e '.results[] | select(.issue_severity == "HIGH")' bandit_report.json > /dev/null; then
echo "High severity issues found!"
exit 1
fi
artifacts:
paths:
- bandit_report.json
when: always
only:
- main
- develop
创建.github/workflows/security-scan.yml:
name: Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
bandit-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Bandit
run: pip install bandit
- name: Run Bandit Scan
run: |
bandit -r . -f json -o bandit_report.json
cat bandit_report.json
- name: Upload Bandit Report
uses: actions/upload-artifact@v3
with:
name: bandit-report
path: bandit_report.json
- name: Fail on High Severity
run: |
if jq -e '.results[] | select(.issue_severity == "HIGH")' bandit_report.json > /dev/null; then
echo "High severity issues found!"
exit 1
fi
pipeline {
agent any
stages {
stage('Security Scan') {
steps {
script {
sh 'pip install bandit'
sh 'bandit -r . -f json -o bandit_report.json'
def report = readJSON file: 'bandit_report.json'
def highIssues = report.results.findAll { it.issue_severity == 'HIGH' }
if (highIssues.size() > 0) {
error "Found ${highIssues.size()} high severity issues"
}
}
}
post {
always {
archiveArtifacts artifacts: 'bandit_report.json'
}
}
}
}
}
---
**安全不是一次性检查,而是持续的习惯。**
你的测试代码现在有了安全守门人。每次提交,Bandit都会自动扫描,发现潜在风险。这不是锦上添花,而是雪中送炭。在黑客找到你测试代码的漏洞之前,先自己找到并修复它。
行动指令:今天就在你的测试项目中集成Bandit。3步操作,10分钟配置,换来的是代码安全的长期保障。