Skip to content
第 237 / 250 章架构⏱ 12 分钟阅读

第 237 章:系统与应用安全实战

学习目标

  • 操作系统层安全
  • 网络安全防护
  • 应用层安全编码
  • 安全漏洞修复

一、OWASP Top 10(2021)

排名漏洞防护
1A01: 访问控制失效RBAC + 鉴权
2A02: 加密失败TLS、HTTPS、加密存储
3A03: 注入参数化查询、ORM
4A04: 不安全设计威胁建模 + 安全设计
5A05: 配置错误加固基线、移除默认
6A06: 脆弱过时组件SCA 工具
7A07: 认证失效JWT、MFA
8A08: 软件和数据完整性失效签名 + 校验
9A09: 日志不足集中日志 + 告警
10A10: SSRFURL 白名单

二、SQL 注入防御

2.1 漏洞代码

java
// ❌ 直接拼接
@Query("SELECT u FROM User u WHERE u.name = '" + name + "'")
public List<User> findByName(String name);

攻击:

name = "' OR '1'='1"
SQL: WHERE u.name = '' OR '1'='1'
返回全部用户

2.2 安全方案

java
// ✅ 方案 1:参数化
@Query("SELECT u FROM User u WHERE u.name = :name")
public List<User> findByName(@Param("name") String name);

// ✅ 方案 2:JPA Criteria
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
query.where(cb.equal(root.get("name"), name));

// ✅ 方案 3:MyBatis
<select id="findByName" parameterType="String" resultType="User">
    SELECT * FROM user WHERE name = #{name}
</select>

2.3 输入过滤

java
public class SqlInjectionChecker {
    private static final Pattern SQL_PATTERN = Pattern.compile(
        ".*\\b(select|insert|update|delete|drop|alter|union|exec|;|--|\\*)\\b.*",
        Pattern.CASE_INSENSITIVE
    );

    public static boolean isInjection(String input) {
        return SQL_PATTERN.matcher(input).find();
    }
}

三、XSS 防御

3.1 反射型 XSS

html
<!-- URL: /search?q=<script>alert('xss')</script> -->
<div>搜索结果: <span th:utext="${q}"></span></div>  <!-- ❌ 渲染脚本 -->

3.2 输出编码

html
<!-- Thymeleaf:用 th:text 而不是 th:utext -->
<div>搜索结果: <span th:text="${q}"></span></div>  <!-- ✅ 自动转义 -->

<!-- Vue:默认 {{ }} 转义 -->
<div>{{ q }}</div>   ✅
<div v-html="q"></div>   ❌

3.3 Spring 后端防御

java
@RestController
public class XssFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;

        // 包装 Request
        chain.doFilter(new XssHttpServletRequestWrapper(req), response);
    }
}

class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values != null) {
            return Arrays.stream(values)
                .map(XssUtil::clean)
                .toArray(String[]::new);
        }
        return values;
    }
}

3.4 CSP

http
Content-Security-Policy: default-src 'self';
    script-src 'self' 'unsafe-inline';
    style-src 'self' 'unsafe-inline';
    img-src * data:;

四、CSRF 防御

4.1 攻击原理

4.2 防御方案

同步器 Token

java
// Spring Security 默认开 CSRF
http.csrf().tokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
javascript
// 前端从 Cookie 读 csrf token,放在 Header
axios.get('/api/data', {
    headers: { 'X-CSRF-Token': cookie.CSRF_TOKEN }
});
yaml
server:
  servlet:
    session:
      cookie:
        same-site: Strict       # 不跨站发送
        http-only: true

五、点击劫持

5.1 攻击

html
<!-- 攻击者页面 -->
<iframe src="https://bank.com/transfer?to=attacker&amount=10000"></iframe>
<div style="position:absolute; top:0; left:0; width:100%; height:50px; background:transparent">
    <button>点这里领奖品</button>
</div>

5.2 防御

yaml
# Spring Security
http.headers().frameOptions().deny();   # 拒绝 iframe

# Nginx
add_header X-Frame-Options "DENY" always;
add_header X-Frame-Options "SAMEORIGIN";   # 仅同源

六、SSRF 防御

6.1 漏洞

java
// ❌ URL 直接转发
@PostMapping("/proxy")
public String proxy(@RequestParam String url) {
    return new URL(url).openStream().toString();   // 用户可控
}

// 攻击:url=http://internal.api/admin  → 访问内网

