跳到主要内容

安全测试

安全测试是验证软件系统安全性的测试类型,目的是发现系统中的安全漏洞,防止恶意攻击和数据泄露。安全测试应贯穿软件开发生命周期的每个阶段。

什么是安全测试?

安全测试评估系统的安全特性,验证系统是否能够保护数据和功能免受未经授权的访问和攻击。

安全测试的目标

目标说明
发现漏洞识别系统中的安全弱点和潜在风险
验证防护确认安全机制(认证、授权、加密)有效
合规检查确保符合安全标准和法规要求(如 GDPR、PCI-DSS)
风险评估评估潜在安全风险的影响和可能性
提高意识提高开发团队的安全意识和编码规范

CIA 安全三要素

安全测试围绕 CIA 三要素展开:

  • 机密性(Confidentiality):确保数据只能被授权用户访问,防止信息泄露
  • 完整性(Integrity):确保数据在存储和传输过程中不被篡改
  • 可用性(Availability):确保授权用户能够正常访问系统和服务

安全测试类型

按测试方法分类

类型说明执行时机工具示例
SAST静态应用安全测试,分析源代码开发阶段SonarQube, Checkmarx, Semgrep
DAST动态应用安全测试,模拟外部攻击测试阶段OWASP ZAP, Burp Suite
IAST交互式应用安全测试,运行时分析测试阶段Contrast Security
SCA软件成分分析,检查依赖漏洞开发阶段Snyk, OWASP Dependency-Check

按测试内容分类

测试类型测试内容关键检查点
漏洞扫描自动化扫描已知漏洞CVE 漏洞、配置问题
渗透测试模拟黑客攻击业务逻辑漏洞、权限绕过
认证测试测试身份认证机制密码强度、会话管理
授权测试测试权限控制越权访问、权限提升
数据保护测试测试数据加密敏感数据暴露、传输加密
配置测试测试安全配置默认配置、安全头

OWASP Top 10

OWASP(开放网络应用安全项目)发布的 Top 10 是 Web 应用最常见的安全风险清单,是安全测试的重要参考。

OWASP Top 10:2021

OWASP Top 10:2021 是目前最新正式发布的版本:

排名风险说明
A01访问控制失效 (Broken Access Control)用户可越权访问资源,持续位居首位
A02加密失败 (Cryptographic Failures)敏感数据未加密或加密不当,导致数据泄露
A03注入攻击 (Injection)SQL、命令、LDAP 注入等,用户输入被当作代码执行
A04不安全设计 (Insecure Design)缺乏安全架构设计,安全缺陷在代码之前
A05安全配置错误 (Security Misconfiguration)默认配置、错误配置、开放云存储
A06易受攻击组件 (Vulnerable Components)使用有已知漏洞的依赖库和框架
A07认证失败 (Identification and Authentication Failures)弱密码、会话管理问题、凭证填充
A08软件和数据完整性失败不安全的更新机制、CI/CD 管道安全
A09日志监控不足 (Security Logging and Monitoring Failures)无法检测和响应安全事件
A10服务端请求伪造 SSRF服务端被诱导请求内部资源

各风险详解与测试方法

A01: 访问控制失效

风险描述:访问控制失效是最常见的 Web 应用安全漏洞。当用户可以访问超出其权限范围的资源或执行超出其权限的操作时,就存在访问控制问题。

常见问题

  • 通过修改 URL 访问他人数据
  • 越权操作(水平/垂直越权)
  • API 接口未做权限检查
  • ID 参数可预测

测试示例

import pytest
import requests

class TestAccessControl:
"""访问控制测试"""

BASE_URL = "http://localhost:8000/api"

def test_horizontal_privilege_escalation(self):
"""测试水平越权 - 访问其他用户数据"""
# 用户A登录
login_a = requests.post(f"{self.BASE_URL}/login", json={
"username": "user_a",
"password": "password123"
})
token_a = login_a.json()["token"]

# 用户B的ID
user_b_id = "user_b_123"

# 用户A尝试访问用户B的数据
response = requests.get(
f"{self.BASE_URL}/users/{user_b_id}",
headers={"Authorization": f"Bearer {token_a}"}
)

