第 237 章:系统与应用安全实战
学习目标
- 操作系统层安全
- 网络安全防护
- 应用层安全编码
- 安全漏洞修复
一、OWASP Top 10(2021)
| 排名 | 漏洞 | 防护 |
|---|---|---|
| 1 | A01: 访问控制失效 | RBAC + 鉴权 |
| 2 | A02: 加密失败 | TLS、HTTPS、加密存储 |
| 3 | A03: 注入 | 参数化查询、ORM |
| 4 | A04: 不安全设计 | 威胁建模 + 安全设计 |
| 5 | A05: 配置错误 | 加固基线、移除默认 |
| 6 | A06: 脆弱过时组件 | SCA 工具 |
| 7 | A07: 认证失效 | JWT、MFA |
| 8 | A08: 软件和数据完整性失效 | 签名 + 校验 |
| 9 | A09: 日志不足 | 集中日志 + 告警 |
| 10 | A10: SSRF | URL 白名单 |
二、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());双重 Cookie 提交
javascript
// 前端从 Cookie 读 csrf token,放在 Header
axios.get('/api/data', {
headers: { 'X-CSRF-Token': cookie.CSRF_TOKEN }
});SameSite Cookie
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: tcp9.2 WAF(Web 应用防火墙)
yaml
防护规则:
- SQL 注入阻断
- XSS 阻断
- CC 攻击拦截
- 爬虫 User-Agent 拦截
- 地理黑名单
工具:
- Cloudflare
- 阿里云 WAF
- AWS WAF
- ModSecurity + Nginx9.3 DDoS 防护
| 措施 | 实现 |
|---|---|
| CDN | CloudFlare / 阿里云 CDN |
| 速率限制 | Nginx limit_req |
| IP 黑名单 | 安全组 |
| 域名就近解析 | DNS 智能解析 |
9.4 零信任网络
十、Linux 系统加固
10.1 最小化安装
bash
# 不装多余服务
yum remove telnet rsh
yum install minimal
# 关闭防火墙(已有外部防火墙)
systemctl stop firewalld10.2 SSH 安全
bash
# /etc/ssh/sshd_config
Port 2222 # 改默认端口
PermitRootLogin no # 禁用 root 登录
PasswordAuthentication no # 仅密钥
MaxAuthTries 3
ClientAliveInterval 300
AllowUsers deploy10.3 自动安全更新
bash
# CentOS
yum install yum-cron
systemctl enable yum-cron
# Debian
apt install unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades10.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.011.2 最小镜像
dockerfile
# ❌ base 镜像大,带很多工具
FROM ubuntu:latest
# ✅ alpine 最小
FROM alpine:3.18
# ✅ distroless(甚至连 shell 都没有)
FROM gcr.io/distroless/java17-debian12
USER nonroot11.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
- ALL11.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-credentials11.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 |
| CSRF | Token + SameSite |
| SSRF | URL 白名单 |
| 网络 | WAF + 防火墙 |
| 容器 | 非 root + 最小镜像 |
| 审计 | 集中日志 + 告警 |
动手练习
- 给你的 Controller 加 SQL 注入测试用例
- 配置 CSP 头,观察浏览器拒绝脚本加载
- 用 OWASP ZAP 扫描你的应用
- 加一层 SQL 注入检测 Filter
下一章:第 238 章:审计与合规实战