security-testing security-testing

这个技能涉及通过多种安全测试方法,如SAST、DAST、渗透测试和依赖扫描,来识别和修复应用程序中的安全漏洞,确保数据安全和系统完整性。

渗透测试 0 次安装 0 次浏览 更新于 3/4/2026

name: security-testing description: 通过SAST、DAST、渗透测试和依赖扫描识别安全漏洞。用于安全测试、漏洞扫描、OWASP、SQL注入、XSS、CSRF和渗透测试。

安全测试

概览

安全测试识别应用程序中的漏洞、弱点和威胁,以确保数据保护、防止未经授权的访问并维护系统完整性。它结合了自动化扫描(SAST、DAST)与手动渗透测试和代码审查。

何时使用

  • 测试OWASP Top 10漏洞
  • 扫描依赖项以查找已知漏洞
  • 测试认证和授权
  • 验证输入清理
  • 测试API安全性
  • 检查敏感数据暴露
  • 验证安全头部
  • 测试会话管理

安全测试类型

  • SAST:静态应用安全测试(代码分析)
  • DAST:动态应用安全测试(运行时)
  • IAST:交互式应用安全测试
  • SCA:软件组件分析(依赖项)
  • 渗透测试:手动安全测试
  • 模糊测试:无效/随机输入测试

指令

1. OWASP ZAP (DAST)

# security_scan.py
from zapv2 import ZAPv2
import time