# 应该返回403禁止访问
assert response.status_code == 403, "存在水平越权漏洞"

def test_vertical_privilege_escalation(self):
"""测试垂直越权 - 普通用户访问管理员功能"""
# 普通用户登录
login = requests.post(f"{self.BASE_URL}/login", json={
"username": "normal_user",
"password": "password123"
})
token = login.json()["token"]

# 尝试访问管理员接口
response = requests.post(
f"{self.BASE_URL}/admin/users",
headers={"Authorization": f"Bearer {token}"},
json={"username": "new_user", "role": "admin"}
)

assert response.status_code == 403, "存在垂直越权漏洞"

def test_idor_in_orders(self):
"""测试IDOR(不安全的直接对象引用)"""
# 登录用户
login = requests.post(f"{self.BASE_URL}/login", json={
"username": "user_a",
"password": "password123"
})
token = login.json()["token"]

# 遍历订单ID,尝试访问其他用户订单
for order_id in range(1, 100):
response = requests.get(
f"{self.BASE_URL}/orders/{order_id}",
headers={"Authorization": f"Bearer {token}"}
)

if response.status_code == 200:
order = response.json()
# 验证订单属于当前用户
assert order["user_id"] == "user_a", f"IDOR漏洞: 可访问订单 {order_id}"

防护措施

  • 实施最小权限原则
  • 所有敏感操作都进行权限检查
  • 使用不可预测的标识符
  • 记录访问控制失败的尝试

A02: 加密失败

风险描述:敏感数据未加密或使用弱加密算法,导致数据在传输或存储过程中泄露。

常见问题

  • 敏感数据明文存储
  • 使用弱加密算法(MD5、SHA1)
  • 传输未使用 HTTPS
  • 密钥管理不当

测试示例

import pytest
import requests
import hashlib

class TestEncryption:
"""加密测试"""

BASE_URL = "http://localhost:8000/api"

def test_https_enforced(self):
"""测试是否强制使用 HTTPS"""
response = requests.get("http://localhost:8000", allow_redirects=True)

# 最终URL应该是HTTPS
assert response.url.startswith("https://"), "未强制使用HTTPS"

def test_password_hashing(self):
"""测试密码是否正确哈希存储"""
# 注册用户
response = requests.post(f"{self.BASE_URL}/register", json={
"username": "testuser",
"email": "[email protected]",
"password": "TestPassword123!"
})

# 检查返回的用户信息不包含明文密码
user = response.json()
assert "password" not in user, "密码不应返回"

# 如果有接口返回用户数据,检查密码字段
# 假设可以直接查询数据库(测试环境)
db_user = get_user_from_db("testuser")
assert db_user["password"] != "TestPassword123!", "密码明文存储"
assert db_user["password"].startswith("$2b$"), "应使用bcrypt哈希"

def test_sensitive_data_in_response(self):
"""测试响应中是否泄露敏感数据"""
response = requests.get(f"{self.BASE_URL}/users/1")
data = response.json()

sensitive_fields = ["password", "credit_card", "ssn", "api_key", "secret"]
for field in sensitive_fields:
assert field not in data, f"响应中包含敏感字段: {field}"

def test_cookie_security(self):
"""测试Cookie安全属性"""
session = requests.Session()
session.post(f"{self.BASE_URL}/login", json={
"username": "testuser",
"password": "password123"
})

for cookie in session.cookies:
# 检查Secure属性
assert cookie.secure, f"Cookie {cookie.name} 未设置Secure标志"
# 检查HttpOnly属性
assert cookie.has_nonstandard_attr('HttpOnly'), f"Cookie {cookie.name} 未设置HttpOnly"
# 检查SameSite属性
same_site = cookie.get_nonstandard_attr('SameSite', '')
assert same_site in ['Strict', 'Lax'], f"Cookie {cookie.name} SameSite设置不安全"

防护措施

  • 敏感数据加密存储
  • 使用强加密算法(AES-256、bcrypt)
  • 强制使用 HTTPS
  • 正确管理加密密钥

A03: 注入攻击

风险描述:用户输入被当作代码执行,包括 SQL 注入、命令注入、LDAP 注入等。

SQL 注入测试

import pytest
import requests

class TestSQLInjection:
"""SQL注入测试"""

