零、项目代码和攻击面介绍

                众嗦粥汁,一个spring boot项目里DemoAplication是启动器,各个Controller是主要的攻击面,config是副攻击面,现在我通过AI写了一个spring boot项目并分析它的攻击点,以下是Usercontroller的代码和整个项目的结构

package com.liangning.demo.controller;

import com.liangning.demo.common.Result;
import com.liangning.demo.entity.User;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/user")
public class UserController {

    private final JdbcTemplate jdbcTemplate;

    public UserController(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    // ✅ 查询用户:/user/get?id=1
    @GetMapping("/get")
    public Result<User> getUser(@RequestParam Long id) {
        String sql = "SELECT id, name FROM users WHERE id = ?";
        Map<String, Object> row = jdbcTemplate.queryForMap(sql, id);

        User u = new User();
        u.setId(((Number) row.get("id")).longValue());
        u.setName((String) row.get("name"));

        return Result.ok(u);
    }

    // ✅ 新增用户:/user/add?name=tom
    @GetMapping("/add")
    public Result<Long> addUser(@RequestParam String name) {
        String sql = "INSERT INTO users(name) VALUES (?)";
        jdbcTemplate.update(sql, name);

        // 取最后插入的自增 id(对 MySQL 有效)
        Long id = jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class);

        return Result.ok(id);
    }
}

一、项目背景(最小可复现场景)

这是一个最简单的 Spring Boot Web 项目:

  • Web 框架:Spring Boot

  • Web 端口:8080

  • 数据库:MySQL(3306

  • 表:users

目前对外只暴露 两个接口

  • GET /user/get?id=

  • GET /user/add?name=

这类结构在学习项目、小工具、内部系统中非常常见,但也正因为“简单”,攻击面反而更清晰

二、攻击面地图(Attack Surface Map)

先不谈漏洞,先谈攻击面在哪里

HTTP 请求
  ↓
Controller(/user/get, /user/add)   ← 攻击入口
  ↓
JdbcTemplate / SQL                  ← 高风险区
  ↓
MySQL users 表                      ← 核心资产

攻击面拆解(√ / ×)

  • Controller
    所有 HTTP 参数都是不可信输入

  • SQL 执行层
    一旦输入进入 SQL 语法层,就可能产生注入、越权等问题

  • 数据库
    数据完整性、可用性是最终目标

  • × 浏览器报错/500 页面
    只是症状,不是根因

三、接口级风险分析

1️⃣ /user/add?name=xxx

功能:
users 表插入一条记录,并返回自增 id。

攻击面与风险
  • 参数入口name

  • 潜在风险

    • SQL 注入(如果使用字符串拼接)

    • 脏数据(超长、异常字符)

    • 错误信息泄露(SQL 异常直接返回)

当前实现的安全点(√)
String sql = "INSERT INTO users(name) VALUES (?)";
jdbcTemplate.update(sql, name);
  • 使用 参数化查询

  • 用户输入只作为“数据”,不会进入 SQL 语法层

  • 能天然防御 SQL 注入

✅ 这是正确、安全、推荐的写法(说人话就是把用户输入当字符串,不去执行

2️⃣ /user/get?id=1

功能:
根据 id 查询用户。

攻击面与风险(重点)
  • ID 枚举风险

    • id=1,2,3... 天然可猜

  • 潜在 IDOR(越权)

    • 如果未来加入登录系统,而这里不校验“是否有权限看这个 id”

  • 信息泄露

    • SQL 报错会暴露表名、字段名

这类问题在真实 Web 攻击中非常常见,而且往往不是“技术高深”,而是“设计疏忽”。

四、一次真实遇到的问题:500 错误从何而来?

在访问 /user/get?id=1 时,接口返回 500 Internal Server Error

日志核心信息是:

java.sql.SQLSyntaxErrorException: Table 'demo_db.users' doesn't exist

错误链路复盘

HTTP 请求 → Controller 正常接收 → SQL 正常执行 → MySQL 抛异常(表不存在) → Spring 包装成 500

⚠️ 关键点
这不是 Web 层的问题,也不是 Controller 写错,而是:

数据库结构 ≠ 应用假设的结构

五、根因定位:数据库结构问题,而非代码问题

进一步检查数据库:

USE demo_db; SHOW TABLES;

发现:

  • users 表不存在,或结构不完整

  • 即使存在,也可能缺少 AUTO_INCREMENT

后续又遇到的错误(典型)

Field 'id' doesn't have a default value

这说明:

  • 表中 id 字段 既不是自增

  • 又被设为 NOT NULL

  • 应用层没有传 id → 数据库拒绝

翻译成人话就是就只建了库,里面啥也没有,然后建了库以后又忘了把name设置成必须要有,id设置成自增(可以没有输入进去,没有的话就在上一个的id基础上加1)

六、修复方案(也是防御加固)

1️⃣ 正确设置自增主键

ALTER TABLE users MODIFY id INT NOT NULL AUTO_INCREMENT;

含义是:

  • 当 INSERT 不提供 id

  • 数据库自动生成唯一 id

  • 应用不需要“猜”或“管理”主键

2️⃣ 安全收益(不仅是功能修复)

  • ✅ 防止因主键问题导致的 500

  • ✅ 降低逻辑复杂度

  • ✅ 避免开发者在应用层“手写 id”

  • ✅ 更符合数据库安全与一致性原则

七、从安全角度总结这次实践

核心模型(非常重要)

Controller 是攻击入口,SQL 是高风险区,数据库是资产。

这次实践中学到的 3 个安全结论

  1. 500 错误 ≠ 程序崩溃
    很多时候是数据结构或假设错误

  2. 参数化查询是必须的,不是“最佳实践可选项”

  3. 攻击者和开发者看到的是同一套错误信息

    • 你能靠异常定位问题

    • 攻击者也能靠异常推断表结构


八、后续加固方向

  • name 做长度与字符校验

  • id 做范围校验

  • 错误信息对外统一为业务错误

  • 日志中保留详细异常,前端不暴露

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