class SecurityScanner:
    def __init__(self, target_url, api_key=None):
        self.zap = ZAPv2(apikey=api_key, proxies={
            'http': 'http://localhost:8080',
            'https': 'http://localhost:8080'
        })
        self.target = target_url

    def scan(self):
        """运行完整安全扫描。"""
        print(f"扫描 {self.target}...")

        # 爬取应用程序
        print("爬取中...")
        scan_id = self.zap.spider.scan(self.target)
        while int(self.zap.spider.status(scan_id)) < 100:
            time.sleep(2)
            print(f"爬取进度:{self.zap.spider.status(scan_id)}%")

        # 活跃扫描
        print("运行活跃扫描...")
        scan_id = self.zap.ascan.scan(self.target)
        while int(self.zap.ascan.status(scan_id)) < 100:
            time.sleep(5)
            print(f"扫描进度:{self.zap.ascan.status(scan_id)}%")

        return self.get_results()

    def get_results(self):
        """获取扫描结果。"""
        alerts = self.zap.core.alerts(baseurl=self.target)

        # 按风险等级分组
        results = {
            'high': [],
            'medium': [],
            'low': [],
            'informational': []
        }

        for alert in alerts:
            risk = alert['risk'].lower()
            results[risk].append({
                'name': alert['alert'],
                'description': alert['description'],
                'solution': alert['solution'],
                'url': alert['url'],
                'param': alert.get('param', ''),
                'evidence': alert.get('evidence', '')
            })

        return results

    def report(self, results):
        """生成安全报告。"""
        print("
" + "="*60)
        print("安全扫描结果")
        print("="*60)

        for risk_level in ['high', 'medium', 'low', 'informational']:
            issues = results[risk_level]
            if issues:
                print(f"
{risk_level.upper()} 风险问题:{len(issues)}")
                for issue in issues[:5]:  # 显示前5个
                    print(f"  - {issue['name']}")
                    print(f"    URL: {issue['url']}")
                    if issue['param']:
                        print(f"    参数:{issue['param']}")

        # 如果发现高风险,失败
        if results['high']:
            raise Exception(f"发现 {len(results['high'])} 个高风险漏洞!")

# 使用方法
scanner = SecurityScanner('http://localhost:3000')
results = scanner.scan()
scanner.report(results)

2. SQL注入测试

// tests/security/sql-injection.test.ts
import { test, expect } from '@playwright/test';
import request from 'supertest';
import { app } from '../../src/app';

test.describe('SQL注入保护', () => {
  const sqlInjectionPayloads = [
    "' OR '1'='1",
    "'; DROP TABLE users; --",
    "' UNION SELECT * FROM users --",
    "admin'--",
    "' OR 1=1--",
    "1' AND '1'='1",
  ];

  test('登录应防止SQL注入', async () => {
    for (const payload of sqlInjectionPayloads) {
      const response = await request(app)
        .post('/api/auth/login')
        .send({
          email: payload,
          password: payload,
        });

      // 应返回400/401,而不是500(SQL错误)
      expect([400, 401]).toContain(response.status);
      expect(response.body).not.toMatch(/SQL|syntax|error/i);
    }
  });

  test('搜索应清理输入', async () => {
    for (const payload of sqlInjectionPayloads) {
      const response = await request(app)
        .get('/api/products/search')
        .query({ q: payload });

      // 不应导致SQL错误
      expect(response.status).toBeLessThan(500);
      expect(response.body).not.toMatch(/SQL|syntax/i);
    }
  });

  test('数字参数应验证', async () => {
    const response = await request(app)
      .get('/api/users/abc')  // 非数字ID
      .expect(400);

    expect(response.body.error).toBeTruthy();
  });
});

3. XSS测试

// tests/security/xss.test.js
describe('XSS保护', () => {
  const xssPayloads = [
    '<script>alert("XSS")</script>',
    '<img src=x onerror=alert("XSS")>',
    '<svg onload=alert("XSS")>',
    'javascript:alert("XSS")',
    '<iframe src="javascript:alert(\'XSS\')">',
    '<body onload=alert("XSS")>',
  ];

  test('用户输入应转义', async () => {
    const { page } = await browser.newPage();

    for (const payload of xssPayloads) {
      await page.goto('/');

      // 提交带有XSS负载的评论
      await page.fill('[name="comment"]', payload);
      await page.click('[type="submit"]');

      // 等待评论出现
      await page.waitForSelector('.comment');

      // 检查脚本是否未执行
      const dialogAppeared = await page.evaluate(() => {
        return window.xssDetected || false;
      });

      expect(dialogAppeared).toBe(false);

      // 检查HTML是否已转义
      const commentHTML = await page.$eval('.comment', el => el.innerHTML);
      expect(commentHTML).not.toContain('<script>');
      expect(commentHTML).toContain('&lt;script&gt;');
    }
  });

  test('URL应验证', async () => {
    const response = await request(app)
      .post('/api/links')
      .send({ url: 'javascript:alert("XSS")' })
      .expect(400);

    expect(response.body.error).toMatch(/invalid url/i);
  });
});

4. 认证与授权测试

// tests/security/auth.test.ts
describe('认证安全', () => {
  test('应拒绝弱密码', async () => {
    const weakPasswords = [
      'password',
      '12345678',
      'qwerty',
      'abc123',
      'password123',
    ];

    for (const password of weakPasswords) {
      const response = await request(app)
        .post('/api/users')
        .send({
          email: 'test@example.com',
          password,
        });

      expect(response.status).toBe(400);
      expect(response.body.error).toMatch(/password.*weak|password.*requirements/i);
    }
  });

  test('应限制登录尝试次数', async () => {
    const credentials = {
      email: 'test@example.com',
      password: 'wrongpassword',
    };

    // 尝试10次失败的登录
    for (let i = 0; i < 10; i++) {
      await request(app)
        .post('/api/auth/login')
        .send(credentials);
    }

    // 第11次尝试应受到速率限制
    const response = await request(app)
      .post('/api/auth/login')
      .send(credentials);

    expect(response.status).toBe(429);
    expect(response.body.error).toMatch(/too many attempts|rate limit/i);
  });

  test('应防止未经授权的访问', async () => {
    const response = await request(app)
      .get('/api/admin/users')
      .expect(401);
  });

  test('应防止权限提升', async () => {
    const regularUserToken = await getRegularUserToken();

    const response = await request(app)
      .delete('/api/users/999')  // 尝试删除另一个用户
      .set('Authorization', `Bearer ${regularUserToken}`)
      .expect(403);
  });

  test('JWT令牌应过期', async () => {
    // 创建过期令牌
    const expiredToken = jwt.sign(
      { userId: '123' },
      JWT_SECRET,
      { expiresIn: '-1s' }
    );

    const response = await request(app)
      .get('/api/protected')
      .set('Authorization', `Bearer ${expiredToken}`)
      .expect(401);
  });
});

5. CSRF保护测试

# tests/security/test_csrf.py
import pytest
from flask import session

class TestCSRFProtection:
    def test_post_without_csrf_token_rejected(self, client):
        """没有CSRF令牌的POST请求应被拒绝。"""
        response = client.post('/api/users', json={
            'email': 'test@example.com',
            'name': 'Test'
        })

        assert response.status_code == 403
        assert 'CSRF' in response.json['error']

    def test_post_with_invalid_csrf_token_rejected(self, client):
        """带有无效CSRF令牌的POST应被拒绝。"""
        response = client.post('/api/users',
            json={'email': 'test@example.com'},
            headers={'X-CSRF-Token': 'invalid-token'}
        )

        assert response.status_code == 403

    def test_post_with_valid_csrf_token_accepted(self, client):
        """带有有效CSRF令牌的POST应被接受。"""
        # 获取CSRF令牌
        response = client.get('/api/csrf-token')
        csrf_token = response.json['csrfToken']

        # 在POST中使用令牌
        response = client.post('/api/users',
            json={'email': 'test@example.com', 'name': 'Test'},
            headers={'X-CSRF-Token': csrf_token}
        )

        assert response.status_code == 201

6. 依赖漏洞扫描

# 运行npm audit
npm audit

# 修复漏洞
npm audit fix

# 对于Python - Safety
pip install safety
safety check

# 对于Java - OWASP Dependency Check
mvn org.owasp:dependency-check-maven:check
# .github/workflows/security.yml
name: 安全扫描

on: [push, pull_request]

jobs:
  dependency-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: 运行npm audit
        run: npm audit --audit-level=high

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

  sast-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

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

  dast-scan:
    runs-on: ubuntu-latest
    steps:
      - name: ZAP扫描
        uses: zaproxy/action-baseline@v0.7.0
        with:
          target: 'http://localhost:3000'

7. 安全头部测试

// tests/security/headers.test.ts
test.describe('安全头部', () => {
  test('应有必需的安全头部', async () => {
    const response = await request(app).get('/');

    expect(response.headers).toMatchObject({
      'x-frame-options': 'DENY',
      'x-content-type-options': 'nosniff',
      'x-xss-protection': '1; mode=block',
      'strict-transport-security': expect.stringMatching(/max-age=/),
      'content-security-policy': expect.any(String),
    });
  });

  test('不应暴露敏感头部', async () => {
    const response = await request(app).get('/');

    expect(response.headers['x-powered-by']).toBeUndefined();
    expect(response.headers['server']).not.toMatch(/express|nginx|apache/i);
  });

  test('CSP应防止内联脚本', async ({ page }) => {
    await page.goto('/');

    const cspViolations = [];
    page.on('console', msg => {
      if (msg.type() === 'error' && msg.text().includes('Content Security Policy')) {
        cspViolations.push(msg.text());
      }
    });

    // 尝试注入内联脚本
    await page.evaluate(() => {
      const script = document.createElement('script');
      script.textContent = 'alert("test")';
      document.body.appendChild(script);
    });

    expect(cspViolations.length).toBeGreaterThan(0);
  });
});

8. 秘密检测

# 安装detect-secrets
pip install detect-secrets

# 扫描仓库
detect-secrets scan --all-files --force-use-all-plugins

# 检查硬编码的秘密
git secrets --scan

# TruffleHog用于git历史
trufflehog git https://github.com/user/repo --only-verified

OWASP Top 10测试

  1. 访问控制破坏:测试授权,权限提升
  2. 密码学失败:检查弱加密,暴露的秘密
  3. 注入:SQL、NoSQL、命令注入
  4. 不安全设计:架构缺陷
  5. 安全配置不当:默认配置,不必要的功能
  6. 易受攻击的组件:过时的依赖项
  7. 认证失败:弱密码,会话管理
  8. 软件和数据完整性:未签名的包,不安全的CI/CD
  9. 日志记录失败:日志记录不足,日志中的敏感数据
  10. SSRF:服务器端请求伪造

最佳实践

✅ DO

  • 在CI/CD中运行安全扫描
  • 使用真实攻击向量进行测试
  • 定期扫描依赖项
  • 使用安全头部
  • 实施速率限制
  • 验证和清理所有输入
  • 使用参数化查询
  • 彻底测试认证/授权

❌ DON’T

  • 在代码中存储秘密
  • 信任用户输入
  • 暴露详细的错误消息
  • 跳过依赖项更新
  • 使用默认凭据
  • 忽略安全警告
  • 仅测试快乐路径
  • 提交敏感数据

工具

SAST

  • Semgrep:多语言静态分析
  • SonarQube:代码质量和安全
  • Bandit:Python安全linter
  • ESLint插件:JavaScript安全

DAST

  • OWASP ZAP:Web应用安全扫描器
  • Burp Suite:安全测试平台
  • Nikto:Web服务器扫描器

SCA

  • Snyk:依赖漏洞扫描
  • npm audit:Node.js依赖项
  • OWASP Dependency-Check:多语言
  • Safety:Python依赖项

秘密

  • detect-secrets:预提交钩子
  • GitGuardian:秘密检测
  • TruffleHog:Git历史扫描

渗透测试清单

输入验证

  • [ ] 阻止SQL注入尝试
  • [ ] 转义XSS负载
  • [ ] 防止命令注入
  • [ ] 阻止路径遍历
  • [ ] 文件上传限制

认证

  • [ ] 强密码策略
  • [ ] 失败尝试后账户锁定
  • [ ] 实施会话超时
  • [ ] 安全的密码重置
  • [ ] 可用MFA

授权

  • [ ] 基于角色的访问控制
  • [ ] 防止权限提升
  • [ ] 直接对象引用安全
  • [ ] API端点受保护

数据保护

  • [ ] 加密敏感数据
  • [ ] 强制HTTPS
  • [ ] 安全Cookie(HttpOnly,Secure)
  • [ ] 日志中无秘密
  • [ ] 正确处理PII

示例

另见:持续测试、API契约测试、代码审查分析,以获得全面的安全实践。