CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
DevSecOps 入门/课时/应用安全
RawBlame
课时目录12 节

DevSecOps 入门

  1. 01DevSecOps 概览
  2. 02网络安全基础
  3. 03密码学
  4. 04身份与访问
  5. 05应用安全
  6. 06威胁建模
  7. 07云安全
  8. 08容器安全
  9. 09供应链安全
  10. 10检测与 SIEM
  11. 11Incident 响应
  12. 12合规与扫描

应用安全

OWASP 与安全编码。

35 分钟

应用安全

这一课你将能做到

  1. 说清 CIA 三元组(机密性、完整性、可用性),并能给本站每个组件对应一个 CIA 控制
  2. 识别 OWASP Top 10 中的前 5 项,写出对应 Spring Boot 的防御代码(SQL 注入、XSS、CSRF、不安全反序列化、安全配置错误)
  3. 用真实 Payload 在本站测试注入类漏洞,确认修复后的代码能拦截攻击

为什么要学

应用是最终暴露给攻击者的界面:你的 REST API、管理后台、第三方回调。无论防火墙多厚、TLS 多强,一个 Statement.execute(sql) 拼接的 SQL 注入就能让你全库裸奔。Log4Shell 也是应用层漏洞——不是网络配置问题。

人话:OWASP Top 10 是过去十年全球 web 应用漏洞的排行榜,前 5 条几乎全是输入校验问题。核心哲学只有一个——永远不要信任用户输入:表单、URL 参数、请求头、文件上传、第三方回调,全是攻击面。

先建立图像

CIA 三元组与本站映射:

机密性(C): HTTPS / AES 加密手机号 / JWT 验签
         │
完整性(I): 密码哈希不可改 / 审计日志不可删 / 参数签名
         │
可用性(A): Nginx 限流 / Redis 过期策略 / 数据库连接池上限

一次 SQL 注入的真实路径:

用户输入: ' OR '1'='1' --
          │
拼接 SQL: SELECT * FROM users WHERE username = '' OR '1'='1' --'
          │
结果: 条件永远为真,返回全部用户(包括管理员)

跟着做

步骤 1:SQL 注入——从「拼接」到「参数化」

❌ 错误示范(永不这样写):

public User findByUsername(String username) {
    String sql = "SELECT * FROM users WHERE username = '" + username + "'";
    return jdbcTemplate.queryForObject(sql, User.class);  // 灾难
}

✅ 正确做法(PreparedStatement / JPA):

// JDBC
public User findByUsername(String username) {
    String sql = "SELECT * FROM users WHERE username = ?";
    return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), username);
}

// JPA / Spring Data
public interface UserRepository extends JpaRepository<User, Long> {
    // 自动参数化,不要用 @Query 拼接
    Optional<User> findByUsername(String username);
}

手动构造注入 Payload 验证:

curl "https://coderoadmap.cn/api/users?username=' OR '1'='1' --"
# 正确防御时:返回空或 400,绝不应返回列表

步骤 2:XSS——输出编码 + 内容安全策略

❌ 错误:直接回显用户输入:

@GetMapping("/search")
public String search(@RequestParam String q) {
    return "<div>搜索 " + q + " 的结果</div>";  // 用户传 <script>alert(1)</script> 就执行
}

✅ 正确做法:

// 模板层(Thymeleaf 默认 HTML 编码,安全)
// 如果用前端分离(Vue/React),注意 SSR 时也要编码

// API 层直接返回 JSON,不要返回 HTML
@GetMapping("/search")
public SearchResult search(@RequestParam String q) {
    // 返回 POJO,Spring 自动 JSON 序列化,特殊字符被转义
    return searchService.find(q);
}

Nginx 加 CSP(Content Security Policy)响应头:

add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';" always;

步骤 3:CSRF——无状态 API 关,有状态表单开

纯 JWT + 无 Cookie 的前后端分离接口:不需要 CSRF,因为攻击者发不出自定义 Authorization: Bearer <token> 请求(跨域限制+无 Cookie)。

如果有状态 Session 或混合登录(Form Login),必须开 CSRF:

// 前端发送 POST /login 时,Spring Security 默认要求 _csrf token
http.csrf(csrf -> csrf
    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
    .requireCsrfProtectionMatcher(
        request -> !request.getRequestURI().startsWith("/api/")  // 除 /api 外都要 token
    )
);

步骤 4:不安全反序列化——白名单

❌ 永远不要这样做:

ObjectInputStream ois = new ObjectInputStream(req.getInputStream());
Object obj = ois.readObject();  // 攻击者构造恶意对象链=远程代码执行

✅ 正确做法(用 JSON 反序列化代替原生 Java 序列化):

// 用 Jackson,默认不能反序列化任意类
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(json, User.class);

// 如果必须用 ObjectInputStream,限制白名单
class SafeObjectInputStream extends ObjectInputStream {
    @Override protected Class<?> resolveClass(ObjectStreamClass desc) {
        if (!desc.getName().startsWith("cn.coderoadmap.")) {
            throw new SecurityException("只允许反序列化白名单类");
        }
        return super.resolveClass(desc);
    }
}

步骤 5:安全配置错误——逐项检查

// application.yml 安全基线
server:
  error:
    include-message: never          # 生产环境不暴露异常堆栈
    include-stacktrace: never
  servlet:
    session:
      cookie:
        http-only: true             # 防 JS 读 cookie
        secure: true                # HTTPS 下才传 cookie
        same-site: strict           # 防 CSRF
spring:
  mvc:
    throw-exception-if-no-handler-found: false  # 避免路径枚举

Nginx 上补安全响应头:

add_header X-Frame-Options "DENY" always;                    # 防点击劫持
add_header X-Content-Type-Options "nosniff" always;          # 防 MIME 嗅探
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=()" always;

步骤 6:安全响应头一键验证

curl -I https://coderoadmap.cn
# 逐条检查输出里的:Strict-Transport-Security、X-Frame-Options、
# X-Content-Type-Options、Content-Security-Policy

讲清楚要点

  1. 输入校验是第一防线:无论前端有没有校验,后端永远再校验一次。不为性能牺牲安全——参数校验在请求入口处做,成本远低于一次 SQL 注入后的数据恢复。

  2. 参数化查询是 SQL 注入的银弹:不是手动加转义(迟早漏掉一个),是让数据库驱动自己处理参数。JPA、MyBatis #{}、JDBC ? 都是参数化。

  3. XSS 的本质是输出问题:不是输入不能含尖括号,而是输出到 HTML 时要编码。前后端分离架构天然更安全,因为 API 返回 JSON 而非 HTML。但要注意 SSR、富文本编辑器的例外处理。

  4. 错误信息越模糊越安全:生产环境不要把 java.lang.NullPointerException at UserService.java:45 返回给客户端。打日志给运维看,给客户端的只给"操作失败,请重试"+traceId。

  5. 安全响应头是零成本的补丁:几个 add_header 就能挡住点击劫持、MIME 嗅探、降级攻击。配完测一遍,以后不用管。

你可能会懵的地方

  • MyBatis 里 ${} 和 #{} 有什么区别? #{} 是参数化(安全),${} 是字符串替换(危险,只用于动态表名/列名且必须白名单校验)。绝不把用户输入直接放进 ${}。

  • JWT 接口关了 CSRF,那 Cookie 凭据怎么办? 如果你的 API 同时靠 Session Cookie 认证,CSRF 必须开。纯 JWT(存在 localStorage、Header 传递)天然免疫 CSRF——因为跨域请求发不出自定义 Header(CORS preflight 拦截),且没 Cookie 可利用。

  • 富文本编辑器(允许 HTML)怎么防 XSS? 用成熟库(如 JSoup)做白名单过滤:只允许 <p><b><i><a> 等安全标签和 href 属性。不要用黑名单(你不可能穷举所有恶意标签)。

  • 业务逻辑漏洞算 OWASP Top 10 吗? 2021 版新增 A01 Broken Access Control,涵盖的就是 IDOR、越权。纯注入类漏洞已退居第二,说明现代框架已经大幅减少了 SQL 注入,但业务逻辑校验仍是开发者的直接责任。

  • 序列化攻击只影响 Java 吗? 不是。Python pickle、PHP unserialize、Node vm 都有类似问题。原则一致:永远不要反序列化不受信任来源的数据;必须用就加白名单;优先用 JSON/Protobuf 等不含可执行语义的格式。

错法对照

错法问题正解
字符串拼接 SQLSQL 注入PreparedStatement / #{}
直接回显用户输入到 HTMLXSS模板编码或返回 JSON
ObjectInputStream.readObject 任意来源RCEJSON 反序列化 + 白名单
生产环境返回详细异常堆栈信息泄漏+路径枚举include-message/stacktrace=never
Nginx 不加安全响应头点击劫持、降级、MIME 嗅探X-Frame-Options / HSTS / CSP
信任前端校验绕过太容易后端入口再次校验所有输入

卡点急救

  1. 开了 CSP 后页面样式/脚本不加载:CSP 默认拒绝内联脚本和外部 CDN。解法:开发环境放宽到 script-src 'self' 'unsafe-inline' 'unsafe-eval',生产严格化。把外部资源加入 script-src、style-src 白名单。

  2. MyBatis #{} 处理不了 IN (...) 列表:参数化查询不支持列表展开。解法:用 foreach 动态生成 ? 占位符(MyBatis 自动处理),不是把用户输入拼成字符串放进 IN (...)。

  3. Spring Boot 生产环境还暴露 Whitelabel Error Page:关掉它,配置自定义错误页,或设 server.error.whitelabel.enabled=false,避免错误信息泄漏和路径枚举。

面试常问

  • SQL 注入原理和防御? 原理:用户输入被拼接到 SQL 语句中,改变语法语义。防御:参数化查询(PreparedStatement);ORM/JPA 天然安全;不使用字符串拼接。特殊场景动态表名列名用白名单校验后拼接。

  • XSS 类型和防御? 存储型(存入数据库后展示给所有用户)、反射型(URL 参数直接回显)、DOM 型(前端 JS 不当地操作 DOM)。防御:输出编码(HTML、JS、URL、CSS 上下文不同编码方式)、CSP 限制脚本来源、HttpOnly Cookie。前后端分离+JSON API 天然大幅降低风险。

  • 生产环境为什么不该暴露堆栈? 信息泄漏:框架版本、代码路径、内部类名帮助攻击者定制 payload;路径枚举:从报错能推断存在/不存在的 API。错误处理原则:给用户的 message 模糊,给日志的 detail 完整,靠 traceId 关联。

自测三问

  1. MyBatis #{} 和 ${} 的区别是什么?哪个安全?
  2. 为什么前后端分离的 JWT 架构不需要 CSRF 防护?
  3. 生产环境 server.error 三项配置应该分别设成什么?为什么?

小结一句话

应用安全就是一句话——用户输入永不信任:SQL 参数化、HTML 编码、反序列化白名单、错误信息模糊化,再加一套安全响应头兜底。

延伸

  • OWASP Top 10 (2021)
  • 下一课:威胁建模——STRIDE 和 PASTA 方法论,用本站场景做一次真实威胁建模。
加载本节练习…
← 上一课身份与访问下一课 →威胁建模