6.2 防御

java
public class SSRFValidator {
    private static final List<String> ALLOWED_HOSTS = List.of("api.example.com");
    private static final Set<String> PRIVATE_RANGES = Set.of(
        "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8"
    );

    public static void validate(String urlStr) throws Exception {
        URL url = new URL(urlStr);
        String host = url.getHost();

        // 1. 仅允许白名单
        if (!ALLOWED_HOSTS.contains(host)) {
            throw new SecurityException("Host not allowed");
        }

        // 2. 不允许内网 IP
        InetAddress addr = InetAddress.getByName(host);
        if (addr.isLoopbackAddress() || addr.isSiteLocalAddress()) {
            throw new SecurityException("Private IP not allowed");
        }
    }
}

七、XML 攻击(XXE)

java
// ❌ 默认解析会包含外部实体
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document doc = factory.newDocumentBuilder().parse(input);

// ✅ 关闭外部实体
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

八、反序列化漏洞

java
// ❌ ObjectInputStream 接收任何类
ObjectInputStream ois = new ObjectInputStream(stream);
Object obj = ois.readObject();   // 危险

// ✅ 白名单
ois.setObjectInputFilter(info -> {
    if (info.getSerialClass() == MyDTO.class) {
        return ObjectInputFilter.Status.ALLOWED;
    }
    return ObjectInputFilter.Status.REJECTED;
});

九、网络安全

9.1 防火墙与安全组

yaml
security_group:
  - name: web-server
    ingress:
      - from: 0.0.0.0/0
        port: 443
        protocol: tcp
      - from: 10.0.0.0/8
        port: 22
        protocol: tcp
    egress:
      - to: 0.0.0.0/0
        port: 443
        protocol: tcp

9.2 WAF(Web 应用防火墙)

yaml
防护规则:
  - SQL 注入阻断
  - XSS 阻断
  - CC 攻击拦截
  - 爬虫 User-Agent 拦截
  - 地理黑名单

工具:
  - Cloudflare
  - 阿里云 WAF
  - AWS WAF
  - ModSecurity + Nginx

9.3 DDoS 防护

措施实现
CDNCloudFlare / 阿里云 CDN
速率限制Nginx limit_req
IP 黑名单安全组
域名就近解析DNS 智能解析

9.4 零信任网络

十、Linux 系统加固

10.1 最小化安装

bash
# 不装多余服务
yum remove telnet rsh
yum install minimal

# 关闭防火墙(已有外部防火墙)
systemctl stop firewalld

10.2 SSH 安全

bash
# /etc/ssh/sshd_config
Port 2222                          # 改默认端口
PermitRootLogin no                 # 禁用 root 登录
PasswordAuthentication no         # 仅密钥
MaxAuthTries 3
ClientAliveInterval 300
AllowUsers deploy

10.3 自动安全更新

bash
# CentOS
yum install yum-cron
systemctl enable yum-cron

# Debian
apt install unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

10.4 文件完整性监控

bash
# AIDE
yum install aide
aide --init
aide --check  # 检查变化

10.5 审计日志

bash
# auditd
auditctl -w /etc/passwd -p wa
auditctl -w /var/log/audit/ -p wa

十一、容器安全

11.1 镜像扫描

bash
trivy image myapp:1.0

# 集成 CI
trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:1.0

11.2 最小镜像

dockerfile
# ❌ base 镜像大,带很多工具
FROM ubuntu:latest

# ✅ alpine 最小
FROM alpine:3.18

# ✅ distroless(甚至连 shell 都没有)
FROM gcr.io/distroless/java17-debian12

USER nonroot

11.3 K8s 安全

yaml
# Pod Security Standards
apiVersion: v1
kind: Pod
spec:
  securityContext:
    runAsNonRoot: true              # 非 root
    readOnlyRootFilesystem: true    # 根 FS 只读
    allowPrivilegeEscalation: false # 不允许提权
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: myapp:1.0
      securityContext:
        capabilities:
          drop:                     # 限制 capabilities
            - ALL

11.4 Secret 管理

yaml
# ❌ Secret 写在环境变量
env:
  - name: DB_PASSWORD
    value: root123

# ✅ Mount Secret
volumeMounts:
  - name: db-secret
    mountPath: /etc/secret
    readOnly: true
volumes:
  - name: db-secret
    secret:
      secretName: db-credentials