BASE_URL = "http://localhost:8000"

# 常见SQL注入Payload
SQL_INJECTION_PAYLOADS = [
"' OR '1'='1",
"' OR '1'='1' --",
"' OR '1'='1' /*",
"1' AND '1'='1",
"1; DROP TABLE users--",
"' UNION SELECT NULL--",
"' UNION SELECT NULL, NULL--",
"' UNION SELECT username, password FROM users--",
"admin'--",
"1' WAITFOR DELAY '0:0:5'--",
"1' AND SLEEP(5)--",
"1' OR 1=1#",
"' OR ''='",
]

def test_login_sql_injection(self):
"""测试登录表单SQL注入"""
for payload in self.SQL_INJECTION_PAYLOADS:
response = requests.post(f"{self.BASE_URL}/login", json={
"username": payload,
"password": "anything"
})

# 检查异常响应
assert response.status_code not in [500], \
f"SQL注入可能成功: {payload} (状态码: {response.status_code})"

# 检查是否成功绕过认证
if response.status_code == 200 and "token" in response.json():
pytest.fail(f"SQL注入成功绕过认证: {payload}")

# 检查错误信息是否泄露SQL细节
error_indicators = ["sql", "mysql", "sqlite", "postgres", "syntax", "query"]
response_text = response.text.lower()
for indicator in error_indicators:
assert indicator not in response_text, \
f"错误信息泄露SQL细节: {payload}"

def test_search_sql_injection(self):
"""测试搜索功能SQL注入"""
for payload in self.SQL_INJECTION_PAYLOADS:
response = requests.get(f"{self.BASE_URL}/search", params={
"q": payload
})

# 检查响应是否异常
if response.status_code == 500:
pytest.fail(f"搜索SQL注入可能成功: {payload}")

# 检查是否有异常数据返回
if response.status_code == 200:
data = response.json()
# 如果返回的数据量异常大,可能存在问题
if isinstance(data, list) and len(data) > 1000:
pytest.fail(f"SQL注入可能泄露数据: {payload}")

def test_time_based_sqli(self):
"""测试时间盲注"""
import time

# 正常请求
start = time.time()
requests.get(f"{self.BASE_URL}/users/1")
normal_time = time.time() - start

# 延迟注入
start = time.time()
requests.get(f"{self.BASE_URL}/users/1' AND SLEEP(5)--")
delay_time = time.time() - start

# 如果延迟时间明显增加,存在时间盲注
assert delay_time < normal_time + 2, "可能存在时间盲注漏洞"

命令注入测试

class TestCommandInjection:
"""命令注入测试"""

COMMAND_INJECTION_PAYLOADS = [
"; ls -la",
"| cat /etc/passwd",
"&& whoami",
"|| whoami",
"`whoami`",
"$(whoami)",
"; id",
"| id",
]

def test_command_injection(self):
"""测试命令注入"""
for payload in self.COMMAND_INJECTION_PAYLOADS:
# 假设有一个ping功能
response = requests.get(f"{self.BASE_URL}/ping", params={
"host": payload
})

# 检查是否执行了命令
command_outputs = ["root:", "uid=", "total ", "drwx"]
for output in command_outputs:
assert output not in response.text, \
f"命令注入可能成功: {payload}"

防护措施

  • 使用参数化查询
  • 输入验证和过滤
  • 最小权限原则
  • 使用 ORM 框架

A05: 安全配置错误

风险描述:安全配置不当是最常见的问题之一,包括默认配置、详细的错误信息、开放的云存储等。

测试示例

import pytest
import requests

class TestSecurityConfiguration:
"""安全配置测试"""

BASE_URL = "http://localhost:8000"

def test_default_credentials(self):
"""测试默认凭据"""
default_creds = [
("admin", "admin"),
("admin", "password"),
("admin", "123456"),
("root", "root"),
("test", "test"),
]

for username, password in default_creds:
response = requests.post(f"{self.BASE_URL}/login", json={
"username": username,
"password": password
})

assert response.status_code != 200, \
f"默认凭据可登录: {username}/{password}"