11.5 NetworkPolicy

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}     # 所有 Pod
  policyTypes:
    - Ingress
    - Egress
  # 默认拒绝所有,需要显式 allow

十二、安全编码规范

12.1 输入验证

java
public class InputValidator {

    public static void validateUsername(String username) {
        if (username == null || username.length() < 3 || username.length() > 30) {
            throw new ValidationException("用户名长度 3-30");
        }
        if (!username.matches("[a-zA-Z0-9_]+")) {
            throw new ValidationException("只能包含字母数字下划线");
        }
    }

    public static void validateEmail(String email) {
        if (!email.matches("^[\\w.+-]+@[\\w.-]+\\.[a-zA-Z]{2,}$")) {
            throw new ValidationException("邮箱格式错误");
        }
    }
}

12.2 错误处理不泄露信息

java
// ❌ 异常打印到响应
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handle(Exception e) {
    return ResponseEntity.status(500).body(e.getMessage() + " / " + e.getStackTrace());
}

// ✅ 统一错误响应
@ExceptionHandler(Exception.class)
public ResponseEntity<Result<?>> handle(Exception e) {
    log.error("Server error", e);
    return ResponseEntity.status(500).body(
        Result.error("INTERNAL_ERROR", "服务异常,请稍后")
    );
}

12.3 业务风控校验

java
@PreAuthorize("hasAuthority('SCOPE_admin') or #userId == authentication.principal")
public Order getOrder(Long orderId, Long userId) { ... }

十三、安全审计与告警

13.1 审计日志

java
@Aspect
@Component
public class SecurityAuditAspect {

    @After("@annotation(RequiresAudit) || execution(* com.example.service.*.*(..))")
    public void audit(JoinPoint jp) {
        AuditLog log = new AuditLog();
        log.setService(jp.getTarget().getClass().getSimpleName());
        log.setMethod(jp.getSignature().getName());
        log.setUser(UserContextHolder.getUserId());
        log.setIp(RequestUtil.getClientIp());
        log.setArgs(truncate(JSON.toJSONString(jp.getArgs()), 2000));
        log.setTimestamp(Instant.now());
        auditRepo.save(log);
    }
}

13.2 异常告警

yaml
# 暴力破解
- alert: ExcessiveFailedLogins
  expr: |
    sum(rate(login_failed_total[5m])) > 10
  labels:
    severity: critical

# 异地登录
- alert: GeoAnomalousLogin
  expr: |
    count(
        count by (user_id, country) (login_event{status="success"})
    ) > 1

十四、密码安全

14.1 BCrypt 加盐哈希

java
// 注册
String hash = BCrypt.hashpw(password, BCrypt.gensalt(12));
user.setPassword(hash);

// 登录
if (BCrypt.checkpw(inputPassword, user.getPassword())) {
    // 登录成功
}

14.2 弱密码策略

yaml
要求:
  - 长度 ≥ 8
  - 大小写 + 数字 + 特殊字符
  - 不可为常见弱密码(123456, qwerty)

# Spring Validation
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%]).{8,}$")

14.3 密码防爆

java
// 登录失败计数器
RateLimiter rateLimit = RateLimiter.create(5.0 / 60);   // 每分钟 5 次

@PostMapping("/login")
public Result login(@RequestBody LoginRequest req) {
    String key = "login:fail:" + req.getUsername();
    long fails = redis.incr(key);
    if (fails > 10) {
        // 锁账号 30 分钟
        redis.expire(key, 30, TimeUnit.MINUTES);
        throw new BizException("账户已锁定,请 30 分钟后重试");
    }

    // ... 登录逻辑
}

14.4 多因素认证(MFA)

工具:

  • Google Authenticator (TOTP)
  • 短信验证码
  • 邮箱验证码
  • UKey

十五、本章小结

主题关键
SQL 注入参数化 + ORM
XSS输出转义 + CSP
CSRFToken + SameSite
SSRFURL 白名单
网络WAF + 防火墙
容器非 root + 最小镜像
审计集中日志 + 告警

动手练习

  1. 给你的 Controller 加 SQL 注入测试用例
  2. 配置 CSP 头,观察浏览器拒绝脚本加载
  3. 用 OWASP ZAP 扫描你的应用
  4. 加一层 SQL 注入检测 Filter

下一章:第 238 章:审计与合规实战

本站基于 VitePress 构建 · 由 Codebook 团队维护