def test_sensitive_endpoints(self):
"""测试敏感端点访问"""
sensitive_paths = [
"/.git/config",
"/.env",
"/config.php",
"/backup.sql",
"/phpinfo.php",
"/.htaccess",
"/web.config",
"/server-status",
"/actuator",
"/debug",
"/admin",
]

for path in sensitive_paths:
response = requests.get(f"{self.BASE_URL}{path}")

assert response.status_code in [404, 403], \
f"敏感端点可访问: {path}"

def test_security_headers(self):
"""测试安全响应头"""
response = requests.get(self.BASE_URL)
headers = response.headers

required_headers = {
'X-Frame-Options': ['DENY', 'SAMEORIGIN'],
'X-Content-Type-Options': ['nosniff'],
'Strict-Transport-Security': None, # 只检查存在
'Content-Security-Policy': None,
'X-XSS-Protection': ['1; mode=block', '0'],
}

for header, expected_values in required_headers.items():
assert header in headers, f"缺少安全头: {header}"

if expected_values:
assert headers[header] in expected_values, \
f"安全头 {header} 值不正确: {headers.get(header)}"

def test_error_information_disclosure(self):
"""测试错误信息泄露"""
# 触发错误
response = requests.get(f"{self.BASE_URL}/nonexistent")

sensitive_info = [
"stacktrace",
"debug",
"exception",
"traceback",
"/home/",
"/var/www/",
"password",
"secret",
]

response_text = response.text.lower()
for info in sensitive_info:
assert info not in response_text, \
f"错误信息泄露敏感内容: {info}"

def test_directory_listing(self):
"""测试目录列表"""
response = requests.get(f"{self.BASE_URL}/static/")

# 检查是否有目录列表特征
listing_indicators = [
"Index of",
"<title>Index of",
"Parent Directory",
]

for indicator in listing_indicators:
assert indicator not in response.text, \
"目录列表未禁用"

安全测试工具

OWASP ZAP

OWASP ZAP(Zed Attack Proxy)是一个免费的安全测试工具,支持自动化和手动测试。

安装

# macOS
brew install --cask owasp-zap

# Windows (使用 Chocolatey)
choco install zap

# Linux
sudo apt install zaproxy

命令行扫描

# 快速扫描
zap.sh -cmd -quickurl https://example.com -quickout report.html

# 详细扫描
zap.sh -cmd -quickurl https://example.com -quickout report.html

# API 扫描
zap.sh -cmd -quickurl https://api.example.com/openapi.json -quickout report.html

Python 集成

from zapv2 import ZAPv2

# 连接到 ZAP
zap = ZAPv2(proxies={
'http': 'http://127.0.0.1:8080',
'https': 'http://127.0.0.1:8080'
})

# 扫描目标
target = 'https://example.com'
zap.urlopen(target)

# 爬虫扫描
zap.spider.scan(target)

# 主动扫描
zap.ascan.scan(target)

# 获取结果
alerts = zap.core.alerts()
for alert in alerts:
print(f"风险: {alert['risk']}")
print(f"标题: {alert['alert']}")
print(f"URL: {alert['url']}")
print(f"解决方案: {alert['solution']}")
print("---")

Burp Suite

Burp Suite 是专业的 Web 安全测试工具,提供代理、爬虫、扫描器等功能。

常用功能

  1. 代理拦截:拦截和修改 HTTP 请求
  2. 爬虫:自动发现应用内容
  3. 扫描器:自动发现安全漏洞
  4. Intruder:进行定制化攻击

Python 扩展示例

# Burp Suite 扩展示例 (Jython)
from burp import IBurpExtender, IScannerCheck

class BurpExtender(IBurpExtender, IScannerCheck):
def registerExtenderCallbacks(self, callbacks):
self._callbacks = callbacks
callbacks.setExtensionName("Custom Security Check")
callbacks.registerScannerCheck(self)

def doActiveScan(self, baseRequestResponse, insertionPoint):
# 自定义主动扫描逻辑
return None

def doPassiveScan(self, baseRequestResponse):
# 自定义被动扫描逻辑
return []

SonarQube (SAST)

SonarQube 是代码质量和安全分析平台。

配置文件

# sonar-project.properties
sonar.projectKey=my-project
sonar.sources=src
sonar.tests=tests
sonar.language=py
sonar.python.coverage.reportPaths=coverage.xml
sonar.security.hotspots.enabled=true

运行分析

# 安装 Scanner
# macOS
brew install sonar-scanner

# 运行分析
sonar-scanner

Semgrep

Semgrep 是轻量级的静态分析工具,支持自定义规则。

配置文件

# .semgrep.yml
rules:
- id: insecure-random
patterns:
- pattern: random.random()
message: "使用不安全的随机数生成器,安全场景请使用 secrets 模块"
severity: WARNING
languages: [python]

- id: sql-injection
patterns:
- pattern: cursor.execute(f"...{$VAR}...")
- pattern: cursor.execute("..." + $VAR + "...")
message: "可能的 SQL 注入漏洞,请使用参数化查询"
severity: ERROR
languages: [python]

- id: hardcoded-password
patterns:
- pattern: password = "..."
- pattern: PASSWORD = "..."
message: "硬编码密码,请使用环境变量或配置文件"
severity: ERROR
languages: [python]

运行扫描

# 安装
pip install semgrep

# 运行扫描
semgrep --config .semgrep.yml src/

# 使用在线规则库
semgrep --config auto src/

Snyk (SCA)

Snyk 用于检测和修复依赖中的安全漏洞。

使用方法

# 安装
npm install -g snyk

# 认证
snyk auth

# 扫描项目
snyk test

# 监控项目
snyk monitor

# 修复漏洞
snyk fix

# 扫描容器镜像
snyk container test myimage:latest

GitHub Actions 集成

name: Security Scan

on: [push, pull_request]

jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Run Snyk
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

安全测试流程

1. 范围定义

明确测试范围和目标:

  • 测试的系统边界和IP范围
  • 允许的测试类型和方法
  • 测试时间窗口
  • 数据处理规范
  • 联系人和应急响应流程

2. 信息收集

收集目标系统的信息:

# 域名信息
whois example.com
dig example.com

# 端口扫描
nmap -sV -sC example.com

# 目录扫描
gobuster dir -u https://example.com -w /path/to/wordlist

# 技术栈识别
wappalyzer https://example.com
whatweb https://example.com

# 子域名枚举
subfinder -d example.com

3. 漏洞分析

使用工具和手动方法发现漏洞:

# OWASP ZAP 自动化扫描
zap-baseline.py -t https://example.com

# Nuclei 模板扫描
nuclei -u https://example.com -t /path/to/templates

# Nikto Web 服务器扫描
nikto -h https://example.com

# SQLMap SQL 注入检测
sqlmap -u "https://example.com/page?id=1" --batch

4. 报告编写

编写详细的安全测试报告:

# 安全测试报告

## 1. 执行摘要
- 测试时间:2024-01-01
- 测试范围:https://example.com
- 发现漏洞总数:15
- 严重:2
- 高危:5
- 中危:6
- 低危:2

## 2. 漏洞详情

### 2.1 SQL 注入(严重)

**漏洞编号**:VULN-001
**风险等级**:严重
**影响范围**:用户登录接口
**漏洞位置**:POST /api/login

**漏洞描述**
登录接口的 username 参数存在 SQL 注入漏洞,攻击者可以通过构造特殊输入绕过认证。

**复现步骤**
1. 发送以下请求:

POST /api/login HTTP/1.1 Content-Type: application/json

{"username": "admin'--", "password": "anything"}

2. 服务器返回成功登录响应

**影响**:
攻击者可以绕过认证,获取任意用户权限,读取或修改数据库数据。

**修复建议**:
使用参数化查询替代字符串拼接:
```python
# 不安全
cursor.execute(f"SELECT * FROM users WHERE username='{username}'")

# 安全
cursor.execute("SELECT * FROM users WHERE username=%s", (username,))

3. 修复建议汇总

漏洞优先级修复建议
SQL 注入紧急使用参数化查询
XSS输出编码
配置错误更新安全头

4. 附录

  • 扫描工具报告
  • 测试截图

## 安全测试最佳实践

### 1. 左移安全

在开发早期就进行安全测试:

```yaml
# .github/workflows/security.yml
name: Security

on: [push, pull_request]

jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Semgrep Scan
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/secrets
p/owasp-top-ten

sca:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Snyk Scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

2. 纵深防御

实施多层安全措施:

from functools import wraps
from flask import request, jsonify, g
import jwt

def security_layers(f):
"""多层安全装饰器"""
@wraps(f)
def decorated_function(*args, **kwargs):
# 第一层:认证检查
token = request.headers.get('Authorization', '').replace('Bearer ', '')
if not token:
return jsonify({"error": "Missing token"}), 401

try:
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
g.user_id = payload['user_id']
except jwt.InvalidTokenError:
return jsonify({"error": "Invalid token"}), 401

# 第二层:授权检查
if not has_permission(g.user_id, request.endpoint):
return jsonify({"error": "Permission denied"}), 403

# 第三层:输入验证
data = request.get_json() or {}
if not validate_input(data):
return jsonify({"error": "Invalid input"}), 400

# 第四层:速率限制
if is_rate_limited(g.user_id):
return jsonify({"error": "Rate limit exceeded"}), 429

# 执行业务逻辑
return f(*args, **kwargs)

return decorated_function

3. 安全编码规范

# 输入验证
from pydantic import BaseModel, validator, constr
import re

class UserInput(BaseModel):
username: constr(min_length=3, max_length=20, pattern=r'^[a-zA-Z0-9_]+$')
email: constr(to_lower=True)
password: constr(min_length=8)

@validator('email')
def validate_email(cls, v):
if not re.match(r'^[^@]+@[^@]+\.[^@]+$', v):
raise ValueError('Invalid email format')
return v

@validator('password')
def validate_password(cls, v):
if not re.search(r'[A-Z]', v):
raise ValueError('Password must contain uppercase letter')
if not re.search(r'[a-z]', v):
raise ValueError('Password must contain lowercase letter')
if not re.search(r'[0-9]', v):
raise ValueError('Password must contain digit')
return v

# 安全的密码存储
from werkzeug.security import generate_password_hash, check_password_hash

def create_user(username, password):
"""创建用户,使用安全的密码哈希"""
hashed = generate_password_hash(password, method='pbkdf2:sha256')
user = User(username=username, password_hash=hashed)
db.session.add(user)
db.session.commit()

def verify_user(username, password):
"""验证用户"""
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password_hash, password):
return user
return None

# 安全的 SQL 查询
from sqlalchemy import text

def get_user_by_id(user_id):
"""安全的参数化查询"""
# 使用 ORM
return User.query.filter_by(id=user_id).first()

def search_users(name):
"""安全的原生 SQL"""
result = db.session.execute(
text("SELECT * FROM users WHERE name LIKE :name"),
{"name": f"%{name}%"}
)
return result.fetchall()

4. 安全日志

import logging
from datetime import datetime
from flask import request, g

# 安全事件日志器
security_logger = logging.getLogger('security')
security_logger.setLevel(logging.WARNING)

def log_security_event(event_type, details, severity='WARNING'):
"""记录安全事件"""
log_data = {
"timestamp": datetime.utcnow().isoformat(),
"event_type": event_type,
"ip_address": request.remote_addr,
"user_id": getattr(g, 'user_id', None),
"user_agent": request.user_agent.string,
"url": request.url,
"details": details
}

if severity == 'CRITICAL':
security_logger.critical(str(log_data))
elif severity == 'ERROR':
security_logger.error(str(log_data))
else:
security_logger.warning(str(log_data))

# 使用示例
@app.route("/login", methods=["POST"])
def login():
user = authenticate(request.form)
if user:
log_security_event("LOGIN_SUCCESS", {
"username": user.username
})
return redirect("/dashboard")
else:
log_security_event("LOGIN_FAILED", {
"username": request.form.get('username'),
"reason": "Invalid credentials"
}, severity='WARNING')
return render_template("login.html", error="Invalid credentials")

总结

安全测试是保障软件安全的关键环节。关键要点:

  • 持续安全:安全测试贯穿整个开发生命周期
  • 全面覆盖:覆盖认证、授权、输入验证、配置等各方面
  • 工具辅助:合理使用自动化工具提高效率
  • 流程规范:建立规范的安全测试流程
  • 安全意识:提高团队整体安全意识

参考资源